ethereum.rb 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +26 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +183 -0
- data/Rakefile +11 -0
- data/bin/console +14 -0
- data/bin/install_parity +29 -0
- data/bin/setup +7 -0
- data/contracts/AccountingLib.sol +112 -0
- data/contracts/AuditorInterface.sol +4 -0
- data/contracts/AuditorRegistry.sol +14 -0
- data/contracts/CustodianInterface.sol +27 -0
- data/contracts/CustodianRegistry.sol +40 -0
- data/contracts/DigixConfiguration.sol +68 -0
- data/contracts/Directory.sol +67 -0
- data/contracts/DoublyLinked.sol +54 -0
- data/contracts/GenericInterface.sol +56 -0
- data/contracts/GenericRegistry.sol +15 -0
- data/contracts/Gold.sol +105 -0
- data/contracts/GoldRegistry.sol +82 -0
- data/contracts/GoldTokenLedger.sol +3 -0
- data/contracts/Interface.sol +27 -0
- data/contracts/Minter.sol +3 -0
- data/contracts/Recaster.sol +3 -0
- data/contracts/Testing.sol +59 -0
- data/contracts/VendorInterface.sol +82 -0
- data/contracts/VendorRegistry.sol +39 -0
- data/contracts/classic/Digixbot.sol +106 -0
- data/contracts/classic/DigixbotConfiguration.sol +62 -0
- data/contracts/classic/DigixbotEthereum.sol +86 -0
- data/contracts/classic/DigixbotUsers.sol +103 -0
- data/contracts/classic/Gold.sol +497 -0
- data/contracts/classic/GoldRegistry.sol +503 -0
- data/contracts/classic/GoldTokenLedger.sol +560 -0
- data/contracts/classic/GoldTokenMinter.sol +607 -0
- data/contracts/classic/ParticipantRegistry.sol +94 -0
- data/contracts/classic/QueueSample.sol +54 -0
- data/ethereum.gemspec +35 -0
- data/lib/ethereum.rb +24 -0
- data/lib/ethereum/client.rb +97 -0
- data/lib/ethereum/contract.rb +266 -0
- data/lib/ethereum/contract_event.rb +25 -0
- data/lib/ethereum/contract_initializer.rb +54 -0
- data/lib/ethereum/deployment.rb +49 -0
- data/lib/ethereum/formatter.rb +172 -0
- data/lib/ethereum/function.rb +20 -0
- data/lib/ethereum/function_input.rb +13 -0
- data/lib/ethereum/function_output.rb +14 -0
- data/lib/ethereum/http_client.rb +38 -0
- data/lib/ethereum/initializer.rb +27 -0
- data/lib/ethereum/ipc_client.rb +46 -0
- data/lib/ethereum/project_initializer.rb +28 -0
- data/lib/ethereum/railtie.rb +10 -0
- data/lib/ethereum/singleton.rb +39 -0
- data/lib/ethereum/solidity.rb +47 -0
- data/lib/ethereum/transaction.rb +36 -0
- data/lib/ethereum/version.rb +3 -0
- data/lib/tasks/ethereum_contract.rake +27 -0
- data/lib/tasks/ethereum_node.rake +51 -0
- data/lib/tasks/ethereum_test.rake +32 -0
- data/lib/tasks/ethereum_transaction.rake +24 -0
- metadata +198 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
import "contracts/DigixConfiguration.sol";
|
2
|
+
import "contracts/GenericInterface.sol";
|
3
|
+
import "contracts/Gold.sol";
|
4
|
+
|
5
|
+
contract GoldRegistry is GenericInterface {
|
6
|
+
|
7
|
+
address config;
|
8
|
+
|
9
|
+
Directory.AddressBoolMap entries;
|
10
|
+
Directory.AddressAddressMap custodianPending;
|
11
|
+
|
12
|
+
struct Asset {
|
13
|
+
address vendor;
|
14
|
+
address custodian;
|
15
|
+
bytes32 vendorDoc;
|
16
|
+
bytes32 custodianDoc;
|
17
|
+
}
|
18
|
+
|
19
|
+
mapping (address => Directory.AddressBoolMap) vendorAssets;
|
20
|
+
mapping (address => Directory.AddressBoolMap) custodianAssets;
|
21
|
+
mapping (address => Asset) registeredAssets;
|
22
|
+
|
23
|
+
function GoldRegistry(address _conf) {
|
24
|
+
config = _conf;
|
25
|
+
}
|
26
|
+
|
27
|
+
event AddGold(address indexed gold, address indexed vendor);
|
28
|
+
event CustodianAssignment(address indexed gold, address indexed custodian, address indexed vendor);
|
29
|
+
event RemoveGold(address indexed gold, address indexed custodian);
|
30
|
+
|
31
|
+
function registerGold(address _gold, address _owner, bytes32 _doc) ifvendor {
|
32
|
+
if (!Directory.insert(entries, _gold)) throw;
|
33
|
+
if (!Directory.insert(vendorAssets[msg.sender], _gold)) throw;
|
34
|
+
Asset _asset = registeredAssets[_gold];
|
35
|
+
_asset.vendor = msg.sender;
|
36
|
+
_asset.vendorDoc = _doc;
|
37
|
+
Gold(_gold).register(_owner);
|
38
|
+
}
|
39
|
+
|
40
|
+
function isRegistered(address _gold) public returns (bool) {
|
41
|
+
return Directory.contains(entries, _gold);
|
42
|
+
}
|
43
|
+
|
44
|
+
function isVendorOf(address _gold, address _vndr) public returns (bool) {
|
45
|
+
return Directory.contains(vendorAssets[_vndr], _gold);
|
46
|
+
}
|
47
|
+
|
48
|
+
function isCustodianOf(address _gold, address _cstdn) public returns (bool) {
|
49
|
+
return Directory.contains(custodianAssets[_cstdn], _gold);
|
50
|
+
}
|
51
|
+
|
52
|
+
function delegateCustodian(address _gold, address _cstdn) ifvendor {
|
53
|
+
if (isVendorOf(_gold, msg.sender)) {
|
54
|
+
if (!Directory.insert(custodianPending, _gold, _cstdn)) throw;
|
55
|
+
CustodianAssignment(_gold, _cstdn, msg.sender);
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
function receiveFromVendor(address _gold, bytes32 _doc) ifcustodian {
|
60
|
+
if (Directory.containsAndMatches(custodianPending, _gold, msg.sender)) {
|
61
|
+
if (!Directory.remove(custodianPending, _gold)) throw;
|
62
|
+
if (!Directory.insert(custodianAssets[msg.sender], _gold)) throw;
|
63
|
+
Asset _asset = registeredAssets[_gold];
|
64
|
+
_asset.custodian = msg.sender;
|
65
|
+
_asset.custodianDoc = _doc;
|
66
|
+
Gold(_gold).activate();
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
function submitAudit(address _gold, bytes32 _doc, bool _passed) ifauditor {
|
71
|
+
|
72
|
+
}
|
73
|
+
|
74
|
+
function requestRedemption(address _gold) ifowner {
|
75
|
+
|
76
|
+
}
|
77
|
+
|
78
|
+
function markRedeemed(address _gold) ifcustodian {
|
79
|
+
|
80
|
+
}
|
81
|
+
|
82
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import "contracts/GenericInterface.sol";
|
2
|
+
|
3
|
+
contract Interface is GenericInterface {
|
4
|
+
|
5
|
+
address owner;
|
6
|
+
|
7
|
+
Directory.AddressBoolMap employees;
|
8
|
+
|
9
|
+
modifier ifowner { if(owner == msg.sender) _ }
|
10
|
+
modifier ifemployee { if(isEmployee(msg.sender)) _ }
|
11
|
+
modifier ifemployeeorigin { if(isEmployee(tx.origin)) _ }
|
12
|
+
|
13
|
+
function registerEmployee(address _acct) ifowner {
|
14
|
+
if (!Directory.insert(employees, _acct))
|
15
|
+
throw;
|
16
|
+
}
|
17
|
+
|
18
|
+
function unregisterEmployee(address _acct) ifowner {
|
19
|
+
if (!Directory.remove(employees, _acct))
|
20
|
+
throw;
|
21
|
+
}
|
22
|
+
|
23
|
+
function isEmployee(address _acct) public returns (bool) {
|
24
|
+
return Directory.contains(employees, _acct);
|
25
|
+
}
|
26
|
+
|
27
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import "contracts/AccountingLib.sol";
|
2
|
+
|
3
|
+
contract TestAccounting {
|
4
|
+
AccountingLib.Bank bank;
|
5
|
+
/*
|
6
|
+
* Account Management API
|
7
|
+
*/
|
8
|
+
function getAccountBalance(address accountAddress) constant public returns (uint) {
|
9
|
+
return bank.accountBalances[accountAddress];
|
10
|
+
}
|
11
|
+
|
12
|
+
function deposit() public {
|
13
|
+
deposit(msg.sender);
|
14
|
+
}
|
15
|
+
|
16
|
+
function deposit(address accountAddress) public {
|
17
|
+
/*
|
18
|
+
* Public API for depositing funds in a specified account.
|
19
|
+
*/
|
20
|
+
AccountingLib.deposit(bank, accountAddress, msg.value);
|
21
|
+
AccountingLib.Deposit(msg.sender, accountAddress, msg.value);
|
22
|
+
}
|
23
|
+
|
24
|
+
function addFunds(uint value) public {
|
25
|
+
addFunds(msg.sender, value);
|
26
|
+
}
|
27
|
+
|
28
|
+
function addFunds(address accountAddress, uint value) public {
|
29
|
+
AccountingLib.addFunds(bank, accountAddress, value);
|
30
|
+
}
|
31
|
+
|
32
|
+
function withdraw(uint value) public {
|
33
|
+
/*
|
34
|
+
* Public API for withdrawing funds.
|
35
|
+
*/
|
36
|
+
if (AccountingLib.withdraw(bank, msg.sender, value)) {
|
37
|
+
AccountingLib.Withdrawal(msg.sender, value);
|
38
|
+
}
|
39
|
+
else {
|
40
|
+
AccountingLib.InsufficientFunds(msg.sender, value, bank.accountBalances[msg.sender]);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
function deductFunds(uint value) public {
|
45
|
+
deductFunds(msg.sender, value);
|
46
|
+
}
|
47
|
+
|
48
|
+
function deductFunds(address accountAddress, uint value) public {
|
49
|
+
AccountingLib.deductFunds(bank, accountAddress, value);
|
50
|
+
}
|
51
|
+
|
52
|
+
function setAccountBalance(uint value) public {
|
53
|
+
setAccountBalance(msg.sender, value);
|
54
|
+
}
|
55
|
+
|
56
|
+
function setAccountBalance(address accountAddress, uint value) public {
|
57
|
+
bank.accountBalances[accountAddress] = value;
|
58
|
+
}
|
59
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import "contracts/Interface.sol";
|
2
|
+
import "contracts/Gold.sol";
|
3
|
+
import "contracts/GoldRegistry.sol";
|
4
|
+
import "contracts/DoublyLinked.sol";
|
5
|
+
|
6
|
+
contract VendorInterface is Interface {
|
7
|
+
|
8
|
+
DoublyLinked.List openOrders;
|
9
|
+
|
10
|
+
enum OrderStatus { Open, Cancelled, Completed }
|
11
|
+
|
12
|
+
struct Order {
|
13
|
+
bytes32 sku;
|
14
|
+
address buyer;
|
15
|
+
uint status;
|
16
|
+
}
|
17
|
+
|
18
|
+
mapping (bytes32 => Order) orders;
|
19
|
+
|
20
|
+
function VendorInterface(address _config) {
|
21
|
+
owner = msg.sender;
|
22
|
+
config = _config;
|
23
|
+
}
|
24
|
+
|
25
|
+
function registerGold(address _asset, address _owner, bytes32 _doc) ifemployee {
|
26
|
+
GoldRegistry(goldRegistry()).registerGold(_asset, _owner, _doc);
|
27
|
+
}
|
28
|
+
|
29
|
+
function delegateCustodian(address _asset, address _cstdn) ifemployee {
|
30
|
+
GoldRegistry(goldRegistry()).delegateCustodian(_asset, _cstdn);
|
31
|
+
}
|
32
|
+
|
33
|
+
function append(bytes32 _data) {
|
34
|
+
DoublyLinked.append(openOrders, _data);
|
35
|
+
}
|
36
|
+
|
37
|
+
function createOrder(bytes32 _orderId, bytes32 _sku, address _buyer) {
|
38
|
+
Order _order = orders[_orderId];
|
39
|
+
_order.sku = _sku;
|
40
|
+
_order.buyer = _buyer;
|
41
|
+
_order.status = uint(OrderStatus.Open);
|
42
|
+
DoublyLinked.append(openOrders, _orderId);
|
43
|
+
}
|
44
|
+
|
45
|
+
function start() returns (uint80) {
|
46
|
+
return DoublyLinked.iterate_start(openOrders);
|
47
|
+
}
|
48
|
+
|
49
|
+
function getOrder(uint80 _idx) returns (bytes32 _orderId, bytes32 _sku, address _buyer) {
|
50
|
+
_orderId = DoublyLinked.iterate_get(openOrders, _idx);
|
51
|
+
_sku = orders[_orderId].sku;
|
52
|
+
_buyer = orders[_orderId].buyer;
|
53
|
+
}
|
54
|
+
|
55
|
+
function next(uint80 _idx) returns(uint80) {
|
56
|
+
return DoublyLinked.iterate_next(openOrders, _idx);
|
57
|
+
}
|
58
|
+
|
59
|
+
function valid(uint80 _idx) returns(bool) {
|
60
|
+
return DoublyLinked.iterate_valid(openOrders, _idx);
|
61
|
+
}
|
62
|
+
|
63
|
+
function prev(uint80 _idx) returns(uint80) {
|
64
|
+
return DoublyLinked.iterate_prev(openOrders, _idx);
|
65
|
+
}
|
66
|
+
|
67
|
+
function processOrder(bytes32 _orderId) returns (bool success) {
|
68
|
+
var it = DoublyLinked.iterate_start(openOrders);
|
69
|
+
while (DoublyLinked.iterate_valid(openOrders, it)) {
|
70
|
+
if (DoublyLinked.iterate_get(openOrders, it) == _orderId) {
|
71
|
+
DoublyLinked.remove(openOrders, it);
|
72
|
+
orders[_orderId].status = uint(OrderStatus.Completed);
|
73
|
+
return true;
|
74
|
+
}
|
75
|
+
it = DoublyLinked.iterate_next(openOrders, it);
|
76
|
+
}
|
77
|
+
return false;
|
78
|
+
}
|
79
|
+
|
80
|
+
}
|
81
|
+
|
82
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import "contracts/GenericRegistry.sol";
|
2
|
+
|
3
|
+
contract VendorRegistry is GenericRegistry {
|
4
|
+
|
5
|
+
Directory.AddressBoolMap vendors;
|
6
|
+
|
7
|
+
struct Vendor {
|
8
|
+
bytes32 name;
|
9
|
+
}
|
10
|
+
|
11
|
+
mapping (address => Vendor) vendorNames;
|
12
|
+
|
13
|
+
function VendorRegistry(address _conf) {
|
14
|
+
config = _conf;
|
15
|
+
}
|
16
|
+
|
17
|
+
function register(address _acct) ifadmin {
|
18
|
+
if (!Directory.insert(vendors, _acct))
|
19
|
+
throw;
|
20
|
+
}
|
21
|
+
|
22
|
+
function unregister(address _acct) ifadmin {
|
23
|
+
if (!Directory.remove(vendors, _acct))
|
24
|
+
throw;
|
25
|
+
}
|
26
|
+
|
27
|
+
function isVendor(address _acct) public returns (bool) {
|
28
|
+
return Directory.contains(vendors, _acct);
|
29
|
+
}
|
30
|
+
|
31
|
+
function setVendorName(address _vendor, bytes32 _name) ifadmin {
|
32
|
+
vendorNames[_vendor].name = _name;
|
33
|
+
}
|
34
|
+
|
35
|
+
function getVendorName(address _vendor) public returns (bytes32) {
|
36
|
+
return vendorNames[_vendor].name;
|
37
|
+
}
|
38
|
+
|
39
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import "contracts/DigixbotUsers.sol";
|
2
|
+
import "contracts/DigixbotConfiguration.sol";
|
3
|
+
|
4
|
+
contract Coin {
|
5
|
+
function getBotContract() returns(address );
|
6
|
+
function getUserId(address _address) returns(bytes32 );
|
7
|
+
function withdrawCoin(bytes32 _user,uint256 _amount);
|
8
|
+
function depositCoin(bytes32 _uid,uint256 _amt);
|
9
|
+
function getBalance(bytes32 _uid) returns(uint256 );
|
10
|
+
function totalBalance() returns(uint256 );
|
11
|
+
function getConfig() returns(address );
|
12
|
+
function getUsersContract() returns(address );
|
13
|
+
function sendCoin(bytes32 _sender,bytes32 _recipient,uint256 _amt);
|
14
|
+
}
|
15
|
+
|
16
|
+
contract Digixbot {
|
17
|
+
address owner;
|
18
|
+
address config;
|
19
|
+
|
20
|
+
function Digixbot(address _config) {
|
21
|
+
owner = msg.sender;
|
22
|
+
config = _config;
|
23
|
+
}
|
24
|
+
|
25
|
+
modifier ifowner { if(msg.sender == owner) _ }
|
26
|
+
|
27
|
+
function getConfig() public returns (address) {
|
28
|
+
return config;
|
29
|
+
}
|
30
|
+
|
31
|
+
function addUser(bytes32 _userid) ifowner {
|
32
|
+
DigixbotUsers(getUsersContract()).addUser(_userid);
|
33
|
+
}
|
34
|
+
|
35
|
+
function setUserAccount(bytes32 _userid, address _account) ifowner {
|
36
|
+
bool _acctlock = accountLockCheck(_userid);
|
37
|
+
if (_acctlock == false) {
|
38
|
+
DigixbotUsers(getUsersContract()).setUserAccount(_userid, _account);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
function getUserAccount(bytes32 _userid) public returns (address) {
|
43
|
+
return DigixbotUsers(getUsersContract()).getUserAccount(_userid);
|
44
|
+
}
|
45
|
+
|
46
|
+
function getUsersContract() public returns (address) {
|
47
|
+
return DigixbotConfiguration(config).getUsersContract();
|
48
|
+
}
|
49
|
+
|
50
|
+
function getCoinWallet(bytes4 _coin) public returns(address) {
|
51
|
+
return DigixbotConfiguration(config).getCoinWallet(_coin);
|
52
|
+
}
|
53
|
+
|
54
|
+
function userCheck(bytes32 _id) public returns(bool) {
|
55
|
+
return DigixbotUsers(getUsersContract()).userCheck(_id);
|
56
|
+
}
|
57
|
+
|
58
|
+
function sendCoin(bytes4 _coin, bytes32 _from, bytes32 _to, uint _amount) ifowner {
|
59
|
+
bool _tiplock = tipLockCheck(_from);
|
60
|
+
if (_tiplock == false) {
|
61
|
+
Coin(getCoinWallet(_coin)).sendCoin(_from, _to, _amount);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
function withdrawCoin(bytes4 _coin, bytes32 _userid, uint _amount) ifowner {
|
66
|
+
Coin(getCoinWallet(_coin)).withdrawCoin(_userid, _amount);
|
67
|
+
}
|
68
|
+
|
69
|
+
function getCoinBalance(bytes4 _coin, bytes32 _userid) public returns(uint) {
|
70
|
+
return Coin(getCoinWallet(_coin)).getBalance(_userid);
|
71
|
+
}
|
72
|
+
|
73
|
+
function getTotalBalance(bytes4 _coin) public returns(uint) {
|
74
|
+
return Coin(getCoinWallet(_coin)).totalBalance();
|
75
|
+
}
|
76
|
+
|
77
|
+
function accountLockCheck(bytes32 _id) public returns (bool) {
|
78
|
+
return DigixbotUsers(getUsersContract()).accountLockCheck(_id);
|
79
|
+
}
|
80
|
+
|
81
|
+
function tipLockCheck(bytes32 _id) public returns (bool) {
|
82
|
+
return DigixbotUsers(getUsersContract()).tipLockCheck(_id);
|
83
|
+
}
|
84
|
+
|
85
|
+
function lockAccount(bytes32 _id) ifowner {
|
86
|
+
DigixbotUsers(getUsersContract()).lockAccount(_id);
|
87
|
+
}
|
88
|
+
|
89
|
+
function lockTip(bytes32 _id) ifowner {
|
90
|
+
DigixbotUsers(getUsersContract()).lockTip(_id);
|
91
|
+
}
|
92
|
+
|
93
|
+
function unlockAccount() {
|
94
|
+
address _userscontract = getUsersContract();
|
95
|
+
bytes32 _userid = DigixbotUsers(_userscontract).getUserId(msg.sender);
|
96
|
+
DigixbotUsers(_userscontract).unlockAccount(_userid);
|
97
|
+
}
|
98
|
+
|
99
|
+
function unlockTip() {
|
100
|
+
address _userscontract = getUsersContract();
|
101
|
+
bytes32 _userid = DigixbotUsers(_userscontract).getUserId(msg.sender);
|
102
|
+
DigixbotUsers(_userscontract).unlockTip(_userid);
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
contract DigixbotConfiguration {
|
2
|
+
|
3
|
+
struct Coin {
|
4
|
+
bytes4 name;
|
5
|
+
address wallet;
|
6
|
+
bool locked;
|
7
|
+
}
|
8
|
+
|
9
|
+
address owner;
|
10
|
+
address botcontract;
|
11
|
+
address userscontract;
|
12
|
+
bool locked;
|
13
|
+
|
14
|
+
mapping(bytes4 => Coin) coins;
|
15
|
+
|
16
|
+
modifier ifowner { if (msg.sender == owner) _ }
|
17
|
+
modifier unlesslocked { if (locked == false) _ }
|
18
|
+
|
19
|
+
function DigixbotConfiguration() {
|
20
|
+
owner = msg.sender;
|
21
|
+
locked = false;
|
22
|
+
}
|
23
|
+
|
24
|
+
function getOwner() public constant returns (address) {
|
25
|
+
return owner;
|
26
|
+
}
|
27
|
+
|
28
|
+
function setBotContract(address _botcontract) ifowner unlesslocked {
|
29
|
+
botcontract = _botcontract;
|
30
|
+
}
|
31
|
+
|
32
|
+
function getBotContract() public returns (address) {
|
33
|
+
return botcontract;
|
34
|
+
}
|
35
|
+
|
36
|
+
function setUsersContract(address _userscontract) ifowner unlesslocked {
|
37
|
+
userscontract = _userscontract;
|
38
|
+
}
|
39
|
+
|
40
|
+
function getUsersContract() public returns (address) {
|
41
|
+
return userscontract;
|
42
|
+
}
|
43
|
+
|
44
|
+
function lockConfiguration() ifowner {
|
45
|
+
locked = true;
|
46
|
+
}
|
47
|
+
|
48
|
+
function addCoin(bytes4 _name, address _wallet) ifowner {
|
49
|
+
Coin _cta = coins[_name];
|
50
|
+
if (_cta.locked == false) {
|
51
|
+
_cta.name = _name;
|
52
|
+
_cta.wallet = _wallet;
|
53
|
+
_cta.locked = true;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
function getCoinWallet(bytes4 _coin) public constant returns (address) {
|
58
|
+
return coins[_coin].wallet;
|
59
|
+
}
|
60
|
+
|
61
|
+
}
|
62
|
+
|