Browse Source

Wallet: Add ERC20/ERC721 token

pull/18/head
Sun 4 years ago
parent
commit
ffd3deb344
  1. 33
      lib/apps/wallet/models.dart
  2. 186
      lib/apps/wallet/page.dart
  3. 2
      lib/l10n/localizations.dart
  4. 4
      lib/l10n/localizations_en.dart
  5. 4
      lib/l10n/localizations_zh.dart
  6. 2
      src/account.rs
  7. 652
      src/apps/wallet/mod.rs
  8. 32
      src/apps/wallet/models.rs
  9. 156
      src/apps/wallet/rpc.rs
  10. 3
      src/migrate/wallet.rs

33
lib/apps/wallet/models.dart

@ -79,7 +79,7 @@ extension NetworkExtension on Network { @@ -79,7 +79,7 @@ extension NetworkExtension on Network {
case Network.EthTestRinkeby:
return ['Rinkeby Test Network', Colors.orange];
case Network.EthTestKovan:
return ['Rinkeby Test Network', Colors.orange];
return ['Kovan Test Network', Colors.orange];
case Network.EthLocal:
return ['Localhost 8545', Color(0xFF6174FF)];
case Network.BtcMain:
@ -188,6 +188,25 @@ class Address { @@ -188,6 +188,25 @@ class Address {
}
}
Token mainToken(Network network) {
switch (this.chain) {
case ChainToken.ETH:
case ChainToken.ERC20:
case ChainToken.ERC721:
Token token = Token.eth(network);
if (this.balances.containsKey(network)) {
token.balance(this.balances[network]!);
}
return token;
case ChainToken.BTC:
Token token = Token.btc(network);
if (this.balances.containsKey(network)) {
token.balance(this.balances[network]!);
}
return token;
}
}
split_balance(String s) {
if (s.length > 0) {
Map<Network, String> balances = {};
@ -240,13 +259,14 @@ class Token { @@ -240,13 +259,14 @@ class Token {
}
}
Token.fromList(List params) {
Token.fromList(List params, String balance) {
this.id = params[0];
this.chain = ChainTokenExtension.fromInt(params[1]);
this.network = NetworkExtension.fromInt(params[2]);
this.name = params[3];
this.contract = params[4];
this.decimal = params[5];
this.balance(balance);
}
Token.eth(Network network) {
@ -259,6 +279,15 @@ class Token { @@ -259,6 +279,15 @@ class Token {
this.decimal = 8;
}
String short() {
final len = this.contract.length;
if (len > 10) {
return this.contract.substring(0, 6) + '...' + this.contract.substring(len - 4, len);
} else {
return this.contract;
}
}
balance(String number) {
this.balanceString = number;
this.amount = double.parse(unit_balance(number, this.decimal, 8));

186
lib/apps/wallet/page.dart

@ -7,9 +7,11 @@ import 'package:esse/utils/better_print.dart'; @@ -7,9 +7,11 @@ import 'package:esse/utils/better_print.dart';
import 'package:esse/l10n/localizations.dart';
import 'package:esse/widgets/button_text.dart';
import 'package:esse/widgets/input_text.dart';
import 'package:esse/widgets/shadow_dialog.dart';
import 'package:esse/widgets/default_core_show.dart';
import 'package:esse/global.dart';
import 'package:esse/provider.dart';
import 'package:esse/options.dart';
import 'package:esse/global.dart';
import 'package:esse/rpc.dart';
import 'package:esse/apps/wallet/models.dart';
@ -34,18 +36,14 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -34,18 +36,14 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
Token _mainToken = Token();
List<Token> _tokens = [];
List tokens = [
['ETH', '2000', '2000', 'assets/logo/logo_eth.png'],
['USDT', '2000', '2000', 'assets/logo/logo_tether.png'],
['XXX', '100', '1000', 'assets/logo/logo_erc20.png'],
['wBTC', '100', '1000', 'assets/logo/logo_btc.png'],
];
@override
void initState() {
_tabController = new TabController(length: 2, vsync: this);
rpc.addListener('wallet-generate', _walletGenerate, false);
rpc.addListener('wallet-import', _walletGenerate, false);
rpc.addListener('wallet-balance', _walletBalance, false);
super.initState();
Future.delayed(Duration.zero, _load);
}
@ -69,13 +67,27 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -69,13 +67,27 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
final address = params[0];
final network = NetworkExtension.fromInt(params[1]);
if (address == this._selectedAddress!.address && network == this._selectedNetwork!) {
final contract = params[2];
final balance = params[3];
// TODO check token.
final balance = params[2];
if (params.length == 4) {
final token = Token.fromList(params[3], balance);
bool isNew = true;
int key = 0;
this._tokens.asMap().forEach((k, t) {
if (t.contract == token.contract) {
isNew = false;
key = k;
}
});
if (isNew) {
this._tokens.add(token);
} else {
this._tokens[key].balance(balance);
}
} else {
this._mainToken = Token.eth(network);
this._mainToken.balance(balance);
}
setState(() {});
}
}
@ -108,6 +120,10 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -108,6 +120,10 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
this._selectedNetwork!.toInt(), this._selectedAddress!.address
]);
}
this._mainToken = address.mainToken(this._selectedNetwork!);
for (var i = 0; i < this._tokens.length; i++) {
this._tokens[i].balance('0');
}
}
_changeNetwork(Network network) {
@ -115,6 +131,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -115,6 +131,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
rpc.send('wallet-balance', [
this._selectedNetwork!.toInt(), this._selectedAddress!.address
]);
this._tokens.clear();
}
@override
@ -136,7 +153,8 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -136,7 +153,8 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
child: ElevatedButton(
style: ElevatedButton.styleFrom(onPrimary: color.surface),
onPressed: () {
rpc.send('wallet-generate', [ChainToken.ETH.toInt(), ""]);
final pin = context.read<AccountProvider>().activedAccount.pin;
rpc.send('wallet-generate', [ChainToken.ETH.toInt(), pin]);
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
@ -213,7 +231,9 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -213,7 +231,9 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
if (value == 0) {
rpc.send('wallet-generate', [this._selectedAddress!.chain.toInt(), ""]);
} else if (value == 1) {
//
showShadowDialog(context, Icons.vertical_align_bottom, lang.importAccount,
_ImportAccount(chain: this._selectedAddress!.chain), 20.0
);
} else if (value == 2) {
//
} else {
@ -360,32 +380,37 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -360,32 +380,37 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
children: [
ListView.separated(
separatorBuilder: (BuildContext context, int index) => const Divider(),
itemCount: tokens.length + 1,
itemCount: this._tokens.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == tokens.length) {
if (index == this._tokens.length) {
return TextButton(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0),
child: Text('Add new Token' + ' ( ERC20 / ERC721 )')
),
onPressed: () {
//
},
onPressed: () => showShadowDialog(
context, Icons.paid, 'Token', _ImportToken(
chain: this._selectedAddress!.chain,
network: this._selectedNetwork!,
address: this._selectedAddress!.address
), 10.0
),
);
} else {
final token = this._tokens[index];
return ListTile(
leading: Container(
width: 36.0,
height: 36.0,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(tokens[index][3]),
image: AssetImage(token.logo),
fit: BoxFit.cover,
),
),
),
title: Text(tokens[index][1] + ' ' + tokens[index][0]),
subtitle: Text('\$' + tokens[index][2]),
title: Text("${token.amount} ${token.name}",),
subtitle: Text(token.short()),
trailing: IconButton(icon: Icon(Icons.arrow_forward_ios),
onPressed: () {}),
);
@ -422,3 +447,120 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -422,3 +447,120 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
);
}
}
class _ImportAccount extends StatelessWidget {
final ChainToken chain;
TextEditingController _nameController = TextEditingController();
FocusNode _nameFocus = FocusNode();
_ImportAccount({Key? key, required this.chain}) : super(key: key);
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
_nameFocus.requestFocus();
return Column(
children: [
Container(
padding: EdgeInsets.only(bottom: 20.0),
child: InputText(
icon: Icons.vpn_key,
text: lang.secretKey,
controller: _nameController,
focus: _nameFocus),
),
ButtonText(
text: lang.send,
action: () {
final secret = _nameController.text.trim();
if (secret.length < 32) {
return;
}
rpc.send('wallet-import', [chain.toInt(), secret]);
Navigator.pop(context);
}),
]
);
}
}
class _ImportToken extends StatefulWidget {
final ChainToken chain;
final Network network;
final String address;
_ImportToken({Key? key, required this.chain, required this.network, required this.address}) : super(key: key);
@override
_ImportTokenState createState() => _ImportTokenState();
}
class _ImportTokenState extends State<_ImportToken> {
TextEditingController _nameController = TextEditingController();
FocusNode _nameFocus = FocusNode();
ChainToken _selectedChain = ChainToken.ERC20;
Widget _chain(ChainToken value, String show, color) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Radio(
value: value,
groupValue: _selectedChain,
onChanged: (ChainToken? n) => setState(() {
if (n != null) {
_selectedChain = n;
}
})),
_selectedChain == value
? Text(show, style: TextStyle(color: color.primary))
: Text(show),
]
);
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
final params = widget.network.params();
_nameFocus.requestFocus();
return Column(
children: [
Text(params[0], style: TextStyle(color: params[1], fontWeight: FontWeight.bold)),
const SizedBox(height: 20.0),
if (widget.chain.isEth())
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_chain(ChainToken.ERC20, "ERC20", color),
_chain(ChainToken.ERC721, "ERC721", color),
]
),
const SizedBox(height: 10.0),
Container(
padding: EdgeInsets.only(bottom: 20.0),
child: InputText(
icon: Icons.location_on,
text: lang.contract + ' (0x...)',
controller: _nameController,
focus: _nameFocus),
),
ButtonText(
text: lang.send,
action: () {
final contract = _nameController.text.trim();
if (contract.length < 20) {
return;
}
rpc.send('wallet-token-import', [
_selectedChain.toInt(), widget.network.toInt(), widget.address, contract
]);
Navigator.pop(context);
}),
]
);
}
}

