Squashed commit of the following:

commit 574702eaa472332d4624a9a655157585efc84dd3
Author: Hanh <hanh425@gmail.com>
Date:   Fri Nov 12 09:35:00 2021 +0800

    Refactor multisig to crate

commit 1c133aa083ea41b9fb33c9b5e56816ad4c5ffc89
Author: Hanh <hanh425@gmail.com>
Date:   Sat Nov 6 00:32:41 2021 +0800

    Send & MultiSend

commit f3dd0822edd6860e764c248c7f31acaf2c074d97
Author: Hanh <hanh425@gmail.com>
Date:   Tue Nov 2 18:19:48 2021 +0800

    Improved Charts

commit 17c365ad24c88f7a79de8ad5fd2114187f5ac33e
Author: Hanh <hanh425@gmail.com>
Date:   Thu Oct 28 19:38:19 2021 +0800

    Split account UI
This commit is contained in:
Hanh 2021-11-12 09:44:46 +08:00
parent f77bf079cf
commit 04958c291a
56 changed files with 2109 additions and 975 deletions

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "native/zcash-params"]
path = native/zcash-params
url = https://github.com/hhanh00/zcash-params.git
[submodule "native/zcash-multisig"]
path = native/zcash-multisig
url = https://github.com/hhanh00/zcash-multisig.git

200
Cargo.lock generated
View File

