Browse Source

12 new spv coins

pkg_automation_electrum
pbca26 7 years ago
parent
commit
3008f26863
  1. 11
      main.js
  2. 2
      package.json
  3. 132
      routes/electrumjs/electrumServers.js
  4. 327
      routes/electrumjs/electrumjs.networks.js
  5. 10
      routes/shepherd/addCoinShortcuts.js
  6. 31
      routes/shepherd/electrum/createtx.js
  7. 41
      routes/shepherd/electrum/keys.js

11
main.js

@ -56,6 +56,7 @@ app.setVersion(appBasicInfo.version);
shepherd.createAgamaDirs();
const appSessionHash = md5(Date.now().toString());
const _spvFees = shepherd.getSpvFees();
shepherd.writeLog(`app init ${appSessionHash}`);
shepherd.writeLog(`app info: ${appBasicInfo.name} ${appBasicInfo.version}`);
@ -407,6 +408,7 @@ function createWindow(status, hideLoadingWindow) {
mainWindow.activeSection = 'wallets';
mainWindow.argv = process.argv;
mainWindow.getAssetChainPorts = shepherd.getAssetChainPorts;
mainWindow.spvFees = _spvFees;
if (appConfig.dev) {
mainWindow.loadURL('http://127.0.0.1:3000');
@ -491,7 +493,9 @@ function createWindow(status, hideLoadingWindow) {
let _appClosingInterval;
// shepherd.killRogueProcess('marketmaker');
if (process.argv.indexOf('dexonly') > -1) {
shepherd.killRogueProcess('marketmaker');
}
if (!Object.keys(shepherd.coindInstanceRegistry).length ||
!appConfig.stopNativeDaemonsOnQuit) {
closeApp();
@ -527,8 +531,9 @@ app.on('window-all-closed', () => {
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('before-quit', (event) => {
shepherd.log('before-quit');
// shepherd.killRogueProcess('marketmaker');
if (process.argv.indexOf('dexonly') > -1) {
shepherd.killRogueProcess('marketmaker');
}
/*if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced

2
package.json

@ -1,7 +1,7 @@
{
"name": "agama-app",
"productName": "Agama",
"version": "0.2.24",
"version": "0.2.25",
"description": "Agama Wallet Desktop App",
"main": "main.js",
"scripts": {

132
routes/electrumjs/electrumServers.js

@ -1,8 +1,12 @@
let electrumServers = {
/*zcash: {
/*zcash: { 2 bytes address fix is required
address: '173.212.225.176',
port: 50032,
proto: 'tcp',
serverList: [
'173.212.225.176:50032',
'136.243.45.140:50032'
],
},*/
coqui: { // !estimatefee
address: 'electrum1.cipig.net',
@ -169,13 +173,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000000,
abbr: 'DOGE',
serverList: [
'173.212.225.176:50015',
'136.243.45.140:50015'
],
},
viacoin: { // !estimatefee
address: 'vialectrum.bitops.me',
port: 50002,
proto: 'ssl',
address: '173.212.225.176',
port: 50033,
proto: 'tcp',
txfee: 100000,
abbr: 'VIA',
serverList: [
'173.212.225.176:50033',
'136.243.45.140:50033'
],
},
vertcoin: {
address: '173.212.225.176',
@ -183,6 +195,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'VTC',
serverList: [
'173.212.225.176:50088',
'136.243.45.140:50088'
],
},
namecoin: {
address: '173.212.225.176',
@ -190,6 +206,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'NMC',
serverList: [
'173.212.225.176:50036',
'136.243.45.140:50036'
],
},
monacoin: { // !estimatefee
address: '173.212.225.176',
@ -197,13 +217,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'MONA',
serverList: [
'173.212.225.176:50002',
'136.243.45.140:50002'
],
},
litecoin: {
address: '173.212.225.176',
port: 50012,
proto: 'tcp',
txfee: 10000,
txfee: 30000,
abbr: 'LTC',
serverList: [
'173.212.225.176:50012',
'136.243.45.140:50012'
],
},
faircoin: {
address: '173.212.225.176',
@ -211,13 +239,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 1000000,
abbr: 'FAIR',
serverList: [
'173.212.225.176:50005',
'136.243.45.140:50005'
],
},
digibyte: {
dgb: {
address: '173.212.225.176',
port: 50022,
proto: 'tcp',
txfee: 100000,
abbr: 'DGB',
serverList: [
'173.212.225.176:50022',
'136.243.45.140:50022'
],
},
dash: {
address: '173.212.225.176',
@ -225,6 +261,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 10000,
abbr: 'DASH',
serverList: [
'173.212.225.176:50098',
'136.243.45.140:50098'
],
},
crown: {
address: '173.212.225.176',
@ -232,31 +272,97 @@ let electrumServers = {
proto: 'tcp',
txfee: 10000,
abbr: 'CRW',
serverList: [
'173.212.225.176:50041',
'136.243.45.140:50041'
],
},
bitcoin: {
address: '173.212.225.176',
port: 50001,
/*btc: {
address: 'electrum1.cipig.net',
port: 10000,
proto: 'tcp',
abbr: 'BTC',
serverList: [
'electrum1.cipig.net:10000',
'electrum2.cipig.net:10000'
],
},
btg: {
address: '173.212.225.176',
port: 10052,
proto: 'tcp',
abbr: 'BTG',
txfee: 10000,
serverList: [
'173.212.225.176:10052',
'94.130.224.11:10052'
],
},
/*blk: { can't decode tx!
address: 'electrum1.cipig.net',
port: 10054,
proto: 'tcp',
abbr: 'BLK',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10054',
'electrum2.cipig.net:10054'
],
},*/
sib: {
address: 'electrum1.cipig.net',
port: 10050,
proto: 'tcp',
abbr: 'SIB',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10050',
'electrum2.cipig.net:10050'
],
},
/*bch: {
address: 'electrum1.cipig.net',
port: 10051,
proto: 'tcp',
abbr: 'BCH',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10051',
'electrum2.cipig.net:10051'
],
},*/
argentum: { // !estimatefee
address: '173.212.225.176',
port: 50081,
proto: 'tcp',
txfee: 50000,
abbr: 'ARG',
serverList: [
'173.212.225.176:50081',
'136.243.45.140:50081'
],
},
chips: { // !estimatefee
address: '173.212.225.176',
port: 50076,
address: 'electrum1.cipig.net',
port: 10053,
proto: 'tcp',
txfee: 10000,
abbr: 'CHIPS',
serverList: [
'173.212.225.176:50076',
'136.243.45.140:50076'
'electrum1.cipig.net:10053',
'electrum2.cipig.net:10053'
],
},
};
electrumServers.crw = electrumServers.crown;
electrumServers.fair = electrumServers.faircoin;
electrumServers.arg = electrumServers.argentum;
electrumServers.ltc = electrumServers.litecoin;
electrumServers.mona = electrumServers.litecoin;
electrumServers.nmc = electrumServers.namecoin;
electrumServers.vtc = electrumServers.vertcoin;
electrumServers.via = electrumServers.viacoin;
electrumServers.doge = electrumServers.dogecoin;
module.exports = electrumServers;

327
routes/electrumjs/electrumjs.networks.js

@ -2,196 +2,249 @@
var bitcoin = require('bitcoinjs-lib');
var networks = exports;
Object.keys(bitcoin.networks).forEach(function(key){
networks[key] = bitcoin.networks[key]
Object.keys(bitcoin.networks).forEach((key) => {
networks[key] = bitcoin.networks[key];
});
networks.litecoin = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365
}
networks.dogecoin = {
messagePrefix: '\x19Dogecoin Signed Message:\n',
bip32: {
public: 0x02facafd,
private: 0x02fac398,
},
pubKeyHash: 0x1e,
scriptHash: 0x16,
wif: 0x9e,
dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160
messagePrefix: '\x19Dogecoin Signed Message:\n',
bip32: {
public: 0x02facafd,
private: 0x02fac398,
},
pubKeyHash: 0x1e,
scriptHash: 0x16,
wif: 0x9e,
dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160
};
// https://github.com/monacoinproject/monacoin/blob/master-0.10/src/chainparams.cpp#L161
networks.monacoin = {
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x32,
scriptHash: 0x05,
wif: 0xB2,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x32,
scriptHash: 0x05,
wif: 0xB2,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
};
// https://github.com/gamecredits-project/GameCredits/blob/master/src/chainparams.cpp#L136
networks.game = {
messagePrefix: '\x19GameCredits Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
messagePrefix: '\x19GameCredits Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
};
// https://github.com/dashpay/dash/blob/master/src/chainparams.cpp#L171
networks.dash = {
messagePrefix: '\x19DarkCoin Signed Message:\n',
bip32: {
public: 0x02fe52f8,
private: 0x02fe52cc,
},
pubKeyHash: 0x4c,
scriptHash: 0x10,
wif: 0xcc,
dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155
messagePrefix: '\x19DarkCoin Signed Message:\n',
bip32: {
public: 0x02fe52f8,
private: 0x02fe52cc,
},
pubKeyHash: 0x4c,
scriptHash: 0x10,
wif: 0xcc,
dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155
};
// https://github.com/zcoinofficial/zcoin/blob/c93eccb39b07a6132cb3d787ac18be406b24c3fa/src/base58.h#L275
networks.zcoin = {
messagePrefix: '\x19ZCoin Signed Message:\n',
bip32: {
public: 0x0488b21e, // todo
private: 0x0488ade4, // todo
},
pubKeyHash: 0x52,
scriptHash: 0x07,
wif: 0x52 + 128,
dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59
messagePrefix: '\x19ZCoin Signed Message:\n',
bip32: {
public: 0x0488b21e, // todo
private: 0x0488ade4, // todo
},
pubKeyHash: 0x52,
scriptHash: 0x07,
wif: 0x52 + 128,
dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59
};
// https://raw.githubusercontent.com/jl777/komodo/beta/src/chainparams.cpp
networks.komodo = {
messagePrefix: '\x19Komodo Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
messagePrefix: '\x19Komodo Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
};
networks.viacoin = {
messagePrefix: '\x19Viacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x21,
wif: 0xc7,
dustThreshold: 1000,
messagePrefix: '\x19Viacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x21,
wif: 0xc7,
dustThreshold: 1000,
};
networks.vertcoin = {
messagePrefix: '\x19Vertcoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Vertcoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.namecoin = {
messagePrefix: '\x19Namecoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x34,
scriptHash: 0xd,
wif: 0xb4,
dustThreshold: 1000,
messagePrefix: '\x19Namecoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x34,
scriptHash: 0xd,
wif: 0xb4,
dustThreshold: 1000,
};
networks.faircoin = {
messagePrefix: '\x19Faircoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x5f,
scriptHash: 0x24,
wif: 0xdf,
dustThreshold: 1000,
messagePrefix: '\x19Faircoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x5f,
scriptHash: 0x24,
wif: 0xdf,
dustThreshold: 1000,
};
networks.digibyte = {
messagePrefix: '\x19Digibyte Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1e,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Digibyte Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1e,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.crown = {
messagePrefix: '\x19Crown Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x1c,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Crown Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x1c,
wif: 0x80,
dustThreshold: 1000,
};
networks.argentum = {
messagePrefix: '\x19Argentum Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x17,
scriptHash: 0x5,
wif: 0x97,
dustThreshold: 1000,
messagePrefix: '\x19Argentum Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x17,
scriptHash: 0x5,
wif: 0x97,
dustThreshold: 1000,
};
networks.chips = {
messagePrefix: '\x19Chips Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
messagePrefix: '\x19Chips Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
};
networks.btg = {
messagePrefix: '\x19BitcoinGold Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x26,
scriptHash: 0x17,
wif: 0x80,
dustThreshold: 1000,
};
networks.bch = {
messagePrefix: '\x19BitcoinCash Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.blk = {
messagePrefix: '\x19BlackCoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x19,
scriptHash: 0x55,
wif: 0x99,
dustThreshold: 1000,
};
networks.sib = {
messagePrefix: '\x19SibCoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3f,
scriptHash: 0x28,
wif: 0x80,
dustThreshold: 1000,
};
networks.btc = networks.bitcoin;
networks.crw = networks.crown;
networks.dgb = networks.digibyte;
networks.arg = networks.argentum;
/*networks.zcash = {
messagePrefix: '\x19Zcash Signed Message:\n',
bip32: {

10
routes/shepherd/addCoinShortcuts.js

@ -1,3 +1,5 @@
const electrumServers = require('../electrumjs/electrumServers');
module.exports = (shepherd) => {
shepherd.startSPV = (coin) => {
if (coin === 'KMD+REVS+JUMBLR') {
@ -5,7 +7,13 @@ module.exports = (shepherd) => {
shepherd.addElectrumCoin('REVS');
shepherd.addElectrumCoin('JUMBLR');
} else {
shepherd.addElectrumCoin(coin);
if (process.argv.indexOf('spvcoins=all/add-all') > -1) {
for (let key in electrumServers) {
shepherd.addElectrumCoin(electrumServers[key].abbr);
}
} else {
shepherd.addElectrumCoin(coin);
}
}
}

31
routes/shepherd/electrum/createtx.js

@ -106,9 +106,9 @@ module.exports = (shepherd) => {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
const outputAddress = req.query.address;
const changeAddress = req.query.change;
let value = Number(req.query.value);
const push = req.query.push;
const fee = shepherd.electrumServers[network].txfee;
let fee = shepherd.electrumServers[network].txfee;
let value = Number(req.query.value);
let wif = req.query.wif;
if (req.query.gui) {
@ -163,12 +163,27 @@ module.exports = (shepherd) => {
shepherd.log('targets =>', true);
shepherd.log(targets, true);
const feeRate = 20; // sats/byte
const feeRate = shepherd.getNetworkData(network).messagePrefix === '\x19Komodo Signed Message:\n' || shepherd.getNetworkData(network).messagePrefix === '\x19Chips Signed Message:\n' ? 20 : 0; // sats/byte
if (!feeRate) {
targets[0].value = targets[0].value + fee;
}
shepherd.log(`fee rate ${feeRate}`, true);
shepherd.log(`default fee ${fee}`, true);
shepherd.log(`targets ==>`, true);
shepherd.log(targets, true);
// default coin selection algo blackjack with fallback to accumulative
// make a first run, calc approx tx fee
// if ins and outs are empty reduce max spend by txfee
let { inputs, outputs, fee } = shepherd.coinSelect(utxoListFormatted, targets, feeRate);
const firstRun = shepherd.coinSelect(utxoListFormatted, targets, feeRate);
let inputs = firstRun.inputs;
let outputs = firstRun.outputs;
if (feeRate) {
fee = firstRun.fee;
}
shepherd.log('coinselect res =>', true);
shepherd.log('coinselect inputs =>', true);
@ -204,6 +219,14 @@ module.exports = (shepherd) => {
_change = outputs[1].value;
}
// non komodo coins, subtract fee from output value
if (!feeRate) {
outputs[0].value = outputs[0].value - fee;
shepherd.log('non komodo adjusted outputs, value - default fee =>', true);
shepherd.log(outputs, true);
}
// check if any outputs are unverified
if (inputs &&
inputs.length) {

41
routes/shepherd/electrum/keys.js

@ -1,4 +1,6 @@
const sha256 = require('js-sha256');
const bip39 = require('bip39');
const crypto = require('crypto');
module.exports = (shepherd) => {
shepherd.seedToWif = (seed, network, iguana) => {
@ -32,10 +34,10 @@ module.exports = (shepherd) => {
key.compressed = true;
// shepherd.log(`seed: ${seed}`, true);
// shepherd.log(`network ${network}`, true);
// shepherd.log(`seedtowif priv key ${key.privateWif}`, true);
// shepherd.log(`seedtowif pub key ${key.publicAddress}`, true);
/*shepherd.log(`seed: ${seed}`, true);
shepherd.log(`network ${network}`, true);
shepherd.log(`seedtowif priv key ${key.privateWif}`, true);
shepherd.log(`seedtowif pub key ${key.publicAddress}`, true);*/
return {
priv: key.privateWif,
@ -43,6 +45,23 @@ module.exports = (shepherd) => {
};
}
shepherd.get('/electrum/wiftopub', (req, res, next) => {
let key = shepherd.bitcoinJS.ECPair.fromWIF(req.query.wif, shepherd.electrumJSNetworks[req.query.coin]);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
const successObj = {
msg: 'success',
result: {
keys,
},
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/seedtowif', (req, res, next) => {
const keys = shepherd.seedToWif(req.query.seed, req.query.network, req.query.iguana);
@ -92,9 +111,19 @@ module.exports = (shepherd) => {
res.end(JSON.stringify(successObj));
});
shepherd.getSpvFees = () => {
let _fees = {};
for (let key in shepherd.electrumServers) {
if (shepherd.electrumServers[key].txfee) {
_fees[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key].txfee;
}
}
return _fees;
};
shepherd.post('/electrum/seed/bip39/match', (req, res, next) => {
const bip39 = require('bip39'); // npm i -S bip39
const crypto = require('crypto');
const seed = bip39.mnemonicToSeed(req.body.seed);
const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above
const matchPattern = req.body.match;

Loading…
Cancel
Save