├── .gitignore ├── LICENSE ├── README.md ├── app ├── flutter_vechain │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── android │ │ ├── app │ │ │ ├── build.gradle │ │ │ └── src │ │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── flutter_vechain │ │ │ │ │ │ └── MainActivity.java │ │ │ │ └── res │ │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ └── gradle-wrapper.properties │ │ ├── key.properties │ │ └── settings.gradle │ ├── fonts │ │ └── iconfont.ttf │ ├── images │ │ ├── ethereum.jpg │ │ ├── ethereum.png │ │ ├── vechain.jpeg │ │ ├── vechain.jpg │ │ └── wallet.png │ ├── ios │ │ ├── Flutter │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Podfile.lock │ │ ├── Runner.xcodeproj │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace │ │ │ │ └── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ ├── Runner.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── Runner │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── main.m │ ├── lib │ │ ├── configure │ │ │ └── urls.dart │ │ ├── controller │ │ │ ├── idsql.dart │ │ │ ├── sql.dart │ │ │ └── tokensql.dart │ │ ├── main.dart │ │ ├── models │ │ │ ├── account.dart │ │ │ ├── privkey.dart │ │ │ └── tokens.dart │ │ ├── pages │ │ │ ├── add_token.dart │ │ │ ├── create_wallet.dart │ │ │ ├── home.dart │ │ │ ├── import_wallet.dart │ │ │ ├── send_tx.dart │ │ │ └── wallet_list.dart │ │ ├── provide │ │ │ ├── token.dart │ │ │ └── wallet.dart │ │ ├── requests │ │ │ ├── http_requets.dart │ │ │ ├── push_tx_models.dart │ │ │ ├── unsign_tx_models.dart │ │ │ └── vetrequest_models.dart │ │ ├── router │ │ │ ├── handler.dart │ │ │ └── routers.dart │ │ └── widgets │ │ │ ├── card.dart │ │ │ ├── myicon.dart │ │ │ └── token.dart │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ └── widget_test.dart └── key.jks ├── backend ├── Makefile ├── README.md ├── apihandler │ ├── balance.go │ ├── base.go │ └── transaction.go ├── cmd │ └── contractsrv │ │ └── main.go ├── database │ └── transfer.go ├── docker │ ├── Dockerfile │ └── start.sh ├── go.mod ├── go.sum ├── initialization │ ├── appinit.go │ ├── configure.go │ ├── err.go │ └── mysqlinit.go ├── sql │ └── init.sql ├── utils │ ├── gorequest.go │ ├── others.go │ └── sign │ │ └── rsa.go └── vechainclient │ ├── client.go │ ├── example.md │ └── types.go ├── img ├── app1.jpeg ├── app2.jpeg ├── show.gif └── web.jpg ├── modify.md └── web ├── index.html └── qrcode.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | *idea 14 | *.vscode 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wupengxin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vechain_helper 2 | 一个全栈的唯链支付助手工具 3 | 4 | ### 目的 5 | 6 | 作为一个后端程序员, 一直想尝试一下前端项目。 这便是此项目的由来。 此项目将由APP端, Web前端和后端组成。 7 | APP端使用flutter框架来进行构建的ios&安卓的跨平台app, 8 | 后端技术栈则为Golang+mysql。 9 | web前端暂时只是一个简单的HTML页面。 10 | 11 | ### 项目截图 12 | 13 | 14 | 15 | 16 | ![web](./img/web.jpg) 17 | 18 | ![show](./img/show.gif) 19 | 20 | ### APP安装包说明 21 | 目前只释放了安卓包, ios因为穷没有证书。 22 | 23 | [安卓包下载](https://github.com/wupeaking/vechain_helper/releases/download/v0.1/app-release.apk) 24 | 25 | 26 | ### 注意 27 | 此项目主要是一个练手项目, 所有的代码均是开源的。 28 | 如果你是一个区块链的开发者, 对于唯链(以太坊与其类似)的地址生成, 签名, 发起交易, ERC20代码转账等感兴趣你可以直接阅读 29 | 后端部分相关的代码。 30 | 如果你也是一个flutter新手爱好者, 希望此项目的APP能够给你提供一个参考。 31 | 32 | ### 说明 33 | 此项目只用在唯链的测试网络上, 生产节点禁止使用! 34 | 35 | 36 | ### 另 37 | 38 | 作为一个不知名(垃圾)程序员,依然还是有一颗向上的心。我不知道自己的程序员生涯能持续多久, 但既然还在coding就尽力做好吧。 你的star是对我最好的鼓励。这也许是gayhub存在的最大意义吧~ 39 | 40 | 如果你想自己搭建一个这样的玩具详情可以查看[此文档](./modify.md) -------------------------------------------------------------------------------- /app/flutter_vechain/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /app/flutter_vechain/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /app/flutter_vechain/README.md: -------------------------------------------------------------------------------- 1 | # flutter_vechain 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystorePropertiesFile = rootProject.file("key.properties") 29 | def keystoreProperties = new Properties() 30 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 31 | 32 | android { 33 | compileSdkVersion 28 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "cn.wupengxin.vechain_helper" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | signingConfigs { 50 | release { 51 | keyAlias keystoreProperties['keyAlias'] 52 | keyPassword keystoreProperties['keyPassword'] 53 | storeFile file(keystoreProperties['storeFile']) 54 | storePassword keystoreProperties['storePassword'] 55 | } 56 | } 57 | buildTypes { 58 | release { 59 | signingConfig signingConfigs.release 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | testImplementation 'junit:junit:4.12' 70 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 71 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 72 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 73 | } 74 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 14 | 21 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/java/com/example/flutter_vechain/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.flutter_vechain; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.30' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /app/flutter_vechain/android/key.properties: -------------------------------------------------------------------------------- 1 | storePassword=12345678 2 | keyPassword=12345678 3 | keyAlias=key 4 | storeFile=/Users/wupengxin/project/vechain_helper/app/key.jks -------------------------------------------------------------------------------- /app/flutter_vechain/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /app/flutter_vechain/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/fonts/iconfont.ttf -------------------------------------------------------------------------------- /app/flutter_vechain/images/ethereum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/images/ethereum.jpg -------------------------------------------------------------------------------- /app/flutter_vechain/images/ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/images/ethereum.png -------------------------------------------------------------------------------- /app/flutter_vechain/images/vechain.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/images/vechain.jpeg -------------------------------------------------------------------------------- /app/flutter_vechain/images/vechain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/images/vechain.jpg -------------------------------------------------------------------------------- /app/flutter_vechain/images/wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/images/wallet.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - barcode_scan (0.0.1): 3 | - Flutter 4 | - MTBBarcodeScanner 5 | - Flutter (1.0.0) 6 | - fluttertoast (0.0.2): 7 | - Flutter 8 | - FMDB (2.7.5): 9 | - FMDB/standard (= 2.7.5) 10 | - FMDB/standard (2.7.5) 11 | - MTBBarcodeScanner (5.0.8) 12 | - sqflite (0.0.1): 13 | - Flutter 14 | - FMDB (~> 2.7.2) 15 | - url_launcher (0.0.1): 16 | - Flutter 17 | 18 | DEPENDENCIES: 19 | - barcode_scan (from `.symlinks/plugins/barcode_scan/ios`) 20 | - Flutter (from `.symlinks/flutter/ios`) 21 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 22 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 23 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 24 | 25 | SPEC REPOS: 26 | https://github.com/cocoapods/specs.git: 27 | - FMDB 28 | - MTBBarcodeScanner 29 | 30 | EXTERNAL SOURCES: 31 | barcode_scan: 32 | :path: ".symlinks/plugins/barcode_scan/ios" 33 | Flutter: 34 | :path: ".symlinks/flutter/ios" 35 | fluttertoast: 36 | :path: ".symlinks/plugins/fluttertoast/ios" 37 | sqflite: 38 | :path: ".symlinks/plugins/sqflite/ios" 39 | url_launcher: 40 | :path: ".symlinks/plugins/url_launcher/ios" 41 | 42 | SPEC CHECKSUMS: 43 | barcode_scan: 33f586d02270046fc6559135038b34b5754eaa4f 44 | Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a 45 | fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b 46 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 47 | MTBBarcodeScanner: 5b97f10a037e2560ff104da413343c5ce9413911 48 | sqflite: ff1d9da63c06588cc8d1faf7256d741f16989d5a 49 | url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298 50 | 51 | PODFILE CHECKSUM: aff02bfeed411c636180d6812254b2daeea14d09 52 | 53 | COCOAPODS: 1.6.1 54 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_vechain 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | NSCameraUsageDescription 45 | Camera permission is required for barcode scanning. 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/flutter_vechain/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/configure/urls.dart: -------------------------------------------------------------------------------- 1 | // 整个URL列表 2 | final rootURL = "https://wupengxin.cn/vechain_helper/api/"; 3 | 4 | 5 | final Map APIS = { 6 | "balances": rootURL+"balance/", // get方法 获取某些账户的VET余额 balance/$account?currency=xx,yy,zzz 7 | "unsignTx": rootURL+"unsigned_tx", // put方法 创建交易请求 8 | "pushTx": rootURL+"push_tx" // put方法 广播交易 9 | }; -------------------------------------------------------------------------------- /app/flutter_vechain/lib/controller/idsql.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | import 'package:path/path.dart'; 3 | 4 | class WalletID { 5 | static final String tableName = "wallet_id_table"; 6 | int id; 7 | // toMap 为了写入SQL 8 | Map toMap() { 9 | var map = { 10 | "id": id, 11 | "wallet_id": "wallet_id", 12 | }; 13 | return map; 14 | } 15 | 16 | static WalletID fromMap(Map map) { 17 | int id = map["id"]; 18 | return WalletID()..id = id; 19 | } 20 | } 21 | 22 | // 查询当前选择的钱包ID 23 | class WalletIDSQL { 24 | Database db; 25 | 26 | openSqlite() async { 27 | // 获取数据库文件的存储路径 28 | var databasesPath = await getDatabasesPath(); 29 | String path = join(databasesPath, 'flutter_vechain.db'); 30 | 31 | //根据数据库文件路径和数据库版本号创建数据库表 32 | db = await openDatabase(path, version: 1, 33 | onCreate: (Database db, int version) async { 34 | // 创建数据表 35 | await db.execute(''' 36 | CREATE TABLE ${WalletID.tableName} ( 37 | id INTEGER, 38 | wallet_id TEXT) 39 | '''); 40 | }); 41 | } 42 | 43 | // 插入当前ID 44 | Future replaceInto(WalletID e) async { 45 | List maps = await db.query(WalletID.tableName, 46 | columns: [ 47 | "id", 48 | "wallet_id", 49 | ], 50 | where: 'wallet_id = ?', 51 | whereArgs: ["wallet_id"]); 52 | if (maps == null || maps.length == 0) { 53 | // 直接插入 54 | e.id = await db.insert(WalletID.tableName, e.toMap()); 55 | return e; 56 | } else { 57 | // 更新 58 | await db.update(WalletID.tableName, e.toMap(), where: 'wallet_id = ?', whereArgs: ["wallet_id"]); 59 | return e; 60 | } 61 | } 62 | 63 | 64 | // 根据ID查找书籍信息 65 | Future currentWalletID() async { 66 | List maps = await db.query(WalletID.tableName, 67 | columns: [ 68 | "id", 69 | ], 70 | where: 'wallet_id= ?', 71 | whereArgs: ['wallet_id']); 72 | if (maps.length > 0) { 73 | return WalletID.fromMap(maps.first); 74 | } 75 | return null; 76 | } 77 | 78 | // 记得及时关闭数据库,防止内存泄漏 79 | close() async { 80 | await db.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/controller/sql.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | import 'package:path/path.dart'; 3 | import '../models/privkey.dart'; 4 | import './idsql.dart'; 5 | import '../models/tokens.dart'; 6 | 7 | class SQLDAO { 8 | Database db; 9 | 10 | openSqlite() async { 11 | // 获取数据库文件的存储路径 12 | var databasesPath = await getDatabasesPath(); 13 | String path = join(databasesPath, 'flutter_vechain.db'); 14 | 15 | //根据数据库文件路径和数据库版本号创建数据库表 16 | db = await openDatabase(path, version: 1, 17 | onCreate: (Database db, int version) async { 18 | // 创建钱包数据表 19 | await db.execute(''' 20 | CREATE TABLE ${PrivkeyManager.tableName} ( 21 | id INTEGER PRIMARY KEY, 22 | privkey TEXT, 23 | passwd TEXT, 24 | walletname TEXT ) 25 | '''); 26 | 27 | // 创建当前选择的钱包数据表 28 | await db.execute(''' 29 | CREATE TABLE ${WalletID.tableName} ( 30 | id INTEGER, 31 | wallet_id TEXT) 32 | '''); 33 | // 创建token数据表 34 | await db.execute(''' 35 | CREATE TABLE ${Token.tableName} ( 36 | id INTEGER PRIMARY KEY, 37 | address TEXT, 38 | symbol TEXT, 39 | bits INTEGER) 40 | '''); 41 | }); 42 | } 43 | 44 | // 插入一条钱包信息 45 | Future insert(PrivkeyManager e) async { 46 | e.id = await db.insert(PrivkeyManager.tableName, e.toMap()); 47 | return e; 48 | } 49 | 50 | Future isExistWallet(String name) async { 51 | List maps = await db.query(PrivkeyManager.tableName, 52 | columns: [ 53 | "id", 54 | "privkey", 55 | "passwd", 56 | "walletname", 57 | ], 58 | where: 'walletname = ?', 59 | whereArgs: [name]); 60 | if (maps == null || maps.length == 0) { 61 | return false; 62 | } else { 63 | return true; 64 | } 65 | } 66 | 67 | // 查找所有书籍信息 68 | Future> queryAll() async { 69 | List maps = await db.query(PrivkeyManager.tableName, columns: [ 70 | "id", 71 | "privkey", 72 | "passwd", 73 | "walletname", 74 | ]); 75 | 76 | if (maps == null || maps.length == 0) { 77 | return null; 78 | } 79 | 80 | List pks = []; 81 | for (int i = 0; i < maps.length; i++) { 82 | pks.add(PrivkeyManager.fromMap(maps[i])); 83 | } 84 | return pks; 85 | } 86 | 87 | // 根据ID查找书籍信息 88 | Future getPrivkeyManager(int id) async { 89 | List maps = await db.query(PrivkeyManager.tableName, 90 | columns: [ 91 | "id", 92 | "privkey", 93 | "passwd", 94 | "walletname", 95 | ], 96 | where: 'id = ?', 97 | whereArgs: [id]); 98 | if (maps.length > 0) { 99 | return PrivkeyManager.fromMap(maps.first); 100 | } 101 | return null; 102 | } 103 | 104 | // 根据ID删除 105 | // Future delete(int id) async { 106 | // return await db.delete(tableBook, where: '$columnId = ?', whereArgs: [id]); 107 | // } 108 | 109 | // 更新 110 | Future update(PrivkeyManager pk) async { 111 | return await db.update(PrivkeyManager.tableName, pk.toMap(), 112 | where: 'id = ?', whereArgs: [pk.id]); 113 | } 114 | 115 | // 记得及时关闭数据库,防止内存泄漏 116 | close() async { 117 | await db.close(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/controller/tokensql.dart: -------------------------------------------------------------------------------- 1 | import '../models/tokens.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | import 'package:path/path.dart'; 4 | 5 | class TokenSQL { 6 | Database db; 7 | 8 | openSqlite() async { 9 | // 获取数据库文件的存储路径 10 | var databasesPath = await getDatabasesPath(); 11 | String path = join(databasesPath, 'flutter_vechain.db'); 12 | 13 | //根据数据库文件路径和数据库版本号创建数据库表 14 | db = await openDatabase( 15 | path, 16 | version: 1, 17 | ); 18 | } 19 | 20 | // 插入一条钱包信息 21 | Future insert(Token e) async { 22 | e.id = await db.insert(Token.tableName, e.toMap()); 23 | return e; 24 | } 25 | 26 | Future isExist(String address) async { 27 | List maps = await db.query(Token.tableName, 28 | columns: [ 29 | "id", 30 | ], 31 | where: 'address = ?', 32 | whereArgs: [address]); 33 | if (maps == null || maps.length == 0) { 34 | return false; 35 | } else { 36 | return true; 37 | } 38 | } 39 | 40 | // 查找所有 41 | Future> queryAll() async { 42 | List maps = await db.query(Token.tableName, columns: [ 43 | "id", 44 | "address", 45 | "symbol", 46 | "bits", 47 | ]); 48 | 49 | if (maps == null || maps.length == 0) { 50 | return null; 51 | } 52 | 53 | List tks = []; 54 | for (int i = 0; i < maps.length; i++) { 55 | tks.add(Token.fromMap(maps[i])); 56 | } 57 | return tks; 58 | } 59 | 60 | // 根据ID查找书籍信息 61 | Future getToken(String address) async { 62 | List maps = await db.query(Token.tableName, 63 | columns: [ 64 | "id", 65 | "address", 66 | "symbol", 67 | "bits", 68 | ], 69 | where: 'address = ?', 70 | whereArgs: [address]); 71 | if (maps.length > 0) { 72 | return Token.fromMap(maps.first); 73 | } 74 | return null; 75 | } 76 | 77 | // 更新 78 | Future update(Token e) async { 79 | return await db.update(Token.tableName, e.toMap(), 80 | where: 'address = ?', whereArgs: [e.address]); 81 | } 82 | 83 | // 记得及时关闭数据库,防止内存泄漏 84 | close() async { 85 | await db.close(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/models/account.dart: -------------------------------------------------------------------------------- 1 | class Accout { 2 | // 账户地址 3 | String address; 4 | // 余额 5 | String balance; 6 | 7 | // 8 | String vthoBalance; 9 | // 私钥 10 | String privKey; 11 | 12 | Accout(this.address, this.privKey, {this.balance: "0", this.vthoBalance: "0"}); 13 | 14 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/models/privkey.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:web3dart/web3dart.dart'; 3 | import 'package:web3dart/src/utils/crypto.dart' as crypto; 4 | import "package:web3dart/src/utils/numbers.dart" as numbers; 5 | import 'dart:convert'; 6 | import 'dart:math'; 7 | 8 | dmeo() { 9 | // 加载私钥 10 | Credentials fromHex = Credentials.fromPrivateKeyHex( 11 | "FE63CAA490F55068F89FF4A5E4B5FA94CCF2A32F8CDCF02555B2B0290AB00CFB"); 12 | 13 | //List list = '中文'.codeUnits; 14 | 15 | List list = Utf8Encoder().convert('hello world'); 16 | 17 | //String.fromCharCodes (inputAsUint8List) 18 | Uint8List bytes = Uint8List.fromList(list); 19 | crypto.MsgSignature msgSign = 20 | crypto.sign(crypto.sha3(bytes), numbers.intToBytes(fromHex.privateKey)); 21 | print(numbers.bytesToHex(numbers.intToBytes(msgSign.r))); 22 | print(numbers.bytesToHex(numbers.intToBytes(msgSign.s))); 23 | print(numbers.toHex(msgSign.v)); 24 | 25 | print(numbers.bytesToHex(crypto.sha3(bytes))); 26 | } 27 | 28 | // PrivkeyManager 私钥管理 29 | class PrivkeyManager { 30 | Credentials c; 31 | String passwd; // 私钥密码 32 | String walletName; 33 | int id; 34 | static String tableName = "wallet"; 35 | 36 | PrivkeyManager(this.c, {this.passwd: "", this.walletName: ""}); 37 | 38 | static PrivkeyManager randomGenerate() { 39 | var rng = Random.secure(); 40 | return PrivkeyManager(Credentials.createRandom(rng)); 41 | } 42 | 43 | static bool checkPrivateKey(String priv) { 44 | try { 45 | Credentials.fromPrivateKeyHex(priv); 46 | } catch (e) { 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | static PrivkeyManager fromHexString(String priv) { 53 | if (priv.length == 66) { 54 | return PrivkeyManager(Credentials.fromPrivateKeyHex(priv.substring(2))); 55 | } 56 | return PrivkeyManager(Credentials.fromPrivateKeyHex(priv)); 57 | } 58 | 59 | static PrivkeyManager fromMap(Map map) { 60 | int id = map["id"]; 61 | String pri = map["privkey"]; 62 | String pw = map["passwd"]; 63 | String wn = map["walletname"]; 64 | return PrivkeyManager(Credentials.fromPrivateKeyHex(pri), 65 | passwd: pw, walletName: wn) 66 | ..id = id; 67 | } 68 | 69 | String addrToString() { 70 | return c.address.toString(); 71 | } 72 | 73 | // 十六进制格式的私钥 74 | String hexPrivkey() { 75 | return numbers.bytesToHex(numbers.intToBytes(c.privateKey)); 76 | } 77 | 78 | // 79 | setPasswd(String pw) { 80 | passwd = pw; 81 | } 82 | 83 | // toMap 为了写入SQL 84 | Map toMap() { 85 | var map = { 86 | "privkey": hexPrivkey(), 87 | "passwd": passwd, 88 | "walletname": walletName, 89 | }; 90 | return map; 91 | } 92 | 93 | // 消息签名 94 | String signMsg(String hexStr) { 95 | var bytes = numbers.hexToBytes(hexStr); 96 | Uint8List hashBytes = Uint8List.fromList(bytes); 97 | crypto.MsgSignature msgSign = 98 | crypto.sign(hashBytes, numbers.intToBytes(c.privateKey)); 99 | String v = "0"+(msgSign.v-27).toString(); 100 | 101 | return numbers.bytesToHex(numbers.intToBytes(msgSign.r)) + 102 | numbers.bytesToHex(numbers.intToBytes(msgSign.s)) + 103 | v; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/models/tokens.dart: -------------------------------------------------------------------------------- 1 | class Token { 2 | // 合约地址 3 | String address; 4 | // token 符号 5 | String symbol; 6 | // 余额 7 | String balance; 8 | 9 | static final String tableName = "token"; 10 | 11 | // 位数 12 | int bits; 13 | 14 | int id; 15 | 16 | Token(this.address, this.symbol, {this.balance: "0", this.bits: 18}); 17 | 18 | // toMap 为了写入SQL 19 | Map toMap() { 20 | var map = { 21 | "address": address, 22 | "symbol": symbol, 23 | "bits": bits, 24 | }; 25 | return map; 26 | } 27 | 28 | static Token fromMap(Map map) { 29 | int id = map["id"]; 30 | String addr = map["address"]; 31 | String sy = map["symbol"]; 32 | int b = map["bits"]; 33 | return Token(addr, sy, bits: b) 34 | ..id = id; 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/add_token.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/myicon.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../router/routers.dart'; 5 | import '../models/tokens.dart'; 6 | import '../controller/tokensql.dart'; 7 | import '../provide/token.dart'; 8 | import 'package:provide/provide.dart'; 9 | 10 | // 添加token页面 11 | class AddTokenPage extends StatefulWidget { 12 | @override 13 | _AddTokenPageState createState() => _AddTokenPageState(); 14 | } 15 | 16 | class _AddTokenPageState extends State { 17 | TextEditingController tokenName = TextEditingController(); 18 | TextEditingController tokenSymbol = TextEditingController(); 19 | TextEditingController tokenBits = TextEditingController(); 20 | 21 | bool buttonEnable = false; 22 | // 是否正在创建 23 | bool isCreating = false; 24 | 25 | // 检查创建钱包是否使能 26 | _checkBtnEnable() { 27 | if (tokenName.text != "" && 28 | tokenSymbol.text != "" && 29 | tokenBits.text != "" ) { 30 | setState(() { 31 | buttonEnable = true; 32 | }); 33 | } else { 34 | setState(() { 35 | buttonEnable = false; 36 | }); 37 | } 38 | } 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | tokenName.addListener(_checkBtnEnable); 44 | tokenSymbol.addListener(_checkBtnEnable); 45 | tokenBits.addListener(_checkBtnEnable); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | title: Text("添加Token"), 53 | ), 54 | body: SingleChildScrollView( 55 | child: Container( 56 | margin: EdgeInsets.only(top: ScreenUtil.getInstance().setHeight(80)), 57 | padding: EdgeInsets.only( 58 | left: ScreenUtil.getInstance().setWidth(40), 59 | right: ScreenUtil.getInstance().setWidth(40)), 60 | child: Column( 61 | children: [ 62 | Container( 63 | margin: EdgeInsets.only(bottom: 15), 64 | child: TextField( 65 | autofocus: true, 66 | controller: tokenName, 67 | decoration: InputDecoration( 68 | // labelText: "", 69 | hintText: "输入token地址", 70 | prefixIcon: Icon( 71 | MyIcon.address, 72 | size: 20, 73 | )), 74 | ), 75 | ), 76 | Container( 77 | margin: EdgeInsets.only(bottom: 15), 78 | child: TextField( 79 | controller: tokenSymbol, 80 | decoration: InputDecoration( 81 | // labelText: "密码", 82 | hintText: "symbol", 83 | prefixIcon: Icon(MyIcon.symbol)), 84 | obscureText: false, 85 | 86 | ), 87 | ), 88 | Container( 89 | margin: EdgeInsets.only(bottom: 15), 90 | child: TextField( 91 | controller: tokenBits, 92 | decoration: InputDecoration( 93 | // labelText: "密码", 94 | hintText: "bits", 95 | prefixIcon: Icon(MyIcon.bits)), 96 | keyboardType: TextInputType.number, 97 | ), 98 | ), 99 | Container( 100 | width: double.maxFinite, 101 | height: ScreenUtil.getInstance().setHeight(90), 102 | child: FlatButton( 103 | color: buttonEnable 104 | ? Color.fromRGBO(82, 195, 216, 1) 105 | : Colors.grey, 106 | onPressed: () { 107 | _addToken(tokenName.text, tokenSymbol.text, int.parse(tokenBits.text)); 108 | }, 109 | child: Text( 110 | "添加Token", 111 | style: 112 | TextStyle(fontSize: ScreenUtil.getInstance().setSp(35)), 113 | ), 114 | textColor: Colors.white, 115 | shape: RoundedRectangleBorder( 116 | borderRadius: BorderRadius.circular(10)), 117 | ), 118 | ), 119 | _loadingBuild(), 120 | ], 121 | ), 122 | ), 123 | ), 124 | ); 125 | } 126 | 127 | Widget _loadingBuild() { 128 | if (isCreating) { 129 | print("is loading"); 130 | return Container( 131 | child: Padding( 132 | padding: const EdgeInsets.all(8.0), 133 | child: Center( 134 | child: CircularProgressIndicator(), 135 | ), 136 | ), 137 | ); 138 | } 139 | return Container( 140 | width: 0, 141 | height: 0, 142 | ); 143 | } 144 | 145 | showInfo(String content) { 146 | showDialog( 147 | context: context, 148 | builder: (context) { 149 | return AlertDialog( 150 | actions: [ 151 | RaisedButton( 152 | color: Color.fromRGBO(82, 195, 216, 1), 153 | textColor: Colors.white, 154 | child: Text("确定"), 155 | onPressed: () { 156 | GlobalRouter.r.pop(context); 157 | }, 158 | ) 159 | ], 160 | title: Text(content), 161 | ); 162 | }); 163 | } 164 | 165 | _addToken(String tokenAddr, String symbol, int bits ) async { 166 | if (!buttonEnable) { 167 | return; 168 | } 169 | if(tokenAddr.length != 42){ 170 | showInfo("地址格式错误"); 171 | setState(() { 172 | isCreating = false; 173 | }); 174 | return; 175 | } 176 | 177 | // 开启加载动画 178 | setState(() { 179 | isCreating = true; 180 | }); 181 | 182 | var token = Token(tokenAddr, symbol, bits: bits); 183 | // 打开数据库 184 | var db = TokenSQL(); 185 | await db.openSqlite(); 186 | var isExist = await db.isExist(tokenAddr); 187 | if (isExist) { 188 | await db.close(); 189 | showInfo("token已经被添加"); 190 | setState(() { 191 | isCreating = false; 192 | }); 193 | return; 194 | } 195 | 196 | await db.insert(token); 197 | await db.close(); 198 | setState(() { 199 | isCreating = false; 200 | }); 201 | 202 | final cur = Provide.value(context); 203 | if(cur.tokens != null){ 204 | var tks = cur.tokens; 205 | tks.add(token); 206 | print("添加tokens: ${tks.length}"); 207 | cur.changeTokens(tks); 208 | } 209 | 210 | showDialog( 211 | context: context, 212 | builder: (context) { 213 | return AlertDialog( 214 | actions: [ 215 | RaisedButton( 216 | color: Color.fromRGBO(82, 195, 216, 1), 217 | textColor: Colors.white, 218 | child: Text("确定"), 219 | onPressed: () { 220 | // 第一次弹回本面 221 | GlobalRouter.r.pop(context); 222 | // 第二次弹回上一个页面 223 | GlobalRouter.r.pop(context); 224 | }, 225 | ) 226 | ], 227 | title: Text("创建成功"), 228 | ); 229 | }); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/create_wallet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/myicon.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../router/routers.dart'; 5 | import '../models/privkey.dart'; 6 | import '../controller/sql.dart'; 7 | import '../provide/wallet.dart'; 8 | import 'package:provide/provide.dart'; 9 | 10 | /** 11 | AppBar({ 12 | Key key, 13 | this.leading,//在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮 14 | this.automaticallyImplyLeading = true, 15 | this.title,//Toolbar 中主要内容,通常显示为当前界面的标题文字 16 | this.actions,//一个 Widget 列表,代表 Toolbar 中所显示的菜单,对于常用的菜单,通常使用 IconButton 来表示;对于不常用的菜单通常使用 PopupMenuButton 来显示为三个点,点击后弹出二级菜单 17 | this.flexibleSpace,//一个显示在 AppBar 下方的控件,高度和 AppBar 高度一样,可以实现一些特殊的效果,该属性通常在 SliverAppBar 中使用 18 | this.bottom,//一个 AppBarBottomWidget 对象,通常是 TabBar。用来在 Toolbar 标题下面显示一个 Tab 导航栏 19 | this.elevation = 4.0,//纸墨设计中控件的 z 坐标顺序,默认值为 4,对于可滚动的 SliverAppBar,当 SliverAppBar 和内容同级的时候,该值为 0, 当内容滚动 SliverAppBar 变为 Toolbar 的时候,修改 elevation 的值 20 | this.backgroundColor,//APP bar 的颜色,默认值为 ThemeData.primaryColor。改值通常和下面的三个属性一起使用 21 | this.brightness,//App bar 的亮度,有白色和黑色两种主题,默认值为 ThemeData.primaryColorBrightness 22 | this.iconTheme,//App bar 上图标的颜色、透明度、和尺寸信息。默认值为 ThemeData.primaryIconTheme 23 | this.textTheme,//App bar 上的文字样式。默认值为 ThemeData.primaryTextTheme 24 | this.primary = true, 25 | this.centerTitle,//标题是否居中显示,默认值根据不同的操作系统,显示方式不一样,true居中 false居左 26 | this.titleSpacing = NavigationToolbar.kMiddleSpacing, 27 | this.toolbarOpacity = 1.0, 28 | this.bottomOpacity = 1.0, 29 | }) 30 | */ 31 | 32 | // 创建钱包页面 33 | class CreateWalletPage extends StatefulWidget { 34 | @override 35 | _CreateWalletPageState createState() => _CreateWalletPageState(); 36 | } 37 | 38 | class _CreateWalletPageState extends State { 39 | TextEditingController walletName = TextEditingController(); 40 | TextEditingController walletpasswd = TextEditingController(); 41 | TextEditingController walletpasswdck = TextEditingController(); 42 | 43 | bool buttonEnable = false; 44 | // 是否正在创建钱包 45 | bool isCreating = false; 46 | 47 | // 检查创建钱包是否使能 48 | _checkBtnEnable() { 49 | if (walletName.text != "" && 50 | walletpasswd.text != "" && 51 | walletpasswdck.text != "" && 52 | walletpasswd.text == walletpasswdck.text) { 53 | setState(() { 54 | buttonEnable = true; 55 | print("ok------"); 56 | }); 57 | } else { 58 | setState(() { 59 | buttonEnable = false; 60 | print("not ok------"); 61 | }); 62 | } 63 | } 64 | 65 | @override 66 | void initState() { 67 | super.initState(); 68 | walletName.addListener(_checkBtnEnable); 69 | walletpasswd.addListener(_checkBtnEnable); 70 | walletpasswdck.addListener(_checkBtnEnable); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return Scaffold( 76 | appBar: AppBar( 77 | title: Text("创建钱包"), 78 | ), 79 | body: SingleChildScrollView( 80 | child: Container( 81 | margin: EdgeInsets.only(top: ScreenUtil.getInstance().setHeight(80)), 82 | padding: EdgeInsets.only( 83 | left: ScreenUtil.getInstance().setWidth(40), 84 | right: ScreenUtil.getInstance().setWidth(40)), 85 | child: Column( 86 | children: [ 87 | Container( 88 | margin: EdgeInsets.only(bottom: 15), 89 | child: TextField( 90 | autofocus: true, 91 | controller: walletName, 92 | decoration: InputDecoration( 93 | // labelText: "", 94 | hintText: "输入钱包名称", 95 | prefixIcon: Icon( 96 | MyIcon.wallet, 97 | size: 20, 98 | )), 99 | ), 100 | ), 101 | Container( 102 | margin: EdgeInsets.only(bottom: 15), 103 | child: TextField( 104 | controller: walletpasswd, 105 | decoration: InputDecoration( 106 | // labelText: "密码", 107 | hintText: "钱包密码", 108 | prefixIcon: Icon(Icons.lock)), 109 | obscureText: true, 110 | ), 111 | ), 112 | Container( 113 | margin: EdgeInsets.only(bottom: 15), 114 | child: TextField( 115 | controller: walletpasswdck, 116 | decoration: InputDecoration( 117 | // labelText: "密码", 118 | hintText: "再次输入钱包密码", 119 | prefixIcon: Icon(Icons.lock)), 120 | obscureText: true, 121 | ), 122 | ), 123 | Container( 124 | width: double.maxFinite, 125 | height: ScreenUtil.getInstance().setHeight(90), 126 | child: FlatButton( 127 | color: buttonEnable 128 | ? Color.fromRGBO(82, 195, 216, 1) 129 | : Colors.grey, 130 | onPressed: () { 131 | _createWallet(walletName.text, walletpasswd.text); 132 | }, 133 | child: Text( 134 | "创建钱包", 135 | style: 136 | TextStyle(fontSize: ScreenUtil.getInstance().setSp(35)), 137 | ), 138 | textColor: Colors.white, 139 | shape: RoundedRectangleBorder( 140 | borderRadius: BorderRadius.circular(10)), 141 | ), 142 | ), 143 | _loadingBuild(), 144 | ], 145 | ), 146 | ), 147 | ), 148 | ); 149 | } 150 | 151 | Widget _loadingBuild() { 152 | if (isCreating) { 153 | print("is loading"); 154 | return Container( 155 | child: Padding( 156 | padding: const EdgeInsets.all(8.0), 157 | child: Center( 158 | child: CircularProgressIndicator(), 159 | ), 160 | ), 161 | ); 162 | } 163 | return Container( 164 | width: 0, 165 | height: 0, 166 | ); 167 | } 168 | 169 | showInfo(String content) { 170 | showDialog( 171 | context: context, 172 | builder: (context) { 173 | return AlertDialog( 174 | actions: [ 175 | RaisedButton( 176 | color: Color.fromRGBO(82, 195, 216, 1), 177 | textColor: Colors.white, 178 | child: Text("确定"), 179 | onPressed: () { 180 | GlobalRouter.r.pop(context); 181 | }, 182 | ) 183 | ], 184 | title: Text(content), 185 | ); 186 | }); 187 | } 188 | 189 | _createWallet(String walletName, String pw) async { 190 | if (!buttonEnable) { 191 | return; 192 | } 193 | // 开启加载动画 194 | setState(() { 195 | isCreating = true; 196 | }); 197 | 198 | var pk = PrivkeyManager.randomGenerate(); 199 | pk.passwd = pw; 200 | pk.walletName = walletName; 201 | // 打开数据库 202 | var db = SQLDAO(); 203 | await db.openSqlite(); 204 | var isExist = await db.isExistWallet(walletName); 205 | if (isExist) { 206 | await db.close(); 207 | showInfo("钱包名称已经存在"); 208 | setState(() { 209 | isCreating = false; 210 | }); 211 | return; 212 | } 213 | 214 | await db.insert(pk); 215 | await db.close(); 216 | setState(() { 217 | isCreating = false; 218 | }); 219 | 220 | final cur = Provide.value(context); 221 | if(cur.privKey == null){ 222 | cur.changePrivKey(pk); 223 | } 224 | 225 | showDialog( 226 | context: context, 227 | builder: (context) { 228 | return AlertDialog( 229 | actions: [ 230 | RaisedButton( 231 | color: Color.fromRGBO(82, 195, 216, 1), 232 | textColor: Colors.white, 233 | child: Text("确定"), 234 | onPressed: () { 235 | // 第一次弹回本面 236 | GlobalRouter.r.pop(context); 237 | // 第二次弹回上一个页面 238 | GlobalRouter.r.pop(context); 239 | }, 240 | ) 241 | ], 242 | title: Text("创建成功"), 243 | ); 244 | }); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/card.dart'; 3 | import '../widgets/token.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import '../models/tokens.dart'; 6 | import '../provide/token.dart'; 7 | import '../controller/tokensql.dart'; 8 | import 'package:provide/provide.dart'; 9 | 10 | class HomePage extends StatefulWidget { 11 | @override 12 | _HomePageState createState() => _HomePageState(); 13 | } 14 | 15 | class _HomePageState extends State { 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | _loadTokens() async { 23 | final curTkProvide = Provide.value(context); 24 | var sql = TokenSQL(); 25 | await sql.openSqlite(); 26 | List tks = await sql.queryAll(); 27 | await sql.close(); 28 | curTkProvide.changeTokens(tks); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | // 首页布局 34 | // 1. 一个类似公交卡组件 显示当前选择的钱包信息 35 | // 2. 列出当前所有添加的token 36 | return Scaffold( 37 | body: SingleChildScrollView( 38 | child: Container( 39 | width: ScreenUtil.getInstance().setWidth(750), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | SelfCard(), 44 | // 展示当前token信息 45 | SelfTokenList(), 46 | ], 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/import_wallet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../widgets/myicon.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import '../router/routers.dart'; 5 | import '../models/privkey.dart'; 6 | import '../controller/sql.dart'; 7 | 8 | // ImportWalletPage 导入钱包页面 9 | class ImportWalletPage extends StatefulWidget { 10 | @override 11 | _ImportWalletPageState createState() => _ImportWalletPageState(); 12 | } 13 | 14 | class _ImportWalletPageState extends State { 15 | bool isCreating = false; 16 | bool buttonEnable = false; 17 | TextEditingController privCtl = TextEditingController(); 18 | TextEditingController walletpasswd = TextEditingController(); 19 | TextEditingController walletpasswdck = TextEditingController(); 20 | TextEditingController walletName = TextEditingController(); 21 | 22 | // 检查创建钱包是否使能 23 | _checkBtnEnable() { 24 | if (privCtl.text != "" && 25 | walletpasswd.text != "" && 26 | walletpasswdck.text != "" && 27 | walletpasswd.text == walletpasswdck.text && 28 | walletName.text != "") { 29 | setState(() { 30 | buttonEnable = true; 31 | }); 32 | } else { 33 | setState(() { 34 | buttonEnable = false; 35 | }); 36 | } 37 | } 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | privCtl.addListener(_checkBtnEnable); 43 | walletpasswd.addListener(_checkBtnEnable); 44 | walletpasswdck.addListener(_checkBtnEnable); 45 | walletName.addListener(_checkBtnEnable); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | title: Text("导入钱包"), 53 | ), 54 | body: SingleChildScrollView( 55 | child: Container( 56 | margin: EdgeInsets.only(top: ScreenUtil.getInstance().setHeight(80)), 57 | padding: EdgeInsets.only( 58 | left: ScreenUtil.getInstance().setWidth(40), 59 | right: ScreenUtil.getInstance().setWidth(40)), 60 | child: Column( 61 | children: [ 62 | Container( 63 | margin: EdgeInsets.only(bottom: 15), 64 | child: TextField( 65 | autofocus: true, 66 | controller: privCtl, 67 | decoration: InputDecoration( 68 | border: OutlineInputBorder(), hintText: "请输入16进制私钥"), 69 | maxLines: 5, 70 | ), 71 | ), 72 | Container( 73 | margin: EdgeInsets.only(bottom: 15), 74 | child: TextField( 75 | autofocus: true, 76 | controller: walletName, 77 | decoration: InputDecoration( 78 | // labelText: "", 79 | hintText: "输入钱包名称", 80 | prefixIcon: Icon( 81 | MyIcon.wallet, 82 | size: 20, 83 | )), 84 | ), 85 | ), 86 | Container( 87 | margin: EdgeInsets.only(bottom: 15), 88 | child: TextField( 89 | controller: walletpasswd, 90 | decoration: InputDecoration( 91 | // labelText: "密码", 92 | hintText: "钱包密码", 93 | prefixIcon: Icon(Icons.lock)), 94 | obscureText: true, 95 | ), 96 | ), 97 | Container( 98 | margin: EdgeInsets.only(bottom: 15), 99 | child: TextField( 100 | controller: walletpasswdck, 101 | decoration: InputDecoration( 102 | // labelText: "密码", 103 | hintText: "再次输入钱包密码", 104 | prefixIcon: Icon(Icons.lock)), 105 | obscureText: true, 106 | ), 107 | ), 108 | Container( 109 | width: double.maxFinite, 110 | height: ScreenUtil.getInstance().setHeight(90), 111 | child: FlatButton( 112 | color: buttonEnable 113 | ? Color.fromRGBO(82, 195, 216, 1) 114 | : Colors.grey, 115 | onPressed: () { 116 | _createWallet(walletName.text, walletpasswd.text); 117 | }, 118 | child: Text( 119 | "导入钱包", 120 | style: 121 | TextStyle(fontSize: ScreenUtil.getInstance().setSp(35)), 122 | ), 123 | textColor: Colors.white, 124 | shape: RoundedRectangleBorder( 125 | borderRadius: BorderRadius.circular(10)), 126 | ), 127 | ), 128 | _loadingBuild(), 129 | ], 130 | ), 131 | ), 132 | ), 133 | ); 134 | } 135 | 136 | Widget _loadingBuild() { 137 | if (isCreating) { 138 | print("is loading"); 139 | return Container( 140 | child: Padding( 141 | padding: const EdgeInsets.all(8.0), 142 | child: Center( 143 | child: CircularProgressIndicator(), 144 | ), 145 | ), 146 | ); 147 | } 148 | return Container( 149 | width: 0, 150 | height: 0, 151 | ); 152 | } 153 | 154 | showInfo(String content) { 155 | showDialog( 156 | context: context, 157 | builder: (context) { 158 | return AlertDialog( 159 | actions: [ 160 | RaisedButton( 161 | color: Color.fromRGBO(82, 195, 216, 1), 162 | textColor: Colors.white, 163 | child: Text("确定"), 164 | onPressed: () { 165 | GlobalRouter.r.pop(context); 166 | }, 167 | ) 168 | ], 169 | title: Text(content), 170 | ); 171 | }); 172 | } 173 | 174 | _createWallet(String walletName, String pw) async { 175 | if (!buttonEnable) { 176 | return; 177 | } 178 | // todo:: 此处需要更改 179 | if (privCtl.text.length != 64 && privCtl.text.length != 66) { 180 | showInfo("私钥格式错误, ${privCtl.text.length}"); 181 | return; 182 | } 183 | 184 | // 开启加载动画 185 | setState(() { 186 | isCreating = true; 187 | }); 188 | 189 | // 检查钱包名称是否存在 190 | // 打开数据库 191 | var db = SQLDAO(); 192 | await db.openSqlite(); 193 | var isExist = await db.isExistWallet(walletName); 194 | if (isExist) { 195 | await db.close(); 196 | showInfo("钱包名称已经存在"); 197 | setState(() { 198 | isCreating = false; 199 | }); 200 | return; 201 | } 202 | 203 | // 检查私钥格式是否正确 204 | if (!PrivkeyManager.checkPrivateKey(privCtl.text)) { 205 | await db.close(); 206 | showInfo("私钥格式错误"); 207 | setState(() { 208 | isCreating = false; 209 | }); 210 | return; 211 | } 212 | 213 | var pk = PrivkeyManager.fromHexString(privCtl.text); 214 | pk.passwd = pw; 215 | pk.walletName = walletName; 216 | await db.insert(pk); 217 | await db.close(); 218 | setState(() { 219 | isCreating = false; 220 | }); 221 | 222 | showDialog( 223 | context: context, 224 | builder: (context) { 225 | return AlertDialog( 226 | actions: [ 227 | RaisedButton( 228 | color: Color.fromRGBO(82, 195, 216, 1), 229 | textColor: Colors.white, 230 | child: Text("确定"), 231 | onPressed: () { 232 | // 第一次弹回本页面 233 | GlobalRouter.r.pop(context); 234 | // 第二次弹回上一个页面 235 | GlobalRouter.r.pop(context); 236 | }, 237 | ) 238 | ], 239 | title: Text("创建成功"), 240 | ); 241 | }); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/send_tx.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import 'package:provide/provide.dart'; 4 | import '../provide/wallet.dart'; 5 | import '../models/privkey.dart'; 6 | import '../requests/http_requets.dart'; 7 | import '../requests/push_tx_models.dart'; 8 | import '../router/routers.dart'; 9 | import 'package:url_launcher/url_launcher.dart'; 10 | import 'package:flutter/gestures.dart'; 11 | 12 | // 发起交易页 13 | 14 | class TransactionPage extends StatefulWidget { 15 | // 发起方地址 16 | String to; 17 | // 金额 18 | String value; 19 | // 携带数据 20 | String data; 21 | // 币种 22 | String currency; 23 | // txType 交易类型 VET VTHO ERC20 CALL 24 | String txType; 25 | // 待签名内容 26 | String needSign; 27 | // 请求ID 28 | String requestID; 29 | TransactionPage(this.to, this.value, this.data, this.currency, this.txType, 30 | this.needSign, this.requestID) 31 | : super(); 32 | 33 | @override 34 | _TransactionPageState createState() => _TransactionPageState(); 35 | } 36 | 37 | class _TransactionPageState extends State { 38 | bool isSending = false; 39 | List _listW = []; 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final cur = Provide.value(context); 49 | if (cur.privKey == null) { 50 | return Scaffold( 51 | appBar: AppBar( 52 | title: Text("发起交易"), 53 | ), 54 | body: Center( 55 | child: Text("当前钱包不存在, 不能创建任何交易~"), 56 | )); 57 | } 58 | 59 | _listW.clear(); 60 | _listW.add(_coustomText("发起方地址:")); 61 | _listW.add(_coustomTextFiled(cur.privKey.addrToString())); 62 | 63 | _listW.add(_coustomText("\n接收方地址:")); 64 | _listW.add( 65 | _coustomTextFiled(widget.to), 66 | ); 67 | 68 | _listW.add(_coustomText("\n交易类型:")); 69 | _listW.add(_coustomTextFiled(widget.txType.toUpperCase())); 70 | 71 | if (widget.txType.toLowerCase() == "erc20") { 72 | _listW.add(_coustomText("\n合约地址:")); 73 | _listW.add( 74 | _coustomTextFiled(widget.currency), 75 | ); 76 | } 77 | 78 | _listW.add(_coustomText("\nValue:")); 79 | _listW.add( 80 | _coustomTextFiled(widget.value), 81 | ); 82 | 83 | _listW.add(_coustomText("\ndata:")); 84 | _listW.add( 85 | _coustomTextFiled(widget.data, maxLine: 5), 86 | ); 87 | 88 | _listW.add(_coustomButton(widget.needSign, cur.privKey)); 89 | _listW.add(_loadingBuild()); 90 | 91 | return Scaffold( 92 | appBar: AppBar( 93 | title: Text("导入钱包"), 94 | ), 95 | body: SingleChildScrollView( 96 | child: Container( 97 | margin: EdgeInsets.only(top: ScreenUtil.getInstance().setHeight(80)), 98 | padding: EdgeInsets.only( 99 | left: ScreenUtil.getInstance().setWidth(40), 100 | right: ScreenUtil.getInstance().setWidth(40)), 101 | child: Column( 102 | crossAxisAlignment: CrossAxisAlignment.start, 103 | children: _listW, 104 | ), 105 | ), 106 | ), 107 | ); 108 | } 109 | 110 | Widget _coustomTextFiled(String c, {int maxLine = 1}) { 111 | return TextField( 112 | maxLines: maxLine, 113 | maxLengthEnforced: false, 114 | style: TextStyle(color: Color.fromRGBO(82, 195, 216, 1)), 115 | enabled: false, 116 | controller: TextEditingController(text: c), 117 | decoration: InputDecoration( 118 | contentPadding: EdgeInsets.only(top: 10, bottom: 10, left: 5), 119 | border: OutlineInputBorder( 120 | borderSide: BorderSide(color: Colors.black, width: 2), 121 | borderRadius: BorderRadius.all(Radius.circular(5.0))), 122 | fillColor: Color.fromRGBO(82, 195, 216, 1), 123 | ), 124 | ); 125 | } 126 | 127 | Widget _coustomText(String c, {int maxLine = 1}) { 128 | return Text(c, 129 | style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(30))); 130 | } 131 | 132 | Widget _coustomButton(String hash, PrivkeyManager priv) { 133 | return Container( 134 | margin: EdgeInsets.only(top: 10), 135 | width: double.maxFinite, 136 | height: ScreenUtil.getInstance().setHeight(90), 137 | child: FlatButton( 138 | color: Color.fromRGBO(82, 195, 216, 1), 139 | onPressed: () { 140 | if (isSending) { 141 | return; 142 | } 143 | _signTx(hash, priv); 144 | }, 145 | child: Text( 146 | isSending ? "正在交易..." : "发起交易", 147 | style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(35)), 148 | ), 149 | textColor: Colors.white, 150 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), 151 | ), 152 | ); 153 | } 154 | 155 | Widget _loadingBuild() { 156 | print("加载-------"); 157 | if (isSending) { 158 | print("正在加载---------------"); 159 | return Container( 160 | child: Padding( 161 | padding: const EdgeInsets.all(8.0), 162 | child: Center( 163 | child: CircularProgressIndicator(), 164 | ), 165 | ), 166 | ); 167 | } 168 | return Container( 169 | width: 0, 170 | height: 0, 171 | ); 172 | } 173 | 174 | // 175 | _signTx(String hash, PrivkeyManager priv) async { 176 | setState(() { 177 | isSending = true; 178 | }); 179 | String signConent = priv.signMsg(hash); 180 | var result = await pushtx_request(widget.requestID, signConent); 181 | await Future.delayed(Duration(seconds: 3)); 182 | if (result is String) { 183 | showInfo(result); 184 | setState(() { 185 | isSending = false; 186 | }); 187 | return; 188 | } 189 | var r = (result as PushTxModel); 190 | if (r.code != "0") { 191 | showInfo(r.message); 192 | setState(() { 193 | isSending = false; 194 | }); 195 | return; 196 | } 197 | _showDetails(r.data.txId); 198 | setState(() { 199 | isSending = false; 200 | }); 201 | } 202 | 203 | showInfo(String content) { 204 | showDialog( 205 | context: context, 206 | builder: (context) { 207 | return AlertDialog( 208 | actions: [ 209 | RaisedButton( 210 | color: Color.fromRGBO(82, 195, 216, 1), 211 | textColor: Colors.white, 212 | child: Text("确定"), 213 | onPressed: () { 214 | GlobalRouter.r.pop(context); 215 | }, 216 | ) 217 | ], 218 | title: Text(content), 219 | ); 220 | }); 221 | } 222 | 223 | _showDetails(String txID) { 224 | showDialog( 225 | context: context, 226 | builder: (context) { 227 | return AlertDialog( 228 | actions: [ 229 | RaisedButton( 230 | color: Color.fromRGBO(82, 195, 216, 1), 231 | textColor: Colors.white, 232 | child: Text("确定"), 233 | onPressed: () { 234 | GlobalRouter.r.pop(context); 235 | }, 236 | ) 237 | ], 238 | title: RichText( 239 | text: TextSpan(children: [ 240 | TextSpan( 241 | text: "交易发起成功!可以点击下方链接查看详情~ \n", 242 | style: TextStyle(color: Colors.black)), 243 | TextSpan( 244 | text: txID, 245 | style: TextStyle( 246 | color: Colors.green, 247 | decoration: TextDecoration.underline), 248 | recognizer: TapGestureRecognizer() 249 | ..onTap = () { 250 | launch( 251 | "https://testnet.veforge.com/transactions/${txID}", 252 | forceSafariVC: false); 253 | }), 254 | ]), 255 | ), 256 | ); 257 | }); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/pages/wallet_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/privkey.dart'; 3 | import '../controller/sql.dart'; 4 | import '../controller/idsql.dart'; 5 | import 'package:provide/provide.dart'; 6 | import '../provide/wallet.dart'; 7 | 8 | class WalletListPage extends StatefulWidget { 9 | @override 10 | _WalletListPageState createState() => _WalletListPageState(); 11 | } 12 | 13 | class _WalletListPageState extends State { 14 | List _priKeys = []; 15 | int _curIndex = 0; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _loadWallet(); 21 | } 22 | 23 | _loadWallet() async { 24 | var db = SQLDAO(); 25 | await db.openSqlite(); 26 | _priKeys = await db.queryAll(); 27 | // print("${_priKeys.length}"); 28 | await db.close(); 29 | 30 | // 查询钱包ID 31 | var dbID = WalletIDSQL(); 32 | await dbID.openSqlite(); 33 | WalletID wid = await dbID.currentWalletID(); 34 | if (wid != null) { 35 | _curIndex = wid.id; 36 | } else { 37 | _curIndex = 0; 38 | } 39 | await dbID.close(); 40 | setState(() {}); 41 | } 42 | 43 | _updateWalletID(int id) async { 44 | var dbID = WalletIDSQL(); 45 | await dbID.openSqlite(); 46 | await dbID.replaceInto(WalletID()..id = id); 47 | await dbID.close(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Scaffold( 53 | appBar: AppBar( 54 | title: Text("钱包列表"), 55 | ), 56 | body: Container( 57 | child: Center( 58 | child: _buildList(), 59 | ), 60 | ), 61 | ); 62 | } 63 | 64 | Widget _buildList() { 65 | final currentWallet = Provide.value(context); 66 | if (_priKeys==null || _priKeys.length == 0) { 67 | return Text("不存在"); 68 | } 69 | return ListView.builder( 70 | itemCount: _priKeys.length, 71 | itemBuilder: (context, index) { 72 | return ListTile( 73 | subtitle: Text("${_priKeys[index].addrToString()}"), 74 | title: Text("${_priKeys[index].walletName}"), 75 | leading: Icon( 76 | Icons.money_off, 77 | color: _curIndex == _priKeys[index].id ? Colors.green : Colors.grey, 78 | ), 79 | onTap: () { 80 | print("钱包ID: ${_priKeys[index].id}"); 81 | setState(() { 82 | _curIndex = _priKeys[index].id; 83 | }); 84 | _updateWalletID(_priKeys[index].id); 85 | 86 | currentWallet.changePrivKey(_priKeys[index]); 87 | print("当前钱包改变"); 88 | }, 89 | ); 90 | }, 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/provide/token.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/tokens.dart'; 3 | 4 | // 代表当前token列表数据 5 | class TokensState with ChangeNotifier { 6 | List tokens = []; 7 | 8 | changeTokens(List l) { 9 | tokens = l; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/provide/wallet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/privkey.dart'; 3 | 4 | // 代表当前钱包数据 5 | class CurrentWalletState with ChangeNotifier { 6 | PrivkeyManager privKey; 7 | 8 | changePrivKey(PrivkeyManager p) { 9 | privKey = p; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/requests/http_requets.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'dart:async'; 3 | import 'dart:io'; 4 | import '../configure/urls.dart'; 5 | import './unsign_tx_models.dart'; 6 | import './vetrequest_models.dart'; 7 | import './push_tx_models.dart'; 8 | 9 | Future getBalance(String account, String currency) async { 10 | // 创建DIO 11 | var dio = Dio(); 12 | // 设置请求头为 application/x-www-form-urlencoded 13 | try { 14 | // dio.options.contentType = 15 | // ContentType.parse("application/x-www-form-urlencoded"); 16 | var query = {'currency': currency}; 17 | Response> resp = 18 | await dio.get(APIS['balances']+account, queryParameters: query); 19 | if (resp.statusCode != 200) { 20 | return "网络请求失败"; 21 | } 22 | print(resp.data); 23 | return VETRequest.fromJson(resp.data); 24 | } catch (e) { 25 | return e.toString(); 26 | } 27 | } 28 | 29 | Future unsigntx_request(String from, String to, String amount, String currency, String txType) async { 30 | // 创建DIO 31 | var dio = Dio(); 32 | try { 33 | dio.options.contentType = 34 | ContentType.parse("application/json"); 35 | var query = { 36 | 'from': from, 37 | 'to': to, 38 | 'amount': amount, 39 | 'currency': currency, 40 | 'txType': txType, 41 | }; 42 | Response> resp = 43 | await dio.put(APIS['unsignTx'], data: query); 44 | if (resp.statusCode != 200) { 45 | return "网络请求失败"; 46 | } 47 | print(resp.data); 48 | return UnsignTxModel.fromJson(resp.data); 49 | } catch (e) { 50 | return e.toString(); 51 | } 52 | } 53 | 54 | Future pushtx_request(String requestID, String sign) async { 55 | // 创建DIO 56 | var dio = Dio(); 57 | try { 58 | dio.options.contentType = 59 | ContentType.parse("application/json"); 60 | var query = { 61 | "request_id": requestID, 62 | "sign": sign 63 | }; 64 | Response> resp = 65 | await dio.put(APIS['pushTx'], data: query); 66 | if (resp.statusCode != 200) { 67 | return "网络请求失败"; 68 | } 69 | print(resp.data); 70 | return PushTxModel.fromJson(resp.data); 71 | } catch (e) { 72 | return e.toString(); 73 | } 74 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/requests/push_tx_models.dart: -------------------------------------------------------------------------------- 1 | class PushTxModel { 2 | String code; 3 | String message; 4 | PushTxResult data; 5 | 6 | PushTxModel({this.code, this.message, this.data}); 7 | 8 | PushTxModel.fromJson(Map json) { 9 | code = json['code']; 10 | message = json['message']; 11 | data = json['data'] != null ? new PushTxResult.fromJson(json['data']) : null; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = new Map(); 16 | data['code'] = this.code; 17 | data['message'] = this.message; 18 | if (this.data != null) { 19 | data['data'] = this.data.toJson(); 20 | } 21 | return data; 22 | } 23 | } 24 | 25 | class PushTxResult { 26 | String txId; 27 | String requestId; 28 | 29 | PushTxResult({this.txId, this.requestId}); 30 | 31 | PushTxResult.fromJson(Map json) { 32 | txId = json['tx_id']; 33 | requestId = json['request_id']; 34 | } 35 | 36 | Map toJson() { 37 | final Map data = new Map(); 38 | data['tx_id'] = this.txId; 39 | data['request_id'] = this.requestId; 40 | return data; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/flutter_vechain/lib/requests/unsign_tx_models.dart: -------------------------------------------------------------------------------- 1 | class UnsignTxModel { 2 | String code; 3 | String message; 4 | UnsignTxResult data; 5 | 6 | UnsignTxModel({this.code, this.message, this.data}); 7 | 8 | UnsignTxModel.fromJson(Map json) { 9 | code = json['code']; 10 | message = json['message']; 11 | data = json['data'] != null ? new UnsignTxResult.fromJson(json['data']) : null; 12 | } 13 | 14 | Map toJson() { 15 | final Map data = new Map(); 16 | data['code'] = this.code; 17 | data['message'] = this.message; 18 | if (this.data != null) { 19 | data['data'] = this.data.toJson(); 20 | } 21 | return data; 22 | } 23 | } 24 | 25 | class UnsignTxResult { 26 | String needSignContent; 27 | String requestId; 28 | String from; 29 | String to; 30 | String amount; 31 | String txType; 32 | 33 | UnsignTxResult( 34 | {this.needSignContent, 35 | this.requestId, 36 | this.from, 37 | this.to, 38 | this.amount, 39 | this.txType}); 40 | 41 | UnsignTxResult.fromJson(Map json) { 42 | needSignContent = json['need_sign_content']; 43 | requestId = json['request_id']; 44 | from = json['from']; 45 | to = json['to']; 46 | amount = json['amount']; 47 | txType = json['tx_type']; 48 | } 49 | 50 | Map toJson() { 51 | final Map data = new Map(); 52 | data['need_sign_content'] = this.needSignContent; 53 | data['request_id'] = this.requestId; 54 | data['from'] = this.from; 55 | data['to'] = this.to; 56 | data['amount'] = this.amount; 57 | data['tx_type'] = this.txType; 58 | return data; 59 | } 60 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/requests/vetrequest_models.dart: -------------------------------------------------------------------------------- 1 | 2 | class VETRequest { 3 | String code; 4 | String message; 5 | List data; 6 | 7 | VETRequest({this.code, this.message, this.data}); 8 | 9 | VETRequest.fromJson(Map json) { 10 | code = json['code']; 11 | message = json['message']; 12 | if (json['data'] != null) { 13 | data = new List(); 14 | json['data'].forEach((v) { 15 | data.add(new BalanceResult.fromJson(v)); 16 | }); 17 | } 18 | } 19 | 20 | Map toJson() { 21 | final Map data = new Map(); 22 | data['code'] = this.code; 23 | data['message'] = this.message; 24 | if (this.data != null) { 25 | data['data'] = this.data.map((v) => v.toJson()).toList(); 26 | } 27 | return data; 28 | } 29 | } 30 | 31 | class BalanceResult { 32 | String contractAddress; 33 | String balance; 34 | 35 | BalanceResult({this.contractAddress, this.balance}); 36 | 37 | BalanceResult.fromJson(Map json) { 38 | contractAddress = json['contract_address']; 39 | balance = json['balance']; 40 | } 41 | 42 | Map toJson() { 43 | final Map data = new Map(); 44 | data['contract_address'] = this.contractAddress; 45 | data['balance'] = this.balance; 46 | return data; 47 | } 48 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/router/handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../pages/create_wallet.dart'; 4 | import '../pages/import_wallet.dart'; 5 | import '../pages/wallet_list.dart'; 6 | import '../pages/add_token.dart'; 7 | import '../pages/send_tx.dart'; 8 | import '../main.dart'; 9 | 10 | var rootHandler = Handler( 11 | handlerFunc: (BuildContext context, Map> params) { 12 | return IndexPage(); 13 | }); 14 | 15 | var createWalletHandler = Handler( 16 | handlerFunc: (BuildContext context, Map> params) { 17 | return CreateWalletPage(); 18 | }); 19 | 20 | var importWalletHandler = Handler( 21 | handlerFunc: (BuildContext context, Map> params) { 22 | return ImportWalletPage(); 23 | }); 24 | 25 | var listWalletHandler = Handler( 26 | handlerFunc: (BuildContext context, Map> params) { 27 | return WalletListPage(); 28 | }); 29 | 30 | var addTokenHandler = Handler( 31 | handlerFunc: (BuildContext context, Map> params) { 32 | return AddTokenPage(); 33 | }); 34 | 35 | var txHandler = Handler( 36 | handlerFunc: (BuildContext context, Map> params) { 37 | return TransactionPage(params["to"]?.first, params["value"]?.first, 38 | params["data"]?.first, params["currency"]?.first, params["txType"]?.first, 39 | params["needSign"]?.first, params["requestID"]?.first, 40 | ); 41 | }); -------------------------------------------------------------------------------- /app/flutter_vechain/lib/router/routers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluro/fluro.dart'; 3 | import './handler.dart'; 4 | 5 | 6 | class GlobalRouter { 7 | static Router r; 8 | 9 | // 路由路径 10 | static String root = "/"; 11 | static String createWallet = "/create_wallet"; 12 | static String importWallet = "/import_wallet"; 13 | static String listWallet = "/list_wallet"; 14 | static String addToken = "/add_token"; 15 | static String sendTx = "/send_tx/:to/:value/:data/:currency/:txType/:needSign/:requestID"; 16 | 17 | static void configureRoutes(Router router) { 18 | r = router; 19 | router.notFoundHandler = new Handler( 20 | handlerFunc: (BuildContext context, Map> params) { 21 | print("ROUTE WAS NOT FOUND !!!"); 22 | }); 23 | router.define(root, handler: rootHandler); 24 | router.define(createWallet, handler: createWalletHandler); 25 | router.define(importWallet, handler: importWalletHandler); 26 | router.define(listWallet, handler: listWalletHandler); 27 | router.define(addToken, handler: addTokenHandler); 28 | router.define(sendTx, handler: txHandler); 29 | } 30 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/widgets/myicon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | // 自己的ICON 4 | class MyIcon { 5 | // 二维码 6 | static const IconData srcode = const _MyIconData(0xe646); 7 | // 钱包 8 | static const IconData wallet = const _MyIconData(0xe609); 9 | // 扫一扫 10 | static const IconData scan = const _MyIconData(0xe60b); 11 | // 创建钱包 12 | static const IconData createWallt = const _MyIconData(0xe619); 13 | 14 | // 地址 15 | static const IconData address = const _MyIconData(0xe63e); 16 | // 符号 17 | static const IconData symbol = const _MyIconData(0xe607); 18 | // 位数 19 | static const IconData bits = const _MyIconData(0xe61f); 20 | // 关于 21 | static const IconData about = const _MyIconData(0xe6a1); 22 | // 导入钱包 23 | static const IconData importWallt = const _MyIconData(0xe621); 24 | // 切换钱包 25 | static const IconData change = const _MyIconData(0xe629); 26 | } 27 | 28 | class _MyIconData extends IconData { 29 | const _MyIconData(int codePoint) 30 | : super( 31 | codePoint, 32 | fontFamily: 'selfIcon', 33 | ); 34 | } -------------------------------------------------------------------------------- /app/flutter_vechain/lib/widgets/token.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | import '../models/tokens.dart'; 4 | import '../provide/token.dart'; 5 | import '../provide/wallet.dart'; 6 | import '../controller/tokensql.dart'; 7 | import 'package:provide/provide.dart'; 8 | import '../router/routers.dart'; 9 | import '../requests/http_requets.dart'; 10 | import '../requests/vetrequest_models.dart'; 11 | import 'package:url_launcher/url_launcher.dart'; 12 | import 'package:flutter/gestures.dart'; 13 | import 'dart:math'; 14 | 15 | class SelfTokenList extends StatefulWidget { 16 | @override 17 | _SelfTokenListState createState() => _SelfTokenListState(); 18 | } 19 | 20 | class _SelfTokenListState extends State { 21 | List _tokenList = []; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _loadTokens(); 27 | } 28 | 29 | _loadTokens() async { 30 | var sql = TokenSQL(); 31 | await sql.openSqlite(); 32 | List tks = await sql.queryAll(); 33 | await sql.close(); 34 | if (tks != null) { 35 | setState(() { 36 | _tokenList = tks; 37 | }); 38 | } else { 39 | return; 40 | } 41 | // 加载成功之后 要告诉状态管理 42 | if (tks.length != 0) { 43 | final cur = Provide.value(context); 44 | cur.changeTokens(tks); 45 | } 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Container( 51 | margin: EdgeInsets.only(top: 20), 52 | padding: EdgeInsets.all(20), 53 | height: ScreenUtil.getInstance().setHeight(800), 54 | child: Column( 55 | children: [ 56 | // 列表头 57 | _titleBuild(), 58 | // token list 59 | Expanded( 60 | flex: 1, 61 | child: _tokenListBuild(), 62 | ), 63 | // 占位 64 | Container( 65 | height: 0, 66 | child: Provide( 67 | builder: (context, child, value) { 68 | // 69 | if (value.tokens.length != 0) { 70 | _tokenList = value.tokens; 71 | } 72 | // 拼接tokens 73 | List tokens = _tokenList.map((e) { 74 | return e.address; 75 | }).toList(); 76 | 77 | final wallet = Provide.value(context); 78 | if (wallet.privKey != null) { 79 | // 调用接口 请求余额 80 | _request_balance( 81 | wallet.privKey.addrToString(), tokens.join(",")); 82 | } 83 | 84 | print("token 列表发送变化 ${_tokenList}"); 85 | return Text(""); 86 | }, 87 | ), 88 | ), 89 | Container( 90 | height: 0, 91 | child: Provide( 92 | builder: (context, child, value) { 93 | // 94 | print("钱包状态发生变化: ${value.privKey}"); 95 | if (value.privKey == null) { 96 | return Text(""); 97 | } 98 | if (_tokenList.length == 0) { 99 | return Text(""); 100 | } 101 | // 拼接tokens 102 | List tokens = _tokenList.map((e) { 103 | return e.address; 104 | }).toList(); 105 | _request_balance( 106 | value.privKey.addrToString(), tokens.join(",")); 107 | return Text(""); 108 | }, 109 | ), 110 | ), 111 | ], 112 | ), 113 | ); 114 | } 115 | 116 | Widget _titleBuild() { 117 | return Row( 118 | children: [ 119 | Text( 120 | "Tokens", 121 | style: TextStyle( 122 | fontWeight: FontWeight.bold, 123 | fontSize: ScreenUtil.getInstance().setSp(58), 124 | ), 125 | ), 126 | // 拉伸 127 | Expanded( 128 | flex: 1, 129 | child: Text(""), 130 | ), 131 | IconButton( 132 | icon: Icon(Icons.add_circle), 133 | color: Color.fromRGBO(82, 195, 216, 1), 134 | onPressed: () { 135 | GlobalRouter.r.navigateTo(context, "add_token"); 136 | }, 137 | ) 138 | ], 139 | ); 140 | } 141 | 142 | Widget _tokenListBuild() { 143 | if (_tokenList.length == 0) { 144 | return Container( 145 | margin: EdgeInsets.only(top: 20), 146 | child: Text( 147 | "当前没有添加任何token信息 点击+号进行添加", 148 | style: TextStyle( 149 | fontWeight: FontWeight.normal, 150 | fontSize: ScreenUtil.getInstance().setSp(38), 151 | color: Colors.black12, 152 | ), 153 | ), 154 | ); 155 | } 156 | 157 | return Container( 158 | child: ListView.builder( 159 | itemBuilder: (c, i) { 160 | return ListTile( 161 | leading: IconButton( 162 | icon: Icon(Icons.delete_forever), 163 | onPressed: () {}, 164 | color: Color.fromRGBO(82, 195, 216, 0.5), 165 | ), 166 | title: Row( 167 | children: [ 168 | Text( 169 | _tokenList[i].symbol, 170 | style: TextStyle(color: Colors.black), 171 | ), 172 | Expanded( 173 | flex: 1, 174 | child: Text(""), 175 | ), 176 | Expanded( 177 | flex: 1, 178 | child: Text( 179 | "余额: ${_tokenList[i].balance}", 180 | overflow: TextOverflow.ellipsis, 181 | style: TextStyle(color: Colors.black12), 182 | ), 183 | ), 184 | ], 185 | ), 186 | subtitle: Text( 187 | _tokenList[i].address, 188 | maxLines: 1, 189 | overflow: TextOverflow.ellipsis, 190 | ), 191 | onTap: () { 192 | print("点击合约详情"); 193 | _showDetails(_tokenList[i].address, _tokenList[i].symbol, 194 | _tokenList[i].balance, _tokenList[i].bits); 195 | }, 196 | ); 197 | }, 198 | itemCount: _tokenList.length, 199 | )); 200 | } 201 | 202 | Future _request_balance(String addr, String tokens) async { 203 | var onValue = await getBalance(addr, tokens); 204 | 205 | if (onValue is String) { 206 | print("请求异常: ${onValue}"); 207 | } else if (onValue is VETRequest) { 208 | if (onValue.code != "0") { 209 | print("请求接口异常: ${onValue.message}"); 210 | return false; 211 | } 212 | Map tokensBalance = {}; 213 | 214 | for (var i = 0; i < onValue.data.length; i++) { 215 | tokensBalance[onValue.data[i].contractAddress] = 216 | onValue.data[i].balance; 217 | } 218 | 219 | for (var i = 0; i < _tokenList.length; i++) { 220 | if (tokensBalance[_tokenList[i].address] != null) { 221 | _tokenList[i].balance = tokensBalance[_tokenList[i].address]; 222 | } 223 | } 224 | print("请求成功: ${onValue.data}"); 225 | } 226 | 227 | return true; 228 | } 229 | 230 | _showDetails(String contract, String symbol, String balance, int bits) { 231 | showDialog( 232 | context: context, 233 | builder: (context) { 234 | return AlertDialog( 235 | actions: [ 236 | RaisedButton( 237 | color: Color.fromRGBO(82, 195, 216, 1), 238 | textColor: Colors.white, 239 | child: Text("确定"), 240 | onPressed: () { 241 | // 第一次弹回本面 242 | GlobalRouter.r.pop(context); 243 | // 第二次弹回上一个页面 244 | // GlobalRouter.r.pop(context); 245 | }, 246 | ) 247 | ], 248 | title: RichText( 249 | text: TextSpan(children: [ 250 | TextSpan(text: "地址:", style: TextStyle(color: Colors.black)), 251 | TextSpan( 252 | text: '${contract}', 253 | style: TextStyle( 254 | color: Colors.green, 255 | decoration: TextDecoration.underline), 256 | recognizer: TapGestureRecognizer() 257 | ..onTap = () { 258 | launch( 259 | "https://testnet.veforge.com/accounts/${contract}/tokenTransfers", 260 | forceSafariVC: false); 261 | }), 262 | TextSpan( 263 | text: '\n\n Symbol: ${symbol}', 264 | style: TextStyle(color: Colors.black), 265 | ), 266 | TextSpan( 267 | text: '\n\n 余额: ${double.parse(balance) / pow(10, 18)}', 268 | style: TextStyle(color: Colors.black), 269 | ), 270 | ]), 271 | ), 272 | ); 273 | }); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /app/flutter_vechain/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.8" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.1" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.1.0" 25 | barcode_scan: 26 | dependency: "direct main" 27 | description: 28 | name: barcode_scan 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.0.4" 39 | charcode: 40 | dependency: transitive 41 | description: 42 | name: charcode 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.2" 46 | collection: 47 | dependency: transitive 48 | description: 49 | name: collection 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.14.11" 53 | convert: 54 | dependency: transitive 55 | description: 56 | name: convert 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.1" 60 | cookie_jar: 61 | dependency: transitive 62 | description: 63 | name: cookie_jar 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.0" 67 | crypto: 68 | dependency: transitive 69 | description: 70 | name: crypto 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.0.6" 74 | cupertino_icons: 75 | dependency: "direct main" 76 | description: 77 | name: cupertino_icons 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "0.1.2" 81 | dart_config: 82 | dependency: transitive 83 | description: 84 | path: "." 85 | ref: HEAD 86 | resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde 87 | url: "https://github.com/MarkOSullivan94/dart_config.git" 88 | source: git 89 | version: "0.5.0" 90 | dio: 91 | dependency: "direct main" 92 | description: 93 | name: dio 94 | url: "https://pub.dartlang.org" 95 | source: hosted 96 | version: "2.1.3" 97 | fluro: 98 | dependency: "direct main" 99 | description: 100 | name: fluro 101 | url: "https://pub.dartlang.org" 102 | source: hosted 103 | version: "1.4.0" 104 | flutter: 105 | dependency: "direct main" 106 | description: flutter 107 | source: sdk 108 | version: "0.0.0" 109 | flutter_easyrefresh: 110 | dependency: "direct main" 111 | description: 112 | name: flutter_easyrefresh 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "1.2.7" 116 | flutter_launcher_icons: 117 | dependency: "direct main" 118 | description: 119 | name: flutter_launcher_icons 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.6.1" 123 | flutter_page_indicator: 124 | dependency: transitive 125 | description: 126 | name: flutter_page_indicator 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "0.0.3" 130 | flutter_screenutil: 131 | dependency: "direct main" 132 | description: 133 | name: flutter_screenutil 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.5.2" 137 | flutter_swiper: 138 | dependency: "direct main" 139 | description: 140 | name: flutter_swiper 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "1.1.6" 144 | flutter_test: 145 | dependency: "direct dev" 146 | description: flutter 147 | source: sdk 148 | version: "0.0.0" 149 | fluttertoast: 150 | dependency: "direct main" 151 | description: 152 | name: fluttertoast 153 | url: "https://pub.dartlang.org" 154 | source: hosted 155 | version: "3.0.4" 156 | http: 157 | dependency: transitive 158 | description: 159 | name: http 160 | url: "https://pub.dartlang.org" 161 | source: hosted 162 | version: "0.12.0+2" 163 | http_parser: 164 | dependency: transitive 165 | description: 166 | name: http_parser 167 | url: "https://pub.dartlang.org" 168 | source: hosted 169 | version: "3.1.3" 170 | image: 171 | dependency: transitive 172 | description: 173 | name: image 174 | url: "https://pub.dartlang.org" 175 | source: hosted 176 | version: "2.0.7" 177 | matcher: 178 | dependency: transitive 179 | description: 180 | name: matcher 181 | url: "https://pub.dartlang.org" 182 | source: hosted 183 | version: "0.12.5" 184 | meta: 185 | dependency: transitive 186 | description: 187 | name: meta 188 | url: "https://pub.dartlang.org" 189 | source: hosted 190 | version: "1.1.6" 191 | path: 192 | dependency: transitive 193 | description: 194 | name: path 195 | url: "https://pub.dartlang.org" 196 | source: hosted 197 | version: "1.6.2" 198 | pedantic: 199 | dependency: transitive 200 | description: 201 | name: pedantic 202 | url: "https://pub.dartlang.org" 203 | source: hosted 204 | version: "1.5.0" 205 | petitparser: 206 | dependency: transitive 207 | description: 208 | name: petitparser 209 | url: "https://pub.dartlang.org" 210 | source: hosted 211 | version: "2.1.1" 212 | pointycastle: 213 | dependency: transitive 214 | description: 215 | name: pointycastle 216 | url: "https://pub.dartlang.org" 217 | source: hosted 218 | version: "1.0.1" 219 | provide: 220 | dependency: "direct main" 221 | description: 222 | name: provide 223 | url: "https://pub.dartlang.org" 224 | source: hosted 225 | version: "1.0.2" 226 | quiver: 227 | dependency: transitive 228 | description: 229 | name: quiver 230 | url: "https://pub.dartlang.org" 231 | source: hosted 232 | version: "2.0.2" 233 | sky_engine: 234 | dependency: transitive 235 | description: flutter 236 | source: sdk 237 | version: "0.0.99" 238 | source_span: 239 | dependency: transitive 240 | description: 241 | name: source_span 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "1.5.5" 245 | sqflite: 246 | dependency: "direct main" 247 | description: 248 | name: sqflite 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "1.1.5" 252 | stack_trace: 253 | dependency: transitive 254 | description: 255 | name: stack_trace 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "1.9.3" 259 | stream_channel: 260 | dependency: transitive 261 | description: 262 | name: stream_channel 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "2.0.0" 266 | string_scanner: 267 | dependency: transitive 268 | description: 269 | name: string_scanner 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "1.0.4" 273 | synchronized: 274 | dependency: transitive 275 | description: 276 | name: synchronized 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "2.1.0" 280 | term_glyph: 281 | dependency: transitive 282 | description: 283 | name: term_glyph 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "1.1.0" 287 | test_api: 288 | dependency: transitive 289 | description: 290 | name: test_api 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "0.2.4" 294 | transformer_page_view: 295 | dependency: transitive 296 | description: 297 | name: transformer_page_view 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "0.1.6" 301 | tuple: 302 | dependency: transitive 303 | description: 304 | name: tuple 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "1.0.2" 308 | typed_data: 309 | dependency: transitive 310 | description: 311 | name: typed_data 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "1.1.6" 315 | url_launcher: 316 | dependency: "direct main" 317 | description: 318 | name: url_launcher 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "5.0.2" 322 | uuid: 323 | dependency: transitive 324 | description: 325 | name: uuid 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "1.0.3" 329 | vector_math: 330 | dependency: transitive 331 | description: 332 | name: vector_math 333 | url: "https://pub.dartlang.org" 334 | source: hosted 335 | version: "2.0.8" 336 | web3dart: 337 | dependency: "direct main" 338 | description: 339 | name: web3dart 340 | url: "https://pub.dartlang.org" 341 | source: hosted 342 | version: "0.4.5" 343 | xml: 344 | dependency: transitive 345 | description: 346 | name: xml 347 | url: "https://pub.dartlang.org" 348 | source: hosted 349 | version: "3.3.1" 350 | yaml: 351 | dependency: transitive 352 | description: 353 | name: yaml 354 | url: "https://pub.dartlang.org" 355 | source: hosted 356 | version: "2.1.15" 357 | sdks: 358 | dart: ">=2.2.0 <3.0.0" 359 | flutter: ">=1.2.1 <2.0.0" 360 | -------------------------------------------------------------------------------- /app/flutter_vechain/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_vechain 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | dio: ^2.0.9 27 | # 轮播图插件 28 | flutter_swiper : ^1.1.6 29 | # 屏幕适配插件 30 | flutter_screenutil: ^0.5.2 31 | # 设置APP图标插件 32 | flutter_launcher_icons: ^0.6.1 33 | # 启动电话 短信 浏览器插件 34 | url_launcher: ^5.0.2 35 | # 状态管理插件 36 | provide: ^1.0.2 37 | # 上拉刷新加载插件 38 | flutter_easyrefresh: ^1.2.7 39 | # 轻提示插件 40 | fluttertoast: ^3.0.4 41 | # 路由插件 42 | fluro: ^1.4.0 43 | # 以太坊web3 dart包 44 | web3dart: ^0.4.5 45 | # 操作sqlite 46 | sqflite: ^1.1.0 47 | # 扫描二维码 48 | barcode_scan: 1.0.0 49 | 50 | dev_dependencies: 51 | flutter_test: 52 | sdk: flutter 53 | 54 | flutter_icons: 55 | image_path: "images/wallet.png" 56 | android: true 57 | ios: true 58 | 59 | # For information on the generic Dart part of this file, see the 60 | # following page: https://www.dartlang.org/tools/pub/pubspec 61 | 62 | # The following section is specific to Flutter. 63 | flutter: 64 | 65 | # The following line ensures that the Material Icons font is 66 | # included with your application, so that you can use the icons in 67 | # the material Icons class. 68 | uses-material-design: true 69 | 70 | # To add assets to your application, add an assets section, like this: 71 | assets: 72 | - images/ethereum.jpg 73 | - images/ethereum.png 74 | - images/vechain.jpg 75 | - images/vechain.jpeg 76 | 77 | # An image asset can refer to one or more resolution-specific "variants", see 78 | # https://flutter.io/assets-and-images/#resolution-aware. 79 | 80 | # For details regarding adding assets from package dependencies, see 81 | # https://flutter.io/assets-and-images/#from-packages 82 | 83 | # To add custom fonts to your application, add a fonts section here, 84 | # in this "flutter" section. Each entry in this list should have a 85 | # "family" key with the font family name, and a "fonts" key with a 86 | # list giving the asset and other descriptors for the font. For 87 | # example: 88 | # fonts: 89 | # - family: Schyler 90 | # fonts: 91 | # - asset: fonts/Schyler-Regular.ttf 92 | # - asset: fonts/Schyler-Italic.ttf 93 | # style: italic 94 | # - family: Trajan Pro 95 | # fonts: 96 | # - asset: fonts/TrajanPro.ttf 97 | # - asset: fonts/TrajanPro_Bold.ttf 98 | # weight: 700 99 | fonts: 100 | - family: selfIcon 101 | fonts: 102 | - asset: fonts/iconfont.ttf 103 | # For details regarding fonts from package dependencies, 104 | # see https://flutter.io/custom-fonts/#from-packages 105 | -------------------------------------------------------------------------------- /app/flutter_vechain/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_vechain/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /app/key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/app/key.jks -------------------------------------------------------------------------------- /backend/Makefile: -------------------------------------------------------------------------------- 1 | CUR_DIR := $(shell pwd) 2 | gitcommit := $(shell git rev-parse --short=8 HEAD) 3 | build: 4 | # go.mod 中包名被定义为github.com/wupeaking/CDIB 所以传递连接参数要加上包名 5 | export GO111MODULE=on && go build -ldflags "-X github.com/wupeaking/CDIB/initialization.Version=${gitcommit}" -o contractserver.x cmd/contractsrv/main.go 6 | 7 | .PHONY: clean 8 | 9 | clean: 10 | rm -rf asynctasks.x 11 | rm -rf contractserver.x 12 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # vechian_helper 2 | 一个唯链支付助手后端服务 3 | 4 | 5 | 6 | ![build status](https://travis-ci.com/wupeaking/CDIB.svg?token=hr2sptwtPB3qdtD9pcsz&branch=master) 7 | 8 | ## 如何部署私有的后端应用 9 | 10 | ### 启动唯链客户端 11 | 12 | ```shell 13 | $> docker run --privileged -d \ 14 | -v /root/.org.vechain.thor:/root/.org.vechain.thor -p 8669:8669 -p 11235:11235 -p 11235:11235/udp \ 15 | --name thor-node vechain/thor --network test --api-addr 0.0.0.0:8669 16 | ``` 17 | 18 | ### 数据库 19 | - 创建数据库 20 | - 执行初始化脚本 脚本位于 ./sql/init.sql 21 | 22 | ### 部署方式 23 | 24 | #### 二进制形式编译 25 | 26 | ```shell 27 | # 克隆源码 28 | $> git clone github.com/wupeaking/vechain_helper.git 29 | 30 | # 编译 31 | $> cd vechain_helper/backend && make 32 | 33 | # backend目录下会生成contractserver.x二进制文件 34 | 35 | ``` 36 | 37 | #### docker形式编译 38 | 39 | ```shell 40 | # docker镜像已经加入了CI中 只需要拉取镜像即可 41 | $> docker pull wupengxin/vechain_helper 42 | ``` 43 | 44 | #### 启动参数说明 45 | 46 | ```shell 47 | ./contractserver.x --help 48 | -dbaddr string 49 | 数据库ip:port (default "127.0.0.1:3306") 50 | -dbname string 51 | 数据库名称 (default "cdib") 52 | -dbpasswd string 53 | 数据库密码 (default "888888") 54 | -dbuser string 55 | 数据库用户名 (default "root") 56 | -debug 57 | 开启debug模式 58 | -port string 59 | 服务端监听端口 (default "31312") 60 | -produce 61 | 是否是生产环境 62 | -rpcaddr string 63 | rpc地址 (default "http://127.0.0.1:8669") 64 | -rpcpassword string 65 | rpc地址 (default "admin") 66 | -rpcuser string 67 | rpc地址 (default "admin") 68 | 69 | ``` 70 | 71 | #### 启动示例 72 | ```shell 73 | # 二进制文件启动示例 74 | $> contractserver.x -rpcaddr http://192.168.2.144:8669 75 | 76 | # docker启动示例 77 | 78 | # 启动合约服务 79 | $> docker run --name vechain_helper -p 31313:31312 -d wupengxin/vechain_helper ./contractserver.x \ 80 | -rpcaddr http://127.0.0.1:8669 -dbaddr 127.0.0.1:3306 -dbpasswd passwd \ 81 | -dbuser user -dbname cdib -produce false 82 | 83 | 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /backend/apihandler/balance.go: -------------------------------------------------------------------------------- 1 | package apihandler 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "github.com/ethereum/go-ethereum/accounts/abi" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/labstack/echo" 11 | "github.com/wupeaking/vechainhelper/initialization" 12 | "github.com/wupeaking/vechainhelper/utils" 13 | "github.com/wupeaking/vechainhelper/vechainclient" 14 | "math/big" 15 | "net/http" 16 | "strings" 17 | ) 18 | 19 | // Balance 查询账户余额 get /balance/:account?currency=vet,vtho,0x12121,0x43434 20 | func Balance(ctx echo.Context) error { 21 | 22 | c, ok := ctx.(*initialization.CustomContext) 23 | if !ok { 24 | return errors.New("转换自定义上下文异常") 25 | } 26 | 27 | account := c.Param("account") 28 | currency := c.QueryParam("currency") 29 | 30 | var allCurrency []string 31 | if currency == "" { 32 | allCurrency = []string{"vet"} 33 | } else { 34 | allCurrency = strings.Split(currency, ",") 35 | } 36 | 37 | type resultT struct { 38 | Contract string `json:"contract_address"` 39 | Balance string `json:"balance"` 40 | } 41 | 42 | var balanceResults []resultT 43 | 44 | for i := 0; i < len(allCurrency); i++ { 45 | balance, err := queryBalance(allCurrency[i], account, c.BlockClient) 46 | if err != nil { 47 | ctx.Logger().Errorf("查询token余额出错, err: %v", err.Error()) 48 | balanceResults = append(balanceResults, resultT{Contract: allCurrency[i], Balance: "0"}) 49 | }else{ 50 | balanceResults = append(balanceResults, resultT{Contract: allCurrency[i], Balance: balance.String()}) 51 | } 52 | } 53 | 54 | // 封装数据 返回 55 | result := struct { 56 | Code string `json:"code"` 57 | Msg string `json:"message"` 58 | Data interface{} `json:"data"` 59 | }{ 60 | Code: initialization.Success.ErrCode(), 61 | Msg: initialization.Success.ErrMsg(), 62 | Data: balanceResults, 63 | } 64 | c.JSON(http.StatusOK, result) 65 | return nil 66 | } 67 | 68 | //queryBalance 查询本币或者token余额 69 | func queryBalance(currency string, account string, cli *vechain.Client) (*big.Int, error) { 70 | currency = strings.ToLower(currency) 71 | if currency == "vet" || currency == "vtho" { 72 | balance, err := cli.BalanceByAddress(context.Background(), account) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if currency == "vet" { 77 | return balance.Balance, nil 78 | } 79 | return balance.Energy, nil 80 | } 81 | 82 | if !utils.CheckAddress(currency) { 83 | return nil, errors.New("无效合约地址") 84 | } 85 | 86 | /* 87 | function balanceOf(address _owner) public view returns (uint256 balance) 88 | */ 89 | 90 | method := abi.Method{Name: "balanceOf", Const: false} 91 | addressType, _ := abi.NewType("address", nil) 92 | uin256Type, _ := abi.NewType("uint256", nil) 93 | 94 | _owner := abi.Argument{Name: "_owner", Type: addressType, Indexed: false} 95 | _value := abi.Argument{Name: "balance", Type: uin256Type, Indexed: false} 96 | method.Inputs = abi.Arguments{_owner} 97 | method.Outputs = abi.Arguments{_value} 98 | 99 | toAddr := common.HexToAddress(account) 100 | argsData, _ := method.Inputs.Pack(toAddr) 101 | input := append(method.Id(), argsData...) 102 | 103 | result, err := cli.SimulateContract(context.Background(), fmt.Sprintf("0x%0x", input), 104 | "0", currency) 105 | 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | if result.VMErr != "" { 111 | return nil, errors.New(result.VMErr) 112 | } 113 | 114 | //value := reflect.New(reflect.TypeOf(big.NewInt(0))) 115 | value := big.NewInt(0) 116 | resultData, _ := hex.DecodeString(result.Data[2:]) 117 | 118 | err = method.Outputs.Unpack(&value, resultData) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | // value.Elem().Interface().(*big.Int) 124 | return value, nil 125 | } 126 | -------------------------------------------------------------------------------- /backend/apihandler/base.go: -------------------------------------------------------------------------------- 1 | package apihandler 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/accounts/abi" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/rlp" 9 | "github.com/labstack/echo" 10 | "github.com/vechain/thor/thor" 11 | "github.com/vechain/thor/tx" 12 | "github.com/wupeaking/vechainhelper/database" 13 | "github.com/wupeaking/vechainhelper/initialization" 14 | "github.com/wupeaking/vechainhelper/vechainclient" 15 | "math/big" 16 | "net/http" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | func errPackage(ctx echo.Context, err initialization.ErrDesc) { 22 | // 封装数据 返回 23 | ctx.JSON(http.StatusOK, 24 | struct { 25 | Code string `json:"code"` 26 | Msg string `json:"message"` 27 | }{ 28 | Code: err.ErrCode(), 29 | Msg: err.ErrMsg(), 30 | }, 31 | ) 32 | } 33 | 34 | //calcTransferGasUsed 模拟计算实际转账需要的gas 35 | func calcTransferGasUsed(curreny string, amount *big.Int, clauses []database.Clause, cli *vechain.Client) (uint64, error) { 36 | // https://github.com/vechain/thor/wiki/FAQ#what-is-intrinsic-gas- 37 | if strings.ToLower(curreny) == "vet" { 38 | return 5000 + uint64(len(clauses))*16000, nil 39 | } 40 | 41 | tokenContarct, ok := initialization.TokenContractMap[curreny] 42 | if !ok { 43 | return 0, fmt.Errorf("未知的token: %s", curreny) 44 | } 45 | //address _to, uint256 _value 46 | method := abi.Method{Name: "transfer", Const: false} 47 | addressType, _ := abi.NewType("address", nil) 48 | uin256Type, _ := abi.NewType("uint256", nil) 49 | 50 | _to := abi.Argument{Name: "_to", Type: addressType, Indexed: false} 51 | _value := abi.Argument{Name: "_value", Type: uin256Type, Indexed: false} 52 | method.Inputs = abi.Arguments{_to, _value} 53 | 54 | toAddr := common.HexToAddress("0xb9b7e0cb2edf5ea031c8b297a5a1fa20379b6a0a") 55 | argsData, _ := method.Inputs.Pack(toAddr, amount) 56 | data := append(method.Id(), argsData...) 57 | 58 | result, err := cli.SimulateContract(context.Background(), fmt.Sprintf("0x%0x", data), 59 | "0", tokenContarct) 60 | 61 | if err != nil { 62 | return 0, err 63 | } 64 | if result.Reverted { 65 | return 0, fmt.Errorf("虚拟机执行reverted") 66 | } 67 | // txGas + (clauses.type + dataGas + vmGas)*len(clauses) 68 | 69 | // 精确datagas计算 70 | //dataGas := func (input []byte) uint64 { 71 | // const zgas = 4 72 | // const nzgas = 68 73 | // var gas uint64 74 | // for i := 0; i vtho(wei) (gas/1000)*10^18=gas*10^15 88 | totalUsedVtho := new(big.Int).Mul(big.NewInt(int64(gasUsed)), big.NewInt(10e15)) 89 | if allVtho.Cmp(totalUsedVtho) < 0 { 90 | ctx.Logger().Errorf("创建未签名交易过程中, vtho不足以支付矿工费用") 91 | errPackage(c, initialization.BalanceErr) 92 | return nil 93 | } 94 | 95 | // 构造未签名的原始交易 96 | encode, needSign, err := constructUnSignTxData(request.Currency, uint32(blk.BlockNum), clause, gasUsed) 97 | if err != nil { 98 | ctx.Logger().Errorf("创建未签名交易过程中, 构造原始交易出错, address: %s, err:%s", 99 | request.From, err.Error()) 100 | errPackage(c, initialization.TransferErr) 101 | return nil 102 | } 103 | u4 := uuid.NewV4() 104 | id := u4.String() 105 | 106 | // 插入数据 107 | if err := database.InsertTxOrder(c.DB, id, request.From, request.To, 108 | request.Currency, request.Amount, fmt.Sprintf("%d", gasUsed), 0, encode); err != nil { 109 | ctx.Logger().Errorf("创建未签名交易过程中, 添加交易订单信息到数据库出错, 交易编号:[%s], err: %s", 110 | id, err.Error()) 111 | errPackage(c, initialization.DataBaseErr) 112 | return nil 113 | } 114 | 115 | // 封装数据 返回 116 | result := struct { 117 | Code string `json:"code"` 118 | Msg string `json:"message"` 119 | Data interface{} `json:"data"` 120 | }{ 121 | Code: initialization.Success.ErrCode(), 122 | Msg: initialization.Success.ErrMsg(), 123 | Data: struct { 124 | NeedSignContent string `json:"need_sign_content"` 125 | ID string `json:"request_id"` 126 | From string `json:"from"` 127 | To string `json:"to"` 128 | Amount string `json:"amount"` 129 | TxType string `json:"tx_type"` 130 | }{NeedSignContent: needSign, ID: id, From: request.From, To:request.To, Amount:request.Amount, TxType: txType}, 131 | } 132 | c.JSON(http.StatusOK, result) 133 | return nil 134 | 135 | } 136 | 137 | //PushTx put /sign_tx 广播签名的交易 {"request_id": xxxx, "sign": xxxx} 138 | func PushTx(ctx echo.Context) error { 139 | c, ok := ctx.(*initialization.CustomContext) 140 | if !ok { 141 | return errors.New("转换自定义上下文异常") 142 | } 143 | 144 | //解析请求参数 145 | request := struct { 146 | ID string `json:"request_id"` 147 | Sign string `json:"sign"` 148 | }{} 149 | if err := ctx.Bind(&request); err != nil { 150 | ctx.Logger().Warnf("广播交易过程中, 请求参数反序列化失败: %s", err.Error()) 151 | errPackage(c, initialization.ParamError) 152 | return nil 153 | } 154 | 155 | /* 156 | 1. 查询数据库中是否有此交易 157 | 2. 对交易进行rlp解码 158 | 3. 放入签名内容 159 | 4. 进行rlp编码之后 广播 160 | */ 161 | 162 | rlpData, err := database.SelectTxRlp(c.DB, request.ID) 163 | if err != nil { 164 | ctx.Logger().Warnf("广播交易过程中, 此请求的ID不存在: %s", err.Error()) 165 | errPackage(c, initialization.DataBaseErr) 166 | return nil 167 | } 168 | 169 | rlpBytes, err := hex.DecodeString(rlpData) 170 | if err != nil { 171 | ctx.Logger().Warnf("广播交易过程中, rlp转换为字节失败: %s", err.Error()) 172 | errPackage(c, initialization.EncodeError) 173 | return nil 174 | } 175 | 176 | signByts, err := hex.DecodeString(request.Sign) 177 | if err != nil { 178 | ctx.Logger().Warnf("广播交易过程中, sign转换失败: %s", err.Error()) 179 | errPackage(c, initialization.EncodeError) 180 | return nil 181 | } 182 | 183 | trx := tx.Transaction{} 184 | if rlp.DecodeBytes(rlpBytes, &trx) != nil { 185 | ctx.Logger().Warnf("广播交易过程中, rlp解码未签名交易失败: %s", err.Error()) 186 | errPackage(c, initialization.EncodeError) 187 | return nil 188 | } 189 | 190 | signTx := trx.WithSignature(signByts) 191 | d, err := rlp.EncodeToBytes(signTx) 192 | if err != nil { 193 | ctx.Logger().Warnf("广播交易过程中, rlp转换签名交易失败: %s", err.Error()) 194 | errPackage(c, initialization.EncodeError) 195 | return nil 196 | } 197 | txID, err := c.BlockClient.PushTx(context.Background(), hex.EncodeToString(d)) 198 | if err != nil { 199 | ctx.Logger().Warnf("广播交易过程中, 广播交易失败: %s", err.Error()) 200 | errPackage(c, initialization.CallAPIErr) 201 | return nil 202 | } 203 | 204 | // 封装数据 返回 205 | result := struct { 206 | Code string `json:"code"` 207 | Msg string `json:"message"` 208 | Data interface{} `json:"data"` 209 | }{ 210 | Code: initialization.Success.ErrCode(), 211 | Msg: initialization.Success.ErrMsg(), 212 | Data: struct { 213 | TxID string `json:"tx_id"` 214 | ID string `json:"request_id"` 215 | }{TxID: txID, ID: request.ID}, 216 | } 217 | c.JSON(http.StatusOK, result) 218 | return nil 219 | } 220 | -------------------------------------------------------------------------------- /backend/cmd/contractsrv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "github.com/wupeaking/vechainhelper/apihandler" 6 | "github.com/wupeaking/vechainhelper/initialization" 7 | ) 8 | 9 | func init() { 10 | // rpc 配置 11 | flag.StringVar(&initialization.RPCAddr, "rpcaddr", "http://127.0.0.1:8669", "rpc地址") 12 | flag.StringVar(&initialization.RPCUser, "rpcuser", "admin", "rpc地址") 13 | flag.StringVar(&initialization.RPCPassword, "rpcpassword", "admin", "rpc地址") 14 | 15 | flag.StringVar(&initialization.Port, "port", initialization.Port, "服务端监听端口") 16 | flag.BoolVar(&initialization.Debug, "debug", initialization.Debug, "开启debug模式") 17 | 18 | // 数据库 19 | flag.StringVar(&initialization.DBAddr, "dbaddr", initialization.DBAddr, "数据库ip:port") 20 | flag.StringVar(&initialization.DBName, "dbname", initialization.DBName, "数据库名称") 21 | flag.StringVar(&initialization.DBUser, "dbuser", initialization.DBUser, "数据库用户名") 22 | flag.StringVar(&initialization.DBPasswd, "dbpasswd", initialization.DBPasswd, "数据库密码") 23 | flag.BoolVar(&initialization.IsProduce, "produce", false, "是否是生产环境") 24 | 25 | flag.StringVar(&initialization.ABIFile, "abipath", initialization.ABIFile, "abi文件路径") 26 | 27 | flag.Parse() 28 | } 29 | 30 | func main() { 31 | // 创建web实例 32 | app, err := initialization.NewWebApp() 33 | if err != nil { 34 | app.Logger.Fatal(err) 35 | } 36 | app.Debug = initialization.Debug 37 | 38 | if initialization.IsProduce { 39 | initialization.ChainTag = 0x4a 40 | } else { 41 | initialization.ChainTag = 0x27 42 | } 43 | initialization.SwitchKey() 44 | 45 | // Routes 46 | app.GET("/balance/:account", apihandler.Balance) 47 | app.PUT("/unsigned_tx", apihandler.UnSignTx) 48 | app.PUT("/push_tx", apihandler.PushTx) 49 | 50 | // Start server 51 | app.Logger.Info("version: ", initialization.Version) 52 | app.Logger.Fatal(app.Start(":" + initialization.Port)) 53 | } 54 | -------------------------------------------------------------------------------- /backend/database/transfer.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | "math/big" 7 | ) 8 | 9 | // 数据库层相关的操作 10 | type Clause struct { 11 | To string 12 | Amount *big.Int 13 | } 14 | 15 | // InsertTxOrder 插入交易请求 16 | func InsertTxOrder(db *sqlx.DB, requestID, from, to, currency, amount, gasUsed string, status int, rlp string) error { 17 | // 拼接SQL 18 | sqlStr := fmt.Sprintf("insert into transaction_records (request_id, `from`,"+ 19 | " `to`, currency, amount, gas_used, status, rlp) values ('%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s') ", 20 | requestID, from, to, currency, amount, gasUsed, status, rlp) 21 | 22 | println(sqlStr) 23 | _, err := db.Exec(sqlStr) 24 | return err 25 | } 26 | 27 | // InsertTxStatus 插入交易状态 28 | func InsertTxStatus(db *sqlx.DB, txID, requestID string, status int) error { 29 | sqlStr := fmt.Sprintf(`insert into transfer_status (tx_hash, request_id, status) values ( '%s', '%s', %d )`, 30 | txID, requestID, status) 31 | 32 | _, err := db.Exec(sqlStr) 33 | return err 34 | 35 | } 36 | 37 | //SelectTxRlp 查询交易的RLP内容 38 | func SelectTxRlp(db *sqlx.DB, id string) (string, error) { 39 | sqlStr := fmt.Sprintf(`select rlp from transaction_records where request_id = '%s'`, 40 | id) 41 | var result string 42 | err := db.QueryRowx(sqlStr).Scan(&result) 43 | 44 | return result, err 45 | 46 | } 47 | -------------------------------------------------------------------------------- /backend/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.11-alpine3.8 2 | LABEL author="wupenxin " 3 | 4 | ARG GIT_PWD=abcd 5 | 6 | ENV HOME /opt/vechain_helper 7 | 8 | 9 | # 更新软件包 下载源码 编译 删除不需要的内容 只有放在同一层 才会缩小构建出的镜像 10 | RUN mkdir -p $HOME/bin && mkdir -p $HOME/src && apk update && apk add curl && apk add git && apk add gcc && \ 11 | apk add libc-dev && \ 12 | mkdir -p /go/src/github.com/wupeaking && cd /go/src/github.com/wupeaking && git clone https://github.com/wupeaking/vechain_helper.git vechain_helper && \ 13 | cd vechain_helper/backend && export GO111MODULE=on && export GOPROXY=https://goproxy.io && go build -ldflags "-X github.com/wupeaking/vechain_helper/initialization.Version=`git rev-parse --short=8 HEAD`" -o contractserver.x cmd/contractsrv/main.go && \ 14 | mv contractserver.x $HOME/bin && chmod +x $HOME/bin/contractserver.x && \ 15 | rm -rf /go/src/github.com/wupeaking/vechain_helper && apk del git && apk del gcc && rm -rf /go/pkg/mod 16 | 17 | 18 | # 安装守护进程 19 | RUN curl -L https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 -o /usr/local/bin/dumb-init && \ 20 | chmod +x /usr/local/bin/dumb-init 21 | 22 | WORKDIR $HOME/bin/ 23 | 24 | COPY start.sh $HOME/bin 25 | RUN chmod +x start.sh 26 | 27 | ENTRYPOINT ["dumb-init", "--"] 28 | CMD ["sh", "-x", "/opt/vechain_helper/bin/start.sh"] -------------------------------------------------------------------------------- /backend/docker/start.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | echo "please use cmd eg contractserver.x, asynctasks.x ..." -------------------------------------------------------------------------------- /backend/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wupeaking/vechainhelper 2 | 3 | require ( 4 | github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d // indirect 5 | github.com/davecgh/go-spew v1.1.1 // indirect 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 7 | github.com/ethereum/go-ethereum v1.8.21 8 | github.com/go-sql-driver/mysql v1.4.1 9 | github.com/go-stack/stack v1.8.0 // indirect 10 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect 11 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect 12 | github.com/hashicorp/golang-lru v0.5.0 // indirect 13 | github.com/jmoiron/sqlx v1.2.0 14 | github.com/jtolds/gls v4.2.1+incompatible // indirect 15 | github.com/kr/pretty v0.1.0 // indirect 16 | github.com/labstack/echo v3.3.10+incompatible 17 | github.com/labstack/gommon v0.2.8 18 | github.com/mattn/go-colorable v0.0.9 // indirect 19 | github.com/mattn/go-isatty v0.0.4 // indirect 20 | github.com/moul/http2curl v1.0.0 21 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect 22 | github.com/satori/go.uuid v1.2.0 //indirect 23 | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect 24 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect 25 | github.com/stretchr/testify v1.3.0 // indirect 26 | github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 // indirect 27 | github.com/valyala/bytebufferpool v1.0.0 // indirect 28 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect 29 | github.com/vechain/thor v1.0.5 30 | golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c // indirect 31 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd 32 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc // indirect 33 | google.golang.org/appengine v1.4.0 // indirect 34 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 35 | gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /backend/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 2 | github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= 3 | github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= 4 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 5 | github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 6 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 7 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 8 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 9 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 10 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 11 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 16 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 17 | github.com/ethereum/go-ethereum v1.8.21 h1:ofzsxFj+zKhj1k3uVa8/MJCCptqKEh/6DXq4LwdUM4E= 18 | github.com/ethereum/go-ethereum v1.8.21/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= 19 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 20 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 21 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 22 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 23 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 24 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 25 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 26 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 27 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 28 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 29 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 30 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= 31 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 32 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 33 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 34 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 35 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 36 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 37 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 38 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 39 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 40 | github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= 41 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 42 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 43 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 44 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 45 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 46 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 47 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 48 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 49 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 50 | github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= 51 | github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= 52 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 53 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 54 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 55 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 56 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 57 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 58 | github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= 59 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 60 | github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= 61 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= 62 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 63 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 64 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 65 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 66 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 67 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 68 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 69 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= 70 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 71 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 72 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 73 | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= 74 | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 75 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= 76 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 79 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 80 | github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2zDhJJABEofdb51uMRNb8eqIVtdducs= 81 | github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= 82 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 83 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 84 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= 85 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= 86 | github.com/vechain/thor v1.0.5 h1:Zew+JD1AkIV59sJVQJEUWPsKLqOKzpi9fajBchoC74w= 87 | github.com/vechain/thor v1.0.5/go.mod h1:ONUny3Nr0yQ3ZiGRiJn8h8vFI8pcebBnfcVv6kTcaLk= 88 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 89 | golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c h1:MWY7h75sb9ioBR+s5Zgq1JYXxhbZvrSP2okwLi3ItmI= 90 | golang.org/x/crypto v0.0.0-20190129210102-ccddf3741a0c/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 91 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 92 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 93 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 94 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 95 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc h1:WiYx1rIFmx8c0mXAFtv5D/mHyKe1+jmuP7PViuwqwuQ= 98 | golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 99 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 100 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 101 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 102 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 103 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 104 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 105 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 106 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 107 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 108 | gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951 h1:DMTcQRFbEH62YPRWwOI647s2e5mHda3oBPMHfrLs2bw= 109 | gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA= 110 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 111 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 112 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 113 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 114 | -------------------------------------------------------------------------------- /backend/initialization/appinit.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | "github.com/labstack/echo" 7 | "github.com/labstack/echo/middleware" 8 | "github.com/labstack/gommon/log" 9 | "github.com/wupeaking/vechainhelper/vechainclient" 10 | "net/http" 11 | ) 12 | 13 | var ( 14 | // WebApp web实例 15 | WebApp *echo.Echo 16 | // DB 数据实例 17 | DB *sqlx.DB 18 | 19 | //Version 20 | Version string 21 | ) 22 | 23 | // CustomContext 自定义上下文 24 | type CustomContext struct { 25 | echo.Context 26 | // 数据库实例 27 | DB *sqlx.DB 28 | // vechain 客户端 29 | BlockClient *vechain.Client 30 | } 31 | 32 | // NewDB 新的全局DB 33 | func NewDB() (*sqlx.DB, error) { 34 | if DB != nil { 35 | return DB, nil 36 | } 37 | // 初始化数据库 38 | var db *sqlx.DB 39 | db, err := MySQLInit() 40 | DB = db 41 | return DB, err 42 | } 43 | 44 | // NewWebApp 新的web实例 45 | func NewWebApp() (*echo.Echo, error) { 46 | if WebApp != nil { 47 | return WebApp, nil 48 | } 49 | WebApp = echo.New() 50 | WebApp.HideBanner = true 51 | WebApp.HTTPErrorHandler = httpErrorHandler 52 | WebApp.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ 53 | Format: "[${time_rfc3339}] method=${method} uri=${uri} status=${status} remote_ip=${remote_ip} \n", 54 | })) 55 | WebApp.Logger.(*log.Logger).SetHeader(`[${time_rfc3339_nano}] ` + 56 | `${long_file}@${line}`) 57 | if Debug { 58 | WebApp.Logger.SetLevel(log.DEBUG) 59 | } else { 60 | WebApp.Logger.SetLevel(log.INFO) 61 | } 62 | 63 | db, err := NewDB() 64 | if err != nil { 65 | WebApp.Logger.Fatal("连接数据库失败: ", err.Error()) 66 | return WebApp, err 67 | } 68 | 69 | // 创建一个全局客户端 70 | vc, _ := vechain.NewVeChainClient(RPCAddr, RPCUser, RPCPassword, TimeOut) 71 | 72 | // 使用一个自定义中间件 将db注入上下文中 73 | WebApp.Use(func(h echo.HandlerFunc) echo.HandlerFunc { 74 | return func(c echo.Context) error { 75 | cc := &CustomContext{c, db, vc} 76 | return h(cc) 77 | } 78 | }) 79 | return WebApp, nil 80 | } 81 | 82 | func httpErrorHandler(err error, c echo.Context) { 83 | println("httpErrorHandler======================") 84 | var ( 85 | code = http.StatusInternalServerError 86 | msg interface{} 87 | ) 88 | if he, ok := err.(*echo.HTTPError); ok { 89 | code = he.Code 90 | msg = he.Message 91 | if he.Internal != nil { 92 | msg = fmt.Sprintf("%v, %v", err, he.Internal) 93 | } 94 | } else if WebApp.Debug { 95 | msg = err.Error() 96 | } else { 97 | msg = http.StatusText(code) 98 | } 99 | 100 | // Send response 101 | if !c.Response().Committed { 102 | if c.Request().Method == echo.HEAD { 103 | err = c.NoContent(code) 104 | } else { 105 | // 106 | err = c.JSON(200, map[string]string{"code": InternalServerError.ErrCode(), "data": "", "message": msg.(string)}) 107 | } 108 | if err != nil { 109 | WebApp.Logger.Error(err) 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /backend/initialization/configure.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | var ( 4 | // vechain 节点配置 5 | // RPCAddr rpc地址 6 | RPCAddr string 7 | // RPCUser rpc用户名 8 | RPCUser string 9 | // RPCPassword rpc密码 10 | RPCPassword string 11 | // TimeOut http超时时间 单位为S 12 | TimeOut = 10 13 | // ChainTag 链标签 测试链为0x27 生产链为0x4a 14 | ChainTag byte 15 | 16 | // Port 服务端口号 17 | Port = "31312" 18 | // Debug 是否开启debug模式 19 | Debug = false 20 | 21 | // mysql 配置 22 | // DBAddr mysql地址 23 | DBAddr = "127.0.0.1:3306" 24 | // DBUser 用户名 25 | DBUser = "root" 26 | // DBPasswd 密码 27 | DBPasswd = "888888" 28 | // DBName 数据库名 29 | DBName = "cdib" 30 | // DBMaxIdleTime 数据库连接池最大保持空闲时间ms 31 | DBMaxIdleTime = 1000 32 | // DBMaxIdle 数据库连接池最大保持空闲个数 33 | DBMaxIdle = 1 34 | // DBMaxOverflow 最大容许溢出数量 35 | DBMaxOverflow = 50 36 | 37 | // IsProduce 是否是生产环境 38 | IsProduce bool 39 | 40 | // ABIFile abi路径 41 | ABIFile = "./abi.json" 42 | //ContractAddr 合约地址 测试环境为0x55cac45d72232217fe868d29df309b4f17c10911 43 | ContractAddr = "0x55cac45d72232217fe868d29df309b4f17c10911" 44 | 45 | //TokenContractMap 符合和地址映射 46 | TokenContractMap = map[string]string{ 47 | "vet": "", 48 | "vtho": "0x0000000000000000000000000000456E65726779", 49 | "vtt": "0x0000000000000000000000000000456E65726779", 50 | } 51 | ) 52 | 53 | //PrivateKey 私钥 54 | var PrivateKey string 55 | 56 | //PublicKey 公钥 ` 57 | var PublicKey string 58 | 59 | //TestPrivateKey 测试环境 私钥 60 | const TestPrivateKey = `-----BEGIN PRIVATE KEY----- 61 | -----END PRIVATE KEY-----` 62 | 63 | //TestPublicKey 测试环境 公钥 64 | const TestPublicKey = `-----BEGIN RSA PUBLICK KEY----- 65 | -----END RSA PUBLICK KEY-----` 66 | 67 | // ProducePrivateKey 生产环境 68 | const ProducePrivateKey = `-----BEGIN PRIVATE KEY----- 69 | -----END PRIVATE KEY-----` 70 | 71 | //ProducePublicKey 生产环境 公钥 72 | const ProducePublicKey = `-----BEGIN RSA PUBLICK KEY----- 73 | -----END RSA PUBLICK KEY-----` 74 | 75 | //SwitchKey 根据环境切换公私 76 | func SwitchKey() { 77 | if IsProduce { 78 | PrivateKey = ProducePrivateKey 79 | PublicKey = ProducePublicKey 80 | } else { 81 | PrivateKey = TestPrivateKey 82 | PublicKey = TestPublicKey 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /backend/initialization/err.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | // ErrDesc 错误列表 4 | type ErrDesc struct { 5 | errCode string 6 | errMsg string 7 | } 8 | 9 | func (e ErrDesc) String() string { 10 | return e.errMsg 11 | } 12 | 13 | // ErrCode ... 14 | func (e ErrDesc) ErrCode() string { 15 | return e.errCode 16 | } 17 | 18 | // ErrMsg ... 19 | func (e ErrDesc) ErrMsg() string { 20 | return e.errMsg 21 | } 22 | 23 | func newErr(code, msg string) ErrDesc { 24 | return ErrDesc{code, msg} 25 | } 26 | 27 | // 错误码定义 28 | var ( 29 | InternalServerError = newErr("10500", "内部服务错误") 30 | Success = newErr("0", "成功") 31 | ParamError = newErr("10000", "参数错误") 32 | PrivKeyError = newErr("20000", "私钥错误") 33 | DeployError = newErr("20001", "部署合约错误") 34 | TxSignError = newErr("20002", "交易签名错误") 35 | EncodeError = newErr("20003", "编码错误") 36 | CallContractErr = newErr("20004", "调用合约出错") 37 | DataBaseErr = newErr("20005", "数据库操作错误") 38 | CallAPIErr = newErr("20006", "API调用错误") 39 | BalanceErr = newErr("20007", "余额不足") 40 | TransferErr = newErr("20008", "交易订单错误") 41 | ) 42 | -------------------------------------------------------------------------------- /backend/initialization/mysqlinit.go: -------------------------------------------------------------------------------- 1 | package initialization 2 | 3 | import ( 4 | "fmt" 5 | _ "github.com/go-sql-driver/mysql" //init mysql 6 | "github.com/jmoiron/sqlx" 7 | "time" 8 | ) 9 | 10 | //MySQLInit mysql的初始化 11 | func MySQLInit() (*sqlx.DB, error) { 12 | 13 | dbURI := fmt.Sprintf( 14 | "%s:%s@tcp(%s)/%s?parseTime=true&charset=utf8mb4", 15 | DBUser, 16 | DBPasswd, 17 | DBAddr, 18 | DBName) 19 | 20 | DB, err := sqlx.Open("mysql", dbURI) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | DB.SetConnMaxLifetime(time.Millisecond * time.Duration(DBMaxIdleTime)) 26 | DB.SetMaxIdleConns(DBMaxIdle) 27 | DB.SetMaxOpenConns(DBMaxOverflow + DBMaxIdle) 28 | 29 | err = DB.Ping() 30 | if err != nil { 31 | return nil, err 32 | } 33 | return DB, nil 34 | } 35 | -------------------------------------------------------------------------------- /backend/sql/init.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : 127.0.0.1 5 | Source Server Type : MySQL 6 | Source Server Version : 50719 7 | Source Host : localhost 8 | Source Database : cdib 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50719 12 | File Encoding : utf-8 13 | 14 | Date: 05/08/2019 11:52:28 AM 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for `transaction_records` 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `transaction_records`; 24 | CREATE TABLE `transaction_records` ( 25 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', 26 | `request_id` varchar(70) NOT NULL DEFAULT '' COMMENT '请求ID', 27 | `from` varchar(64) NOT NULL DEFAULT '' COMMENT '发起方', 28 | `to` varchar(64) NOT NULL DEFAULT '' COMMENT '接收方', 29 | `currency` varchar(16) NOT NULL COMMENT '货币名称', 30 | `amount` decimal(38,0) unsigned NOT NULL DEFAULT '0' COMMENT '转账金额', 31 | `gas_used` decimal(38,0) unsigned NOT NULL DEFAULT '0' COMMENT '支付手续费', 32 | `status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '当前订单是否已经执行', 33 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 34 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 35 | `rlp` text NOT NULL COMMENT 'rlp编码', 36 | PRIMARY KEY (`id`), 37 | UNIQUE KEY `request_id` (`request_id`) USING BTREE, 38 | KEY `currency` (`currency`) USING HASH 39 | ) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4; 40 | 41 | SET FOREIGN_KEY_CHECKS = 1; 42 | -------------------------------------------------------------------------------- /backend/utils/others.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // 小工具 8 | 9 | // SpliceURL 拼接URL 10 | func SpliceURL(a ...string) string { 11 | url := a[0] 12 | if len(a) < 1 { 13 | return url 14 | } 15 | 16 | for _, str := range a[1:] { 17 | var f, l string 18 | if strings.HasPrefix(str, "/") { 19 | l = str[1:] 20 | } else { 21 | l = str 22 | } 23 | 24 | if strings.HasSuffix(url, "/") { 25 | f = url 26 | } else { 27 | f = url + "/" 28 | } 29 | 30 | url = f + l 31 | } 32 | return url 33 | } 34 | 35 | //CheckAddress 校验地址是否有效 36 | func CheckAddress(addr string) bool { 37 | if len(addr) == 42 { 38 | return true 39 | } else { 40 | return false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /backend/utils/sign/rsa.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | // RAS非对称加密算法包 4 | 5 | import ( 6 | "crypto" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "crypto/x509" 10 | "encoding/pem" 11 | "errors" 12 | "sort" 13 | "strings" 14 | ) 15 | 16 | func packageData(originalData []byte, packageSize int) (r [][]byte) { 17 | var src = make([]byte, len(originalData)) 18 | copy(src, originalData) 19 | 20 | r = make([][]byte, 0) 21 | if len(src) <= packageSize { 22 | return append(r, src) 23 | } 24 | for len(src) > 0 { 25 | var p = src[:packageSize] 26 | r = append(r, p) 27 | src = src[packageSize:] 28 | if len(src) <= packageSize { 29 | r = append(r, src) 30 | break 31 | } 32 | } 33 | return r 34 | } 35 | 36 | // RSAEncrypt 公钥加密 37 | func RSAEncrypt(plaintext, key []byte) ([]byte, error) { 38 | var err error 39 | var block *pem.Block 40 | block, _ = pem.Decode(key) 41 | if block == nil { 42 | return nil, errors.New("public key error") 43 | } 44 | var pubInterface interface{} 45 | pubInterface, err = x509.ParsePKIXPublicKey(block.Bytes) 46 | if err != nil { 47 | return nil, err 48 | } 49 | var pub = pubInterface.(*rsa.PublicKey) 50 | return rsa.EncryptPKCS1v15(rand.Reader, pub, plaintext) 51 | 52 | } 53 | 54 | // RSADecrypt 私钥解密 55 | func RSADecrypt(ciphertext, key []byte) ([]byte, error) { 56 | var err error 57 | var block *pem.Block 58 | block, _ = pem.Decode(key) 59 | if block == nil { 60 | return nil, errors.New("private key error") 61 | } 62 | 63 | pri, err := x509.ParsePKCS8PrivateKey(block.Bytes) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return rsa.DecryptPKCS1v15(rand.Reader, pri.(*rsa.PrivateKey), ciphertext) 68 | 69 | } 70 | 71 | // SignPKCS1v15 私钥签名 72 | func SignPKCS1v15(src, key []byte, hash crypto.Hash) ([]byte, error) { 73 | var h = hash.New() 74 | h.Write(src) 75 | var hashed = h.Sum(nil) 76 | 77 | var err error 78 | var block *pem.Block 79 | block, _ = pem.Decode(key) 80 | if block == nil { 81 | return nil, errors.New("private key error") 82 | } 83 | 84 | //var pri *rsa.PrivateKey 85 | pri, err := x509.ParsePKCS8PrivateKey(block.Bytes) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return rsa.SignPKCS1v15(rand.Reader, pri.(*rsa.PrivateKey), hash, hashed) 90 | } 91 | 92 | //VerifyPKCS1v15 公钥验证 93 | func VerifyPKCS1v15(src, sig, key []byte, hash crypto.Hash) error { 94 | var h = hash.New() 95 | h.Write(src) 96 | var hashed = h.Sum(nil) 97 | 98 | var err error 99 | var block *pem.Block 100 | block, _ = pem.Decode(key) 101 | if block == nil { 102 | return errors.New("public key error") 103 | } 104 | 105 | var pubInterface interface{} 106 | pubInterface, err = x509.ParsePKIXPublicKey(block.Bytes) 107 | if err != nil { 108 | return err 109 | } 110 | var pub = pubInterface.(*rsa.PublicKey) 111 | 112 | return rsa.VerifyPKCS1v15(pub, hash, hashed, sig) 113 | } 114 | 115 | // ProcessArgs 按规则处理参数 116 | func ProcessArgs(args map[string]string) []byte { 117 | // 获取所有的key 118 | keys := []string{} 119 | keyValues := []string{} 120 | for key := range args { 121 | keys = append(keys, key) 122 | } 123 | // 进行一次排序 124 | notEmptyKeys := []string{} 125 | sort.Strings(keys) 126 | for _, k := range keys { 127 | if args[k] == "" { 128 | continue 129 | } 130 | notEmptyKeys = append(notEmptyKeys, k) 131 | keyValues = append(keyValues, k+"="+args[k]) 132 | } 133 | // 构成URL参数 134 | needSign := strings.Join(keyValues, "&") 135 | return []byte(needSign) 136 | 137 | } 138 | -------------------------------------------------------------------------------- /backend/vechainclient/client.go: -------------------------------------------------------------------------------- 1 | package vechain 2 | 3 | // vechain RPC客户端包 4 | 5 | import ( 6 | "context" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | gorequest "github.com/wupeaking/vechainhelper/utils" 11 | "math/big" 12 | "time" 13 | ) 14 | 15 | // Client vechain客户端 16 | type Client struct { 17 | Addr string // rpc地址 18 | User string // rpc用户名 19 | Passwd string // rpc密码 20 | timeout int // 超时时间 21 | } 22 | 23 | var ( 24 | //ErrNotExist 不存在错误 25 | ErrNotExist = errors.New("not exist") 26 | ) 27 | 28 | // NewVeChainClient 新的客户端 29 | func NewVeChainClient(rawurl string, rpcUser, rpcPasswd string, timeout int) (*Client, error) { 30 | xCli := new(Client) 31 | xCli.Addr = rawurl 32 | xCli.User = rpcUser 33 | xCli.Passwd = rpcPasswd 34 | xCli.timeout = timeout 35 | return xCli, nil 36 | } 37 | 38 | // BlockInfo 获取最新区块信息 39 | func (vc *Client) BlockInfo(ctx context.Context, blkNum uint64) (*BlockDetail, error) { 40 | request := gorequest.New() 41 | request.Header.Set("Content-type", "application/json") 42 | var path string 43 | if blkNum == 0 { 44 | path = "/blocks/best" 45 | } else { 46 | path = fmt.Sprintf("/blocks/%d", blkNum) 47 | } 48 | url := gorequest.SpliceURL(vc.Addr, path) 49 | _, respBody, err := request.Get(url).Timeout(time.Duration(vc.timeout) * time.Second).End() 50 | if err != nil { 51 | return nil, err[0] 52 | } 53 | 54 | respStru := struct { 55 | BlockDetail 56 | }{} 57 | e := json.Unmarshal([]byte(respBody), &respStru) 58 | if e != nil { 59 | return nil, e 60 | } 61 | if respStru.BlockDetail.BlockNum == 0 { 62 | return nil, errors.New("返回区块高度为0") 63 | } 64 | 65 | return &respStru.BlockDetail, nil 66 | } 67 | 68 | // GetTxReceiptByhash 获取交易hash获取交易简介 69 | func (vc *Client) GetTxReceiptByhash(ctx context.Context, txHash string) (*TransactionReceipt, error) { 70 | request := gorequest.New() 71 | request.Header.Set("Content-type", "application/json") 72 | url := gorequest.SpliceURL(vc.Addr, fmt.Sprintf("/transactions/%s/receipt", txHash)) 73 | _, respBody, err := request.Get(url).Timeout(time.Duration(vc.timeout) * time.Second).End() 74 | if err != nil { 75 | return nil, err[0] 76 | } 77 | if respBody == "null" { 78 | return nil, ErrNotExist 79 | } 80 | 81 | respStru := struct { 82 | TransactionReceipt 83 | }{} 84 | e := json.Unmarshal([]byte(respBody), &respStru) 85 | if e != nil { 86 | return nil, e 87 | } 88 | if respStru.GasUsed == 0 { 89 | return nil, errors.New("交易不存在") 90 | } 91 | 92 | return &respStru.TransactionReceipt, nil 93 | } 94 | 95 | // GetTxInfoByhash 获取交易hash获取交易简介 96 | func (vc *Client) GetTxInfoByhash(ctx context.Context, txHash string) (*Transaction, error) { 97 | request := gorequest.New() 98 | request.Header.Set("Content-type", "application/json") 99 | url := gorequest.SpliceURL(vc.Addr, fmt.Sprintf("/transactions/%s", txHash)) 100 | _, respBody, err := request.Get(url).Timeout(time.Duration(vc.timeout) * time.Second).End() 101 | if err != nil { 102 | return nil, err[0] 103 | } 104 | 105 | if respBody == "null" { 106 | return nil, ErrNotExist 107 | } 108 | 109 | respStru := struct { 110 | Transaction 111 | }{} 112 | e := json.Unmarshal([]byte(respBody), &respStru) 113 | if e != nil { 114 | return nil, e 115 | } 116 | if respStru.Hash == "" { 117 | return nil, errors.New("交易不存在") 118 | } 119 | 120 | return &respStru.Transaction, nil 121 | } 122 | 123 | // BalanceByAddress 获取账户余额 124 | func (vc *Client) BalanceByAddress(ctx context.Context, addr string) (*Balances, error) { 125 | request := gorequest.New() 126 | request.Header.Set("Content-type", "application/json") 127 | url := gorequest.SpliceURL(vc.Addr, fmt.Sprintf("/accounts/%s", addr)) 128 | _, respBody, err := request.Get(url).Timeout(time.Duration(vc.timeout) * time.Second).End() 129 | if err != nil { 130 | return nil, err[0] 131 | } 132 | 133 | balance := new(Balances) 134 | balance.Balance, _ = new(big.Int).SetString("0", 0) 135 | balance.Energy, _ = new(big.Int).SetString("0", 0) 136 | 137 | respStru := struct { 138 | Balance string `json:"balance"` 139 | Energy string `json:"energy"` 140 | HasCode bool `json:"hasCode"` 141 | }{} 142 | 143 | e := json.Unmarshal([]byte(respBody), &respStru) 144 | if e != nil { 145 | return balance, e 146 | } 147 | if respStru.Balance == "" { 148 | return balance, nil 149 | } 150 | 151 | b, bf := new(big.Int).SetString(respStru.Balance, 0) 152 | if !bf { 153 | return balance, errors.New("解析余额参数失败") 154 | } 155 | energey, ef := new(big.Int).SetString(respStru.Energy, 0) 156 | if !ef { 157 | return balance, errors.New("解析余额参数失败") 158 | } 159 | balance.Balance = b 160 | balance.Energy = energey 161 | 162 | return balance, nil 163 | } 164 | 165 | // PushTx 推送交易 166 | func (vc *Client) PushTx(ctx context.Context, content string) (string, error) { 167 | request := gorequest.New() 168 | request.Header.Set("Content-type", "application/json") 169 | url := gorequest.SpliceURL(vc.Addr, "/transactions") 170 | requestBody := fmt.Sprintf(`{"raw": "0x%s"}`, content) 171 | 172 | resp, respBody, err := request.Post(url).Timeout(time.Duration(vc.timeout) * time.Second). 173 | SendString(requestBody).End() 174 | 175 | if err != nil { 176 | return "", err[0] 177 | } 178 | if resp.StatusCode != 200 { 179 | return "", fmt.Errorf("resp: %v, respBody: %v", resp, respBody) 180 | } 181 | 182 | respStru := struct { 183 | TxID string `json:"id"` 184 | }{} 185 | 186 | e := json.Unmarshal([]byte(respBody), &respStru) 187 | if e != nil { 188 | return "", e 189 | } 190 | return respStru.TxID, nil 191 | } 192 | 193 | // SimulateContract 模拟执行账户 194 | func (vc *Client) SimulateContract(ctx context.Context, data string, value string, contractAddr string) (ContractRet, error) { 195 | request := gorequest.New() 196 | request.Header.Set("Content-type", "application/json") 197 | url := gorequest.SpliceURL(vc.Addr, fmt.Sprintf("accounts/*?revision=best")) 198 | 199 | // 构建请求参数 200 | requestBody := fmt.Sprintf(` 201 | { 202 | "clauses": [ 203 | { 204 | "to": "%s", 205 | "value": "0x%s", 206 | "data": "%s" 207 | } 208 | ], 209 | "gas" : 30000000, 210 | "gasPrice" : "0" 211 | }`, contractAddr, value, data) 212 | 213 | ret := make([]ContractRet, 0) 214 | resp, respBody, err := request.Post(url).Timeout(time.Duration(vc.timeout) * time.Second). 215 | SendString(requestBody).End() 216 | if err != nil { 217 | return ContractRet{}, err[0] 218 | } 219 | 220 | if resp.StatusCode != 200 { 221 | return ContractRet{}, fmt.Errorf("resp: %v, respBody: %v", resp, respBody) 222 | } 223 | 224 | e := json.Unmarshal([]byte(respBody), &ret) 225 | if e != nil { 226 | return ContractRet{}, e 227 | } 228 | 229 | return ret[0], nil 230 | } 231 | 232 | // FilterEventLog 过滤事件日志 233 | func (vc *Client) FilterEventLog(ctx context.Context, sBlock, eBlock uint64, addr, topic string) ([]EventLog, error) { 234 | request := gorequest.New() 235 | request.Header.Set("Content-type", "application/json") 236 | url := gorequest.SpliceURL(vc.Addr, fmt.Sprintf("/logs/event")) 237 | 238 | // 构建请求参数 239 | requestBody := fmt.Sprintf(` 240 | { 241 | "range": { 242 | "unit": "block", 243 | "from": %d, 244 | "to": %d 245 | }, 246 | "criteriaSet": [ 247 | { 248 | "address": "%s", 249 | "topic0": "%s" 250 | } 251 | ], 252 | "order": "asc" 253 | }`, sBlock, eBlock, addr, topic) 254 | 255 | ret := make([]EventLog, 0) 256 | resp, respBody, err := request.Post(url).Timeout(time.Duration(vc.timeout) * time.Second). 257 | SendString(requestBody).End() 258 | if err != nil { 259 | return ret, err[0] 260 | } 261 | 262 | if resp.StatusCode != 200 { 263 | return ret, fmt.Errorf("resp: %v, respBody: %v", resp, respBody) 264 | } 265 | 266 | e := json.Unmarshal([]byte(respBody), &ret) 267 | if e != nil { 268 | return ret, e 269 | } 270 | 271 | return ret, nil 272 | } 273 | -------------------------------------------------------------------------------- /backend/vechainclient/example.md: -------------------------------------------------------------------------------- 1 | // {### 一份区块详情格式} 2 | 3 | ```json 4 | { 5 | "timestamp": "2018-06-07T04:21:42.500", 6 | "producer": "creazyracoon", 7 | "confirmed": 0, 8 | "previous": "000a9125abad882e6a7937f5e0d394ac8a2180d3ce6365d762f187047d402fcc", 9 | "transaction_mroot": "0ec26e7b29e8e26b6a823b50be28b9562161cf68da7aa5afa7a30c317b2c5ac3", 10 | "action_mroot": "33acf64ae2257fdf126816d4cf9741c33926f9c7672b72d5159ae7d0776291ee", 11 | "schedule_version": 44, 12 | "new_producers": null, 13 | "header_extensions": [], 14 | "producer_signature": "SIG_K1_KaCQofDV2PrpVvFPrMwsKSR91iw5nEG3qZR1vFBMrgNtKBSBMpSTLCn8mA2nAuUxM7G9my3CdeCwaeSJwcy81rekeejd7K", 15 | "transactions": [ 16 | { 17 | "status": "executed", 18 | "cpu_usage_us": 577, 19 | "net_usage_words": 16, 20 | "trx": { 21 | "id": "da7adfcd313217bbf7e63c06cf00110ccfc00ed1bda188ba3542a4cbf859203a", 22 | "signatures": [ 23 | "SIG_K1_KgBxkQ81TCi3CYGziCds3BiR2fQd7rFc9j2Z21LoUPwK4ut7GWt2315ioQbcutE9mPs8yQj2xYjQZ16Mj3SRhLMko63CiB" 24 | ], 25 | "compression": "none", 26 | "packed_context_free_data": "", 27 | "context_free_data": [], 28 | "packed_trx": "11b3185bd48f4ef64b02000000000100a6823403ea3055000000572d3ccdcd01000000000000003000000000a8ed32322300000000000000300000000000b0967a102700000000000004454f530000000002313100", 29 | "transaction": { 30 | "expiration": "2018-06-07T04:22:41", 31 | "ref_block_num": 36820, 32 | "ref_block_prefix": 38532686, 33 | "max_net_usage_words": 0, 34 | "max_cpu_usage_ms": 0, 35 | "delay_sec": 0, 36 | "context_free_actions": [], 37 | "actions": [ 38 | { 39 | "account": "eosio.token", 40 | "name": "transfer", 41 | "authorization": [ 42 | { 43 | "actor": "a", 44 | "permission": "active" 45 | } 46 | ], 47 | "data": { 48 | "from": "a", 49 | "to": "jeff", 50 | "quantity": "1.0000 EOS", 51 | "memo": "11" 52 | }, 53 | "hex_data": "00000000000000300000000000b0967a102700000000000004454f5300000000023131" 54 | } 55 | ], 56 | "transaction_extensions": [] 57 | } 58 | } 59 | } 60 | ], 61 | "block_extensions": [], 62 | "id": "000a912638bdb6d094b6af9ae0251f85cecbc4a48f25538e8ee9cd03e518d2b8", 63 | "block_num": 692518, 64 | "ref_block_prefix": 2595206804 65 | } 66 | ``` 67 | 68 | ### 交易信息返回格式 69 | ```json 70 | { 71 | "id":"4e8fdd91058a90363e03d4539db0a757855d73ba1045b34467f4287b3eca720b", 72 | "trx":{ 73 | "receipt":{ 74 | "status":"executed", 75 | "cpu_usage_us":2819, 76 | "net_usage_words":20, 77 | "trx":[ 78 | 1, 79 | { 80 | "signatures":[ 81 | "SIG_K1_K8bymZS9FoaTYycshYuceoniEGHrHJAyDwnyQfGh74WNjMqah6eLHFiDAPS8JcVYftys5zxS4T3iaVp2mqwxSNmzixn6Gs" 82 | ], 83 | "compression":"none", 84 | "packed_context_free_data":"", 85 | "packed_trx":"e7b4185b2502fdf85cb2000000000100a6823403ea3055000000572d3ccdcd0100a6823403ea305500000000a8ed32324100a6823403ea305500000857619db1ca00ca9a3b0000000004454f5300000000205472616e736665722066726f6d206e6f70726f6d20746f207869616f6d696e6700" 86 | } 87 | ] 88 | }, 89 | "trx":{ 90 | "expiration":"2018-06-07T04:30:31", 91 | "ref_block_num":549, 92 | "ref_block_prefix":2992437501, 93 | "max_net_usage_words":0, 94 | "max_cpu_usage_ms":0, 95 | "delay_sec":0, 96 | "context_free_actions":[ 97 | 98 | ], 99 | "actions":[ 100 | { 101 | "account":"eosio.token", 102 | "name":"transfer", 103 | "authorization":[ 104 | { 105 | "actor":"eosio.token", 106 | "permission":"active" 107 | } 108 | ], 109 | "data":{ 110 | "from":"eosio.token", 111 | "to":"testuser1", 112 | "quantity":"100000.0000 EOS", 113 | "memo":"Transfer from noprom to xiaoming" 114 | }, 115 | "hex_data":"00a6823403ea305500000857619db1ca00ca9a3b0000000004454f5300000000205472616e736665722066726f6d206e6f70726f6d20746f207869616f6d696e67" 116 | } 117 | ], 118 | "transaction_extensions":[ 119 | 120 | ], 121 | "signatures":[ 122 | "SIG_K1_K8bymZS9FoaTYycshYuceoniEGHrHJAyDwnyQfGh74WNjMqah6eLHFiDAPS8JcVYftys5zxS4T3iaVp2mqwxSNmzixn6Gs" 123 | ], 124 | "context_free_data":[ 125 | 126 | ] 127 | } 128 | }, 129 | "block_time":"2018-06-07T04:30:01.500", 130 | "block_num":551, 131 | "last_irreversible_block":719, 132 | "traces":[ 133 | { 134 | "receipt":{ 135 | "receiver":"eosio.token", 136 | "act_digest":"97e6c9cb742f76d188fc6fcc4618fdee63fe5c4392330d609a53e7fc72f52776", 137 | "global_sequence":559, 138 | "recv_sequence":3, 139 | "auth_sequence":[ 140 | [ 141 | "eosio.token", 142 | 5 143 | ] 144 | ], 145 | "code_sequence":1, 146 | "abi_sequence":1 147 | }, 148 | "act":{ 149 | "account":"eosio.token", 150 | "name":"transfer", 151 | "authorization":[ 152 | { 153 | "actor":"eosio.token", 154 | "permission":"active" 155 | } 156 | ], 157 | "data":{ 158 | "from":"eosio.token", 159 | "to":"testuser1", 160 | "quantity":"100000.0000 EOS", 161 | "memo":"Transfer from noprom to xiaoming" 162 | }, 163 | "hex_data":"00a6823403ea305500000857619db1ca00ca9a3b0000000004454f5300000000205472616e736665722066726f6d206e6f70726f6d20746f207869616f6d696e67" 164 | }, 165 | "elapsed":2153, 166 | "cpu_usage":0, 167 | "console":"", 168 | "total_cpu_usage":0, 169 | "trx_id":"4e8fdd91058a90363e03d4539db0a757855d73ba1045b34467f4287b3eca720b", 170 | "inline_traces":[ 171 | { 172 | "receipt":{ 173 | "receiver":"testuser1", 174 | "act_digest":"97e6c9cb742f76d188fc6fcc4618fdee63fe5c4392330d609a53e7fc72f52776", 175 | "global_sequence":560, 176 | "recv_sequence":1, 177 | "auth_sequence":[ 178 | [ 179 | "eosio.token", 180 | 6 181 | ] 182 | ], 183 | "code_sequence":1, 184 | "abi_sequence":1 185 | }, 186 | "act":{ 187 | "account":"eosio.token", 188 | "name":"transfer", 189 | "authorization":[ 190 | { 191 | "actor":"eosio.token", 192 | "permission":"active" 193 | } 194 | ], 195 | "data":{ 196 | "from":"eosio.token", 197 | "to":"testuser1", 198 | "quantity":"100000.0000 EOS", 199 | "memo":"Transfer from noprom to xiaoming" 200 | }, 201 | "hex_data":"00a6823403ea305500000857619db1ca00ca9a3b0000000004454f5300000000205472616e736665722066726f6d206e6f70726f6d20746f207869616f6d696e67" 202 | }, 203 | "elapsed":50, 204 | "cpu_usage":0, 205 | "console":"", 206 | "total_cpu_usage":0, 207 | "trx_id":"4e8fdd91058a90363e03d4539db0a757855d73ba1045b34467f4287b3eca720b", 208 | "inline_traces":[ 209 | 210 | ] 211 | } 212 | ] 213 | }, 214 | { 215 | "receipt":{ 216 | "receiver":"testuser1", 217 | "act_digest":"97e6c9cb742f76d188fc6fcc4618fdee63fe5c4392330d609a53e7fc72f52776", 218 | "global_sequence":560, 219 | "recv_sequence":1, 220 | "auth_sequence":[ 221 | [ 222 | "eosio.token", 223 | 6 224 | ] 225 | ], 226 | "code_sequence":1, 227 | "abi_sequence":1 228 | }, 229 | "act":{ 230 | "account":"eosio.token", 231 | "name":"transfer", 232 | "authorization":[ 233 | { 234 | "actor":"eosio.token", 235 | "permission":"active" 236 | } 237 | ], 238 | "data":{ 239 | "from":"eosio.token", 240 | "to":"testuser1", 241 | "quantity":"100000.0000 EOS", 242 | "memo":"Transfer from noprom to xiaoming" 243 | }, 244 | "hex_data":"00a6823403ea305500000857619db1ca00ca9a3b0000000004454f5300000000205472616e736665722066726f6d206e6f70726f6d20746f207869616f6d696e67" 245 | }, 246 | "elapsed":50, 247 | "cpu_usage":0, 248 | "console":"", 249 | "total_cpu_usage":0, 250 | "trx_id":"4e8fdd91058a90363e03d4539db0a757855d73ba1045b34467f4287b3eca720b", 251 | "inline_traces":[ 252 | 253 | ] 254 | } 255 | ] 256 | } 257 | ``` 258 | -------------------------------------------------------------------------------- /backend/vechainclient/types.go: -------------------------------------------------------------------------------- 1 | package vechain 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | /* 8 | { 9 | "number": 1, 10 | "id": "0x00000001c458949985a6d86b7139690b8811dd3b4647c02d4f41cdefb7d32327", 11 | "size": 238, 12 | "parentID": "0x00000002a0c772179aa43cb6bb55d0b31369f9e92014c88a50b2cb99f9be1c5d", 13 | "timestamp": 1523156271, 14 | "gasLimit": 10000000, 15 | "beneficiary": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 16 | "gasUsed": 0, 17 | "totalScore": 101, 18 | "txsRoot": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", 19 | "stateRoot": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", 20 | "receiptsRoot": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", 21 | "signer": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 22 | "isTrunk": true, 23 | "transactions": [ 24 | "0x4de71f2d588aa8a1ea00fe8312d92966da424d9939a511fc0be81e65fad52af8" 25 | ] 26 | } 27 | */ 28 | 29 | // BlockDetail 区块详情 30 | type BlockDetail struct { 31 | BlockNum uint64 `json:"number"` 32 | Hash string `json:"id"` //区块hash 33 | TimeStamp uint64 `json:"timestamp"` // 时间戳 34 | Trxs []string `json:"transactions"` // 交易列表 35 | Producer string `json:"signer"` 36 | PrvBlock string `json:"parentID"` // 前一个区块 37 | TrxMroot string `json:"txsRoot"` // 交易默克尔树 38 | StatMroot string `json:"stateRoot"` // 交易默克尔树 39 | ReceiptMroot string `json:"receiptsRoot"` 40 | } 41 | 42 | /* 43 | { 44 | "id": "0x4de71f2d588aa8a1ea00fe8312d92966da424d9939a511fc0be81e65fad52af8", 45 | "chainTag": 1, 46 | "blockRef": "0x00000001511fc0be", 47 | "expiration": 30, 48 | "clauses": [], 49 | "gasPriceCoef": 128, 50 | "gas": 21000, 51 | "origin": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 52 | "nonce": "0xd92966da424d9939", 53 | "dependsOn": "null", 54 | "size": 180, 55 | "meta": { 56 | "blockID": "0x00000001c458949985a6d86b7139690b8811dd3b4647c02d4f41cdefb7d32327", 57 | "blockNumber": 1, 58 | "blockTimestamp": 1523156271 59 | }, 60 | "raw": "0xf86981ba800adad994000000000000000000000000000000000000746f82271080018252088001c0b8414792c9439594098323900e6470742cd877ec9f9906bca05510e421f3b013ed221324e77ca10d3466b32b1800c72e12719b213f1d4c370305399dd27af962626400" 61 | } 62 | */ 63 | 64 | // Transaction ... 65 | type Transaction struct { 66 | Hash string `json:"id"` 67 | BlockRef string `json:"blockRef"` 68 | Expiration int `json:"expiration"` 69 | GasPriceCoef int `json:"gasPriceCoef"` 70 | Gas int `json:"gas"` 71 | Origin string `json:"origin"` 72 | Nonce string `json:"nonce"` 73 | Meta struct { 74 | BlockID string `json:"blockID"` 75 | BlockNum int64 `json:"blockNumber"` 76 | BlockTimeStamp int64 `json:"blockTimestamp"` 77 | } `json:"meta"` 78 | } 79 | 80 | /* 81 | { 82 | "gasUsed": 21000, 83 | "gasPayer": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 84 | "paid": "0x723daf2", 85 | "reward": "0x723daf2", 86 | "reverted": false, 87 | "outputs": [ 88 | { 89 | "contractAddress:'0x7567d83b7b8d80addcb281a71d54fc7b3364ffed'": null, 90 | "events": [ 91 | { 92 | "address": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 93 | "topics": "0x4de71f2d588aa8a1ea00fe8312d92966da424d9939a511fc0be81e65fad52af8", 94 | "data": "0x4de71f2d588aa8a1ea00fe8312d92966da424d9939a511fc0be81e65fad52af8" 95 | } 96 | ], 97 | "transfers": [ 98 | { 99 | "sender": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 100 | "recipient": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed", 101 | "amount": "0x123f" 102 | } 103 | ] 104 | } 105 | ], 106 | "meta": { 107 | "blockID": "0x00000001c458949985a6d86b7139690b8811dd3b4647c02d4f41cdefb7d32327", 108 | "blockNumber": 1, 109 | "blockTimestamp": 1523156271, 110 | "txID": "0x4de71f2d588aa8a1ea00fe8312d92966da424d9939a511fc0be81e65fad52af8", 111 | "txOrigin": "0x7567d83b7b8d80addcb281a71d54fc7b3364ffed" 112 | } 113 | } 114 | */ 115 | 116 | // TransactionReceipt 交易收据格式 117 | type TransactionReceipt struct { 118 | GasUsed int64 `json:"gasUsed"` 119 | GasPayer string `json:"gasPayer"` 120 | Reverted bool `json:"reverted"` 121 | Meta struct { 122 | BlockID string `json:"blockID"` 123 | BlockNum int64 `json:"blockNumber"` 124 | BlockTimeStamp int64 `json:"blockTimestamp"` 125 | TxID string `json:"txID"` 126 | TxOrigin string `json:"txOrigin"` 127 | } `json:"meta"` 128 | Outputs []output `json:"outputs"` 129 | } 130 | 131 | // output 输出信息 132 | type output struct { 133 | ContractAddress string `json:"contractAddress"` 134 | Events []event `json:"events"` 135 | Transfers []transfer `json:"transfers"` 136 | } 137 | 138 | type event struct { 139 | Address string `json:"address"` 140 | Topics []string `json:"topics"` 141 | Data string `json:"data"` 142 | } 143 | 144 | type meta struct { 145 | BlockID string `json:"blockID"` 146 | BlockNum int64 `json:"blockNumber"` 147 | BlockTimeStamp int64 `json:"blockTimestamp"` 148 | TxID string `json:"txID"` 149 | TxOrigin string `json:"txOrigin"` 150 | } 151 | 152 | type transfer struct { 153 | Sender string `json:"sender"` 154 | Recipient string `json:"recipient"` 155 | Amount string `json:"amount"` 156 | } 157 | 158 | // Balances 余额详情 159 | type Balances struct { 160 | Balance *big.Int 161 | Energy *big.Int 162 | } 163 | 164 | /* 165 | { 166 | "data": "0x", 167 | "events": [], 168 | "transfers": [], 169 | "gasUsed": 0, 170 | "reverted": true, 171 | "vmError": "insufficient balance for transfer" 172 | } 173 | */ 174 | 175 | // ContractRet 执行合约返回的结果 176 | type ContractRet struct { 177 | Data string `json:"data"` 178 | Events []event `json:"events"` 179 | GasUsed uint64 `json:"gasUsed"` 180 | Reverted bool `json:"reverted"` 181 | VMErr string `json:"vmError"` 182 | } 183 | 184 | // EventLog 事件日志 185 | type Event = event 186 | type EventLog struct { 187 | Event `json:",inline"` 188 | Meta meta `json:"meta"` 189 | EventName string 190 | } 191 | -------------------------------------------------------------------------------- /img/app1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/img/app1.jpeg -------------------------------------------------------------------------------- /img/app2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/img/app2.jpeg -------------------------------------------------------------------------------- /img/show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/img/show.gif -------------------------------------------------------------------------------- /img/web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wupeaking/vechain_helper/6672d6a9f66d55de35ea144c17b8651f710cbf5c/img/web.jpg -------------------------------------------------------------------------------- /modify.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 本项目是包含完整的前后端项目, 如果想在自己的环境搭建一套这样的玩具, 按如下流程修改即可。 3 | 4 | ### 后端部署 5 | 6 | 后端可以直接使用我的公有docker镜像仓库直接部署即可(docker pull wupengxin/vechain_helper)。 只需要保证vechain的测试节点正常运行, 数据库相关schema正确构建。 详细的部署文档可以查看后端[readme](./backend/README.md) 7 | 8 | ### APP端修改 9 | 10 | 只需要修改 app/flutter_vechain/lib/configure/url.dart文件中的rootURL为自己部署节点的根路径即可。 11 | 12 | ### web端 13 | 14 | WEB端只是生成交易信息的二维码, 不需要任何修改。 -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 简单的唯链支付助手页面 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |

一个简单的唯链支付小助手

39 |

只需要简单的几行输入 扫码即可发起交易

40 |

了解更多

42 |
43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 | 52 |
53 | 54 |
55 |
56 | 57 |
58 | 62 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 | 75 | 76 |
77 | 78 |
79 | 80 | 81 | 82 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 152 | 153 | 154 | 155 | --------------------------------------------------------------------------------