@ -47,18 +47,18 @@ checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]]
name = "aho-corasick"
version = "0.7.18"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "allo-isolate"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95c5a443c8b833fc57ee060bb52639fb687d94e640737e71be5c8cccbbbb790c"
checksum = "e13e2dba4602fed6c46f7d5cae67fe0fb9a6af1ce01848228f7533a8450c73be"
dependencies = [
"atomic",
]
@ -92,9 +92,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.44"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
[[package]]
name = "arrayref"
@ -341,9 +341,9 @@ dependencies = [
[[package]]
name = "bstr"
version = "0.2.17"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
dependencies = [
"lazy_static",
"memchr",
@ -390,7 +390,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38728c31b994e4b849cf59feefb4a8bf26acd299ee0b92c9fb35bd14ad4b8dfa"
dependencies = [
"clap 2.33.3",
"clap",
"heck",
"indexmap",
"log",
@ -452,43 +452,12 @@ dependencies = [
"ansi_term",
"atty",
"bitflags",
"strsim 0.8.0",
"textwrap 0.11.0",
"strsim",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "3.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim 0.10.0",
"termcolor",
"textwrap 0.14.2",
"unicase",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clipboard-win"
version = "4.2.2"
@ -554,7 +523,7 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
dependencies = [
"atty",
"cast",
"clap 2.33.3",
"clap",
"criterion-plot",
"csv",
"itertools 0.10.1",
@ -869,7 +838,7 @@ dependencies = [
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=6baf1ba63261dda09e5b1d4b476977842a1f61c8#6baf1ba63261dda09e5b1d4b476977842a1f61c8"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=1518b145f8ee67e144fa8337c7dfd4c8cff899c9#1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
dependencies = [
"blake2b_simd",
"byteorder",
@ -1449,9 +1418,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.105"
version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]]
name = "libsecp256k1"
@ -1503,9 +1472,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.4.1"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
@ -1599,9 +1568,9 @@ dependencies = [
[[package]]
name = "nom"
version = "6.1.2"
version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
dependencies = [
"bitvec 0.19.5",
"funty",
@ -1689,15 +1658,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "os_str_bytes"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
dependencies = [
"memchr",
]
[[package]]
name = "pairing"
version = "0.19.0"
@ -1805,30 +1765,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@ -2084,9 +2020,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.4"
version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
dependencies = [
"aho-corasick",
"memchr",
@ -2399,9 +2335,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.68"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
dependencies = [
"itoa",
"ryu",
@ -2555,12 +2491,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "1.0.0"
@ -2589,11 +2519,13 @@ name = "sync"
version = "0.1.0"
dependencies = [
"anyhow",
"bech32",
"bincode",
"blake2b_simd",
"bls12_381",
"byteorder",
"chrono",
"clap 3.0.0-beta.5",
"clap",
"criterion",
"dotenv",
"env_logger",
@ -2609,6 +2541,7 @@ dependencies = [
"prost",
"protobuf",
"rand 0.8.4",
"rand_chacha 0.3.1",
"rayon",
"reqwest",
"ripemd160",
@ -2627,7 +2560,8 @@ dependencies = [
"tonic-build",
"zcash_address",
"zcash_client_backend",
"zcash_params 0.1.0 (git+https://github.com/hhanh00/zcash-params.git?branch=main)",
"zcash_multisig 0.1.0 (git+https://github.com/hhanh00/zcash-multisig.git?rev=f6ab9d9efcdc064a4b6f77623a263899b62a7bd7)",
"zcash_params 0.1.0 (git+https://github.com/hhanh00/zcash-params.git?rev=f54214d0a6752188efd1404e39b10be58a27ea0f)",
"zcash_primitives",
"zcash_proofs",
]
@ -2682,15 +2616,6 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.30"
@ -2819,9 +2744,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee"
dependencies = [
"autocfg",
"bytes",
@ -2836,9 +2761,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd"
checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095"
dependencies = [
"proc-macro2",
"quote",
@ -2858,9 +2783,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f"
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
dependencies = [
"futures-core",
"pin-project-lite",
@ -2869,9 +2794,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
dependencies = [
"bytes",
"futures-core",
@ -3021,15 +2946,6 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.7"
@ -3139,6 +3055,7 @@ dependencies = [
"once_cell",
"sync",
"tokio",
"zcash_multisig 0.1.0",
"zcash_primitives",
]
@ -3325,7 +3242,7 @@ dependencies = [
[[package]]
name = "zcash_client_backend"
version = "0.5.0"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=6baf1ba63261dda09e5b1d4b476977842a1f61c8#6baf1ba63261dda09e5b1d4b476977842a1f61c8"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=1518b145f8ee67e144fa8337c7dfd4c8cff899c9#1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
dependencies = [
"base64",
"bech32",
@ -3346,10 +3263,31 @@ dependencies = [
"zcash_primitives",
]
[[package]]
name = "zcash_multisig"
version = "0.1.0"
dependencies = [
"anyhow",
"futures 0.3.17",
"jubjub",
"tokio",
]
[[package]]
name = "zcash_multisig"
version = "0.1.0"
source = "git+https://github.com/hhanh00/zcash-multisig.git?rev=f6ab9d9efcdc064a4b6f77623a263899b62a7bd7#f6ab9d9efcdc064a4b6f77623a263899b62a7bd7"
dependencies = [
"anyhow",
"futures 0.3.17",
"jubjub",
"tokio",
]
[[package]]
name = "zcash_note_encryption"
version = "0.0.0"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=6baf1ba63261dda09e5b1d4b476977842a1f61c8#6baf1ba63261dda09e5b1d4b476977842a1f61c8"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=1518b145f8ee67e144fa8337c7dfd4c8cff899c9#1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
dependencies = [
"blake2b_simd",
"byteorder",
@ -3373,13 +3311,14 @@ dependencies = [
"jubjub",
"lazy_static",
"rand 0.8.4",
"serde",
"zcash_primitives",
]
[[package]]
name = "zcash_params"
version = "0.1.0"
source = "git+https://github.com/hhanh00/zcash-params.git?branch=main#f8eb2641dd7c4f3e57cd8c928334c1ae4e4284a4"
source = "git+https://github.com/hhanh00/zcash-params.git?rev=f54214d0a6752188efd1404e39b10be58a27ea0f#f54214d0a6752188efd1404e39b10be58a27ea0f"
dependencies = [
"bls12_381",
"byteorder",
@ -3390,13 +3329,14 @@ dependencies = [
"jubjub",
"lazy_static",
"rand 0.8.4",
"serde",
"zcash_primitives",
]
[[package]]
name = "zcash_primitives"
version = "0.5.0"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=6baf1ba63261dda09e5b1d4b476977842a1f61c8#6baf1ba63261dda09e5b1d4b476977842a1f61c8"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=1518b145f8ee67e144fa8337c7dfd4c8cff899c9#1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
dependencies = [
"aes",
"bitvec 0.20.4",
@ -3426,7 +3366,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.5.0"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=6baf1ba63261dda09e5b1d4b476977842a1f61c8#6baf1ba63261dda09e5b1d4b476977842a1f61c8"
source = "git+https://github.com/hhanh00/librustzcash.git?rev=1518b145f8ee67e144fa8337c7dfd4c8cff899c9#1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
dependencies = [
"bellman",
"blake2b_simd",
@ -3443,18 +3383,18 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.2.0"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7"
checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73"
dependencies = [
"proc-macro2",
"quote",

View File

@ -41,7 +41,6 @@ android {
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "me.hanh.zwallet"
minSdkVersion 24
targetSdkVersion 30

View File

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.hanh.zwallettest">
package="me.hanh.zwallet">
<application
android:label="ZWalletTest"
android:label="ZWallet"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
@ -27,7 +27,7 @@
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="zcashtest"/>
android:scheme="zcash"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@ -5,6 +5,7 @@
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:windowFullscreen">false</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
@ -15,4 +16,4 @@
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
</resources>

View File

@ -19,4 +19,4 @@
<style name="QuickActions">
<item name="android:windowBackground">@drawable/quick_actions</item>
</style>
</resources>
</resources>

View File

@ -10,14 +10,14 @@ zcashtest)
export APP_NAME=zwallettest
;;
zcash)
export APP_TITLE=WarpWallet
export APP_NAME=warpwallet
export APP_TITLE=ZWallet
export APP_NAME=zwallet
;;
esac
cp assets/$COIN.png assets/icon.png
cp lib/coin/$COIN.dart lib/coin/coindef.dart
cp native/zcash-sync/src/coindef/$COIN.rs native/zcash-sync/src/coin.rs
cp native/zcash-params/src/coindef/$COIN.rs native/zcash-params/src/coin.rs
mo pubspec.yaml.tpl > pubspec.yaml
mo android/app/src/main/AndroidManifest.xml.tpl > android/app/src/main/AndroidManifest.xml
@ -28,3 +28,6 @@ flutter pub run flutter_launcher_icons:main
flutter pub run flutter_app_name
flutter pub run build_runner build
flutter pub run flutter_native_splash:create
(cd packages/warp_api_ffi; flutter pub get; flutter pub run build_runner build)

View File

@ -560,4 +560,4 @@
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
}

View File

@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="256" height="256"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>
</document>

View File

@ -11,7 +11,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>WarpWallet</string>
<string>YWallet</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@ -49,16 +49,16 @@
<false/>
<key>UIStatusBarHidden</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(COIN)</string>
</array>
</dict>
</array>
</dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>$(COIN)</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:intl/intl.dart';
import 'package:k_chart/entity/index.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:sensors_plus/sensors_plus.dart';
@ -125,6 +126,7 @@ class _AccountPageState extends State<AccountPage>
final theme = Theme.of(this.context);
final hasTAddr = accountManager.taddress.isNotEmpty;
final qrSize = getScreenSize(context) / 2.5;
final hasMultisign = accountManager.canPay || accountManager.active.share != null;
return Scaffold(
appBar: AppBar(
@ -154,11 +156,12 @@ class _AccountPageState extends State<AccountPage>
if (accountManager.canPay)
PopupMenuItem(
child: Text(s.coldStorage), value: "Cold"),
if (accountManager.canPay)
PopupMenuItem(
child: Text(s.multipay), value: "MultiPay"),
PopupMenuItem(
child: Text(s.multipay), value: "MultiPay"),
PopupMenuItem(
child: Text(s.broadcast), value: "Broadcast"),
if (coin.supportsMultisig && hasMultisign) PopupMenuItem(
child: Text(s.multisig), value: "Multisig"),
PopupMenuItem(
child: Text(s.settings), value: "Settings"),
PopupMenuItem(child: Text(s.help), value: "Help"),
@ -176,6 +179,10 @@ class _AccountPageState extends State<AccountPage>
padding: EdgeInsets.all(20),
child: Center(
child: Column(children: [
Observer(builder: (context) {
final share = accountManager.active.share;
return share != null ? Text("MULTISIG ${share.index}/${share.participants}") : SizedBox();
}),
Observer(builder: (context) {
final _1 = eta.eta;
final _2 = syncStatus.syncedHeight;
@ -464,6 +471,9 @@ class _AccountPageState extends State<AccountPage>
case "Broadcast":
_broadcast();
break;
case "Multisig":
_multisig();
break;
case "Settings":
_settings();
break;
@ -519,6 +529,10 @@ class _AccountPageState extends State<AccountPage>
}
}
_multisig() {
Navigator.of(context).pushNamed('/multisig');
}
_convertToWatchOnly() {
accountManager.convertToWatchOnly();
Navigator.of(context).pop();
@ -580,7 +594,7 @@ class BudgetState extends State<BudgetWidget>
style: Theme.of(context).textTheme.headline6),
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
Expanded(child: Padding(padding: EdgeInsets.only(right: 20),
child: LineChartTimeSeries(accountManager.accountBalances)))
child: LineChartTimeSeries.fromTimeSeries(accountManager.accountBalances)))
]))),
],
);
@ -659,7 +673,7 @@ class PnLChart extends StatelessWidget {
@override
Widget build(BuildContext context) {
final series = _createSeries(pnls, seriesIndex, context);
return LineChartTimeSeries(series);
return LineChartTimeSeries.fromTimeSeries(series);
}
static double _seriesData(PnL pnl, int index) {

View File

@ -142,11 +142,11 @@ class AccountManagerState extends State<AccountManagerPage> {
builder: (context) => AlertDialog(
title: Text(S.of(context).changeAccountName),
content: TextField(controller: _accountNameController),
actions: confirmButtons(context, _changeAccountName)));
actions: confirmButtons(context, () { _changeAccountName(account); })));
}
_changeAccountName() {
accountManager.changeAccountName(_accountNameController.text);
_changeAccountName(Account account) {
accountManager.changeAccountName(account, _accountNameController.text);
Navigator.of(context).pop();
}

View File

@ -18,12 +18,15 @@ class BackupState extends State<BackupPage> {
final _backupController = TextEditingController();
final _skController = TextEditingController();
final _ivkController = TextEditingController();
final _shareController = TextEditingController();
Future<bool> _init() async {
backup = await accountManager.getBackup(widget.accountId ?? accountManager.active.id);
_backupController.text = backup.value();
_skController.text = backup.sk ?? "";
_ivkController.text = backup.ivk;
final share = backup.share;
_shareController.text = share?.value ?? "";
return true;
}
@ -38,6 +41,8 @@ class BackupState extends State<BackupPage> {
Widget _build(BuildContext context, AsyncSnapshot<void> snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
final name = backup.name;
final share = backup.share;
final type = backup.type;
final theme = Theme.of(context);
return SingleChildScrollView(child: Card(
@ -46,7 +51,7 @@ class BackupState extends State<BackupPage> {
TextField(
decoration: InputDecoration(
labelText: S.of(context).backupDataRequiredForRestore, prefixIcon: IconButton(icon: Icon(Icons.save),
onPressed: () => _showQR(backup.value()))),
onPressed: () => _showQR(backup.value(), "$name - backup"))),
controller: _backupController,
minLines: 3,
maxLines: 10,
@ -55,7 +60,7 @@ class BackupState extends State<BackupPage> {
),
if (type == 0) TextField(
decoration: InputDecoration(labelText: S.of(context).secretKey, prefixIcon: IconButton(icon: Icon(Icons.vpn_key),
onPressed: () => _showQR(backup.sk!))),
onPressed: () => _showQR(backup.sk!, "$name - sk"))),
controller: _skController,
minLines: 3,
maxLines: 10,
@ -64,13 +69,22 @@ class BackupState extends State<BackupPage> {
),
if (type != 2) TextField(
decoration: InputDecoration(labelText: S.of(context).viewingKey, prefixIcon: IconButton(icon: Icon(Icons.visibility),
onPressed: () => _showQR(backup.ivk))),
onPressed: () => _showQR(backup.ivk, "$name - vk"))),
controller: _ivkController,
minLines: 3,
maxLines: 10,
readOnly: true,
style: theme.textTheme.caption
),
if (share != null) TextField(
decoration: InputDecoration(labelText: S.of(context).secretShare, prefixIcon: IconButton(icon: Icon(Icons.share),
onPressed: () => _showQR(share.value, "$name - ms ${share.index}/${share.participants}"))),
controller: _shareController,
minLines: 3,
maxLines: 10,
readOnly: true,
style: theme.textTheme.caption
),
Padding(padding: EdgeInsets.symmetric(vertical: 4)),
Text(S.of(context).tapAnIconToShowTheQrCode),
Container(margin: EdgeInsets.all(8), padding: EdgeInsets.all(8), decoration: BoxDecoration(border: Border.all(width: 2, color: theme.primaryColor), borderRadius: BorderRadius.circular(4)),child:
@ -81,5 +95,5 @@ class BackupState extends State<BackupPage> {
));
}
_showQR(String text) => showQR(context, text);
_showQR(String text, String title) => showQR(context, text, title);
}

View File

@ -1,154 +1,29 @@
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:flutter_palette/flutter_palette.dart';
import 'package:warp_api/warp_api.dart';
import 'package:k_chart/k_chart_widget.dart';
import 'package:k_chart/flutter_k_chart.dart';
import 'store.dart';
import 'main.dart';
class LineChartTimeSeries extends StatefulWidget {
final List<TimeSeriesPoint<double>> timeseries;
class LineChartTimeSeries extends StatelessWidget {
List<KLineEntity> datas;
LineChartTimeSeries(this.timeseries);
@override
LineChartTimeSeriesState createState() => LineChartTimeSeriesState();
}
class LineChartTimeSeriesState extends State<LineChartTimeSeries> {
final numberFormat = NumberFormat.compact();
bool showAvg = false;
var _minX = 0.0;
var _maxX = 0.0;
var _minY = 0.0;
var _maxY = 0.0;
LineChartTimeSeries(this.datas);
factory LineChartTimeSeries.fromTimeSeries(List<TimeSeriesPoint<double>> quotes) =>
LineChartTimeSeries(quotes.map((q) => KLineEntity.fromCustom(time: q.day * DAY_MS, open: q.value, close: q.value,
high: q.value, low: q.value, amount: 0, vol: 0)).toList());
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1.7,
child: LineChart(
mainData(),
));
}
LineChartData mainData() {
final theme = Theme.of(context);
double minY = double.maxFinite;
double maxY = 0.0;
final spots = widget.timeseries.map((ab) {
if (minY > ab.value) minY = ab.value;
if (maxY < ab.value) maxY = ab.value;
return FlSpot(ab.day.toDouble(), ab.value);
}).toList();
_minX = spots.first.x;
_maxX = spots.last.x;
_minY = minY;
_maxY = maxY;
var yInterval = 1.1 * (_maxY - _minY) / 6;
if (yInterval == 0) {
yInterval = 1;
}
List<Color> gradientColors = [
theme.colorScheme.secondary,
theme.colorScheme.primary,
];
final textStyle = theme.textTheme.bodyText1!;
final bgColor = theme.backgroundColor;
return LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: theme.colorScheme.secondary.withOpacity(0.2),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: theme.colorScheme.secondary.withOpacity(0.2),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
rightTitles: SideTitles(showTitles: false),
topTitles: SideTitles(showTitles: false),
bottomTitles: SideTitles(
showTitles: true,
reservedSize: 22,
getTextStyles: (context, value) => TextStyle(
color: theme.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 12),
getTitles: (v) {
final dt = DateTime.fromMillisecondsSinceEpoch(v.toInt() * DAY_MS);
return DateFormat.Md().format(dt);
},
),
leftTitles: SideTitles(
showTitles: true,
getTextStyles: (context, value) => TextStyle(
color: theme.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 14,
),
getTitles: (v) {
return numberFormat.format(v);
},
reservedSize: 40,
margin: 12,
),
),
borderData: FlBorderData(
show: true, border: Border.all(color: theme.colorScheme.secondary, width: 1)),
minX: _minX,
maxX: _maxX,
minY: _minY,
maxY: _maxY,
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: false,
colors: gradientColors,
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors:
gradientColors.map((color) => color.withOpacity(0.3)).toList(),
),
),
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: bgColor,
getTooltipItems: (touchedSpots) =>
touchedSpots.map((LineBarSpot spot) {
final x = spot.x;
final dt = DateTime.fromMillisecondsSinceEpoch(x.toInt() * DAY_MS);
final xdt = DateFormat.Md().format(dt);
final y = decimalFormat(spot.y, 3);
return LineTooltipItem("$xdt - $y",
textStyle);
}).toList()
)
return Container(
child: KChartWidget(datas,
ChartStyle(), ChartColors(context: context),
isLine: true,
volHidden: true,
showInfoDialog: true,
mainState: MainState.MA,
secondaryState: SecondaryState.NONE,
)
);
}
}

View File

@ -10,6 +10,6 @@ class Coin {
LWInstance("Lightwalletd", "https://lite.ycash.xyz:9067"),
];
bool supportsUA = false;
bool supportsMultisig = true;
List<int> weights = [5, 25, 250];
}

View File

@ -11,5 +11,6 @@ class Coin {
LWInstance("Zecwallet", "https://lwdv3.zecwallet.co"),
];
bool supportsUA = false;
bool supportsMultisig = false;
List<double> weights = [0.05, 0.25, 2.50];
}

View File

@ -10,5 +10,6 @@ class Coin {
LWInstance("Lightwalletd", "https://testnet.lightwalletd.com:9067"),
];
bool supportsUA = false;
bool supportsMultisig = true;
List<double> weights = [0.05, 0.25, 2.50];
}

View File

@ -24,22 +24,29 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(ticker) =>
"Are you sure you want to save your contacts? It will cost 0.01 m${ticker}";
static String m2(ticker) =>
static String m2(address, amount) =>
"Do you want to sign a transaction to ${address} for ${amount}";
static String m3(ticker) =>
"Do you want to transfer your entire transparent balance to your shielded address? A Network fee of 0.01 m${ticker} will be deducted.";
static String m3(ticker) => "Receive ${ticker}";
static String m4(num) => "${num} more signers needed";
static String m4(ticker) => "Send ${ticker}";
static String m5(ticker) => "Receive ${ticker}";
static String m5(ticker) => "Send ${ticker} to...";
static String m6(ticker) => "Send ${ticker}";
static String m6(amount, ticker, count) =>
static String m7(ticker) => "Send ${ticker} to...";
static String m8(amount, ticker, count) =>
"Sending a total of ${amount} ${ticker} to ${count} recipients";
static String m7(aZEC, ticker, address) =>
static String m9(aZEC, ticker, address) =>
"Sending ${aZEC} ${ticker} to ${address}";
static String m8(currency) => "Use ${currency}";
static String m10(text) => "${text} copied to clipboard";
static String m11(currency) => "Use ${currency}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@ -102,9 +109,13 @@ class MessageLookup extends MessageLookupByLibrary {
"color": MessageLookupByLibrary.simpleMessage("Color"),
"confirmDeleteAccount": MessageLookupByLibrary.simpleMessage(
"Are you SURE you want to DELETE this account? You MUST have a BACKUP to recover it. This operation is NOT reversible."),
"confirmSignATransactionToAddressFor": m2,
"confirmSigning":
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
"contactName": MessageLookupByLibrary.simpleMessage("Contact Name"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"createANewAccount":
MessageLookupByLibrary.simpleMessage("Tap + to add a new account"),
"createANewContactAndItWillShowUpHere":
@ -120,14 +131,18 @@ class MessageLookup extends MessageLookupByLibrary {
"doYouWantToDeleteTheSecretKeyAndConvert":
MessageLookupByLibrary.simpleMessage(
"Do you want to DELETE the secret key and convert this account to a watch-only account? You will not be able to spend from this device anymore. This operation is NOT reversible."),
"doYouWantToTransferYourEntireTransparentBalanceTo": m2,
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
"duplicateAccount":
MessageLookupByLibrary.simpleMessage("Duplicate Account"),
"duplicateContact": MessageLookupByLibrary.simpleMessage(
"Another contact has this address"),
"enterSecretShareIfAccountIsMultisignature":
MessageLookupByLibrary.simpleMessage(
"Enter secret share if account is multi-signature"),
"enterSeed": MessageLookupByLibrary.simpleMessage(
"Enter Seed, Secret Key or Viewing Key. Leave blank for a new account"),
"excludedNotes": MessageLookupByLibrary.simpleMessage("Excluded Notes"),
"fileSaved": MessageLookupByLibrary.simpleMessage("File saved"),
"gold": MessageLookupByLibrary.simpleMessage("Gold"),
"height": MessageLookupByLibrary.simpleMessage("Height"),
"help": MessageLookupByLibrary.simpleMessage("Help"),
@ -150,6 +165,11 @@ class MessageLookup extends MessageLookupByLibrary {
"mm": MessageLookupByLibrary.simpleMessage("M/M"),
"multiPay": MessageLookupByLibrary.simpleMessage("Multi Pay"),
"multipay": MessageLookupByLibrary.simpleMessage("MultiPay"),
"multipleAddresses":
MessageLookupByLibrary.simpleMessage("multiple addresses"),
"multisig": MessageLookupByLibrary.simpleMessage("Multisig"),
"multisigShares":
MessageLookupByLibrary.simpleMessage("Multisig Shares"),
"na": MessageLookupByLibrary.simpleMessage("N/A"),
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Name is empty"),
"newSnapAddress":
@ -164,6 +184,7 @@ class MessageLookup extends MessageLookupByLibrary {
"notEnoughBalance":
MessageLookupByLibrary.simpleMessage("Not enough balance"),
"notes": MessageLookupByLibrary.simpleMessage("Notes"),
"numMoreSignersNeeded": m4,
"numberOfConfirmationsNeededBeforeSpending":
MessageLookupByLibrary.simpleMessage(
"Number of Confirmations Needed before Spending"),
@ -190,7 +211,7 @@ class MessageLookup extends MessageLookupByLibrary {
"purple": MessageLookupByLibrary.simpleMessage("Purple"),
"qty": MessageLookupByLibrary.simpleMessage("Qty"),
"realized": MessageLookupByLibrary.simpleMessage("Realized"),
"receive": m3,
"receive": m5,
"receivePayment":
MessageLookupByLibrary.simpleMessage("Receive Payment"),
"rescan": MessageLookupByLibrary.simpleMessage("Rescan"),
@ -209,16 +230,17 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Scan starting momentarily"),
"secondary": MessageLookupByLibrary.simpleMessage("Secondary"),
"secretKey": MessageLookupByLibrary.simpleMessage("Secret Key"),
"secretShare": MessageLookupByLibrary.simpleMessage("Secret Share"),
"seed": MessageLookupByLibrary.simpleMessage("Seed"),
"selectAccount": MessageLookupByLibrary.simpleMessage("Select Account"),
"selectNotesToExcludeFromPayments":
MessageLookupByLibrary.simpleMessage(
"Select notes to EXCLUDE from payments"),
"send": MessageLookupByLibrary.simpleMessage("Send"),
"sendCointicker": m4,
"sendCointickerTo": m5,
"sendingATotalOfAmountCointickerToCountRecipients": m6,
"sendingAzecCointickerToAddress": m7,
"sendCointicker": m6,
"sendCointickerTo": m7,
"sendingATotalOfAmountCointickerToCountRecipients": m8,
"sendingAzecCointickerToAddress": m9,
"server": MessageLookupByLibrary.simpleMessage("Server"),
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"shieldTranspBalance":
@ -230,9 +252,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Shield Transparent Balance When Sending"),
"shieldingInProgress":
MessageLookupByLibrary.simpleMessage("Shielding in progress..."),
"sign": MessageLookupByLibrary.simpleMessage("Sign Transaction"),
"spendable": MessageLookupByLibrary.simpleMessage("Spendable"),
"spendableBalance":
MessageLookupByLibrary.simpleMessage("Spendable Balance"),
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
"synching": MessageLookupByLibrary.simpleMessage("Synching"),
"table": MessageLookupByLibrary.simpleMessage("Table"),
"tapAnIconToShowTheQrCode": MessageLookupByLibrary.simpleMessage(
@ -246,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Tap QR Code for Transparent Address"),
"tapTransactionForDetails":
MessageLookupByLibrary.simpleMessage("Tap Transaction for Details"),
"textCopiedToClipboard": m10,
"theme": MessageLookupByLibrary.simpleMessage("Theme"),
"themeEditor": MessageLookupByLibrary.simpleMessage("Theme Editor"),
"thisAccountAlreadyExists": MessageLookupByLibrary.simpleMessage(
@ -274,7 +299,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Unshielded Balance"),
"unsignedTransactionFile":
MessageLookupByLibrary.simpleMessage("Unsigned Transaction File"),
"useSettingscurrency": m8,
"useSettingscurrency": m11,
"useTransparentBalance":
MessageLookupByLibrary.simpleMessage("Use Transparent Balance"),
"useUa": MessageLookupByLibrary.simpleMessage("Use UA"),

View File

@ -24,22 +24,27 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(ticker) =>
"Estás seguro de que quieres guardar tus contactos? Costará 0,01 mZEC ";
static String m2(ticker) =>
static String m2(address, amount) =>
"Do you want to sign a transaction to ${address} for ${amount}";
static String m3(ticker) =>
"¿Quiere transferir su saldo transparente a su dirección blindada? ";
static String m3(ticker) => "Recibir ${ticker}";
static String m5(ticker) => "Recibir ${ticker}";
static String m4(ticker) => "Enviar ${ticker}";
static String m6(ticker) => "Enviar ${ticker}";
static String m5(ticker) => "Enviar ${ticker} a…";
static String m7(ticker) => "Enviar ${ticker} a…";
static String m6(amount, ticker, count) =>
static String m8(amount, ticker, count) =>
"Enviando un total de ${amount} ${ticker} a ${count} direcciones";
static String m7(aZEC, ticker, address) =>
static String m9(aZEC, ticker, address) =>
"Enviado ${aZEC} ${ticker} a ${address}";
static String m8(currency) => "Utilizar ${currency}";
static String m10(text) => "${text} copied to clipboard";
static String m11(currency) => "Utilizar ${currency}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@ -102,10 +107,14 @@ class MessageLookup extends MessageLookupByLibrary {
"color": MessageLookupByLibrary.simpleMessage("Color"),
"confirmDeleteAccount": MessageLookupByLibrary.simpleMessage(
"¿Está SEGURO de que desea BORRAR esta cuenta? DEBE tener una COPIA DE SEGURIDAD para recuperarla. Esta operación NO es reversible."),
"confirmSignATransactionToAddressFor": m2,
"confirmSigning":
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
"contactName":
MessageLookupByLibrary.simpleMessage("Nombre de contacto"),
"contacts": MessageLookupByLibrary.simpleMessage("Contactos"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"createANewAccount": MessageLookupByLibrary.simpleMessage(
"Crea una cuenta y aparecerá aquí."),
"createANewContactAndItWillShowUpHere":
@ -123,13 +132,17 @@ class MessageLookup extends MessageLookupByLibrary {
"doYouWantToDeleteTheSecretKeyAndConvert":
MessageLookupByLibrary.simpleMessage(
"¿Quiere BORRAR la clave secreta y convertir esta cuenta a solo lectura? Ya no podrá gastar desde este dispositivo. Esta operación NO es reversible."),
"doYouWantToTransferYourEntireTransparentBalanceTo": m2,
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
"duplicateAccount":
MessageLookupByLibrary.simpleMessage("Cuenta duplicada"),
"enterSecretShareIfAccountIsMultisignature":
MessageLookupByLibrary.simpleMessage(
"Enter secret share if account is multi-signature"),
"enterSeed": MessageLookupByLibrary.simpleMessage(
"Ingrese Semilla, Clave Secreta o Clave Lectura. Dejar en blanco para una nueva cuenta "),
"excludedNotes":
MessageLookupByLibrary.simpleMessage("Notas Excluidas"),
"fileSaved": MessageLookupByLibrary.simpleMessage("File saved"),
"gold": MessageLookupByLibrary.simpleMessage("Oro"),
"height": MessageLookupByLibrary.simpleMessage("Altura"),
"help": MessageLookupByLibrary.simpleMessage("Ayuda"),
@ -151,6 +164,11 @@ class MessageLookup extends MessageLookupByLibrary {
"mm": MessageLookupByLibrary.simpleMessage("M/M"),
"multiPay": MessageLookupByLibrary.simpleMessage("Multi Pagos"),
"multipay": MessageLookupByLibrary.simpleMessage("MultiPagos"),
"multipleAddresses":
MessageLookupByLibrary.simpleMessage("multiple addresses"),
"multisig": MessageLookupByLibrary.simpleMessage("Multisig"),
"multisigShares":
MessageLookupByLibrary.simpleMessage("Multisig Shares"),
"na": MessageLookupByLibrary.simpleMessage("N/A"),
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Nombre vacio"),
"newSnapAddress":
@ -192,7 +210,7 @@ class MessageLookup extends MessageLookupByLibrary {
"purple": MessageLookupByLibrary.simpleMessage("Morada"),
"qty": MessageLookupByLibrary.simpleMessage("Cantidad"),
"realized": MessageLookupByLibrary.simpleMessage("Dio Cuenta"),
"receive": m3,
"receive": m5,
"receivePayment":
MessageLookupByLibrary.simpleMessage("Recibir un pago"),
"rescan": MessageLookupByLibrary.simpleMessage("Escanear"),
@ -212,6 +230,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Escaneo comenzando momentáneamente "),
"secondary": MessageLookupByLibrary.simpleMessage("Secundario"),
"secretKey": MessageLookupByLibrary.simpleMessage("Llave Secreta"),
"secretShare": MessageLookupByLibrary.simpleMessage("Secret Share"),
"seed": MessageLookupByLibrary.simpleMessage("Semilla"),
"selectAccount":
MessageLookupByLibrary.simpleMessage("Seleccionar cuenta"),
@ -219,10 +238,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Seleccionar Notas a EXCLUIR de los pagos"),
"send": MessageLookupByLibrary.simpleMessage("Enviar"),
"sendCointicker": m4,
"sendCointickerTo": m5,
"sendingATotalOfAmountCointickerToCountRecipients": m6,
"sendingAzecCointickerToAddress": m7,
"sendCointicker": m6,
"sendCointickerTo": m7,
"sendingATotalOfAmountCointickerToCountRecipients": m8,
"sendingAzecCointickerToAddress": m9,
"server": MessageLookupByLibrary.simpleMessage("Servidor"),
"settings": MessageLookupByLibrary.simpleMessage("Ajustes"),
"shieldTranspBalance":
@ -231,9 +250,11 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Blindar Saldo Transparente"),
"shieldingInProgress":
MessageLookupByLibrary.simpleMessage("Blindaje en progreso…"),
"sign": MessageLookupByLibrary.simpleMessage("Sign"),
"spendable": MessageLookupByLibrary.simpleMessage("Gastable"),
"spendableBalance":
MessageLookupByLibrary.simpleMessage("Saldo Gastable"),
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
"synching": MessageLookupByLibrary.simpleMessage("Sincronizando"),
"table": MessageLookupByLibrary.simpleMessage("Lista"),
"tapAnIconToShowTheQrCode": MessageLookupByLibrary.simpleMessage(
@ -247,6 +268,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Pinchar QR para Dirección Transparente"),
"tapTransactionForDetails": MessageLookupByLibrary.simpleMessage(
"Toque Transacción para detalles"),
"textCopiedToClipboard": m10,
"theme": MessageLookupByLibrary.simpleMessage("Tema"),
"themeEditor": MessageLookupByLibrary.simpleMessage("Editora de temas"),
"thisAccountAlreadyExists":
@ -275,7 +297,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Saldo sin blindaje"),
"unsignedTransactionFile": MessageLookupByLibrary.simpleMessage(
"Archivo de transaccion sin firmar"),
"useSettingscurrency": m8,
"useSettingscurrency": m11,
"useTransparentBalance":
MessageLookupByLibrary.simpleMessage("Usar Saldo Transp"),
"useUa": MessageLookupByLibrary.simpleMessage("Usar UA"),

View File

@ -24,22 +24,27 @@ class MessageLookup extends MessageLookupByLibrary {
static String m1(ticker) =>
"Voulez vous sauver vos contacts? Ceci coute 0.01 m${ticker}";
static String m2(ticker) =>
static String m2(address, amount) =>
"Do you want to sign a transaction to ${address} for ${amount}";
static String m3(ticker) =>
"Voulez-vous transférer l\'intégralité de votre solde transparent à votre adresse protégée?";
static String m3(ticker) => "Recevoir ${ticker}";
static String m5(ticker) => "Recevoir ${ticker}";
static String m4(ticker) => "Envoyer ${ticker}";
static String m6(ticker) => "Envoyer ${ticker}";
static String m5(ticker) => "Envoyer ${ticker} à...";
static String m7(ticker) => "Envoyer ${ticker} à...";
static String m6(amount, ticker, count) =>
static String m8(amount, ticker, count) =>
"Envoi d\'un total de ${amount} ${ticker} à ${count} destinataires";
static String m7(aZEC, ticker, address) =>
static String m9(aZEC, ticker, address) =>
"Envoi de ${aZEC} ${ticker} à ${address}";
static String m8(currency) => "Utiliser ${currency}";
static String m10(text) => "${text} copied to clipboard";
static String m11(currency) => "Utiliser ${currency}";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@ -104,9 +109,13 @@ class MessageLookup extends MessageLookupByLibrary {
"color": MessageLookupByLibrary.simpleMessage("Couleur"),
"confirmDeleteAccount": MessageLookupByLibrary.simpleMessage(
"Êtes-vous SUR de vouloir SUPPRIMER ce compte ? Vous DEVEZ avoir une SAUVEGARDE pour le récupérer. Cette opération n\'est PAS réversible."),
"confirmSignATransactionToAddressFor": m2,
"confirmSigning":
MessageLookupByLibrary.simpleMessage("Confirm Signing"),
"confs": MessageLookupByLibrary.simpleMessage("Confs"),
"contactName": MessageLookupByLibrary.simpleMessage("Nom du Contact"),
"contacts": MessageLookupByLibrary.simpleMessage("Mes Contacts"),
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
"createANewAccount": MessageLookupByLibrary.simpleMessage(
"Créez un nouveau compte et il apparaîtra ici"),
"createANewContactAndItWillShowUpHere":
@ -125,12 +134,16 @@ class MessageLookup extends MessageLookupByLibrary {
"doYouWantToDeleteTheSecretKeyAndConvert":
MessageLookupByLibrary.simpleMessage(
"Voulez-vous SUPPRIMER la clé secrète et convertir ce compte en un compte d\'observation ? Vous ne pourrez plus dépenser depuis cet appareil. Cette opération n\'est PAS réversible."),
"doYouWantToTransferYourEntireTransparentBalanceTo": m2,
"doYouWantToTransferYourEntireTransparentBalanceTo": m3,
"duplicateAccount":
MessageLookupByLibrary.simpleMessage("Compte en double"),
"enterSecretShareIfAccountIsMultisignature":
MessageLookupByLibrary.simpleMessage(
"Enter secret share if account is multi-signature"),
"enterSeed": MessageLookupByLibrary.simpleMessage(
"Entrez la graine, la clé secrète ou la clé de visualisation. Laissez vide pour un nouveau compte"),
"excludedNotes": MessageLookupByLibrary.simpleMessage("Billets exclus"),
"fileSaved": MessageLookupByLibrary.simpleMessage("File saved"),
"gold": MessageLookupByLibrary.simpleMessage("Or"),
"height": MessageLookupByLibrary.simpleMessage("Hauteur"),
"help": MessageLookupByLibrary.simpleMessage("Aide"),
@ -152,6 +165,11 @@ class MessageLookup extends MessageLookupByLibrary {
"mm": MessageLookupByLibrary.simpleMessage("Virtuel"),
"multiPay": MessageLookupByLibrary.simpleMessage("Envoyer à plusieurs"),
"multipay": MessageLookupByLibrary.simpleMessage("Envoyer à plusieurs"),
"multipleAddresses":
MessageLookupByLibrary.simpleMessage("multiple addresses"),
"multisig": MessageLookupByLibrary.simpleMessage("Multisig"),
"multisigShares":
MessageLookupByLibrary.simpleMessage("Multisig Shares"),
"na": MessageLookupByLibrary.simpleMessage("N/D"),
"nameIsEmpty": MessageLookupByLibrary.simpleMessage("Le nom est vide"),
"newSnapAddress": MessageLookupByLibrary.simpleMessage(
@ -194,7 +212,7 @@ class MessageLookup extends MessageLookupByLibrary {
"purple": MessageLookupByLibrary.simpleMessage("Violet"),
"qty": MessageLookupByLibrary.simpleMessage("Quantité"),
"realized": MessageLookupByLibrary.simpleMessage("Réalisé"),
"receive": m3,
"receive": m5,
"receivePayment":
MessageLookupByLibrary.simpleMessage("Recevoir un payment"),
"rescan": MessageLookupByLibrary.simpleMessage("Parcourir à nouveau"),
@ -213,6 +231,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Le scan démarre momentanément"),
"secondary": MessageLookupByLibrary.simpleMessage("Secondaire"),
"secretKey": MessageLookupByLibrary.simpleMessage("Clé secrète"),
"secretShare": MessageLookupByLibrary.simpleMessage("Secret Share"),
"seed": MessageLookupByLibrary.simpleMessage("Graine"),
"selectAccount":
MessageLookupByLibrary.simpleMessage("Choisissez un compte"),
@ -220,10 +239,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Sélectionnez les billets à EXCLURE des paiements"),
"send": MessageLookupByLibrary.simpleMessage("Envoyer"),
"sendCointicker": m4,
"sendCointickerTo": m5,
"sendingATotalOfAmountCointickerToCountRecipients": m6,
"sendingAzecCointickerToAddress": m7,
"sendCointicker": m6,
"sendCointickerTo": m7,
"sendingATotalOfAmountCointickerToCountRecipients": m8,
"sendingAzecCointickerToAddress": m9,
"server": MessageLookupByLibrary.simpleMessage("Serveur"),
"settings": MessageLookupByLibrary.simpleMessage("Paramètres"),
"shieldTranspBalance": MessageLookupByLibrary.simpleMessage(
@ -232,9 +251,11 @@ class MessageLookup extends MessageLookupByLibrary {
"Masquer le solde transparent"),
"shieldingInProgress":
MessageLookupByLibrary.simpleMessage("Masquage en cours..."),
"sign": MessageLookupByLibrary.simpleMessage("Sign"),
"spendable": MessageLookupByLibrary.simpleMessage("Dépensable"),
"spendableBalance":
MessageLookupByLibrary.simpleMessage("Montant dépensable"),
"splitAccount": MessageLookupByLibrary.simpleMessage("Split Account"),
"synching":
MessageLookupByLibrary.simpleMessage("Synchronisation en cours"),
"table": MessageLookupByLibrary.simpleMessage("Tableau"),
@ -249,6 +270,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Appuyez sur le code QR pour l\'adresse transparente"),
"tapTransactionForDetails": MessageLookupByLibrary.simpleMessage(
"Presser sur une Transaction pour plus de details"),
"textCopiedToClipboard": m10,
"theme": MessageLookupByLibrary.simpleMessage("Thème"),
"themeEditor": MessageLookupByLibrary.simpleMessage("Editeur de Thème"),
"thisAccountAlreadyExists":
@ -277,7 +299,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Solde Transparent"),
"unsignedTransactionFile": MessageLookupByLibrary.simpleMessage(
"Fichier de transaction non signée"),
"useSettingscurrency": m8,
"useSettingscurrency": m11,
"useTransparentBalance": MessageLookupByLibrary.simpleMessage(
"Utiliser le Solde Transparent"),
"useUa": MessageLookupByLibrary.simpleMessage("Utiliser UA"),

View File

@ -1661,6 +1661,136 @@ class S {
args: [],
);
}
/// `Multisig`
String get multisig {
return Intl.message(
'Multisig',
name: 'multisig',
desc: '',
args: [],
);
}
/// `Enter secret share if account is multi-signature`
String get enterSecretShareIfAccountIsMultisignature {
return Intl.message(
'Enter secret share if account is multi-signature',
name: 'enterSecretShareIfAccountIsMultisignature',
desc: '',
args: [],
);
}
/// `Secret Share`
String get secretShare {
return Intl.message(
'Secret Share',
name: 'secretShare',
desc: '',
args: [],
);
}
/// `File saved`
String get fileSaved {
return Intl.message(
'File saved',
name: 'fileSaved',
desc: '',
args: [],
);
}
/// `{num} more signers needed`
String numMoreSignersNeeded(Object num) {
return Intl.message(
'$num more signers needed',
name: 'numMoreSignersNeeded',
desc: '',
args: [num],
);
}
/// `Sign Transaction`
String get sign {
return Intl.message(
'Sign Transaction',
name: 'sign',
desc: '',
args: [],
);
}
/// `Split Account`
String get splitAccount {
return Intl.message(
'Split Account',
name: 'splitAccount',
desc: '',
args: [],
);
}
/// `Confirm Signing`
String get confirmSigning {
return Intl.message(
'Confirm Signing',
name: 'confirmSigning',
desc: '',
args: [],
);
}
/// `Do you want to sign a transaction to {address} for {amount}`
String confirmSignATransactionToAddressFor(Object address, Object amount) {
return Intl.message(
'Do you want to sign a transaction to $address for $amount',
name: 'confirmSignATransactionToAddressFor',
desc: '',
args: [address, amount],
);
}
/// `Multisig Shares`
String get multisigShares {
return Intl.message(
'Multisig Shares',
name: 'multisigShares',
desc: '',
args: [],
);
}
/// `Copy`
String get copy {
return Intl.message(
'Copy',
name: 'copy',
desc: '',
args: [],
);
}
/// `{text} copied to clipboard`
String textCopiedToClipboard(Object text) {
return Intl.message(
'$text copied to clipboard',
name: 'textCopiedToClipboard',
desc: '',
args: [text],
);
}
/// `multiple addresses`
String get multipleAddresses {
return Intl.message(
'multiple addresses',
name: 'multipleAddresses',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@ -159,5 +159,18 @@
"color": "Color",
"accentColor": "Accent Color",
"primary": "Primary",
"secondary": "Secondary"
"secondary": "Secondary",
"multisig": "Multisig",
"enterSecretShareIfAccountIsMultisignature": "Enter secret share if account is multi-signature",
"secretShare": "Secret Share",
"fileSaved": "File saved",
"numMoreSignersNeeded": "{num} more signers needed",
"sign": "Sign Transaction",
"splitAccount": "Split Account",
"confirmSigning": "Confirm Signing",
"confirmSignATransactionToAddressFor": "Do you want to sign a transaction to {address} for {amount}",
"multisigShares": "Multisig Shares",
"copy": "Copy",
"textCopiedToClipboard": "{text} copied to clipboard",
"multipleAddresses": "multiple addresses"
}

View File

@ -159,5 +159,17 @@
"color": "Color",
"accentColor": "Acento Color",
"primary": "Primario",
"secondary": "Secundario"
"secondary": "Secundario",
"multisig": "Multisig",
"enterSecretShareIfAccountIsMultisignature": "Enter secret share if account is multi-signature",
"secretShare": "Secret Share",
"fileSaved": "File saved",
"sign": "Sign",
"splitAccount": "Split Account",
"confirmSigning": "Confirm Signing",
"confirmSignATransactionToAddressFor": "Do you want to sign a transaction to {address} for {amount}",
"multisigShares": "Multisig Shares",
"copy": "Copy",
"textCopiedToClipboard": "{text} copied to clipboard",
"multipleAddresses": "multiple addresses"
}

View File

@ -159,5 +159,17 @@
"color": "Couleur",
"accentColor": "Couleur d'accent",
"primary": "Primaire",
"secondary": "Secondaire"
"secondary": "Secondaire",
"multisig": "Multisig",
"enterSecretShareIfAccountIsMultisignature": "Enter secret share if account is multi-signature",
"secretShare": "Secret Share",
"fileSaved": "File saved",
"sign": "Sign",
"splitAccount": "Split Account",
"confirmSigning": "Confirm Signing",
"confirmSignATransactionToAddressFor": "Do you want to sign a transaction to {address} for {amount}",
"multisigShares": "Multisig Shares",
"copy": "Copy",
"textCopiedToClipboard": "{text} copied to clipboard",
"multipleAddresses": "multiple addresses"
}

View File

@ -31,6 +31,7 @@ import 'account_manager.dart';
import 'backup.dart';
import 'coin/coindef.dart';
import 'multisend.dart';
import 'multisign.dart';
import 'payment_uri.dart';
import 'settings.dart';
import 'restore.dart';
@ -92,6 +93,7 @@ void handleQuickAction(BuildContext context, String shortcut) {
void main() {
WidgetsFlutterBinding.ensureInitialized();
final home = ZWalletApp();
// final home = MultisigPage();
runApp(FutureBuilder(
future: settings.restore(),
builder: (context, snapshot) {
@ -127,6 +129,9 @@ void main() {
TransactionPage(routeSettings.arguments as Tx),
'/backup': (context) => BackupPage(routeSettings.arguments as int?),
'/multipay': (context) => MultiPayPage(),
'/multisig': (context) => MultisigPage(),
'/multisign': (context) => MultisigAggregatorPage(routeSettings.arguments as TxSummary),
'/multisig_shares': (context) => MultisigSharesPage(routeSettings.arguments as String),
'/edit_theme': (context) => ThemeEditorPage(onSaved: settings.updateCustomThemeColors),
};
return MaterialPageRoute(builder: routes[routeSettings.name]!);
@ -264,14 +269,26 @@ String unwrapUA(String address) {
return zaddr.isNotEmpty ? zaddr : address;
}
void showQR(BuildContext context, String text) {
void showQR(BuildContext context, String text, String title) {
final s = S.of(context);
showDialog(
context: context,
barrierColor: Colors.black,
builder: (context) => AlertDialog(
content: Container(
width: double.maxFinite,
child: QrImage(data: text, backgroundColor: Colors.white)),
child: SingleChildScrollView(child: Column(children: [
QrImage(data: text, backgroundColor: Colors.white),
Padding(padding: EdgeInsets.all(8)),
Text(title, style: Theme.of(context).textTheme.subtitle1),
Padding(padding: EdgeInsets.all(4)),
ElevatedButton.icon(onPressed: () {
Navigator.of(context).pop();
Clipboard.setData(ClipboardData(text: text));
final snackBar = SnackBar(content: Text(s.textCopiedToClipboard(title)));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
}, icon: Icon(Icons.copy), label: Text(s.copy))
])))
));
}

View File

@ -2,17 +2,18 @@ import 'dart:convert';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:warp/store.dart';
import 'package:warp_api/warp_api.dart';
import 'package:warp_api/types.dart';
import 'dualmoneyinput.dart';
import 'main.dart';
import 'generated/l10n.dart';
import 'send.dart';
class MultiPayPage extends StatefulWidget {
@override
@ -51,14 +52,9 @@ class MultiPayState extends State<MultiPayPage> {
}
_add() async {
final r = await showDialog<Recipient>(
context: context,
barrierDismissible: false,
builder: (context) => Dialog(
child: PayRecipient(),
));
if (r != null) {
multipayData.addRecipient(r);
final recipient = await Navigator.of(context).pushNamed('/send', arguments: SendPageArgs(isMulti: true, recipients: multipayData.recipients)) as Recipient?;
if (recipient != null) {
multipayData.addRecipient(recipient);
}
}
@ -88,17 +84,7 @@ class MultiPayState extends State<MultiPayPage> {
cancelValue: false)));
if (approved) {
final s = S.of(context);
Navigator.of(context).pop();
final snackBar1 = SnackBar(content: Text(s.preparingTransaction));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
final recipientsJson = jsonEncode(multipayData.recipients);
final tx = await WarpApi.sendMultiPayment(accountManager.active.id,
recipientsJson, settings.anchorOffset, (p) {});
final snackBar2 = SnackBar(content: Text("${s.txId}: $tx"));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
await send(context, multipayData.recipients, false);
multipayData.clear();
await accountManager.fetchAccountData(true);
@ -106,105 +92,6 @@ class MultiPayState extends State<MultiPayPage> {
}
}
class PayRecipient extends StatefulWidget {
PayRecipient();
@override
State<StatefulWidget> createState() => PayRecipientState();
}
class PayRecipientState extends State<PayRecipient> {
final _formKey = GlobalKey<FormState>();
final _addressController = TextEditingController();
final _memoController = TextEditingController();
final amountKey = GlobalKey<DualMoneyInputState>();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
}, child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(children: <Widget>[
Row(children: <Widget>[
Expanded(
child: TypeAheadFormField(
textFieldConfiguration: TextFieldConfiguration(
decoration: InputDecoration(
labelText:
S.of(context).sendCointickerTo(coin.ticker)),
controller: _addressController,
minLines: 4,
maxLines: 10,
keyboardType: TextInputType.multiline),
validator: _checkAddress,
onSuggestionSelected: (Contact contact) {
_addressController.text = contact.name;
},
suggestionsCallback: (String pattern) {
return contacts.contacts.where((c) =>
c.name.toLowerCase().contains(pattern.toLowerCase()));
},
itemBuilder: (BuildContext context, Contact c) =>
ListTile(title: Text(c.name)),
noItemsFoundBuilder: (_) => SizedBox(),
),
),
IconButton(
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan)
]),
DualMoneyInputWidget(key: amountKey),
TextFormField(
decoration: InputDecoration(labelText: S.of(context).memo),
minLines: 4,
maxLines: null,
keyboardType: TextInputType.multiline,
controller: _memoController,
),
ButtonBar(
children: confirmButtons(context, _onAdd,
okLabel: S.of(context).add, okIcon: Icon(MdiIcons.plus)))
]),
)));
}
void _onScan() async {
final address = await scanCode(context);
if (address != null)
setState(() {
_addressController.text = address;
});
}
void _onAdd() {
final form = _formKey.currentState!;
if (form.validate()) {
form.save();
final amount = amountKey.currentState!.amount;
final c =
contacts.contacts.where((c) => c.name == _addressController.text);
final address =
c.isEmpty ? unwrapUA(_addressController.text) : c.first.address;
final r = Recipient(
address, amount, _memoController.text);
Navigator.of(context).pop(r);
}
}
String? _checkAddress(String? v) {
if (v == null || v.isEmpty) return S.of(context).addressIsEmpty;
final c = contacts.contacts.where((c) => c.name == v);
if (c.isNotEmpty) return null;
final zaddr = WarpApi.getSaplingFromUA(v);
if (zaddr.isNotEmpty) return null;
if (!WarpApi.validAddress(v)) return S.of(context).invalidAddress;
return null;
}
}
class NoRecipient extends StatelessWidget {
@override
Widget build(BuildContext context) {

299
lib/multisign.dart Normal file
View File

@ -0,0 +1,299 @@
import 'dart:convert';
import 'dart:isolate';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:warp/store.dart';
import 'package:warp_api/warp_api.dart';
import 'generated/l10n.dart';
import 'package:convert/convert.dart';
import 'main.dart';
const PORT = 4000;
class MultisigAggregatorPage extends StatefulWidget {
final TxSummary txSummary;
MultisigAggregatorPage(this.txSummary);
@override
MultisigAggregatorState createState() => MultisigAggregatorState();
}
class MultisigAggregatorState extends State<MultisigAggregatorPage> {
String _wifiIP = '';
ShareInfo? _shareInfo;
int _peerCount = 0;
@override
void initState() {
super.initState();
Future.microtask(() async {
var peerPort = ReceivePort();
var peerStream = peerPort.asBroadcastStream();
peerStream.listen((_) {
print("Received a new peer");
_peerCount += 1;
});
final info = NetworkInfo();
final wifiIP = await info.getWifiIP() ?? '';
final shareInfo = await accountManager.getShareInfo(accountManager.active.id);
_runAggregator(peerPort.sendPort);
setState(() {
_wifiIP = wifiIP;
_shareInfo = shareInfo;
});
});
}
@override
void dispose() {
_stopAggregator();
super.dispose();
}
@override
Widget build(BuildContext context) {
final qrSize = getScreenSize(context) / 2.5;
final shareInfo = _shareInfo;
return Scaffold(
appBar: AppBar(
title: Text('Sign Multisig Transaction'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(children: [
QrImage(
data: txSummaryString,
size: qrSize,
backgroundColor: Colors.white),
Padding(padding: EdgeInsets.all(8)),
Text('Scan this code on your other devices'),
]),
if (shareInfo != null)
SignerPie(qrSize.toInt(), _peerCount + 1,
shareInfo.threshold, shareInfo.participants),
],
)));
}
void _runAggregator(SendPort port) async {
final s = S.of(context);
await WarpApi.runAggregator(accountManager.active.share!.value, PORT, port);
// Need to wait a bit for the service to spin up
await Future.delayed(Duration(seconds: 3));
final tx = await WarpApi.submitTx(widget.txSummary.txJson, PORT);
SnackBar snackBar;
if (tx.startsWith("00")) { // first byte is success/error
final txId = WarpApi.broadcastHex(tx.substring(2));
snackBar = SnackBar(content: Text("${s.txId}: $txId"));
}
else {
final msgHex = tx.substring(2);
final s = hex.decode(msgHex);
final dec = Utf8Decoder();
final msg = dec.convert(s);
snackBar = SnackBar(content: Text(msg));
}
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
Navigator.of(context).pop();
}
void _stopAggregator() {
WarpApi.stopAggregator();
}
String get txSummaryString =>
"http://$_wifiIP:$PORT|${widget.txSummary.address}|${widget.txSummary.amount}";
}
class SignerPie extends StatelessWidget {
final int size;
final int available;
final int threshold;
final int participants;
SignerPie(this.size, this.available, this.threshold, this.participants);
@override
Widget build(BuildContext context) {
final remaining = threshold - available;
return Column(children: [
SizedBox(
width: 150,
height: 150,
child: PieChart(PieChartData(
sectionsSpace: 1,
centerSpaceRadius: 0,
startDegreeOffset: -90,
sections: showingSections()))),
Text(S.of(context).numMoreSignersNeeded(remaining)),
]);
}
List<PieChartSectionData> showingSections() {
final value = 100 / participants;
final size = this.size / 3;
return List.generate(
participants,
(i) {
if (i < available) {
return PieChartSectionData(
color: Colors.green, title: '', value: value, radius: size);
} else if (i < threshold) {
return PieChartSectionData(
color: Colors.red, title: '', value: value, radius: size);
}
return PieChartSectionData(
color: Colors.green, title: '', value: value, radius: size);
},
);
}
}
const SIGNER_PORT = 4001;
class MultisigPage extends StatefulWidget {
@override
MultisigState createState() => MultisigState();
}
class MultisigState extends State<MultisigPage> {
String _aggregatorUrl = "";
String _address = "";
int _amount = 0;
final _formKey = GlobalKey<FormState>();
int _participants = 2;
int _threshold = 2;
@override
Widget build(BuildContext context) {
final canMultisign = accountManager.active.share != null;
return Scaffold(
appBar: AppBar(title: Text(S.of(context).multisig)),
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(children: [
if (canMultisign) Card(
elevation: 1,
child: Padding(
padding: EdgeInsets.all(32),
child: Center(
child: ElevatedButton.icon(
onPressed: _scan,
icon: Icon(MdiIcons.signatureFreehand),
label: Text(S.of(context).sign))))),
if (accountManager.canPay) Card(
elevation: 1,
child: Padding(
padding: EdgeInsets.all(32),
child: Center(
child: Column(children: [
Text('Number of Participants'),
Slider(min: 2, max: 10, divisions: 8, onChanged: _onChangedParticipants, value: _participants.toDouble()),
Padding(padding: EdgeInsets.all(4)),
Text('Threshold (Number of Signers Required)'),
Slider(min: 2, max: 10, divisions: 8, onChanged: _onChangedThreshold, value: _threshold.toDouble()),
Padding(padding: EdgeInsets.all(8)),
Text('$_threshold / $_participants', style: Theme.of(context).textTheme.headline5),
Padding(padding: EdgeInsets.all(8)),
ElevatedButton.icon(onPressed: _split, icon: Icon(MdiIcons.rhombusSplit), label: Text(S.of(context).splitAccount))
])
)))
]))));
}
_onChangedParticipants(v) {
setState(() {
_participants = v.toInt();
if (_threshold > _participants)
_threshold = _participants;
});
}
_onChangedThreshold(v) {
setState(() {
_threshold = v.toInt();
if (_threshold > _participants)
_threshold = _participants;
});
}
_scan() async {
final s = S.of(context);
final txSummaryString = await scanCode(context);
if (txSummaryString == null) return;
final txParts = txSummaryString.split('|');
_aggregatorUrl = txParts[0];
_address = txParts[1];
_amount = int.parse(txParts[2]);
final amount = amountToString(_amount);
final approved = await showMessageBox(
context,
s.confirmSigning,
s.confirmSignATransactionToAddressFor(_address, amount),
s.approve);
if (approved) await sign();
}
_split() async {
final s = S.of(context);
final shareString = WarpApi.splitAccount(_threshold, _participants, accountManager.active.id);
Navigator.of(context).pushNamed('/multisig_shares', arguments: shareString);
}
sign() async {
final info = NetworkInfo();
final wifiIP = await info.getWifiIP();
final myUrl = 'http://$wifiIP:$SIGNER_PORT';
final share = accountManager.active.share;
if (share != null && _aggregatorUrl != null) {
final errorCode = await WarpApi.runMultiSigner(
share.value, _aggregatorUrl, myUrl, SIGNER_PORT);
String? msg;
switch (errorCode) {
case 0:
msg = "Transaction signed";
break;
case 1:
msg = "Duplicate signer";
break;
}
if (msg != null) {
final snackBar = SnackBar(content: Text(msg));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
}
}
Navigator.of(context).pop();
}
}
class MultisigSharesPage extends StatelessWidget {
final String shareString;
MultisigSharesPage(this.shareString);
@override
Widget build(BuildContext context) {
final shares = shareString.split('|').asMap().entries.toList();
final account = accountManager.active;
return Scaffold(
appBar: AppBar(title: Text(S.of(context).multisigShares)),
body: ListView(
padding: EdgeInsets.all(8),
children: ListTile.divideTiles(context: context, tiles: [
for (var share in shares)
ListTile(title: Text(share.value), trailing: Icon(MdiIcons.qrcode),
onTap: () => showQR(context, share.value, "${account.name} - ms ${share.key+1}/${shares.length}"))
]).toList())
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:intl/intl.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'main.dart';
import 'store.dart';
@ -25,6 +25,7 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
padding: EdgeInsets.all(8),
scrollDirection: Axis.vertical,
child: Observer(builder: (context) {
accountManager.sortedNotes;
return PaginatedDataTable(
columns: [
DataColumn(
@ -58,6 +59,9 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
],
header: Text(S.of(context).selectNotesToExcludeFromPayments,
style: Theme.of(context).textTheme.bodyText2),
actions: [
IconButton(onPressed: _selectInverse, icon: Icon(MdiIcons.selectInverse)),
],
columnSpacing: 16,
showCheckboxColumn: false,
availableRowsPerPage: [5, 10, 25, 100],
@ -74,6 +78,10 @@ class _NoteState extends State<NoteWidget> with AutomaticKeepAliveClientMixin {
_onRowSelected(Note note) {
accountManager.excludeNote(note);
}
_selectInverse() {
accountManager.invertExcludedNotes();
}
}
class NotesDataSource extends DataTableSource {

View File

@ -16,25 +16,30 @@ class _RestorePageState extends State<RestorePage> {
final _formKey = GlobalKey<FormState>();
final _keyController = TextEditingController();
final _nameController = TextEditingController();
final _shareController = TextEditingController();
var _validKey = true;
var _isVK = false;
@override
Widget build(BuildContext context) {
final s = S.of(context);
return Scaffold(
appBar: AppBar(
title: Text("${coin.symbol} Wallet"),
),
body: Form(
body: GestureDetector(
onTap: () { FocusScope.of(context).unfocus(); },
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(children: [
TextFormField(
decoration: InputDecoration(labelText: S.of(context).accountName),
decoration: InputDecoration(labelText: s.accountName),
controller: _nameController,
validator: (String? name) {
if (name == null || name.isEmpty)
return S.of(context).accountNameIsRequired;
return s.accountNameIsRequired;
return null;
},
),
@ -43,9 +48,9 @@ class _RestorePageState extends State<RestorePage> {
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: S.of(context).key,
labelText: s.key,
hintText:
S.of(context).enterSeed),
s.enterSeed),
minLines: 4,
maxLines: 4,
controller: _keyController,
@ -55,12 +60,29 @@ class _RestorePageState extends State<RestorePage> {
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScan)
],
),
if (_isVK) Row(
children: [
Expanded(
child: TextFormField(
decoration: InputDecoration(
labelText: s.secretShare,
hintText: s.enterSecretShareIfAccountIsMultisignature),
minLines: 4,
maxLines: 4,
controller: _shareController,
// TODO: Check share
)),
IconButton(
icon: new Icon(MdiIcons.qrcodeScan), onPressed: _onScanShare)
],
),
ButtonBar(children:
confirmButtons(context, _validKey ? _onOK : null, okLabel: S.of(context).add, okIcon: Icon(Icons.add)))
]))));
confirmButtons(context, _validKey ? _onOK : null, okLabel: s.add, okIcon: Icon(Icons.add)))
])))));
}
_onOK() async {
final s = S.of(context);
if (_formKey.currentState!.validate()) {
final account =
WarpApi.newAccount(_nameController.text, _keyController.text);
@ -69,18 +91,20 @@ class _RestorePageState extends State<RestorePage> {
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: Text(S.of(context).duplicateAccount),
content: Text(S.of(context).thisAccountAlreadyExists),
title: Text(s.duplicateAccount),
content: Text(s.thisAccountAlreadyExists),
actions: [
ElevatedButton.icon(
onPressed: () {
Navigator.of(context).pop();
},
label: Text(S.of(context).ok),
label: Text(s.ok),
icon: Icon(Icons.done))
]));
}
else {
if (_shareController.text.isNotEmpty)
accountManager.storeShareSecret(account, _shareController.text);
await accountManager.refresh();
if (_keyController.text != "") {
syncStatus.setAccountRestored(true);
@ -97,7 +121,9 @@ class _RestorePageState extends State<RestorePage> {
_checkKey(key) {
setState(() {
_validKey = key == "" || WarpApi.validKey(key);
final keyType = WarpApi.validKey(key);
_validKey = key == "" || keyType >= 0;
_isVK = keyType == 2;
});
}
@ -106,7 +132,15 @@ class _RestorePageState extends State<RestorePage> {
if (key != null)
setState(() {
_keyController.text = key;
_validKey = key == "" || WarpApi.validKey(key);
_checkKey(key);
});
}
void _onScanShare() async {
final key = await scanCode(context);
if (key != null)
setState(() {
_shareController.text = key;
});
}
}

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:warp/dualmoneyinput.dart';
import 'package:warp_api/types.dart';
import 'package:warp_api/warp_api.dart';
import 'package:decimal/decimal.dart';
import 'package:path_provider/path_provider.dart';
@ -25,6 +26,8 @@ class SendPage extends StatefulWidget {
@override
SendState createState() => SendState();
bool get isMulti => args?.isMulti ?? false;
}
class SendState extends State<SendPage> {
@ -46,11 +49,15 @@ class SendState extends State<SendPage> {
var _useMillis = true;
var _useTransparent = settings.shieldBalance;
ReactionDisposer? _newBlockAutorunDispose;
final _fee = DEFAULT_FEE;
var _usedBalance = 0;
@override
initState() {
if (widget.args?.contact != null)
_addressController.text = widget.args!.contact!.address;
final recipients = widget.args?.recipients ?? [];
_usedBalance = recipients.fold(0, (acc, r) => acc + r.amount);
final uri = widget.args?.uri;
if (uri != null)
@ -67,7 +74,8 @@ class SendState extends State<SendPage> {
final excludedBalance = await accountManager.getExcludedBalance();
final underConfirmedBalance =
await accountManager.getUnderConfirmedBalance();
final unconfirmedSpentBalance = await accountManager.getUnconfirmedSpentBalance();
final unconfirmedSpentBalance =
await accountManager.getUnconfirmedSpentBalance();
final unconfirmedBalance = accountManager.unconfirmedBalance;
setState(() {
_sBalance = sBalance;
@ -129,9 +137,13 @@ class SendState extends State<SendPage> {
icon: new Icon(MdiIcons.qrcodeScan),
onPressed: _onScan)
]),
DualMoneyInputWidget(key: _amountKey, child: TextButton(child: Text(s.max), onPressed: _onMax), spendable: spendable),
BalanceTable(_sBalance, _tBalance, _useTransparent, _excludedBalance,
_underConfirmedBalance, change),
DualMoneyInputWidget(
key: _amountKey,
child:
TextButton(child: Text(s.max), onPressed: _onMax),
spendable: spendable),
BalanceTable(_sBalance, _tBalance, _useTransparent,
_excludedBalance, _underConfirmedBalance, change, _usedBalance, _fee),
ExpansionPanelList(
expansionCallback: (_, isExpanded) {
setState(() {
@ -157,7 +169,7 @@ class SendState extends State<SendPage> {
title: Text(s.roundToMillis),
value: _useMillis,
onChanged: _setUseMillis),
if (accountManager.canPay)
if (accountManager.canPay && !widget.isMulti)
CheckboxListTile(
title: Text(s.useTransparentBalance),
value: _useTransparent,
@ -169,7 +181,9 @@ class SendState extends State<SendPage> {
labelText: s.maxAmountPerNote),
keyboardType: TextInputType.number,
controller: _maxAmountController,
inputFormatters: [makeInputFormatter(amountInput?.useMillis)],
inputFormatters: [
makeInputFormatter(amountInput?.useMillis)
],
validator: _checkMaxAmountPerNote,
onSaved: _onSavedMaxAmountPerNote,
)),
@ -180,7 +194,8 @@ class SendState extends State<SendPage> {
Padding(padding: EdgeInsets.all(8)),
ButtonBar(
children: confirmButtons(context, _onSend,
okLabel: s.send, okIcon: Icon(MdiIcons.send)))
okLabel: widget.isMulti ? s.add : s.send,
okIcon: Icon(MdiIcons.send)))
])))));
}
@ -276,68 +291,52 @@ class SendState extends State<SendPage> {
form.save();
final amount = amountInput?.amount ?? 0;
final aZEC = amountToString(amount);
final approved = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
title: Text(s.pleaseConfirm),
content: SingleChildScrollView(
child: Text(s.sendingAzecCointickerToAddress(
aZEC, coin.ticker, _address))),
actions: confirmButtons(
context, () => Navigator.of(context).pop(true),
okLabel: s.approve, cancelValue: false)));
final approved = widget.isMulti ||
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) => AlertDialog(
title: Text(s.pleaseConfirm),
content: SingleChildScrollView(
child: Text(s.sendingAzecCointickerToAddress(
aZEC, coin.ticker, _address))),
actions: confirmButtons(
context, () => Navigator.of(context).pop(true),
okLabel: s.approve, cancelValue: false)));
if (approved) {
Navigator.of(context).pop();
final snackBar1 = SnackBar(content: Text(s.preparingTransaction));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
int maxAmountPerNote = (_maxAmountPerNote * ZECUNIT_DECIMAL).toInt();
final memo = _memoController.text;
final address = unwrapUA(_address);
final recipient = Recipient(
address,
amount,
memo,
maxAmountPerNote,
);
if (accountManager.canPay) {
if (settings.protectSend &&
!await authenticate(context, s.pleaseAuthenticateToSend)) return;
final tx = await compute(
sendPayment,
PaymentParams(
accountManager.active.id,
address,
amount,
memo,
maxAmountPerNote,
settings.anchorOffset,
_useTransparent,
progressPort.sendPort));
final snackBar2 = SnackBar(content: Text("${s.txId}: $tx"));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
await accountManager.fetchAccountData(true);
} else {
Directory tempDir = await getTemporaryDirectory();
String filename = "${tempDir.path}/tx.json";
final msg = WarpApi.prepareTx(accountManager.active.id, address,
amountInput?.amount ?? 0, memo, maxAmountPerNote, settings.anchorOffset, filename);
Share.shareFiles([filename], subject: s.unsignedTransactionFile);
final snackBar2 = SnackBar(content: Text(msg));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
}
if (!widget.isMulti)
// send closes the page
await send(context, [recipient], _useTransparent);
else
Navigator.of(context).pop(recipient);
}
}
}
int amountInZAT(Decimal v) => (v * ZECUNIT_DECIMAL).toInt();
String amountFromZAT(int v) =>
(Decimal.fromInt(v) / ZECUNIT_DECIMAL).toString();
get spendable => math.max((_useTransparent ? _tBalance : 0) +
_sBalance - _excludedBalance - _underConfirmedBalance - DEFAULT_FEE, 0);
get spendable => math.max(
(_useTransparent ? _tBalance : 0) +
_sBalance -
_excludedBalance -
_underConfirmedBalance -
_usedBalance -
_fee,
0);
get change => _unconfirmedSpentBalance + _unconfirmedBalance;
@ -351,9 +350,11 @@ class BalanceTable extends StatelessWidget {
final int excludedBalance;
final int underConfirmedBalance;
final int change;
final int used;
final int fee;
BalanceTable(this.sBalance, this.tBalance, this.useTBalance, this.excludedBalance,
this.underConfirmedBalance, this.change);
BalanceTable(this.sBalance, this.tBalance, this.useTBalance,
this.excludedBalance, this.underConfirmedBalance, this.change, this.used, this.fee);
@override
Widget build(BuildContext context) {
@ -371,23 +372,31 @@ class BalanceTable extends StatelessWidget {
]));
return Container(
decoration: BoxDecoration(border: Border.all(color: theme.dividerColor, width: 1),
borderRadius: BorderRadius.circular(8)),
decoration: BoxDecoration(
border: Border.all(color: theme.dividerColor, width: 1),
borderRadius: BorderRadius.circular(8)),
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
BalanceRow(Text(S.of(context).totalBalance), totalBalance),
BalanceRow(Text(S.of(context).underConfirmed), -underConfirmed),
BalanceRow(Text(S.of(context).excludedNotes), -excludedBalance),
if (!useTBalance) BalanceRow(tBalanceLabel, -tBalance),
BalanceRow(Text(S.of(context).spendableBalance), spendable,
style: TextStyle(color: Theme.of(context).primaryColor)),
]));
BalanceRow(Text(S.of(context).totalBalance), totalBalance),
BalanceRow(Text(S.of(context).underConfirmed), -underConfirmed),
BalanceRow(Text(S.of(context).excludedNotes), -excludedBalance),
if (!useTBalance) BalanceRow(tBalanceLabel, -tBalance),
BalanceRow(Text(S.of(context).spendableBalance), spendable,
style: TextStyle(color: Theme.of(context).primaryColor)),
]));
}
get totalBalance => sBalance + tBalance + change;
get totalBalance => sBalance + tBalance + change - used - fee;
get underConfirmed => -underConfirmedBalance - change;
get spendable => math.max(
sBalance + (useTBalance ? tBalance : 0) - excludedBalance - underConfirmedBalance - DEFAULT_FEE, 0);
sBalance +
(useTBalance ? tBalance : 0) -
excludedBalance -
underConfirmedBalance -
used -
fee,
0);
}
class BalanceRow extends StatelessWidget {
@ -402,23 +411,60 @@ class BalanceRow extends StatelessWidget {
return ListTile(
title: label,
trailing: Text(amountToString(amount),
style: TextStyle(fontFeatures: [FontFeature.tabularFigures()]).merge(style)),
style: TextStyle(fontFeatures: [FontFeature.tabularFigures()])
.merge(style)),
visualDensity: VisualDensity(horizontal: 0, vertical: -4));
}
}
sendPayment(PaymentParams param) async {
param.port.send(0);
final tx = await WarpApi.sendPayment(
param.account,
param.address,
param.amount,
param.memo,
param.maxAmountPerNote,
param.anchorOffset,
param.useTransparent, (percent) {
param.port.send(percent);
});
param.port.send(0);
return tx;
Future<void> send(BuildContext context, List<Recipient> recipients, bool useTransparent) async {
final s = S.of(context);
String address = "";
int amount = 0;
for (var r in recipients) {
amount += r.amount;
if (address.isEmpty)
address = r.address;
else
address = s.multipleAddresses;
}
final snackBar1 = SnackBar(content: Text(s.preparingTransaction));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar1);
if (accountManager.canPay) {
if (settings.protectSend &&
!await authenticate(context, s.pleaseAuthenticateToSend)) return;
Navigator.of(context).pop();
final tx = await WarpApi.sendPayment(accountManager.active.id, recipients,
useTransparent, settings.anchorOffset, (progress) {
progressPort.sendPort.send(progress);
});
progressPort.sendPort.send(0);
final snackBar2 = SnackBar(content: Text("${s.txId}: $tx"));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
await accountManager.fetchAccountData(true);
} else {
Directory tempDir = await getTemporaryDirectory();
String filename = "${tempDir.path}/tx.json";
final txjson = WarpApi.prepareTx(accountManager.active.id, recipients,
useTransparent, settings.anchorOffset, filename);
if (coin.supportsMultisig && accountManager.active.share != null) {
final txSummary = TxSummary(address, amount, txjson);
Navigator.of(context).pushReplacementNamed('/multisign', arguments: txSummary);
} else {
final file = File(filename);
await file.writeAsString(txjson);
Share.shareFiles([filename], subject: s.unsignedTransactionFile);
final snackBar2 = SnackBar(content: Text(s.fileSaved));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar2);
Navigator.of(context).pop();
}
}
}

View File

@ -237,7 +237,6 @@ class SettingsState extends State<SettingsPage> {
_onSave() async {
final form = _settingsFormKey.currentState!;
if (form.validate()) {
print(_needAuth);
if (_needAuth && !await authenticate(context, S.of(context).protectSendSettingChanged)) return;
form.save();
Navigator.of(context).pop();

View File

@ -10,6 +10,7 @@ import 'package:mobx/mobx.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:warp_api/warp_api.dart';
import 'package:warp_api/types.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;
@ -111,9 +112,11 @@ abstract class _Settings with Store {
protectSend = prefs.getBool('protect_send') ?? false;
primaryColorValue = prefs.getInt('primary') ?? Colors.blue.value;
primaryVariantColorValue = prefs.getInt('primary.variant') ?? Colors.blueAccent.value;
primaryVariantColorValue =
prefs.getInt('primary.variant') ?? Colors.blueAccent.value;
secondaryColorValue = prefs.getInt('secondary') ?? Colors.green.value;
secondaryVariantColorValue = prefs.getInt('secondary.variant') ?? Colors.greenAccent.value;
secondaryVariantColorValue =
prefs.getInt('secondary.variant') ?? Colors.greenAccent.value;
_updateThemeData();
Future.microtask(_loadCurrencies); // lazily
@ -168,28 +171,25 @@ abstract class _Settings with Store {
void _updateThemeData() {
if (theme == 'custom') {
final colors = FlexSchemeColor(primary: Color(primaryColorValue),
final colors = FlexSchemeColor(
primary: Color(primaryColorValue),
primaryVariant: Color(primaryVariantColorValue),
secondary: Color(secondaryColorValue),
secondaryVariant: Color(secondaryVariantColorValue));
final scheme = FlexSchemeData(name: 'custom',
final scheme = FlexSchemeData(
name: 'custom',
description: 'Custom Theme',
light: colors,
dark: colors);
switch (themeBrightness) {
case 'light':
themeData = FlexColorScheme
.light(colors: scheme.light)
.toTheme;
themeData = FlexColorScheme.light(colors: scheme.light).toTheme;
break;
case 'dark':
themeData = FlexColorScheme
.dark(colors: scheme.dark)
.toTheme;
themeData = FlexColorScheme.dark(colors: scheme.dark).toTheme;
break;
}
}
else {
} else {
FlexScheme scheme;
switch (theme) {
case 'gold':
@ -209,21 +209,18 @@ abstract class _Settings with Store {
}
switch (themeBrightness) {
case 'light':
themeData = FlexColorScheme
.light(scheme: scheme)
.toTheme;
themeData = FlexColorScheme.light(scheme: scheme).toTheme;
break;
case 'dark':
themeData = FlexColorScheme
.dark(scheme: scheme)
.toTheme;
themeData = FlexColorScheme.dark(scheme: scheme).toTheme;
break;
}
}
}
@action
Future<void> updateCustomThemeColors(Color primary, Color primaryVariant, Color secondary, Color secondaryVariant) async {
Future<void> updateCustomThemeColors(Color primary, Color primaryVariant,
Color secondary, Color secondaryVariant) async {
final prefs = await SharedPreferences.getInstance();
primaryColorValue = primary.value;
primaryVariantColorValue = primaryVariant.value;
@ -347,7 +344,7 @@ abstract class _AccountManager with Store {
late Database db;
@observable
Account active = Account(0, "", "", 0);
Account active = Account(0, "", "", 0, null);
@observable
bool canPay = false;
@ -454,10 +451,12 @@ abstract class _AccountManager with Store {
Future<Backup> getBackup(int account) async {
final List<Map> res = await db.rawQuery(
"SELECT seed, sk, ivk FROM accounts WHERE id_account = ?1",
"SELECT name, seed, sk, ivk FROM accounts WHERE id_account = ?1",
[account]);
if (res.isEmpty) throw Exception("Account N/A");
final share = await getShareInfo(account);
final row = res[0];
final name = row['name'];
final seed = row['seed'];
final sk = row['sk'];
final ivk = row['ivk'];
@ -467,7 +466,7 @@ abstract class _AccountManager with Store {
else if (sk != null)
type = 1;
else if (ivk != null) type = 2;
return Backup(type, seed, sk, ivk);
return Backup(type, name, seed, sk, ivk, share);
}
Future<int> _getBalance(int accountId) async {
@ -480,29 +479,33 @@ abstract class _AccountManager with Store {
Future<int> getShieldedBalance() async {
return Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
[active.id])) ?? 0;
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
[active.id])) ??
0;
}
Future<int> getUnconfirmedSpentBalance() async {
return Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
[active.id])) ?? 0;
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
[active.id])) ??
0;
}
Future<int> getUnderConfirmedBalance() async {
final height = syncStatus.latestHeight - settings.anchorOffset;
return Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND height > ?2",
[active.id, height])) ?? 0;
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND height > ?2",
[active.id, height])) ??
0;
}
Future<int> getExcludedBalance() async {
final height = syncStatus.latestHeight - settings.anchorOffset;
final amount = Sqflite.firstIntValue(await db.rawQuery(
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL "
"AND height <= ?2 AND excluded",
[active.id, height])) ?? 0;
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL "
"AND height <= ?2 AND excluded",
[active.id, height])) ??
0;
return amount;
}
@ -518,28 +521,45 @@ abstract class _AccountManager with Store {
Future<List<Account>> _list() async {
final List<Map> res = await db.rawQuery(
"WITH notes AS (SELECT a.id_account, a.name, a.address, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account) "
"SELECT id_account, name, address, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account",
"WITH notes AS (SELECT a.id_account, a.name, a.address, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account),"
"accounts2 AS (SELECT id_account, name, address, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account) "
"SELECT a.id_account, a.name, a.address, a.balance, ss.idx, ss.secret, ss.participants, ss.threshold FROM accounts2 a LEFT JOIN secret_shares ss ON a.id_account = ss.account",
[]);
return res
.map((r) =>
Account(r['id_account'], r['name'], r['address'], r['balance']))
.toList();
return res.map((r) {
final shareInfo = r['secret'] != null
? ShareInfo(r['idx'], r['threshold'], r['participants'], r['secret'])
: null;
return Account(
r['id_account'], r['name'], r['address'], r['balance'], shareInfo);
}).toList();
}
@action
Future<void> delete(int account) async {
WarpApi.deleteAccount(account);
if (account == active.id)
resetToDefaultAccount();
if (account == active.id) resetToDefaultAccount();
}
@action
Future<void> changeAccountName(String name) async {
Future<void> changeAccountName(Account account, String name) async {
await db.execute("UPDATE accounts SET name = ?2 WHERE id_account = ?1",
[active.id, name]);
[account.id, name]);
await refresh();
await setActiveAccountId(active.id);
}
@action
void storeShareSecret(int account, String secretKey) {
WarpApi.storeShareSecret(account, secretKey);
}
Future<ShareInfo?> getShareInfo(int accountId) async {
final List<Map> res = await db.rawQuery(
"SELECT idx, threshold, participants, secret FROM secret_shares WHERE account = ?1",
[accountId]);
if (res.isEmpty) return null;
final row = res[0];
return ShareInfo(
row['idx'], row['threshold'], row['participants'], row['secret']);
}
@action
@ -617,8 +637,16 @@ abstract class _AccountManager with Store {
final shortTxid = fullTxId.substring(0, 8);
final timestamp = txDateFormat
.format(DateTime.fromMillisecondsSinceEpoch(row['timestamp'] * 1000));
return Tx(row['id_tx'], row['height'], timestamp, shortTxid, fullTxId,
row['value'] / ZECUNIT, row['address'] ?? "", row['name'], row['memo'] ?? "");
return Tx(
row['id_tx'],
row['height'],
timestamp,
shortTxid,
fullTxId,
row['value'] / ZECUNIT,
row['address'] ?? "",
row['name'],
row['memo'] ?? "");
}).toList();
dataEpoch = DateTime.now().millisecondsSinceEpoch;
@ -629,36 +657,45 @@ abstract class _AccountManager with Store {
List<Note> get sortedNotes {
var notes2 = [...notes];
switch (noteSortConfig.field) {
case "time": return _sort(notes2, (Note note) => note.height, noteSortConfig.order);
case "amount": return _sort(notes2, (Note note) => note.value, noteSortConfig.order);
case "time":
return _sort(notes2, (Note note) => note.height, noteSortConfig.order);
case "amount":
return _sort(notes2, (Note note) => note.value, noteSortConfig.order);
}
return notes2;
}
@action
Future<void> sortNotes(String field) async {
noteSortConfig.sortOn(field);
void sortNotes(String field) {
noteSortConfig = noteSortConfig.sortOn(field);
}
@computed
List<Tx> get sortedTxs {
var txs2 = [...txs];
switch (txSortConfig.field) {
case "time": return _sort(txs2, (Tx tx) => tx.height, txSortConfig.order);
case "amount": return _sort(txs2, (Tx tx) => tx.value, txSortConfig.order);
case "txid": return _sort(txs2, (Tx tx) => tx.txid, txSortConfig.order);
case "address": return _sort(txs2, (Tx tx) => tx.contact ?? tx.address, txSortConfig.order);
case "memo": return _sort(txs2, (Tx tx) => tx.memo, txSortConfig.order);
case "time":
return _sort(txs2, (Tx tx) => tx.height, txSortConfig.order);
case "amount":
return _sort(txs2, (Tx tx) => tx.value, txSortConfig.order);
case "txid":
return _sort(txs2, (Tx tx) => tx.txid, txSortConfig.order);
case "address":
return _sort(
txs2, (Tx tx) => tx.contact ?? tx.address, txSortConfig.order);
case "memo":
return _sort(txs2, (Tx tx) => tx.memo, txSortConfig.order);
}
return txs2;
}
@action
Future<void> sortTx(String field) async {
txSortConfig.sortOn(field);
void sortTx(String field) {
txSortConfig = txSortConfig.sortOn(field);
}
List<C> _sort<C extends HasHeight, T extends Comparable>(List<C> txs, T Function(C) project, SortOrder order) {
List<C> _sort<C extends HasHeight, T extends Comparable>(
List<C> txs, T Function(C) project, SortOrder order) {
switch (order) {
case SortOrder.Ascending:
txs.sort((a, b) => project(a).compareTo(project(b)));
@ -711,7 +748,8 @@ abstract class _AccountManager with Store {
_accountBalances.add(ab);
b -= value;
}
_accountBalances.add(AccountBalance(DateTime.fromMillisecondsSinceEpoch(range.start), b / ZECUNIT));
_accountBalances.add(AccountBalance(
DateTime.fromMillisecondsSinceEpoch(range.start), b / ZECUNIT));
_accountBalances = _accountBalances.reversed.toList();
accountBalances = sampleDaily<AccountBalance, double, double>(
_accountBalances,
@ -814,7 +852,7 @@ abstract class _AccountManager with Store {
var _pnls = [...pnls.reversed];
return _pnls;
}
return pnls;
return pnls;
}
@action
@ -832,6 +870,14 @@ abstract class _AccountManager with Store {
[note.id, note.excluded]);
}
@action
Future<void> invertExcludedNotes() async {
await db.execute(
"UPDATE received_notes SET excluded = NOT(COALESCE(excluded, 0)) WHERE account = ?1",
[active.id]);
notes = notes.map((n) => n.invertExcluded).toList();
}
@action
Future<void> updateTBalance() async {
_updateTBalance(active.id);
@ -843,7 +889,8 @@ abstract class _AccountManager with Store {
}
void autoshield() {
if (settings.autoShieldThreshold != 0.0 && tbalance / ZECUNIT >= settings.autoShieldThreshold) {
if (settings.autoShieldThreshold != 0.0 &&
tbalance / ZECUNIT >= settings.autoShieldThreshold) {
WarpApi.shieldTAddr(active.id);
}
}
@ -869,8 +916,9 @@ class Account {
final String name;
final String address;
final int balance;
final ShareInfo? share;
Account(this.id, this.name, this.address, this.balance);
Account(this.id, this.name, this.address, this.balance, this.share);
}
class PriceStore = _PriceStore with _$PriceStore;
@ -941,17 +989,15 @@ abstract class _SyncStatus with Store {
Future<void> sync(BuildContext context) async {
eta.reset();
syncing = true;
final snackBar =
SnackBar(content: Text(S
.of(context)
.rescanRequested));
final snackBar = SnackBar(content: Text(S.of(context).rescanRequested));
rootScaffoldMessengerKey.currentState?.showSnackBar(snackBar);
setSyncHeight(0);
WarpApi.rewindToHeight(0);
WarpApi.truncateData();
contacts.markContactsDirty(false);
await syncStatus.update();
final params = SyncParams(settings.getTx, settings.anchorOffset, syncPort.sendPort);
final params =
SyncParams(settings.getTx, settings.anchorOffset, syncPort.sendPort);
await compute(WarpApi.warpSync, params);
syncing = false;
eta.reset();
@ -1017,12 +1063,16 @@ abstract class _ETAStore with Store {
final p = prev;
final c = current;
if (p == null || c == null) return "";
if (c.timestamp.millisecondsSinceEpoch == p.timestamp.millisecondsSinceEpoch) return "";
final speed = (c.height - p.height) / (c.timestamp.millisecondsSinceEpoch - p.timestamp.millisecondsSinceEpoch);
if (c.timestamp.millisecondsSinceEpoch ==
p.timestamp.millisecondsSinceEpoch) return "";
final speed = (c.height - p.height) /
(c.timestamp.millisecondsSinceEpoch -
p.timestamp.millisecondsSinceEpoch);
if (speed == 0) return "";
final eta = (syncStatus.latestHeight - c.height) / speed;
if (eta <= 0) return "";
final duration = Duration(milliseconds: eta.floor()).toString().split('.')[0];
final duration =
Duration(milliseconds: eta.floor()).toString().split('.')[0];
return "(ETA: $duration)";
}
}
@ -1109,6 +1159,9 @@ class Note extends HasHeight {
Note(this.id, this.height, this.timestamp, this.value, this.excluded,
this.spent);
Note get invertExcluded =>
Note(id, height, timestamp, value, !excluded, spent);
}
class Tx extends HasHeight {
@ -1142,12 +1195,14 @@ class AccountBalance {
}
class Backup {
int type;
final int type;
final String name;
final String? seed;
final String? sk;
final String ivk;
final ShareInfo? share;
Backup(this.type, this.seed, this.sk, this.ivk);
Backup(this.type, this.name, this.seed, this.sk, this.ivk, this.share);
String value() {
switch (type) {
@ -1181,20 +1236,6 @@ enum SortOrder {
SortOrder nextSortOrder(SortOrder order) =>
SortOrder.values[(order.index + 1) % 3];
@JsonSerializable()
class Recipient {
final String address;
final int amount;
final String memo;
Recipient(this.address, this.amount, this.memo);
factory Recipient.fromJson(Map<String, dynamic> json) =>
_$RecipientFromJson(json);
Map<String, dynamic> toJson() => _$RecipientToJson(this);
}
class PnL {
final DateTime timestamp;
final double price;
@ -1246,29 +1287,21 @@ class TimeRange {
}
class SortConfig {
@observable
String field;
@observable
SortOrder order;
SortConfig(this.field, this.order);
@action
void sortOn(String field) {
if (field != this.field)
order = SortOrder.Ascending;
else
order = nextSortOrder(order);
this.field = field;
SortConfig sortOn(String field) {
final order = field != this.field ? SortOrder.Ascending : nextSortOrder(this.order);
return SortConfig(field, order);
}
String getIndicator(String field) {
if (this.field != field) return '';
if (order == SortOrder.Ascending)
return ' \u2191';
if (order == SortOrder.Descending)
return ' \u2193';
if (order == SortOrder.Ascending) return ' \u2191';
if (order == SortOrder.Descending) return ' \u2193';
return '';
}
}
@ -1288,8 +1321,27 @@ class DecodedPaymentURI {
}
class SendPageArgs {
final bool isMulti;
final Contact? contact;
final String? uri;
final List<Recipient> recipients;
SendPageArgs({this.contact, this.uri});
SendPageArgs({this.isMulti = false, this.contact, this.uri, this.recipients = const[]});
}
class ShareInfo {
final int index;
final int threshold;
final int participants;
final String value;
ShareInfo(this.index, this.threshold, this.participants, this.value);
}
class TxSummary {
final String address;
final int amount;
final String txJson;
TxSummary(this.address, this.amount, this.txJson);
}

View File

@ -22,7 +22,11 @@ hex = "0.4.3"
[dependencies.zcash_primitives]
git = "https://github.com/hhanh00/librustzcash.git"
rev = "6baf1ba63261dda09e5b1d4b476977842a1f61c8"
rev = "1518b145f8ee67e144fa8337c7dfd4c8cff899c9"
[dependencies.zcash_multisig]
git = "https://github.com/hhanh00/zcash-multisig.git"
rev = "ce5baab5a25021950f51a9d53dc6c39ea9fe2997"
[build-dependencies]
cbindgen = "0.19.0"

View File

@ -1,5 +1,6 @@
#ifndef __APPLE__
typedef char int8_t;
typedef short int uint16_t;
typedef long long int int64_t;
typedef long long int uint64_t;
typedef long long int uintptr_t;
@ -17,7 +18,7 @@ void dart_post_cobject(DartPostCObjectFnType ptr);
uint32_t get_latest_height(void);
bool is_valid_key(char *seed);
int8_t is_valid_key(char *seed);
bool valid_address(char *address);
@ -29,18 +30,10 @@ int32_t new_account(char *name, char *data);
int64_t get_mempool_balance(void);
const char *send_payment(uint32_t account,
char *address,
uint64_t amount,
char *memo,
uint64_t max_amount_per_note,
uint32_t anchor_offset,
bool shield_transparent_balance,
int64_t port);
const char *send_multi_payment(uint32_t account,
char *recipients_json,
uint32_t anchor_offset,
bool use_transparent,
int64_t port);
int8_t try_warp_sync(bool get_tx, uint32_t anchor_offset);
@ -59,16 +52,15 @@ char *shield_taddr(uint32_t account);
void set_lwd_url(char *url);
char *prepare_offline_tx(uint32_t account,
char *to_address,
uint64_t amount,
char *memo,
uint64_t max_amount_per_note,
uint32_t anchor_offset,
char *tx_filename);
char *prepare_multi_payment(uint32_t account,
char *recipients_json,
bool use_transparent,
uint32_t anchor_offset);
char *broadcast(char *tx_filename);
char *broadcast_txhex(char *txhex);
uint32_t sync_historical_prices(int64_t now, uint32_t days, char *currency);
char *get_ua(char *sapling_addr, char *transparent_addr);
@ -87,4 +79,18 @@ char *make_payment_uri(char *address, uint64_t amount, char *memo);
char *parse_payment_uri(char *uri);
void store_share_secret(uint32_t account, char *secret);
char *get_share_secret(uint32_t account);
void run_aggregator(char *secret_share, uint16_t port, int64_t send_port);
void shutdown_aggregator(void);
char *submit_multisig_tx(char *tx_json, uint16_t port);
uint32_t run_multi_signer(char *secret_share, char *aggregator_url, char *my_url, uint16_t port);
char *split_account(uint32_t threshold, uint32_t participants, uint32_t account);
void dummy_export(void);

View File

@ -1,5 +1,6 @@
#ifndef __APPLE__
typedef char int8_t;
typedef short int uint16_t;
typedef long long int int64_t;
typedef long long int uint64_t;
typedef long long int uintptr_t;

View File

@ -4,15 +4,23 @@ use android_logger::Config;
use log::{error, info, Level};
use once_cell::sync::OnceCell;
use std::fs::File;
use std::io::{Read, Write};
use std::io::Read;
use std::sync::{Mutex, MutexGuard};
use sync::{broadcast_tx, ChainError, MemPool, Wallet};
use tokio::runtime::Runtime;
use tokio::time::Duration;
use zcash_multisig::{
run_aggregator_service, run_signer_service, signer_connect_to_aggregator, submit_tx,
SecretShare,
};
use zcash_primitives::transaction::builder::Progress;
static RUNTIME: OnceCell<Mutex<Runtime>> = OnceCell::new();
static WALLET: OnceCell<Mutex<Wallet>> = OnceCell::new();
static MEMPOOL: OnceCell<Mutex<MemPool>> = OnceCell::new();
static SYNCLOCK: OnceCell<Mutex<()>> = OnceCell::new();
static MULTISIG_AGG_LOCK: OnceCell<Mutex<MultisigAggregator>> = OnceCell::new();
static MULTISIG_SIGN_LOCK: OnceCell<Mutex<MultisigClient>> = OnceCell::new();
fn get_lock<T>(cell: &OnceCell<Mutex<T>>) -> anyhow::Result<MutexGuard<T>> {
cell.get()
@ -48,6 +56,7 @@ fn log_result_string(result: anyhow::Result<String>) -> String {
pub fn init_wallet(db_path: &str, ld_url: &str) {
android_logger::init_once(Config::default().with_min_level(Level::Info));
info!("Init");
RUNTIME.get_or_init(|| Mutex::new(Runtime::new().unwrap()));
WALLET.get_or_init(|| {
info!("Wallet Init");
let wallet = Wallet::new(db_path, ld_url);
@ -59,6 +68,8 @@ pub fn init_wallet(db_path: &str, ld_url: &str) {
Mutex::new(mempool)
});
SYNCLOCK.get_or_init(|| Mutex::new(()));
MULTISIG_AGG_LOCK.get_or_init(|| Mutex::new(MultisigAggregator::new()));
MULTISIG_SIGN_LOCK.get_or_init(|| Mutex::new(MultisigClient::new()));
}
pub fn new_account(name: &str, data: &str) -> i32 {
@ -192,28 +203,25 @@ fn report_progress(progress: Progress, port: i64) {
}
}
pub fn send_payment(
pub fn send_multi_payment(
account: u32,
address: &str,
amount: u64,
memo: &str,
max_amount_per_note: u64,
anchor_offset: u32,
recipients_json: &str,
use_transparent: bool,
anchor_offset: u32,
port: i64,
) -> String {
let r = get_runtime();
let res = r.block_on(async {
let mut wallet = get_lock(&WALLET)?;
let height = sync::latest_height(&wallet.ld_url).await?;
let recipients = Wallet::parse_recipients(recipients_json)?;
let res = wallet
.send_payment(
.build_sign_send_multi_payment(
account,
address,
amount,
memo,
max_amount_per_note,
anchor_offset,
height,
&recipients,
use_transparent,
anchor_offset,
move |progress| {
report_progress(progress, port);
},
@ -224,25 +232,6 @@ pub fn send_payment(
log_result_string(res)
}
pub fn send_multi_payment(
account: u32,
recipients_json: &str,
anchor_offset: u32,
port: i64,
) -> String {
let r = get_runtime();
let res = r.block_on(async {
let mut wallet = get_lock(&WALLET)?;
let res = wallet
.send_multi_payment(account, recipients_json, anchor_offset, move |progress| {
report_progress(progress, port);
})
.await?;
Ok(res)
});
log_result_string(res)
}
pub fn skip_to_last_height() {
let r = get_runtime();
let res = r.block_on(async {
@ -306,7 +295,8 @@ pub fn shield_taddr(account: u32) -> String {
let r = get_runtime();
let res = r.block_on(async {
let mut wallet = get_lock(&WALLET)?;
wallet.shield_taddr(account).await
let height = sync::latest_height(&wallet.ld_url).await?;
wallet.shield_taddr(account, height).await
});
log_result(res)
}
@ -319,48 +309,50 @@ pub fn set_lwd_url(url: &str) {
log_result(res())
}
pub fn prepare_offline_tx(
pub fn prepare_multi_payment(
account: u32,
to_address: &str,
amount: u64,
memo: &str,
max_amount_per_note: u64,
recipients_json: &str,
use_transparent: bool,
anchor_offset: u32,
tx_filename: &str,
) -> String {
let r = get_runtime();
let res = r.block_on(async {
let wallet = get_lock(&WALLET)?;
let mut wallet = get_lock(&WALLET)?;
let last_height = sync::latest_height(&wallet.ld_url).await?;
let recipients = Wallet::parse_recipients(recipients_json)?;
let tx = wallet
.prepare_payment(
.build_only_multi_payment(
account,
to_address,
amount,
memo,
max_amount_per_note,
last_height,
&recipients,
use_transparent,
anchor_offset,
)
.await?;
let mut file = File::create(tx_filename)?;
writeln!(file, "{}", tx)?;
Ok("File saved".to_string())
Ok(tx)
});
log_result_string(res)
}
async fn _broadcast(tx_filename: &str, ld_url: &str) -> anyhow::Result<String> {
let mut file = File::open(&tx_filename)?;
let mut s = String::new();
file.read_to_string(&mut s)?;
let tx = hex::decode(s.trim_end())?;
broadcast_tx(&tx, ld_url).await
}
pub fn broadcast(tx_filename: &str) -> String {
let r = get_runtime();
let res = r.block_on(async {
let wallet = get_lock(&WALLET)?;
_broadcast(tx_filename, &wallet.ld_url).await
let mut file = File::open(&tx_filename)?;
let mut s = String::new();
file.read_to_string(&mut s)?;
let tx = hex::decode(s.trim_end())?;
broadcast_tx(&tx, &wallet.ld_url).await
});
log_result_string(res)
}
pub fn broadcast_txhex(txhex: &str) -> String {
let r = get_runtime();
let res = r.block_on(async {
let wallet = get_lock(&WALLET)?;
let tx = hex::decode(txhex)?;
broadcast_tx(&tx, &wallet.ld_url).await
});
log_result_string(res)
}
@ -441,3 +433,116 @@ pub fn parse_payment_uri(uri: &str) -> String {
};
log_result(res())
}
pub fn store_share_secret(account: u32, secret: &str) {
let res = || {
let wallet = get_lock(&WALLET)?;
wallet.store_share_secret(account, secret)?;
Ok(())
};
log_result(res())
}
pub fn get_share_secret(account: u32) -> String {
let res = || {
let wallet = get_lock(&WALLET)?;
let secret = wallet.get_share_secret(account)?;
Ok(secret)
};
log_result(res())
}
struct MultisigAggregator {
shutdown_signal: Option<tokio::sync::oneshot::Sender<()>>,
}
impl MultisigAggregator {
pub fn new() -> Self {
MultisigAggregator {
shutdown_signal: None,
}
}
}
pub fn run_aggregator(secret_share: &str, port: u16, send_port: i64) {
let runtime = get_lock(&RUNTIME).unwrap();
let res = runtime.block_on(async {
let mut aggregator = get_lock(&MULTISIG_AGG_LOCK)?;
let (shutdown, _jh) = run_aggregator_service(
port,
secret_share,
Box::new(move || {
let mut null = ().into_dart();
unsafe {
POST_COBJ.map(|p| {
p(send_port, &mut null);
});
}
}),
)
.await?;
aggregator.shutdown_signal = Some(shutdown);
Ok(())
});
log_result(res)
}
pub fn shutdown_aggregator() {
let res = || {
let mut aggregator = get_lock(&MULTISIG_AGG_LOCK)?;
let shutdown_signal = aggregator.shutdown_signal.take();
if let Some(shutdown_signal) = shutdown_signal {
let _ = shutdown_signal.send(());
}
Ok(())
};
log_result(res())
}
pub fn submit_multisig_tx(tx_str: &str, port: u16) -> Vec<u8> {
let r = get_runtime();
let res: anyhow::Result<_> = r.block_on(async {
let raw_tx = submit_tx(port, tx_str).await?;
Ok(raw_tx)
});
let mut v: Vec<u8> = vec![];
match res {
Ok(raw_tx) => {
v.push(0x00);
v.extend(raw_tx);
}
Err(e) => {
v.push(0x01);
v.extend(e.to_string().as_bytes());
}
}
v
}
struct MultisigClient {}
impl MultisigClient {
pub fn new() -> Self {
MultisigClient {}
}
}
pub fn run_multi_signer(secret_share: &str, aggregator_url: &str, my_url: &str, port: u16) -> u32 {
let runtime = get_lock(&RUNTIME).unwrap();
let res = runtime.block_on(async {
let _jh = run_signer_service(port, secret_share).await?;
tokio::time::sleep(Duration::from_secs(3)).await;
let share = SecretShare::decode(secret_share)?;
let error_code = signer_connect_to_aggregator(aggregator_url, my_url, share.index).await?;
Ok(error_code)
});
log_result(res)
}
pub fn split_account(threshold: u32, participants: u32, account: u32) -> String {
let res = || {
let wallet = get_lock(&WALLET)?;
wallet.split_account(threshold as usize, participants as usize, account)
};
log_result(res())
}

View File

@ -29,7 +29,7 @@ pub unsafe extern "C" fn get_latest_height() -> u32 {
}
#[no_mangle]
pub unsafe extern "C" fn is_valid_key(seed: *mut c_char) -> bool {
pub unsafe extern "C" fn is_valid_key(seed: *mut c_char) -> i8 {
let seed = CStr::from_ptr(seed).to_string_lossy();
sync::is_valid_key(&seed)
}
@ -63,41 +63,22 @@ pub unsafe extern "C" fn get_mempool_balance() -> i64 {
api::get_mempool_balance()
}
#[no_mangle]
pub unsafe extern "C" fn send_payment(
account: u32,
address: *mut c_char,
amount: u64,
memo: *mut c_char,
max_amount_per_note: u64,
anchor_offset: u32,
use_transparent: bool,
port: i64,
) -> *const c_char {
let address = CStr::from_ptr(address).to_string_lossy();
let memo = CStr::from_ptr(memo).to_string_lossy();
let tx_id = api::send_payment(
account,
&address,
amount,
&memo,
max_amount_per_note,
anchor_offset,
use_transparent,
port,
);
CString::new(tx_id).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn send_multi_payment(
account: u32,
recipients_json: *mut c_char,
anchor_offset: u32,
use_transparent: bool,
port: i64,
) -> *const c_char {
let recipients_json = CStr::from_ptr(recipients_json).to_string_lossy();
let tx_id = api::send_multi_payment(account, &recipients_json, anchor_offset, port);
let tx_id = api::send_multi_payment(
account,
&recipients_json,
use_transparent,
anchor_offset,
port,
);
CString::new(tx_id).unwrap().into_raw()
}
@ -144,28 +125,15 @@ pub unsafe extern "C" fn set_lwd_url(url: *mut c_char) {
}
#[no_mangle]
pub unsafe extern "C" fn prepare_offline_tx(
pub unsafe extern "C" fn prepare_multi_payment(
account: u32,
to_address: *mut c_char,
amount: u64,
memo: *mut c_char,
max_amount_per_note: u64,
recipients_json: *mut c_char,
use_transparent: bool,
anchor_offset: u32,
tx_filename: *mut c_char,
) -> *mut c_char {
let to_address = CStr::from_ptr(to_address).to_string_lossy();
let memo = CStr::from_ptr(memo).to_string_lossy();
let tx_filename = CStr::from_ptr(tx_filename).to_string_lossy();
let res = api::prepare_offline_tx(
account,
&to_address,
amount,
&memo,
max_amount_per_note,
anchor_offset,
&tx_filename,
);
CString::new(res).unwrap().into_raw()
let recipients_json = CStr::from_ptr(recipients_json).to_string_lossy();
let tx = api::prepare_multi_payment(account, &recipients_json, use_transparent, anchor_offset);
CString::new(tx).unwrap().into_raw()
}
#[no_mangle]
@ -175,6 +143,13 @@ pub unsafe extern "C" fn broadcast(tx_filename: *mut c_char) -> *mut c_char {
CString::new(res).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn broadcast_txhex(txhex: *mut c_char) -> *mut c_char {
let txhex = CStr::from_ptr(txhex).to_string_lossy();
let res = api::broadcast_txhex(&txhex);
CString::new(res).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn sync_historical_prices(now: i64, days: u32, currency: *mut c_char) -> u32 {
let currency = CStr::from_ptr(currency).to_string_lossy();
@ -246,5 +221,59 @@ pub unsafe extern "C" fn parse_payment_uri(uri: *mut c_char) -> *mut c_char {
CString::new(payment_json).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn store_share_secret(account: u32, secret: *mut c_char) {
let secret = CStr::from_ptr(secret).to_string_lossy();
api::store_share_secret(account, &secret);
}
#[no_mangle]
pub unsafe extern "C" fn get_share_secret(account: u32) -> *mut c_char {
let secret = api::get_share_secret(account);
CString::new(secret).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn run_aggregator(secret_share: *mut c_char, port: u16, send_port: i64) {
let secret_share = CStr::from_ptr(secret_share).to_string_lossy();
api::run_aggregator(&secret_share, port, send_port);
}
#[no_mangle]
pub unsafe extern "C" fn shutdown_aggregator() {
api::shutdown_aggregator();
}
#[no_mangle]
pub unsafe extern "C" fn submit_multisig_tx(tx_json: *mut c_char, port: u16) -> *mut c_char {
let tx_json = CStr::from_ptr(tx_json).to_string_lossy();
let raw_tx = api::submit_multisig_tx(&tx_json, port);
let raw_tx = hex::encode(raw_tx);
CString::new(raw_tx).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn run_multi_signer(
secret_share: *mut c_char,
aggregator_url: *mut c_char,
my_url: *mut c_char,
port: u16,
) -> u32 {
let secret_share = CStr::from_ptr(secret_share).to_string_lossy();
let aggregator_url = CStr::from_ptr(aggregator_url).to_string_lossy();
let my_url = CStr::from_ptr(my_url).to_string_lossy();
api::run_multi_signer(&secret_share, &aggregator_url, &my_url, port)
}
#[no_mangle]
pub unsafe extern "C" fn split_account(
threshold: u32,
participants: u32,
account: u32,
) -> *mut c_char {
let r = api::split_account(threshold, participants, account);
CString::new(r).unwrap().into_raw()
}
#[no_mangle]
pub unsafe extern "C" fn dummy_export() {}

1
native/zcash-multisig Submodule

@ -0,0 +1 @@
Subproject commit ce5baab5a25021950f51a9d53dc6c39ea9fe2997

@ -1 +1 @@
Subproject commit f8eb2641dd7c4f3e57cd8c928334c1ae4e4284a4
Subproject commit f54214d0a6752188efd1404e39b10be58a27ea0f

@ -1 +1 @@
Subproject commit 2470a4e6af618aa6a5c85948b1c558d265d54484
Subproject commit ccb25c61f9349c1d4142bbf58ed1bb57f1b9cfeb

View File

@ -7,7 +7,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
@ -28,7 +28,7 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
clock:
dependency: transitive
description:
@ -43,6 +43,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
cupertino_icons:
dependency: "direct main"
description:
@ -64,6 +71,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
@ -74,6 +88,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.3.0"
matcher:
dependency: transitive
description:
@ -87,7 +108,7 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
path:
dependency: transitive
description:
@ -95,6 +116,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
protobuf:
dependency: transitive
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
@ -141,7 +169,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.2"
typed_data:
dependency: transitive
description:
@ -164,5 +192,5 @@ packages:
source: path
version: "0.0.1"
sdks:
dart: ">=2.12.0 <3.0.0"
dart: ">=2.14.0 <3.0.0"
flutter: ">=1.20.0"

View File

@ -0,0 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
part 'types.g.dart';
@JsonSerializable()
class Recipient {
// ignore: non_constant_identifier_names
final String address;
final int amount;
final String memo;
// ignore: non_constant_identifier_names
final int max_amount_per_note;
Recipient(this.address, this.amount, this.memo, this.max_amount_per_note);
factory Recipient.fromJson(Map<String, dynamic> json) =>
_$RecipientFromJson(json);
Map<String, dynamic> toJson() => _$RecipientToJson(this);
}

View File

@ -1,12 +1,15 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:convert/convert.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:ffi/ffi.dart';
import './warp_api_generated.dart';
import 'types.dart';
typedef report_callback = Void Function(Int32);
const DAY_MS = 24 * 3600 * 1000;
@ -21,25 +24,12 @@ class SyncParams {
class PaymentParams {
int account;
String address;
int amount;
String memo;
int maxAmountPerNote;
int anchorOffset;
String recipientsJson;
bool useTransparent;
SendPort port;
PaymentParams(this.account, this.address, this.amount, this.memo, this.maxAmountPerNote,
this.anchorOffset, this.useTransparent, this.port);
}
class MultiPaymentParams {
int account;
String recipientJson;
int anchorOffset;
SendPort port;
MultiPaymentParams(this.account, this.recipientJson, this.anchorOffset, this.port);
PaymentParams(this.account, this.recipientsJson, this.useTransparent, this.anchorOffset, this.port);
}
class CommitContactsParams {
@ -113,8 +103,8 @@ class WarpApi {
return await compute(getLatestHeightIsolateFn, null);
}
static bool validKey(String key) {
return warp_api_lib.is_valid_key(key.toNativeUtf8().cast<Int8>()) != 0;
static int validKey(String key) {
return warp_api_lib.is_valid_key(key.toNativeUtf8().cast<Int8>());
}
static bool validAddress(String address) {
@ -144,29 +134,18 @@ class WarpApi {
return warp_api_lib.get_mempool_balance();
}
static Future<String> sendPayment(int account, String address, int amount, String memo,
int maxAmountPerNote, int anchorOffset, bool useTransparent, void Function(int) f) async {
static Future<String> sendPayment(int account, List<Recipient> recipients, bool useTransparent, int anchorOffset, void Function(int) f) async {
var receivePort = ReceivePort();
receivePort.listen((progress) {
f(progress);
});
final recipientJson = jsonEncode(recipients);
return await compute(
sendPaymentIsolateFn,
PaymentParams(
account, address, amount, memo, maxAmountPerNote, anchorOffset, useTransparent, receivePort.sendPort));
}
static Future<String> sendMultiPayment(int account, String recipientsJson, int anchorOffset, void Function(int) f) async {
var receivePort = ReceivePort();
receivePort.listen((progress) {
f(progress);
});
return await compute(
sendMultiPaymentIsolateFn,
MultiPaymentParams(
account, recipientsJson, anchorOffset, receivePort.sendPort));
account, recipientJson, useTransparent, anchorOffset, receivePort.sendPort));
}
static int getTBalance(int account) {
@ -186,16 +165,14 @@ class WarpApi {
static String prepareTx(
int account,
String toAddress,
int amount,
String memo,
int maxAmountPerNote,
List<Recipient> recipients,
bool useTransparent,
int anchorOffset,
String txFilename) {
final res = warp_api_lib.prepare_offline_tx(account,
toAddress.toNativeUtf8().cast<Int8>(), amount,
memo.toNativeUtf8().cast<Int8>(), maxAmountPerNote, anchorOffset,
txFilename.toNativeUtf8().cast<Int8>());
final recipientsJson = jsonEncode(recipients);
final res = warp_api_lib.prepare_multi_payment(account,
recipientsJson.toNativeUtf8().cast<Int8>(),
useTransparent ? 1 : 0, anchorOffset);
return res.cast<Utf8>().toDartString();
}
@ -204,6 +181,11 @@ class WarpApi {
return res.cast<Utf8>().toDartString();
}
static String broadcastHex(String tx) {
final res = warp_api_lib.broadcast_txhex(tx.toNativeUtf8().cast<Int8>());
return res.cast<Utf8>().toDartString();
}
static Future<int> syncHistoricalPrices(String currency) async {
return await compute(syncHistoricalPricesIsolateFn, currency);
}
@ -241,26 +223,73 @@ class WarpApi {
uri.toNativeUtf8().cast<Int8>());
return json.cast<Utf8>().toDartString();
}
static void storeShareSecret(int account, String secret) {
warp_api_lib.store_share_secret(account, secret.toNativeUtf8().cast<Int8>());
}
static Future<void> runAggregator(String secretShare, int port, SendPort sendPort) async {
compute(runAggregatorIsolateFn, RunAggregatorParams(secretShare, port, sendPort.nativePort));
}
static Future<String> submitTx(String txJson, int port) async {
return await compute(submitTxIsolateFn, SubmitTxParams(txJson, port));
}
static Future<int> runMultiSigner(String secretShare, String aggregatorUrl, String myUrl, int port) async {
return await compute(runMultiSignerIsolateFn, RunMultiSignerParams(secretShare, aggregatorUrl, myUrl, port));
}
static void stopAggregator() {
warp_api_lib.shutdown_aggregator();
}
static String splitAccount(int threshold, int participants, int account) {
return warp_api_lib.split_account(threshold, participants, account).cast<Utf8>().toDartString();
}
}
class RunAggregatorParams {
String secretShare;
int port;
int sendPort;
RunAggregatorParams(this.secretShare, this.port, this.sendPort);
}
void runAggregatorIsolateFn(RunAggregatorParams params) {
warp_api_lib.run_aggregator(params.secretShare.toNativeUtf8().cast<Int8>(), params.port, params.sendPort);
}
class SubmitTxParams {
String txJson;
int port;
SubmitTxParams(this.txJson, this.port);
}
String submitTxIsolateFn(SubmitTxParams params) {
final r = warp_api_lib.submit_multisig_tx(params.txJson.toNativeUtf8().cast<Int8>(), params.port);
return r.cast<Utf8>().toDartString();
}
class RunMultiSignerParams {
String secretShare;
String aggregatorUrl;
String myUrl;
int port;
RunMultiSignerParams(this.secretShare, this.aggregatorUrl, this.myUrl, this.port);
}
int runMultiSignerIsolateFn(RunMultiSignerParams params) {
return warp_api_lib.run_multi_signer(params.secretShare.toNativeUtf8().cast<Int8>(),
params.aggregatorUrl.toNativeUtf8().cast<Int8>(), params.myUrl.toNativeUtf8().cast<Int8>(), params.port);
}
String sendPaymentIsolateFn(PaymentParams params) {
final txId = warp_api_lib.send_payment(
params.account,
params.address.toNativeUtf8().cast<Int8>(),
params.amount,
params.memo.toNativeUtf8().cast<Int8>(),
params.maxAmountPerNote,
params.anchorOffset,
params.useTransparent ? 1 : 0,
params.port.nativePort);
return txId.cast<Utf8>().toDartString();
}
String sendMultiPaymentIsolateFn(MultiPaymentParams params) {
final txId = warp_api_lib.send_multi_payment(
params.account,
params.recipientJson.toNativeUtf8().cast<Int8>(),
params.recipientsJson.toNativeUtf8().cast<Int8>(),
params.anchorOffset,
params.useTransparent ? 1 : 0,
params.port.nativePort);
return txId.cast<Utf8>().toDartString();
}
@ -285,7 +314,7 @@ String shieldTAddrIsolateFn(int account) {
int syncHistoricalPricesIsolateFn(String currency) {
final now = DateTime.now();
final today = DateTime.utc(now.year, now.month, now.day);
return warp_api_lib.sync_historical_prices(today.millisecondsSinceEpoch ~/ 1000, 370, currency.toNativeUtf8().cast<Int8>());
return warp_api_lib.sync_historical_prices(today.millisecondsSinceEpoch ~/ 1000, 365, currency.toNativeUtf8().cast<Int8>());
}
String commitUnsavedContactsIsolateFn(CommitContactsParams params) {
@ -296,3 +325,4 @@ String commitUnsavedContactsIsolateFn(CommitContactsParams params) {
int getTBalanceIsolateFn(int account) {
return warp_api_lib.get_taddr_balance(account);
}

View File

@ -150,43 +150,18 @@ class NativeLibrary {
late final _dart_get_mempool_balance _get_mempool_balance =
_get_mempool_balance_ptr.asFunction<_dart_get_mempool_balance>();
ffi.Pointer<ffi.Int8> send_payment(
int account,
ffi.Pointer<ffi.Int8> address,
int amount,
ffi.Pointer<ffi.Int8> memo,
int max_amount_per_note,
int anchor_offset,
int shield_transparent_balance,
int port,
) {
return _send_payment(
account,
address,
amount,
memo,
max_amount_per_note,
anchor_offset,
shield_transparent_balance,
port,
);
}
late final _send_payment_ptr =
_lookup<ffi.NativeFunction<_c_send_payment>>('send_payment');
late final _dart_send_payment _send_payment =
_send_payment_ptr.asFunction<_dart_send_payment>();
ffi.Pointer<ffi.Int8> send_multi_payment(
int account,
ffi.Pointer<ffi.Int8> recipients_json,
int anchor_offset,
int use_transparent,
int port,
) {
return _send_multi_payment(
account,
recipients_json,
anchor_offset,
use_transparent,
port,
);
}
@ -295,30 +270,25 @@ class NativeLibrary {
late final _dart_set_lwd_url _set_lwd_url =
_set_lwd_url_ptr.asFunction<_dart_set_lwd_url>();
ffi.Pointer<ffi.Int8> prepare_offline_tx(
ffi.Pointer<ffi.Int8> prepare_multi_payment(
int account,
ffi.Pointer<ffi.Int8> to_address,
int amount,
ffi.Pointer<ffi.Int8> memo,
int max_amount_per_note,
ffi.Pointer<ffi.Int8> recipients_json,
int use_transparent,
int anchor_offset,
ffi.Pointer<ffi.Int8> tx_filename,
) {
return _prepare_offline_tx(
return _prepare_multi_payment(
account,
to_address,
amount,
memo,
max_amount_per_note,
recipients_json,
use_transparent,
anchor_offset,
tx_filename,
);
}
late final _prepare_offline_tx_ptr =
_lookup<ffi.NativeFunction<_c_prepare_offline_tx>>('prepare_offline_tx');
late final _dart_prepare_offline_tx _prepare_offline_tx =
_prepare_offline_tx_ptr.asFunction<_dart_prepare_offline_tx>();
late final _prepare_multi_payment_ptr =
_lookup<ffi.NativeFunction<_c_prepare_multi_payment>>(
'prepare_multi_payment');
late final _dart_prepare_multi_payment _prepare_multi_payment =
_prepare_multi_payment_ptr.asFunction<_dart_prepare_multi_payment>();
ffi.Pointer<ffi.Int8> broadcast(
ffi.Pointer<ffi.Int8> tx_filename,
@ -333,6 +303,19 @@ class NativeLibrary {
late final _dart_broadcast _broadcast =
_broadcast_ptr.asFunction<_dart_broadcast>();
ffi.Pointer<ffi.Int8> broadcast_txhex(
ffi.Pointer<ffi.Int8> txhex,
) {
return _broadcast_txhex(
txhex,
);
}
late final _broadcast_txhex_ptr =
_lookup<ffi.NativeFunction<_c_broadcast_txhex>>('broadcast_txhex');
late final _dart_broadcast_txhex _broadcast_txhex =
_broadcast_txhex_ptr.asFunction<_dart_broadcast_txhex>();
int sync_historical_prices(
int now,
int days,
@ -464,6 +447,112 @@ class NativeLibrary {
late final _dart_parse_payment_uri _parse_payment_uri =
_parse_payment_uri_ptr.asFunction<_dart_parse_payment_uri>();
void store_share_secret(
int account,
ffi.Pointer<ffi.Int8> secret,
) {
return _store_share_secret(
account,
secret,
);
}
late final _store_share_secret_ptr =
_lookup<ffi.NativeFunction<_c_store_share_secret>>('store_share_secret');
late final _dart_store_share_secret _store_share_secret =
_store_share_secret_ptr.asFunction<_dart_store_share_secret>();
ffi.Pointer<ffi.Int8> get_share_secret(
int account,
) {
return _get_share_secret(
account,
);
}
late final _get_share_secret_ptr =
_lookup<ffi.NativeFunction<_c_get_share_secret>>('get_share_secret');
late final _dart_get_share_secret _get_share_secret =
_get_share_secret_ptr.asFunction<_dart_get_share_secret>();
void run_aggregator(
ffi.Pointer<ffi.Int8> secret_share,
int port,
int send_port,
) {
return _run_aggregator(
secret_share,
port,
send_port,
);
}
late final _run_aggregator_ptr =
_lookup<ffi.NativeFunction<_c_run_aggregator>>('run_aggregator');
late final _dart_run_aggregator _run_aggregator =
_run_aggregator_ptr.asFunction<_dart_run_aggregator>();
void shutdown_aggregator() {
return _shutdown_aggregator();
}
late final _shutdown_aggregator_ptr =
_lookup<ffi.NativeFunction<_c_shutdown_aggregator>>(
'shutdown_aggregator');
late final _dart_shutdown_aggregator _shutdown_aggregator =
_shutdown_aggregator_ptr.asFunction<_dart_shutdown_aggregator>();
ffi.Pointer<ffi.Int8> submit_multisig_tx(
ffi.Pointer<ffi.Int8> tx_json,
int port,
) {
return _submit_multisig_tx(
tx_json,
port,
);
}
late final _submit_multisig_tx_ptr =
_lookup<ffi.NativeFunction<_c_submit_multisig_tx>>('submit_multisig_tx');
late final _dart_submit_multisig_tx _submit_multisig_tx =
_submit_multisig_tx_ptr.asFunction<_dart_submit_multisig_tx>();
int run_multi_signer(
ffi.Pointer<ffi.Int8> secret_share,
ffi.Pointer<ffi.Int8> aggregator_url,
ffi.Pointer<ffi.Int8> my_url,
int port,
) {
return _run_multi_signer(
secret_share,
aggregator_url,
my_url,
port,
);
}
late final _run_multi_signer_ptr =
_lookup<ffi.NativeFunction<_c_run_multi_signer>>('run_multi_signer');
late final _dart_run_multi_signer _run_multi_signer =
_run_multi_signer_ptr.asFunction<_dart_run_multi_signer>();
ffi.Pointer<ffi.Int8> split_account(
int threshold,
int participants,
int account,
) {
return _split_account(
threshold,
participants,
account,
);
}
late final _split_account_ptr =
_lookup<ffi.NativeFunction<_c_split_account>>('split_account');
late final _dart_split_account _split_account =
_split_account_ptr.asFunction<_dart_split_account>();
void dummy_export() {
return _dummy_export();
}
@ -554,32 +643,11 @@ typedef _c_get_mempool_balance = ffi.Int64 Function();
typedef _dart_get_mempool_balance = int Function();
typedef _c_send_payment = ffi.Pointer<ffi.Int8> Function(
ffi.Uint32 account,
ffi.Pointer<ffi.Int8> address,
ffi.Uint64 amount,
ffi.Pointer<ffi.Int8> memo,
ffi.Uint64 max_amount_per_note,
ffi.Uint32 anchor_offset,
ffi.Int8 shield_transparent_balance,
ffi.Int64 port,
);
typedef _dart_send_payment = ffi.Pointer<ffi.Int8> Function(
int account,
ffi.Pointer<ffi.Int8> address,
int amount,
ffi.Pointer<ffi.Int8> memo,
int max_amount_per_note,
int anchor_offset,
int shield_transparent_balance,
int port,
);
typedef _c_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
ffi.Uint32 account,
ffi.Pointer<ffi.Int8> recipients_json,
ffi.Uint32 anchor_offset,
ffi.Int8 use_transparent,
ffi.Int64 port,
);
@ -587,6 +655,7 @@ typedef _dart_send_multi_payment = ffi.Pointer<ffi.Int8> Function(
int account,
ffi.Pointer<ffi.Int8> recipients_json,
int anchor_offset,
int use_transparent,
int port,
);
@ -648,24 +717,18 @@ typedef _dart_set_lwd_url = void Function(
ffi.Pointer<ffi.Int8> url,
);
typedef _c_prepare_offline_tx = ffi.Pointer<ffi.Int8> Function(
typedef _c_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
ffi.Uint32 account,
ffi.Pointer<ffi.Int8> to_address,
ffi.Uint64 amount,
ffi.Pointer<ffi.Int8> memo,
ffi.Uint64 max_amount_per_note,
ffi.Pointer<ffi.Int8> recipients_json,
ffi.Int8 use_transparent,
ffi.Uint32 anchor_offset,
ffi.Pointer<ffi.Int8> tx_filename,
);
typedef _dart_prepare_offline_tx = ffi.Pointer<ffi.Int8> Function(
typedef _dart_prepare_multi_payment = ffi.Pointer<ffi.Int8> Function(
int account,
ffi.Pointer<ffi.Int8> to_address,
int amount,
ffi.Pointer<ffi.Int8> memo,
int max_amount_per_note,
ffi.Pointer<ffi.Int8> recipients_json,
int use_transparent,
int anchor_offset,
ffi.Pointer<ffi.Int8> tx_filename,
);
typedef _c_broadcast = ffi.Pointer<ffi.Int8> Function(
@ -676,6 +739,14 @@ typedef _dart_broadcast = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> tx_filename,
);
typedef _c_broadcast_txhex = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> txhex,
);
typedef _dart_broadcast_txhex = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> txhex,
);
typedef _c_sync_historical_prices = ffi.Uint32 Function(
ffi.Int64 now,
ffi.Uint32 days,
@ -762,6 +833,76 @@ typedef _dart_parse_payment_uri = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> uri,
);
typedef _c_store_share_secret = ffi.Void Function(
ffi.Uint32 account,
ffi.Pointer<ffi.Int8> secret,
);
typedef _dart_store_share_secret = void Function(
int account,
ffi.Pointer<ffi.Int8> secret,
);
typedef _c_get_share_secret = ffi.Pointer<ffi.Int8> Function(
ffi.Uint32 account,
);
typedef _dart_get_share_secret = ffi.Pointer<ffi.Int8> Function(
int account,
);
typedef _c_run_aggregator = ffi.Void Function(
ffi.Pointer<ffi.Int8> secret_share,
ffi.Uint16 port,
ffi.Int64 send_port,
);
typedef _dart_run_aggregator = void Function(
ffi.Pointer<ffi.Int8> secret_share,
int port,
int send_port,
);
typedef _c_shutdown_aggregator = ffi.Void Function();
typedef _dart_shutdown_aggregator = void Function();
typedef _c_submit_multisig_tx = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> tx_json,
ffi.Uint16 port,
);
typedef _dart_submit_multisig_tx = ffi.Pointer<ffi.Int8> Function(
ffi.Pointer<ffi.Int8> tx_json,
int port,
);
typedef _c_run_multi_signer = ffi.Uint32 Function(
ffi.Pointer<ffi.Int8> secret_share,
ffi.Pointer<ffi.Int8> aggregator_url,
ffi.Pointer<ffi.Int8> my_url,
ffi.Uint16 port,
);
typedef _dart_run_multi_signer = int Function(
ffi.Pointer<ffi.Int8> secret_share,
ffi.Pointer<ffi.Int8> aggregator_url,
ffi.Pointer<ffi.Int8> my_url,
int port,
);
typedef _c_split_account = ffi.Pointer<ffi.Int8> Function(
ffi.Uint32 threshold,
ffi.Uint32 participants,
ffi.Uint32 account,
);
typedef _dart_split_account = ffi.Pointer<ffi.Int8> Function(
int threshold,
int participants,
int account,
);
typedef _c_dummy_export = ffi.Void Function();
typedef _dart_dummy_export = void Function();

View File

@ -1,6 +1,20 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "30.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.0"
args:
dependency: transitive
description:
@ -14,7 +28,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.1"
version: "2.8.1"
boolean_selector:
dependency: transitive
description:
@ -22,6 +36,62 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
build:
dependency: transitive
description:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "7.2.2"
built_collection:
dependency: transitive
description:
name: built_collection
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.1.3"
characters:
dependency: transitive
description:
@ -35,7 +105,14 @@ packages:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:
@ -50,6 +127,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
collection:
dependency: transitive
description:
@ -57,6 +141,27 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
convert:
dependency: "direct main"
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
dart_style:
dependency: transitive
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
fake_async:
dependency: transitive
description:
@ -85,6 +190,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
flutter:
dependency: "direct main"
description: flutter
@ -95,6 +207,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
glob:
dependency: transitive
description:
@ -102,13 +221,55 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
isolate:
dependency: "direct main"
graphs:
dependency: transitive
description:
name: isolate
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
version: "2.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
io:
dependency: transitive
description:
name: io
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.2"
logging:
dependency: transitive
description:
@ -129,7 +290,21 @@ packages:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.7.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
path:
dependency: transitive
description:
@ -144,6 +319,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
protobuf:
dependency: "direct main"
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
quiver:
dependency: transitive
description:
@ -151,11 +354,39 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
source_span:
dependency: transitive
description:
@ -177,6 +408,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
stream_transform:
dependency: transitive
description:
name: stream_transform
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
@ -197,7 +435,14 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.2"
timing:
dependency: transitive
description:
name: timing
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
typed_data:
dependency: transitive
description:
@ -212,6 +457,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
yaml:
dependency: transitive
description:
@ -220,5 +479,5 @@ packages:
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
dart: ">=2.14.0 <3.0.0"
flutter: ">=1.20.0"

View File

@ -11,11 +11,16 @@ dependencies:
flutter:
sdk: flutter
ffi: 1.1.2
convert: ^3.0.1
protobuf: ^2.0.0
json_annotation: ^4.1.0
dev_dependencies:
flutter_test:
sdk: flutter
ffigen: 2.4.2
build_runner: ^2.1.2
json_serializable: ^5.0.0
ffigen:
output: 'lib/warp_api_generated.dart'

View File

@ -315,7 +315,7 @@ packages:
name: fl_chart
url: "https://pub.dartlang.org"
source: hosted
version: "0.40.0"
version: "0.40.2"
flex_color_scheme:
dependency: "direct main"
description:
@ -555,6 +555,15 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
k_chart:
dependency: "direct main"
description:
path: "."
ref: "821f81681f8ee819cd721498f7b28d290f4ebe38"
resolved-ref: "821f81681f8ee819cd721498f7b28d290f4ebe38"
url: "https://github.com/hhanh00/k_chart.git"
source: git
version: "0.4.1"
list_utilities:
dependency: transitive
description:
@ -632,6 +641,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
network_info_plus:
dependency: "direct main"
description:
name: network_info_plus
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
network_info_plus_linux:
dependency: transitive
description:
name: network_info_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
network_info_plus_macos:
dependency: transitive
description:
name: network_info_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
network_info_plus_platform_interface:
dependency: transitive
description:
name: network_info_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
network_info_plus_web:
dependency: transitive
description:
name: network_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
network_info_plus_windows:
dependency: transitive
description:
name: network_info_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
nm:
dependency: transitive
description:
@ -807,6 +858,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
protobuf:
dependency: transitive
description:
name: protobuf
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
pub_semver:
dependency: transitive
description:

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.12+179
version: 1.1.0+184
environment:
sdk: ">=2.12.0 <3.0.0"
@ -45,6 +45,10 @@ dependencies:
flex_color_scheme: ^3.0.1
flutter_colorpicker: ^0.6.0
fl_chart: ^0.40.0
k_chart:
git:
url: https://github.com/hhanh00/k_chart.git
ref: 821f81681f8ee819cd721498f7b28d290f4ebe38
grouped_list: ^4.1.0
json_annotation: ^4.1.0
share_plus: ^2.1.4
@ -65,6 +69,7 @@ dependencies:
uni_links: ^0.5.1
quick_actions: ^0.6.0
csv: ^5.0.0
network_info_plus : ^2.0.2
flutter_localizations:
sdk: flutter
@ -84,7 +89,7 @@ dev_dependencies:
flutter_native_splash: ^1.2.3
flutter_app_name:
name: "YWallet"
name: "ZWallet"
flutter_icons:
android: true

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.12+179
version: 1.1.0+184
environment:
sdk: ">=2.12.0 <3.0.0"
@ -45,6 +45,10 @@ dependencies:
flex_color_scheme: ^3.0.1
flutter_colorpicker: ^0.6.0
fl_chart: ^0.40.0
k_chart:
git:
url: https://github.com/hhanh00/k_chart.git
ref: 821f81681f8ee819cd721498f7b28d290f4ebe38
grouped_list: ^4.1.0
json_annotation: ^4.1.0
share_plus: ^2.1.4
@ -65,6 +69,7 @@ dependencies:
uni_links: ^0.5.1
quick_actions: ^0.6.0
csv: ^5.0.0
network_info_plus : ^2.0.2
flutter_localizations:
sdk: flutter

View File

@ -25,8 +25,9 @@
<title>warp</title>
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" type="text/css" href="splash/style.css">
</head>
<body>
<body style="position: fixed; inset: 0px; overflow: hidden; padding: 0px; margin: 0px; user-select: none; touch-action: none; font: 14px sans-serif; color: red;">
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
@ -94,5 +95,10 @@
loadMainDartJs();
}
</script>
<picture id="splash">
<source srcset="splash/img/light-1x.png 1x, splash/img/light-2x.png 2x, splash/img/light-3x.png 3x, splash/img/light-4x.png 4x" media="(prefers-color-scheme: light) or (prefers-color-scheme: no-preference)">
<source srcset="splash/img/dark-1x.png 1x, splash/img/dark-2x.png 2x, splash/img/dark-3x.png 3x, splash/img/dark-4x.png 4x" media="(prefers-color-scheme: dark)">
<img class="center" src="splash/img/light-1x.png" />
</picture>
</body>
</html>
</html>