Browse Source

Wallet: Add ERC721 support

pull/18/head
Sun 4 years ago
parent
commit
9b83df9f15
  1. BIN
      assets/logo/logo_esse_nft.png
  2. 32
      lib/apps/wallet/models.dart
  3. 94
      lib/apps/wallet/page.dart
  4. 1
      lib/l10n/localizations.dart
  5. 2
      lib/l10n/localizations_en.dart
  6. 2
      lib/l10n/localizations_zh.dart
  7. 1
      lib/provider.dart
  8. 1
      pubspec.yaml
  9. 757
      src/apps/wallet/mod.rs
  10. 123
      src/apps/wallet/models.rs
  11. 83
      src/apps/wallet/rpc.rs
  12. 8
      src/migrate/wallet.rs

BIN
assets/logo/logo_esse_nft.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

32
lib/apps/wallet/models.dart

@ -226,13 +226,13 @@ class Address { @@ -226,13 +226,13 @@ class Address {
case ChainToken.ERC721:
Token token = Token.eth(network);
if (this.balances.containsKey(network)) {
token.balance(this.balances[network]!);
token.updateBalance(this.balances[network]!);
}
return token;
case ChainToken.BTC:
Token token = Token.btc(network);
if (this.balances.containsKey(network)) {
token.balance(this.balances[network]!);
token.updateBalance(this.balances[network]!);
}
return token;
}
@ -270,17 +270,21 @@ class Token { @@ -270,17 +270,21 @@ class Token {
int decimal = 18;
String balanceString = '';
double amount = 0.0;
double fiat = 0.0;
String balance = '0';
double fiat = 0;
Token() {}
double get amount => double.parse(this.balance);
String get logo {
switch (name.toUpperCase()) {
case 'ETH':
return 'assets/logo/logo_eth.png';
case 'USDT':
return 'assets/logo/logo_tether.png';
case 'ESNFT':
return 'assets/logo/logo_esse_nft.png';
default:
if (chain == ChainToken.ERC20) {
return 'assets/logo/logo_erc20.png';
@ -292,6 +296,15 @@ class Token { @@ -292,6 +296,15 @@ class Token {
}
}
bool isNft() {
switch (this.chain) {
case ChainToken.ERC721:
return true;
default:
return false;
}
}
Token.fromList(List params, String balance) {
this.id = params[0];
this.chain = ChainTokenExtension.fromInt(params[1]);
@ -299,7 +312,7 @@ class Token { @@ -299,7 +312,7 @@ class Token {
this.name = params[3];
this.contract = params[4];
this.decimal = params[5];
this.balance(balance);
this.updateBalance(balance);
}
Token.eth(Network network) {
@ -321,9 +334,14 @@ class Token { @@ -321,9 +334,14 @@ class Token {
}
}
balance(String number) {
updateBalance(String number) {
this.balanceString = number;
this.amount = double.parse(unitBalance(number, this.decimal, 8));
if (number.length == 0 || number == '0') {
this.balance = this.decimal > 0 ? '0.0' : '0';
} else {
this.balance = this.decimal == 0 ? number : unitBalance(number, this.decimal, 8);
}
}
}

94
lib/apps/wallet/page.dart

@ -99,11 +99,11 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -99,11 +99,11 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
if (isNew) {
this._tokens.add(token);
} else {
this._tokens[key].balance(balance);
this._tokens[key].updateBalance(balance);
}
} else {
this._mainToken = Token.eth(network);
this._mainToken.balance(balance);
this._mainToken.updateBalance(balance);
}
setState(() {});
}
@ -483,7 +483,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt @@ -483,7 +483,7 @@ class _WalletDetailState extends State<WalletDetail> with SingleTickerProviderSt
),
),
),
title: Text("${token.amount} ${token.name}",),
title: Text("${token.balance} ${token.name}",),
subtitle: Text(token.short()),
trailing: IconButton(icon: Icon(Icons.input, color: color.primary),
onPressed: () => showShadowDialog(
@ -693,6 +693,10 @@ class _TransferTokenState extends State<_TransferToken> { @@ -693,6 +693,10 @@ class _TransferTokenState extends State<_TransferToken> {
String _networkError = '';
bool _checked = false;
List<String> _nft = [];
String _selectNft = '-';
bool _nftInput = false;
@override
void initState() {
super.initState();
@ -706,6 +710,31 @@ class _TransferTokenState extends State<_TransferToken> { @@ -706,6 +710,31 @@ class _TransferTokenState extends State<_TransferToken> {
this._checked = false;
});
});
if (widget.token.isNft()) {
_getNft();
}
}
_getNft() async {
final res = await httpPost(Global.httpRpc, 'wallet-nft', [
widget.address.id, widget.token.id,
]);
if (res.isOk) {
final a = res.params[0];
final t = res.params[1];
res.params[2].forEach((hash) {
this._nft.add(hash);
});
if (this._nft.length > 0) {
this._selectNft = this._nft[0];
this._nftInput = false;
} else {
this._nftInput = true;
}
} else {
this._nftInput = true;
}
setState(() {});
}
_gasPrice(String to, String amount) async {
@ -763,7 +792,7 @@ class _TransferTokenState extends State<_TransferToken> { @@ -763,7 +792,7 @@ class _TransferTokenState extends State<_TransferToken> {
]
),
const SizedBox(height: 20.0),
Text("${widget.token.amount} ${widget.token.name}",
Text("${widget.token.balance} ${widget.token.name}",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 10.0),
if (this._checked)
@ -846,7 +875,7 @@ class _TransferTokenState extends State<_TransferToken> { @@ -846,7 +875,7 @@ class _TransferTokenState extends State<_TransferToken> {
Expanded(
child: InputText(
icon: Icons.person,
text: lang.address + ' (0x...)',
text: lang.account.toLowerCase() + ' (0x...)',
controller: _nameController,
focus: _nameFocus
)),
@ -873,14 +902,65 @@ class _TransferTokenState extends State<_TransferToken> { @@ -873,14 +902,65 @@ class _TransferTokenState extends State<_TransferToken> {
),
]
),
Container(
widget.token.isNft()
? Container(
padding: const EdgeInsets.only(top: 15.0, bottom: 20.0),
child: Row(
children: [
Expanded(
child: this._nftInput
? InputText(
icon: Icons.verified,
text: '0xAa..',
controller: _amountController,
focus: _amountFocus)
: DropdownButtonHideUnderline(
child: Theme(
data: Theme.of(context).copyWith(
canvasColor: color.background,
),
child: DropdownButton<String>(
iconEnabledColor: Color(0xFFADB0BB),
isExpanded: true,
value: this._selectNft,
onChanged: (String? value) {
if (value != null) {
setState(() {
this._selectNft = value;
});
}
},
items: this._nft.map((value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value, maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 16)
),
);
}).toList(),
),
),
)
),
SizedBox(width: 80.0,
child: IconButton(icon: this._nftInput ? Icon(Icons.fact_check, color: color.primary) : Icon(Icons.edit, color: Colors.green),
onPressed: () => setState(() {
this._nftInput = !this._nftInput;
})
),
)
]
)
)
: Container(
padding: const EdgeInsets.only(top: 15.0, bottom: 20.0),
child: Row(
children: [
Expanded(
child: InputText(
icon: Icons.paid,
text: 'Amount(0) or NFT(0x...)',
text: '0.0',
controller: _amountController,
focus: _amountFocus),
),

1
lib/l10n/localizations.dart

@ -125,6 +125,7 @@ abstract class AppLocalizations { @@ -125,6 +125,7 @@ abstract class AppLocalizations {
String get portuguese;
// security page (did)
String get account;
String get createAccount;
String get importAccount;
String get loginChooseAccount;

2
lib/l10n/localizations_en.dart

@ -169,6 +169,8 @@ class AppLocalizationsEn extends AppLocalizations { @@ -169,6 +169,8 @@ class AppLocalizationsEn extends AppLocalizations {
// security page (did)
@override
String get account => 'Account';
@override
String get createAccount => 'Create Account';
@override
String get importAccount => 'Import Account';

2
lib/l10n/localizations_zh.dart

@ -169,6 +169,8 @@ class AppLocalizationsZh extends AppLocalizations { @@ -169,6 +169,8 @@ class AppLocalizationsZh extends AppLocalizations {
// security page (did)
@override
String get account => '账户';
@override
String get createAccount => '新建账户';
@override
String get importAccount => '导入账户';

1
lib/provider.dart

@ -77,6 +77,7 @@ class AccountProvider extends ChangeNotifier { @@ -77,6 +77,7 @@ class AccountProvider extends ChangeNotifier {
() => rpc.send('account-online', [gid]));
initLogined(gid, this.accounts.values.toList());
this.coreShowWidget = DefaultCoreShow();
}
/// when security add account.

1
pubspec.yaml

@ -87,6 +87,7 @@ flutter: @@ -87,6 +87,7 @@ flutter:
- assets/logo/logo_erc20.png
- assets/logo/logo_nft.png
- assets/logo/logo_btc.png
- assets/logo/logo_esse_nft.png
- assets/images/background_light.jpg
- assets/images/background_dark.jpg
- assets/images/image_missing.png

757
src/apps/wallet/mod.rs

@ -230,427 +230,340 @@ pub(crate) const ERC20_ABI: &'static str = r#" @@ -230,427 +230,340 @@ pub(crate) const ERC20_ABI: &'static str = r#"
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"
}
{
"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": [],
"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": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"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"
}
]
"#;

123
src/apps/wallet/models.rs

@ -22,6 +22,7 @@ pub const ETH_KOVAN: &'static str = @@ -22,6 +22,7 @@ pub const ETH_KOVAN: &'static str =
pub const ETH_LOCAL: &'static str =
"http://localhost:8545";
#[derive(Eq, PartialEq)]
pub(crate) enum ChainToken {
ETH,
ERC20,
@ -307,6 +308,7 @@ impl Address { @@ -307,6 +308,7 @@ impl Address {
pub fn _delete(db: &DStorage, id: &i64) -> Result<()> {
let sql = format!("DELETE FROM addresses WHERE id = {}", id);
db.delete(&sql)?;
Balance::delete_by_address(db, id)?;
Ok(())
}
}
@ -411,6 +413,127 @@ impl Token { @@ -411,6 +413,127 @@ impl Token {
pub fn _delete(db: &DStorage, id: &i64) -> Result<()> {
let sql = format!("DELETE FROM tokens WHERE id = {}", id);
db.delete(&sql)?;
Balance::delete_by_token(db, id)?;
Ok(())
}
}
pub(crate) struct Balance {
id: i64,
address: i64,
token: i64,
pub value: String,
}
impl Balance {
pub fn new(address: i64, token: i64, value: String) -> Self {
Self {
address,
token,
value,
id: 0,
}
}
fn from_values(mut v: Vec<DsValue>) -> Self {
Self {
value: v.pop().unwrap().as_string(),
token: v.pop().unwrap().as_i64(),
address: v.pop().unwrap().as_i64(),
id: v.pop().unwrap().as_i64(),
}
}
pub fn list(db: &DStorage, address: &i64, token: &i64) -> Result<Vec<Self>> {
let matrix = db.query(&format!(
"SELECT id, address, token, value FROM balances WHERE address = {} AND token = {}",
address, token
))?;
let mut balances = vec![];
for values in matrix {
balances.push(Self::from_values(values));
}
Ok(balances)
}
/// use for common and erc20.
pub fn update(db: &DStorage, address: &i64, token: &i64, value: &str) -> Result<()> {
let matrix = db.query(&format!(
"SELECT id FROM balances WHERE address = {} AND token = {}",
address, token,
))?;
if matrix.len() > 0 {
let sql = format!(
"UPDATE balances SET value = '{}' WHERE address = {} AND token = {}",
value, address, token
);
db.update(&sql)?;
return Ok(());
}
let sql = format!(
"INSERT INTO balances (address, token, value) VALUES ({}, {}, '{}')",
address, token, value,
);
let _id = db.insert(&sql)?;
Ok(())
}
/// use for erc721 (NFT).
pub fn add(db: &DStorage, address: i64, token: i64, value: String) -> Result<Self> {
let mut matrix = db.query(&format!(
"SELECT id FROM balances WHERE address = {} AND token = {} AND value = '{}'",
address, token, value
))?;
if matrix.len() > 0 {
let id = matrix.pop().unwrap().pop().unwrap().as_i64(); // safe unwrap()
return Ok(Self {
id,
address,
token,
value,
});
}
let sql = format!(
"INSERT INTO balances (address, token, value) VALUES ({}, {}, '{}')",
address, token, value
);
let id = db.insert(&sql)?;
Ok(Self {
id,
address,
token,
value,
})
}
pub fn _get(db: &DStorage, id: &i64) -> Result<Self> {
let mut matrix = db.query(&format!(
"SELECT id, address, token, value FROM balances where id = {}",
id
))?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Self::from_values(values));
}
Err(anyhow!("balance is missing!"))
}
pub fn _delete(db: &DStorage, id: &i64) -> Result<()> {
let sql = format!("DELETE FROM balances WHERE id = {}", id);
db.delete(&sql)?;
Ok(())
}
pub fn delete_by_address(db: &DStorage, address: &i64) -> Result<()> {
let sql = format!("DELETE FROM balances WHERE address = {}", address);
db.delete(&sql)?;
Ok(())
}
pub fn delete_by_token(db: &DStorage, token: &i64) -> Result<()> {
let sql = format!("DELETE FROM balances WHERE token = {}", token);
db.delete(&sql)?;
Ok(())
}
}

83
src/apps/wallet/rpc.rs

@ -11,6 +11,7 @@ use tokio::sync::mpsc::Sender; @@ -11,6 +11,7 @@ use tokio::sync::mpsc::Sender;
use web3::{
contract::{tokens::Tokenize, Contract},
signing::Key,
transports::http::Http,
types::{Address as EthAddress, Bytes, CallRequest, TransactionParameters, U256},
Web3,
};
@ -22,7 +23,7 @@ use crate::{ @@ -22,7 +23,7 @@ use crate::{
};
use super::{
models::{Address, ChainToken, Network, Token},
models::{Address, Balance, ChainToken, Network, Token},
ERC20_ABI, ERC721_ABI,
};
@ -85,13 +86,15 @@ async fn loop_token( @@ -85,13 +86,15 @@ async fn loop_token(
let tokens = Token::list(&db, &network)?;
if let Some(token) = c_token {
let balance = token_balance(&token.contract, &address, &node, &token.chain).await?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let balance = token_balance(&web3, &token.contract, &address, &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)?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let balance = web3.eth().balance(address.parse()?, None).await?;
let balance = balance.to_string();
@ -100,8 +103,9 @@ async fn loop_token( @@ -100,8 +103,9 @@ async fn loop_token(
sender.send(SendMessage::Rpc(0, res, true)).await?;
for token in tokens {
//tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let balance =
token_balance(&token.contract, &address, &node, &token.chain).await?;
token_balance(&web3, &token.contract, &address, &token.chain).await?;
let res = res_balance(gid, &address, &network, &balance, Some(&token));
sender.send(SendMessage::Rpc(0, res, true)).await?;
}
@ -134,7 +138,7 @@ async fn token_check( @@ -134,7 +138,7 @@ async fn token_check(
_ => return Err(anyhow!("not supported")),
};
let node = network.node();
let transport = web3::transports::Http::new(node)?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let contract = Contract::from_json(web3.eth(), addr, abi.as_bytes())?;
@ -148,7 +152,7 @@ async fn token_check( @@ -148,7 +152,7 @@ async fn token_check(
.query("decimals", (), None, Default::default(), None)
.await?
}
_ => 0,
_ => 0, // NFT default no decimal.
};
let mut token = Token::new(chain, network, symbol, c_str, decimal as i64);
@ -165,9 +169,9 @@ async fn token_check( @@ -165,9 +169,9 @@ async fn token_check(
}
async fn token_balance(
web3: &Web3<Http>,
c_str: &str,
address: &str,
node: &str,
chain: &ChainToken,
) -> Result<String> {
let addr: EthAddress = c_str.parse()?;
@ -179,8 +183,6 @@ async fn token_balance( @@ -179,8 +183,6 @@ async fn token_balance(
_ => 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: U256 = contract
@ -203,7 +205,7 @@ async fn token_transfer( @@ -203,7 +205,7 @@ async fn token_transfer(
let amount = U256::from_dec_str(amount_str)?;
let node = network.node();
let transport = web3::transports::Http::new(node)?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let tx = match chain {
@ -262,7 +264,7 @@ async fn token_gas( @@ -262,7 +264,7 @@ async fn token_gas(
let amount = U256::from_dec_str(amount_str)?;
let node = network.node();
let transport = web3::transports::Http::new(node)?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let price = web3.eth().gas_price().await?;
@ -302,6 +304,20 @@ async fn token_gas( @@ -302,6 +304,20 @@ async fn token_gas(
Ok((price.to_string(), gas.to_string()))
}
async fn nft_check(node: &str, c_str: &str, hash: &str) -> Result<String> {
let addr: EthAddress = c_str.parse()?;
let tokenid = U256::from_dec_str(&hash)?;
let transport = Http::new(node)?;
let web3 = Web3::new(transport);
let contract = Contract::from_json(web3.eth(), addr, ERC721_ABI.as_bytes())?;
let owner: EthAddress = contract
.query("ownerOf", (tokenid,), None, Default::default(), None)
.await?;
Ok(format!("{:?}", owner))
}
pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
handler.add_method("wallet-echo", |_, params, _| async move {
Ok(HandleResult::rpc(json!(params)))
@ -508,4 +524,49 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -508,4 +524,49 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
])))
},
);
handler.add_method(
"wallet-nft",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let address = params[0].as_i64().ok_or(RpcError::ParseError)?;
let token = params[1].as_i64().ok_or(RpcError::ParseError)?;
let db = wallet_db(state.layer.read().await.base(), &gid)?;
let nfts = Balance::list(&db, &address, &token)?;
let mut results = vec![];
for nft in nfts {
results.push(nft.value);
}
Ok(HandleResult::rpc(json!([address, token, results])))
},
);
handler.add_method(
"wallet-nft-add",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let address = params[0].as_i64().ok_or(RpcError::ParseError)?;
let token = params[1].as_i64().ok_or(RpcError::ParseError)?;
let hash = params[2].as_str().ok_or(RpcError::ParseError)?.to_owned();
let db = wallet_db(state.layer.read().await.base(), &gid)?;
let t = Token::get(&db, &token)?;
let a = Address::get(&db, &address)?;
if t.chain != ChainToken::ERC721 {
return Err(RpcError::Custom("token is not erc721".to_owned()));
}
let owner = nft_check(t.network.node(), &t.contract, &hash)
.await
.map_err(|e| RpcError::Custom(format!("{:?}", e)))?;
if owner == a.address {
let balance = Balance::add(&db, address, token, hash)?;
Ok(HandleResult::rpc(json!([address, token, balance.value])))
} else {
Err(RpcError::Custom("address is not NFT owner".to_owned()))
}
},
);
}

8
src/migrate/wallet.rs

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

Loading…
Cancel
Save