2
lib/l10n/localizations.dart

@ -263,6 +263,8 @@ abstract class AppLocalizations { @@ -263,6 +263,8 @@ abstract class AppLocalizations {
String get wallet;
String get walletIntro;
String get secretKey;
String get contract;
}
class _AppLocalizationsDelegate

4
lib/l10n/localizations_en.dart

@ -429,4 +429,8 @@ class AppLocalizationsEn extends AppLocalizations { @@ -429,4 +429,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get wallet => 'Wallet';
@override
String get walletIntro => 'Manage your own cryptocurrency.';
@override
String get secretKey => 'Secret Key';
@override
String get contract => 'Contract Address';
}

4
lib/l10n/localizations_zh.dart

@ -429,4 +429,8 @@ class AppLocalizationsZh extends AppLocalizations { @@ -429,4 +429,8 @@ class AppLocalizationsZh extends AppLocalizations {
String get wallet => '钱包';
@override
String get walletIntro => '管理自己的加密货币。';
@override
String get secretKey => '私钥';
@override
String get contract => '合约地址';
}

2
src/account.rs

@ -8,7 +8,7 @@ use tdn_storage::local::{DStorage, DsValue}; @@ -8,7 +8,7 @@ use tdn_storage::local::{DStorage, DsValue};
use crate::utils::crypto::{check_pin, decrypt, decrypt_multiple, encrypt_multiple, hash_pin};
fn mnemonic_lang_to_i64(lang: Language) -> i64 {
fn _mnemonic_lang_to_i64(lang: Language) -> i64 {
match lang {
Language::English => 0,
Language::SimplifiedChinese => 1,

652
src/apps/wallet/mod.rs

@ -2,3 +2,655 @@ mod models; @@ -2,3 +2,655 @@ mod models;
mod rpc;
pub(crate) use rpc::new_rpc_handler;
pub(crate) const ERC20_ABI: &'static str = r#"
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]
"#;
pub(crate) const ERC721_ABI: &'static str = r#"
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
}
],
"name": "mintTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "tokenByIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "tokenOfOwnerByIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
"#;

32
src/apps/wallet/models.rs

@ -50,6 +50,7 @@ impl ChainToken { @@ -50,6 +50,7 @@ impl ChainToken {
}
}
#[derive(Clone, Copy)]
pub(crate) enum Network {
EthMain,
EthTestRopsten,
@ -219,6 +220,15 @@ impl Address { @@ -219,6 +220,15 @@ impl Address {
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let matrix = db.query(&format!(
"SELECT id FROM addresses WHERE chain = {} AND address = '{}'",
self.chain.to_i64(),
self.address
))?;
if matrix.len() > 0 {
return Ok(());
}
let sql = format!(
"INSERT INTO addresses (chain, indx, name, address, secret, balance) VALUES ({}, {}, '{}', '{}', '{}', '{}')",
self.chain.to_i64(),
@ -340,6 +350,16 @@ impl Token { @@ -340,6 +350,16 @@ impl Token {
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let matrix = db.query(&format!(
"SELECT id FROM tokens WHERE network = {} AND contract = '{}'",
self.network.to_i64(),
self.contract
))?;
if matrix.len() > 0 {
return Ok(());
}
// check exists
let sql = format!(
"INSERT INTO tokens (chain, network, name, contract, decimal) VALUES ({}, {}, '{}', '{}', {})",
self.chain.to_i64(),
@ -365,6 +385,18 @@ impl Token { @@ -365,6 +385,18 @@ impl Token {
Ok(tokens)
}
pub fn get(db: &DStorage, id: &i64) -> Result<Self> {
let mut matrix = db.query(&format!(
"SELECT id, chain, network, name, contract, decimal FROM tokens where id = {}",
id
))?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Self::from_values(values));
}
Err(anyhow!("token is missing!"))
}
pub fn _delete(db: &DStorage, id: &i64) -> Result<()> {
let sql = format!("DELETE FROM tokens WHERE id = {}", id);
db.delete(&sql)?;

156
src/apps/wallet/rpc.rs

@ -8,11 +8,14 @@ use tdn::types::{ @@ -8,11 +8,14 @@ use tdn::types::{
use tdn_did::{generate_btc_account, generate_eth_account, secp256k1::SecretKey};
use tdn_storage::local::DStorage;
use tokio::sync::mpsc::Sender;
use web3::signing::Key;
use web3::{contract::Contract, signing::Key, types::Address as EthAddress, Web3};
use crate::{rpc::RpcState, storage::wallet_db, utils::crypto::encrypt};
use super::models::{Address, ChainToken, Network, Token};
use super::{
models::{Address, ChainToken, Network, Token},
ERC20_ABI, ERC721_ABI,
};
const WALLET_DEFAULT_PIN: &'static str = "walletissafe";
@ -30,15 +33,24 @@ fn res_balance( @@ -30,15 +33,24 @@ fn res_balance(
gid: GroupId,
address: &str,
network: &Network,
contract: &str,
balance: &str,
token: Option<&Token>,
) -> RpcParam {
if let Some(t) = token {
rpc_response(
0,
"wallet-balance",
json!([address, network.to_i64(), balance, t.to_rpc()]),
gid,
)
} else {
rpc_response(
0,
"wallet-balance",
json!([address, network.to_i64(), contract, balance]),
json!([address, network.to_i64(), balance]),
gid,
)
}
}
async fn loop_token(
@ -47,38 +59,33 @@ async fn loop_token( @@ -47,38 +59,33 @@ async fn loop_token(
gid: GroupId,
network: Network,
address: String,
c_token: Option<Token>,
) -> Result<()> {
// loop get balance of all tokens.
let node = network.node();
let chain = network.chain();
let tokens = Token::list(&db, &network)?;
if let Some(token) = c_token {
let balance = token_balance(&token.contract, &address, &node, &token.chain).await?;
let res = res_balance(gid, &address, &network, &balance, Some(&token));
sender.send(SendMessage::Rpc(0, res, true)).await?;
} else {
match chain {
ChainToken::ETH => {
let transport = web3::transports::Http::new(node).unwrap();
let web3 = web3::Web3::new(transport);
let balance = web3
.eth()
.balance(address.parse().unwrap(), None)
.await
.unwrap();
let transport = web3::transports::Http::new(node)?;
let web3 = Web3::new(transport);
let balance = web3.eth().balance(address.parse()?, None).await?;
let balance = balance.to_string();
let _ = Address::update_balance(&db, &address, &network, &balance);
let res = res_balance(gid, &address, &network, "", &balance);
let res = res_balance(gid, &address, &network, &balance, None);
sender.send(SendMessage::Rpc(0, res, true)).await?;
for token in tokens {
match token.chain {
ChainToken::ERC20 => {
//
}
ChainToken::ERC721 => {
//
}
_ => {
//
}
}
let balance =
token_balance(&token.contract, &address, &node, &token.chain).await?;
let res = res_balance(gid, &address, &network, &balance, Some(&token));
sender.send(SendMessage::Rpc(0, res, true)).await?;
}
}
ChainToken::BTC => {
@ -86,10 +93,83 @@ async fn loop_token( @@ -86,10 +93,83 @@ async fn loop_token(
}
_ => panic!("nerver here!"),
}
}
Ok(())
}
async fn token_check(
sender: Sender<SendMessage>,
db: DStorage,
gid: GroupId,
chain: ChainToken,
network: Network,
address: String,
c_str: String,
) -> Result<()> {
let account: EthAddress = address.parse()?;
let addr: EthAddress = c_str.parse()?;
let abi = match chain {
ChainToken::ERC20 => ERC20_ABI,
ChainToken::ERC721 => ERC721_ABI,
_ => return Err(anyhow!("not supported")),
};
let node = network.node();
let transport = web3::transports::Http::new(node)?;
let web3 = Web3::new(transport);
let contract = Contract::from_json(web3.eth(), addr, abi.as_bytes())?;
let symbol: String = contract
.query("symbol", (), None, Default::default(), None)
.await?;
let decimal: u64 = match chain {
ChainToken::ERC20 => {
contract
.query("decimals", (), None, Default::default(), None)
.await?
}
_ => 0,
};
let mut token = Token::new(chain, network, symbol, c_str, decimal as i64);
token.insert(&db)?;
let balance: web3::types::U256 = contract
.query("balanceOf", (account,), None, Default::default(), None)
.await?;
let balance = balance.to_string();
let res = res_balance(gid, &address, &network, &balance, Some(&token));
sender.send(SendMessage::Rpc(0, res, true)).await?;
Ok(())
}
async fn token_balance(
c_str: &str,
address: &str,
node: &str,
chain: &ChainToken,
) -> Result<String> {
let addr: EthAddress = c_str.parse()?;
let account: EthAddress = address.parse()?;
let abi = match chain {
ChainToken::ERC20 => ERC20_ABI,
ChainToken::ERC721 => ERC721_ABI,
_ => return Err(anyhow!("not supported")),
};
let transport = web3::transports::Http::new(node)?;
let web3 = Web3::new(transport);
let contract = Contract::from_json(web3.eth(), addr, abi.as_bytes())?;
let balance: web3::types::U256 = contract
.query("balanceOf", (account,), None, Default::default(), None)
.await?;
Ok(balance.to_string())
}
pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
handler.add_method("wallet-echo", |_, params, _| async move {
Ok(HandleResult::rpc(json!(params)))
@ -168,14 +248,40 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -168,14 +248,40 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let network = Network::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?);
let address = params[1].as_str().ok_or(RpcError::ParseError)?.to_owned();
println!("start wallet balances");
let group_lock = state.group.read().await;
let db = wallet_db(group_lock.base(), &gid)?;
let sender = group_lock.sender();
drop(group_lock);
tokio::spawn(loop_token(sender, db, gid, network, address));
let c_str = if params.len() == 4 {
let cid = params[2].as_i64().ok_or(RpcError::ParseError)?;
let token = Token::get(&db, &cid)?;
Some(token)
} else {
None
};
tokio::spawn(loop_token(sender, db, gid, network, address, c_str));
Ok(HandleResult::new())
},
);
handler.add_method(
"wallet-token-import",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let chain = ChainToken::from_i64(params[0].as_i64().ok_or(RpcError::ParseError)?);
let network = Network::from_i64(params[1].as_i64().ok_or(RpcError::ParseError)?);
let address = params[2].as_str().ok_or(RpcError::ParseError)?.to_owned();
let c_str = params[3].as_str().ok_or(RpcError::ParseError)?.to_owned();
let group_lock = state.group.read().await;
let db = wallet_db(group_lock.base(), &gid)?;
let sender = group_lock.sender();
drop(group_lock);
tokio::spawn(token_check(sender, db, gid, chain, network, address, c_str));
Ok(HandleResult::new())
},

3
src/migrate/wallet.rs

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
#[rustfmt::skip]
pub(super) const WALLET_VERSIONS: [&str; 2] = [
pub(super) const WALLET_VERSIONS: [&str; 3] = [
"CREATE TABLE IF NOT EXISTS addresses(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
chain INTEGER NOT NULL,
@ -15,4 +15,5 @@ pub(super) const WALLET_VERSIONS: [&str; 2] = [ @@ -15,4 +15,5 @@ pub(super) const WALLET_VERSIONS: [&str; 2] = [
name TEXT NOT NULL,
contract TEXT NOT NULL,
decimal INTEGER NOT NULL);",
"INSERT INTO tokens (chain, network, name, contract, decimal) VALUES (2, 1, 'USDT', '0xdac17f958d2ee523a2206206994597c13d831ec7', 6);", // default eth mainnet USDT.
];

Loading…
Cancel
Save