diff --git a/Navigation.js b/Navigation.js
index 30109faa6..fc38aa98b 100644
--- a/Navigation.js
+++ b/Navigation.js
@@ -29,6 +29,10 @@ import WalletExport from './screen/wallets/export';
import WalletXpub from './screen/wallets/xpub';
import BuyBitcoin from './screen/wallets/buyBitcoin';
import HodlHodl from './screen/wallets/hodlHodl';
+import HodlHodlViewOffer from './screen/wallets/hodlHodlViewOffer';
+import HodlHodlLogin from './screen/wallets/hodlHodlLogin';
+import HodlHodlWebview from './screen/wallets/hodlHodlWebview';
+import HodlHodlMyContracts from './screen/wallets/hodlHodlMyContracts';
import Marketplace from './screen/wallets/marketplace';
import ReorderWallets from './screen/wallets/reorderWallets';
import SelectWallet from './screen/wallets/selectWallet';
@@ -201,6 +205,17 @@ const LNDCreateInvoiceRoot = () => (
);
+const HodlHodlStack = createStackNavigator();
+const HodlHodlRoot = () => (
+
+
+
+
+
+
+
+);
+
// LightningScanInvoiceStackNavigator === ScanLndInvoiceStack
const ScanLndInvoiceStack = createStackNavigator();
const ScanLndInvoiceRoot = () => (
@@ -236,6 +251,7 @@ const Navigation = () => (
+
} */
@@ -523,6 +526,51 @@ export class AppStorage {
return finalBalance;
}
+ async getHodlHodlApiKey() {
+ try {
+ return await this.getItem(AppStorage.HODL_HODL_API_KEY);
+ } catch (_) {}
+ return false;
+ }
+
+ async getHodlHodlSignatureKey() {
+ try {
+ return await this.getItem(AppStorage.HODL_HODL_SIGNATURE_KEY);
+ } catch (_) {}
+ return false;
+ }
+
+ /**
+ * Since we cant fetch list of contracts from hodlhodl api yet, we have to keep track of it ourselves
+ *
+ * @returns {Promise} String ids of contracts in an array
+ */
+ async getHodlHodlContracts() {
+ try {
+ let json = await this.getItem(AppStorage.HODL_HODL_CONTRACTS);
+ return JSON.parse(json);
+ } catch (_) {}
+ return [];
+ }
+
+ async addHodlHodlContract(id) {
+ let json;
+ try {
+ json = await this.getItem(AppStorage.HODL_HODL_CONTRACTS);
+ json = JSON.parse(json);
+ } catch (_) {
+ json = [];
+ }
+
+ json.push(id);
+ return this.setItem(AppStorage.HODL_HODL_CONTRACTS, JSON.stringify(json));
+ }
+
+ async setHodlHodlApiKey(key, sigKey) {
+ if (sigKey) await this.setItem(AppStorage.HODL_HODL_SIGNATURE_KEY, sigKey);
+ return this.setItem(AppStorage.HODL_HODL_API_KEY, key);
+ }
+
async isAdancedModeEnabled() {
try {
return !!(await this.getItem(AppStorage.ADVANCED_MODE_ENABLED));
diff --git a/class/hodl-hodl-api.js b/class/hodl-hodl-api.js
index be827fa73..a98224ebb 100644
--- a/class/hodl-hodl-api.js
+++ b/class/hodl-hodl-api.js
@@ -1,4 +1,5 @@
import Frisbee from 'frisbee';
+const CryptoJS = require('crypto-js');
export class HodlHodlApi {
static PAGINATION_LIMIT = 'limit'; // int
@@ -74,18 +75,27 @@ export class HodlHodlApi {
async getMyCountryCode() {
const _api = new Frisbee({ baseURI: 'https://ifconfig.co/' });
+ const _api2 = new Frisbee({ baseURI: 'https://geolocation-db.com/' });
let response;
let allowedTries = 6;
while (allowedTries > 0) {
// this API fails a lot, so lets retry several times
- response = await _api.get('/country-iso', {
- headers: { 'Access-Control-Allow-Origin': '*' },
- });
+ response = await _api.get('/country-iso', { headers: { 'Access-Control-Allow-Origin': '*' } });
let body = response.body;
if (typeof body === 'string') body = body.replace('\n', '');
if (!body || body.length !== 2) {
+ // trying api2
+ const response = await _api2.get('/json/', { headers: { 'Access-Control-Allow-Origin': '*' } });
+ body = response.body;
+ let json;
+ try {
+ json = JSON.parse(body);
+ } catch (_) {}
+ if (json && json.country_code) return (this._myCountryCode = json.country_code);
+ // failed, retry
+
allowedTries--;
await (async () => new Promise(resolve => setTimeout(resolve, 3000)))(); // sleep
} else {
@@ -118,6 +128,17 @@ export class HodlHodlApi {
return (this._currencies = json.currencies.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)));
}
+ async getOffer(id) {
+ const response = await this._api.get('/api/v1/offers/' + id, this._getHeadersWithoutAuthorization());
+
+ const json = response.body;
+ if (!json || !json.offer || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.offer;
+ }
+
async getOffers(pagination = {}, filters = {}, sort = {}) {
const uri = [];
for (const key in sort) {
@@ -138,4 +159,141 @@ export class HodlHodlApi {
return (this._offers = json.offers);
}
+
+ createSignature(apiKey, sigKey, nonce) {
+ const sourceMessageForSigning = apiKey + ':' + nonce; // :
+ return CryptoJS.HmacSHA256(sourceMessageForSigning, sigKey).toString(CryptoJS.enc.Hex);
+ }
+
+ /**
+ * @see https://gitlab.com/hodlhodl-public/public_docs/-/blob/master/autologin.md
+ *
+ * @param apiSigKey {string}
+ * @param nonce {integer|null} Optional unix timestamp (sec, not msec), or nothing
+ * @returns {Promise} Token usable for autologin (works only once and only about 30 seconds)
+ */
+ async requestAutologinToken(apiSigKey, nonce) {
+ nonce = nonce || Math.floor(+new Date() / 1000);
+ const signature = this.createSignature(this.apiKey, apiSigKey, nonce);
+
+ const response = await this._api.get('/api/v1/users/login_token?nonce=' + nonce + '&hmac=' + signature, this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.token || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.token;
+ }
+
+ async getMyself() {
+ const response = await this._api.get('/api/v1/users/me', this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.user || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return (this._user = json.user);
+ }
+
+ async acceptOffer(id, version, paymentMethodInstructionId, paymentMethodInstructionVersion, value) {
+ const response = await this._api.post(
+ '/api/v1/contracts',
+ Object.assign({}, this._getHeaders(), {
+ body: {
+ contract: {
+ offer_id: id,
+ offer_version: version,
+ payment_method_instruction_id: paymentMethodInstructionId,
+ payment_method_instruction_version: paymentMethodInstructionVersion,
+ comment: 'I accept your offer',
+ value,
+ },
+ },
+ }),
+ );
+
+ const json = response.body;
+ if (!json || !json.contract || json.status === 'error') {
+ if (json && json.validation_errors) throw new Error(this.validationErrorsToReadable(json.validation_errors));
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.contract;
+ }
+
+ validationErrorsToReadable(errorz) {
+ const ret = [];
+ for (const er of Object.keys(errorz)) {
+ ret.push(errorz[er].join('; '));
+ }
+
+ return ret.join('\n');
+ }
+
+ async getContract(id) {
+ const response = await this._api.get('/api/v1/contracts/' + id, this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.contract || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.contract;
+ }
+
+ verifyEscrowAddress(encryptedSeed, encryptPassword, index, address, witnessScript) {
+ // TODO
+ // @see https://gitlab.com/hodlhodl-public/hodl-client-js
+ return true;
+ }
+
+ /**
+ * This method is used to confirm that client-side validation of escrow data was successful.
+ * This method should be called immediately after escrow address appeared in Getting contract response and this escrow address has been verified locally by the client.
+ *
+ * @param id
+ * @returns {Promise<{}>}
+ */
+ async markContractAsConfirmed(id) {
+ const response = await this._api.post('/api/v1/contracts/' + id + '/confirm', this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.contract || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.contract;
+ }
+
+ /**
+ * Buyer (and only buyer) should call this method when fiat payment was made.
+ * This method could be called only if contract’s status is "in_progress".
+ *
+ * @param id
+ * @returns {Promise<{}>}
+ */
+ async markContractAsPaid(id) {
+ const response = await this._api.post('/api/v1/contracts/' + id + '/mark_as_paid', this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.contract || json.status === 'error') {
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.contract;
+ }
+
+ async cancelContract(id) {
+ const response = await this._api.post('/api/v1/contracts/' + id + '/cancel', this._getHeaders());
+
+ const json = response.body;
+ if (!json || !json.contract || json.status === 'error') {
+ if (json && json.validation_errors) throw new Error(this.validationErrorsToReadable(json.validation_errors));
+ throw new Error('API failure: ' + JSON.stringify(response));
+ }
+
+ return json.contract;
+ }
}
diff --git a/img/hodlhodl-default-avatar.png b/img/hodlhodl-default-avatar.png
new file mode 100644
index 000000000..590a2d5ec
Binary files /dev/null and b/img/hodlhodl-default-avatar.png differ
diff --git a/package-lock.json b/package-lock.json
index fe8672e11..1dd3cd0be 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2644,7 +2644,6 @@
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
- "dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -2825,7 +2824,6 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
@@ -2857,8 +2855,26 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ },
+ "assign": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/assign/-/assign-0.1.7.tgz",
+ "integrity": "sha1-5jv+Ooh7hjCRPCdmPkzJv/Hd0l8=",
+ "requires": {
+ "fusing": "0.4.x"
+ },
+ "dependencies": {
+ "fusing": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/fusing/-/fusing-0.4.0.tgz",
+ "integrity": "sha1-yZBo9Uyj4R3AEYkCFSq/Nnq6Sk0=",
+ "requires": {
+ "emits": "1.0.x",
+ "predefine": "0.1.x"
+ }
+ }
+ }
},
"assign-symbols": {
"version": "1.0.0",
@@ -2893,8 +2909,7 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"atob": {
"version": "2.1.2",
@@ -2919,14 +2934,12 @@
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
- "dev": true
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
- "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
- "dev": true
+ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
},
"babel-cli": {
"version": "6.26.0",
@@ -3727,6 +3740,14 @@
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
+ "back": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/back/-/back-1.0.2.tgz",
+ "integrity": "sha1-qT9ebOaXKZhNWQGiuxbjsBpNY2k=",
+ "requires": {
+ "xtend": "^4.0.0"
+ }
+ },
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -3819,7 +3840,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@@ -4592,16 +4612,53 @@
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz",
"integrity": "sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg=="
},
+ "colornames": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/colornames/-/colornames-0.0.2.tgz",
+ "integrity": "sha1-2BH9bIT1kClJmorEQ2ICk1uSvjE="
+ },
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
+ "colorspace": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.0.1.tgz",
+ "integrity": "sha1-yZx5btMRKLmHalLh7l7gOkpxl0k=",
+ "requires": {
+ "color": "0.8.x",
+ "text-hex": "0.0.x"
+ },
+ "dependencies": {
+ "color": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/color/-/color-0.8.0.tgz",
+ "integrity": "sha1-iQwHw/1OZJU3Y4kRz2keVFi2/KU=",
+ "requires": {
+ "color-convert": "^0.5.0",
+ "color-string": "^0.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
+ "integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
+ },
+ "color-string": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz",
+ "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=",
+ "requires": {
+ "color-name": "^1.0.0"
+ }
+ }
+ }
+ },
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -4951,7 +5008,6 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@@ -5104,8 +5160,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"denodeify": {
"version": "1.2.1",
@@ -5326,6 +5381,16 @@
}
}
},
+ "diagnostics": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.0.1.tgz",
+ "integrity": "sha1-rM2wgMgrsl0N1zQwqeaof7tDFUE=",
+ "requires": {
+ "colorspace": "1.0.x",
+ "enabled": "1.0.x",
+ "kuler": "0.0.x"
+ }
+ },
"didyoumean": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.1.tgz",
@@ -5413,7 +5478,6 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "dev": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@@ -5466,11 +5530,24 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
+ "emits": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/emits/-/emits-1.0.2.tgz",
+ "integrity": "sha1-2yDsZmgyUHHDE0QeMM/ipp6nOFk="
+ },
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
+ "enabled": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
+ "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=",
+ "requires": {
+ "env-variable": "0.0.x"
+ }
+ },
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -5497,6 +5574,11 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
},
+ "env-variable": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz",
+ "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg=="
+ },
"envinfo": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz",
@@ -6494,8 +6576,7 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "3.0.2",
@@ -6516,6 +6597,11 @@
}
}
},
+ "extendible": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz",
+ "integrity": "sha1-4qN+2HEp+0+VM+io11BiMKU5yQU="
+ },
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -6585,11 +6671,49 @@
}
}
},
+ "extract-github": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/extract-github/-/extract-github-0.0.5.tgz",
+ "integrity": "sha1-9UJTbbjBm5g6O+yduW0u8qX/GoY="
+ },
+ "extract-zip": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
+ "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
+ "requires": {
+ "concat-stream": "^1.6.2",
+ "debug": "^2.6.9",
+ "mkdirp": "^0.5.4",
+ "yauzl": "^2.10.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "requires": {
+ "minimist": "^1.2.5"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "dev": true
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fancy-log": {
"version": "1.3.3",
@@ -6605,8 +6729,7 @@
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
- "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
- "dev": true
+ "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-diff": {
"version": "1.2.0",
@@ -6616,8 +6739,7 @@
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"fast-levenshtein": {
"version": "2.0.6",
@@ -6682,6 +6804,14 @@
}
}
},
+ "fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -6841,14 +6971,12 @@
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
- "dev": true
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@@ -7472,6 +7600,14 @@
"resolved": "https://registry.npmjs.org/funpermaproxy/-/funpermaproxy-1.0.1.tgz",
"integrity": "sha512-9pEzs5vnNtR7ZGihly98w/mQ7blsvl68Wj30ZCDAXy7qDN4CWLLjdfjtH/P2m6whsnaJkw15hysCNHMXue+wdA=="
},
+ "fusing": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/fusing/-/fusing-0.2.3.tgz",
+ "integrity": "sha1-0O76+YXSuv3tRK+LGFMW9uQp4ds=",
+ "requires": {
+ "predefine": "0.1.x"
+ }
+ },
"fwd-stream": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fwd-stream/-/fwd-stream-1.0.4.tgz",
@@ -7549,11 +7685,27 @@
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
+ "githulk": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/githulk/-/githulk-0.0.7.tgz",
+ "integrity": "sha1-2Wyinw7EMRfFOOUh1mNWbqhLTv8=",
+ "requires": {
+ "debug": "0.7.x",
+ "extract-github": "0.0.x",
+ "mana": "0.1.x"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz",
+ "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk="
+ }
+ }
+ },
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -7606,14 +7758,12 @@
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "dev": true
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
- "dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
@@ -7631,7 +7781,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
- "dev": true,
"requires": {
"ansi-regex": "^2.0.0"
},
@@ -7639,8 +7788,7 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
}
}
},
@@ -7767,7 +7915,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -8217,8 +8364,7 @@
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
- "dev": true
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"istanbul-lib-coverage": {
"version": "2.0.5",
@@ -8974,8 +9120,7 @@
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"jsc-android": {
"version": "245459.0.0",
@@ -9046,14 +9191,12 @@
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
- "dev": true
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stable-stringify": {
"version": "1.0.1",
@@ -9072,8 +9215,7 @@
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
- "dev": true
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json5": {
"version": "2.1.3",
@@ -9100,7 +9242,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -9134,8 +9275,15 @@
"kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
- "dev": true
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
+ },
+ "kuler": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-0.0.0.tgz",
+ "integrity": "sha1-tmu0a5NOVQ9Z2BiEjgq7pPf1VTw=",
+ "requires": {
+ "colornames": "0.0.2"
+ }
},
"lcid": {
"version": "2.0.0",
@@ -9348,6 +9496,30 @@
"type-check": "~0.3.2"
}
},
+ "licenses": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/licenses/-/licenses-0.0.20.tgz",
+ "integrity": "sha1-8YpXsmp46vKKhz4qN4oz6B9Z0TY=",
+ "requires": {
+ "async": "0.6.x",
+ "debug": "0.8.x",
+ "fusing": "0.2.x",
+ "githulk": "0.0.x",
+ "npm-registry": "0.1.x"
+ },
+ "dependencies": {
+ "async": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.6.2.tgz",
+ "integrity": "sha1-Qf0DijgSwKi8GELs8IumPrA5K+8="
+ },
+ "debug": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-0.8.1.tgz",
+ "integrity": "sha1-IP9NJvXkIstoobrLu2EDmtjBwTA="
+ }
+ }
+ },
"load-json-file": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
@@ -9477,6 +9649,41 @@
"tmpl": "1.0.x"
}
},
+ "mana": {
+ "version": "0.1.41",
+ "resolved": "https://registry.npmjs.org/mana/-/mana-0.1.41.tgz",
+ "integrity": "sha1-fLE/cyGGaGVCKWNcT8Wxfib5O30=",
+ "requires": {
+ "assign": ">=0.1.7",
+ "back": "1.0.x",
+ "diagnostics": "1.0.x",
+ "eventemitter3": "1.2.x",
+ "fusing": "1.0.x",
+ "millisecond": "0.1.x",
+ "request": "2.x.x"
+ },
+ "dependencies": {
+ "emits": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emits/-/emits-3.0.0.tgz",
+ "integrity": "sha1-MnUrupXhcHshlWI4Srm7ix/WL3A="
+ },
+ "eventemitter3": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz",
+ "integrity": "sha1-HIaZHYFq0eUEdQ5zh0Ik7PO+xQg="
+ },
+ "fusing": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fusing/-/fusing-1.0.0.tgz",
+ "integrity": "sha1-VQwV12r5Jld4qgUezkTUAAoJjUU=",
+ "requires": {
+ "emits": "3.0.x",
+ "predefine": "0.1.x"
+ }
+ }
+ }
+ },
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@@ -10270,6 +10477,11 @@
"to-regex": "^3.0.2"
}
},
+ "millisecond": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/millisecond/-/millisecond-0.1.2.tgz",
+ "integrity": "sha1-bMWtOGJByrjniv+WT4cCjuyS2sU="
+ },
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -10532,6 +10744,30 @@
"remove-trailing-separator": "^1.0.1"
}
},
+ "npm-registry": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/npm-registry/-/npm-registry-0.1.13.tgz",
+ "integrity": "sha1-nl2LL9/Bq1mQ1H99674jHXmp6CI=",
+ "requires": {
+ "debug": "0.8.x",
+ "extract-github": "0.0.x",
+ "licenses": "0.0.x",
+ "mana": "0.1.x",
+ "semver": "2.2.x"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-0.8.1.tgz",
+ "integrity": "sha1-IP9NJvXkIstoobrLu2EDmtjBwTA="
+ },
+ "semver": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-2.2.1.tgz",
+ "integrity": "sha1-eUEYKz/8xYC/8cF5QqzfeVHA0hM="
+ }
+ }
+ },
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -10567,8 +10803,7 @@
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "dev": true
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"ob1": {
"version": "0.56.4",
@@ -11033,11 +11268,15 @@
"sha.js": "^2.4.8"
}
},
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+ },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
- "dev": true
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"picomatch": {
"version": "2.2.2",
@@ -11194,6 +11433,14 @@
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
+ "predefine": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/predefine/-/predefine-0.1.2.tgz",
+ "integrity": "sha1-KqkrRJa8H4VU5DpF92v75Q0z038=",
+ "requires": {
+ "extendible": "0.1.x"
+ }
+ },
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -11276,7 +11523,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz",
"integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==",
- "dev": true,
"requires": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.4"
@@ -11320,8 +11566,7 @@
"psl": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
- "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
- "dev": true
+ "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
},
"pump": {
"version": "3.0.0",
@@ -11335,8 +11580,7 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"pushdata-bitcoin": {
"version": "1.0.1",
@@ -12182,12 +12426,20 @@
"integrity": "sha512-es/c1yPRc5aQXNjKuNr0nCgYvuD126bGDRAhq5OaKpnccWHGQorctqxKYRKyNjloOM/NGc97C7DDNnfF1cCfJw=="
},
"react-native-webview": {
- "version": "6.11.1",
- "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-6.11.1.tgz",
- "integrity": "sha512-0OaNCEzdyywJZ70y6Z4fmmuAd2AHYq2AEByIr15z3YetpMXhm9lXUe2V/8BXQIZXeC9FJosj2DAKu48lpZ0nEg==",
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-9.0.2.tgz",
+ "integrity": "sha512-HnQ0+8jt3556QocsYTtmG3Y8eAG88GzmBodDT0PTz4TuLzPJukubj41OFkP7hytZlSdXIxyoV9A06GruyxXeLQ==",
"requires": {
- "escape-string-regexp": "1.0.5",
- "invariant": "2.2.4"
+ "escape-string-regexp": "2.0.0",
+ "invariant": "2.2.4",
+ "rnpm-plugin-windows": "^0.5.1-0"
+ },
+ "dependencies": {
+ "escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
+ }
}
},
"react-refresh": {
@@ -12477,7 +12729,6 @@
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
- "dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@@ -12504,8 +12755,7 @@
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
- "dev": true
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
}
}
},
@@ -12643,6 +12893,63 @@
}
}
},
+ "rnpm-plugin-windows": {
+ "version": "0.5.1-0",
+ "resolved": "https://registry.npmjs.org/rnpm-plugin-windows/-/rnpm-plugin-windows-0.5.1-0.tgz",
+ "integrity": "sha512-0EX2shP1OI18MylpVHmZRhDX5GSdvHDgSQoFDZx/Ir73dt3dPVtz7iNviiz3vPa8/8HgTOog3Xzn/gXxfPRrnw==",
+ "requires": {
+ "chalk": "^1.1.3",
+ "extract-zip": "^1.6.7",
+ "fs-extra": "^7.0.1",
+ "npm-registry": "^0.1.13",
+ "prompts": "^2.3.0",
+ "request": "^2.88.0",
+ "semver": "^6.1.1",
+ "valid-url": "^1.0.9"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ }
+ }
+ },
"rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@@ -12952,8 +13259,7 @@
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
- "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
- "dev": true
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
"sjcl": {
"version": "1.0.8",
@@ -13178,7 +13484,6 @@
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -13558,6 +13863,11 @@
}
}
},
+ "text-hex": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-0.0.0.tgz",
+ "integrity": "sha1-V4+8haapJjbkLdF7QdAhjM6esrM="
+ },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -13694,7 +14004,6 @@
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
"requires": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
@@ -13741,7 +14050,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -13749,8 +14057,7 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"type-check": {
"version": "0.3.2",
@@ -13905,7 +14212,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
- "dev": true,
"requires": {
"punycode": "^2.1.0"
}
@@ -14029,6 +14335,11 @@
"user-home": "^1.1.1"
}
},
+ "valid-url": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz",
+ "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA="
+ },
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -14055,7 +14366,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
@@ -14358,6 +14668,15 @@
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
+ },
+ "yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
+ "requires": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index b475f48f5..4eebb1d6a 100644
--- a/package.json
+++ b/package.json
@@ -137,7 +137,7 @@
"react-native-tooltip": "git+https://github.com/marcosrdz/react-native-tooltip.git",
"react-native-vector-icons": "6.6.0",
"react-native-watch-connectivity": "0.4.2",
- "react-native-webview": "6.11.1",
+ "react-native-webview": "9.0.2",
"react-test-render": "1.1.2",
"readable-stream": "3.6.0",
"rn-nodeify": "10.2.0",
diff --git a/prompt.js b/prompt.js
index 366962bb7..7e8c41312 100644
--- a/prompt.js
+++ b/prompt.js
@@ -1,6 +1,12 @@
+import { Platform } from 'react-native';
import prompt from 'react-native-prompt-android';
module.exports = (title, text, isCancelable = true, type = 'secure-text') => {
+ if (Platform.OS === 'ios' && type === 'numeric') {
+ // `react-native-prompt-android` on ios does not support numeric input
+ type = 'plain-text';
+ }
+
return new Promise((resolve, reject) => {
const buttons = isCancelable
? [
diff --git a/screen/lnd/browser.js b/screen/lnd/browser.js
index 3c95c8c6f..ea4b30cf2 100644
--- a/screen/lnd/browser.js
+++ b/screen/lnd/browser.js
@@ -249,6 +249,7 @@ const styles = StyleSheet.create({
flex: 1,
marginLeft: 4,
minHeight: 33,
+ color: '#81868e',
},
safeURLHome: {
alignContent: 'flex-end',
@@ -455,6 +456,7 @@ export default class Browser extends Component {
onChangeText={text => this.setState({ stateURL: text })}
value={this.state.stateURL}
numberOfLines={1}
+ placeholderTextColor="#81868e"
style={styles.safeURLText}
editable
onSubmitEditing={() => {
@@ -486,11 +488,7 @@ export default class Browser extends Component {
{
- const reloadUrl = this.state.url;
- this.setState({ url: 'about:blank' });
- processedInvoices = {};
- setTimeout(() => this.setState({ url: reloadUrl }), 500);
- // this.webview.reload();
+ this.webview.reload();
}}
>
{!this.state.pageIsLoading ? (
diff --git a/screen/wallets/hodlHodl.js b/screen/wallets/hodlHodl.js
index b297ddd63..b202e8192 100644
--- a/screen/wallets/hodlHodl.js
+++ b/screen/wallets/hodlHodl.js
@@ -1,321 +1,51 @@
/* global alert */
import React, { Component } from 'react';
import {
- Linking,
- StyleSheet,
- View,
- Text,
+ ActivityIndicator,
FlatList,
- TouchableHighlight,
- TouchableOpacity,
+ Image,
Keyboard,
KeyboardAvoidingView,
+ Linking,
Platform,
- Image,
+ RefreshControl,
+ SectionList,
+ StyleSheet,
+ Text,
TextInput,
- ScrollView,
+ TouchableHighlight,
+ TouchableOpacity,
+ View,
} from 'react-native';
-import { BlueNavigationStyle, BlueLoading, BlueCard, SafeBlueArea } from '../../BlueComponents';
+import { BlueButtonLink, BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
import PropTypes from 'prop-types';
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import Modal from 'react-native-modal';
import { Icon } from 'react-native-elements';
+import { AppStorage } from '../../class';
+import NavigationService from '../../NavigationService';
+
+const BlueApp: AppStorage = require('../../BlueApp');
const A = require('../../analytics');
const CURRENCY_CODE_ANY = '_any';
const METHOD_ANY = '_any';
-const styles = StyleSheet.create({
- header: {
- alignItems: 'center',
- flex: 1,
- },
- headerRow: {
- flexDirection: 'row',
- },
- poweredBy: {
- position: 'absolute',
- top: -10,
- left: 0,
- fontSize: 10,
- color: '#0c2550',
- },
- title: {
- fontWeight: 'bold',
- fontSize: 34,
- color: '#0c2550',
- },
- chooseSide: {
- backgroundColor: '#EEF0F4',
- borderRadius: 20,
- width: 100,
- height: 35,
- top: 3,
- paddingLeft: 2,
- paddingBottom: 6,
- paddingTop: 6,
- paddingRight: 0,
- justifyContent: 'center',
- alignItems: 'center',
- flex: 0.65,
- flexDirection: 'row',
- },
- chooseSideText: {
- fontSize: 17,
- fontWeight: '600',
- color: '#9AA0AA',
- },
- chooseSideIcon: {
- paddingLeft: 0,
- paddingRight: 0,
- },
- filter: {
- backgroundColor: '#EEF0F4',
- borderRadius: 20,
- height: 44,
- justifyContent: 'center',
- alignItems: 'center',
- marginTop: 15,
- },
- filterRow: {
- width: '100%',
- alignItems: 'center',
- flex: 1,
- flexDirection: 'row',
- },
- filterLocation: {
- top: 0,
- left: 5,
- color: '#0c2550',
- fontSize: 20,
- fontWeight: '500',
- },
- filterOpenTouch: {
- backgroundColor: '#CCDDF9',
- borderRadius: 20,
- width: 110,
- flex: 1,
- flexDirection: 'row',
- height: 36,
- paddingLeft: 8,
- justifyContent: 'center',
- alignItems: 'center',
- right: 4,
- position: 'absolute',
- },
- filterOpenText: {
- color: '#2f5fb3',
- fontSize: 18,
- fontWeight: '600',
- },
-
- modal: {
- justifyContent: 'flex-end',
- margin: 0,
- },
- modalContent: {
- backgroundColor: '#FFFFFF',
- padding: 22,
- justifyContent: 'center',
- alignItems: 'center',
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- borderColor: 'rgba(0, 0, 0, 0.1)',
- minHeight: 400,
- height: 400,
- },
- modalContentShort: {
- backgroundColor: '#FFFFFF',
- padding: 22,
- justifyContent: 'center',
- alignItems: 'center',
- borderTopLeftRadius: 16,
- borderTopRightRadius: 16,
- borderColor: 'rgba(0, 0, 0, 0.1)',
- minHeight: 200,
- height: 200,
- },
- modalSearch: {
- flexDirection: 'row',
- borderColor: '#EEF0F4',
- borderBottomColor: '#EEF0F4',
- borderWidth: 1.0,
- borderBottomWidth: 0.5,
- backgroundColor: '#EEF0F4',
- minHeight: 48,
- height: 48,
- marginHorizontal: 20,
- alignItems: 'center',
- marginVertical: 8,
- borderRadius: 26,
- width: '100%',
- },
- modalSearchText: {
- flex: 1,
- marginHorizontal: 8,
- minHeight: 33,
- paddingLeft: 6,
- paddingRight: 6,
- },
- modalSearchText2: {
- flex: 1,
- fontSize: 17,
- marginHorizontal: 8,
- minHeight: 33,
- paddingLeft: 6,
- paddingRight: 6,
- },
- modalSearchIcon: {
- left: -10,
- },
- modalList: {
- width: '100%',
- },
-
- itemRoot: {
- backgroundColor: 'white',
- },
- item: {
- backgroundColor: 'white',
- flex: 1,
- flexDirection: 'row',
- paddingTop: 20,
- paddingBottom: 20,
- },
- itemText: {
- fontSize: 20,
- color: '#0c2550',
- },
- itemTextBold: {
- fontWeight: 'bold',
- },
- itemTextNormal: {
- fontWeight: 'normal',
- },
- itemRow: {
- paddingLeft: 10,
- flex: 1,
- flexDirection: 'row',
- },
- itemValue: {
- color: '#9AA0AA',
- right: 0,
- position: 'absolute',
- },
- itemValueText1: {
- fontSize: 18,
- color: '#9AA0AA',
- },
- itemValueText2: {
- fontSize: 20,
- color: '#9AA0AA',
- },
-
- offers: {
- paddingHorizontal: 24,
- },
- offersList: {
- marginTop: 24,
- flex: 1,
- },
- offersListContent: {
- flex: 1,
- justifyContent: 'center',
- paddingHorizontal: 0,
- },
- noOffers: {
- textAlign: 'center',
- color: '#9AA0AA',
- paddingHorizontal: 16,
- },
-
- offer: {
- backgroundColor: 'white',
- paddingTop: 16,
- paddingBottom: 16,
- },
- offerRow: {
- backgroundColor: 'white',
- flex: 1,
- flexDirection: 'row',
- },
- offerAvatar: {
- width: 40,
- height: 40,
- borderRadius: 40,
- },
- offerNickname: {
- color: '#0c2550',
- fontSize: 18,
- fontWeight: '600',
- },
- offerRating: {
- color: '#9AA0AA',
- },
- offerText: {
- color: '#9AA0AA',
- paddingTop: 10,
- },
- offerFooter: {
- flex: 1,
- flexDirection: 'row',
- paddingTop: 10,
- paddingBottom: 10,
- alignItems: 'center',
- },
- offerPrice: {
- backgroundColor: '#EEF0F4',
- borderRadius: 20,
- paddingLeft: 8,
- paddingRight: 8,
- height: 26,
- justifyContent: 'center',
- alignItems: 'center',
- },
- offerPriceText: {
- fontWeight: '600',
- fontSize: 14,
- color: '#9AA0AA',
- },
- offerAmount: {
- color: '#9AA0AA',
- fontSize: 12,
- paddingLeft: 10,
- },
-
- // allOffersText: {
- // fontSize: 12,
- // color: '#9AA0AA',
- // position: 'absolute',
- // top: 0,
- // left: 15,
- // },
-
- paddingLeft: {
- paddingLeft: 10,
- },
- separator: {
- height: 0.5,
- width: '100%',
- backgroundColor: '#C8C8C8',
- },
-});
-
-const HodlApi = new HodlHodlApi();
+const HodlHodlListSections = { OFFERS: 'OFFERS' };
export default class HodlHodl extends Component {
- static navigationOptions = ({ navigation }) => ({
- ...BlueNavigationStyle(),
- title: '',
- });
-
constructor(props) {
super(props);
- /** @type {AbstractWallet} */
- const wallet = props.route.params.wallet;
+ props.navigation.setParams({
+ handleLoginPress: this.handleLoginPress,
+ displayLoginButton: true,
+ handleMyContractsPress: this.handleMyContractsPress,
+ });
this.state = {
+ HodlApi: false,
isLoading: true,
+ isRenderOfferVisible: false,
isChooseSideModalVisible: false,
isChooseCountryModalVisible: false,
isFiltersModalVisible: false,
@@ -324,7 +54,6 @@ export default class HodlHodl extends Component {
currency: false, // means no currency filtering is enabled by default
method: false, // means no payment method filtering is enabled by default
side: HodlHodlApi.FILTERS_SIDE_VALUE_SELL, // means 'show me sell offers as Im buying'
- wallet,
offers: [],
countries: [], // list of hodlhodl supported countries. filled later via api
currencies: [], // list of hodlhodl supported currencies. filled later via api
@@ -334,6 +63,21 @@ export default class HodlHodl extends Component {
};
}
+ handleLoginPress = () => {
+ const handleLoginCallback = (hodlApiKey, hodlHodlSignatureKey) => {
+ BlueApp.setHodlHodlApiKey(hodlApiKey, hodlHodlSignatureKey);
+ const displayLoginButton = !hodlApiKey;
+ const HodlApi = new HodlHodlApi(hodlApiKey);
+ this.setState({ HodlApi, hodlApiKey });
+ this.props.navigation.setParams({ displayLoginButton });
+ };
+ NavigationService.navigate('HodlHodlRoot', { params: { cb: handleLoginCallback }, screen: 'HodlHodlLogin' });
+ };
+
+ handleMyContractsPress = () => {
+ NavigationService.navigate('HodlHodlMyContracts');
+ };
+
/**
* Fetch offers and set those offers into state
*
@@ -344,13 +88,16 @@ export default class HodlHodl extends Component {
[HodlHodlApi.PAGINATION_LIMIT]: 200,
};
const filters = {
- [HodlHodlApi.FILTERS_COUNTRY]: this.state.country,
[HodlHodlApi.FILTERS_SIDE]: this.state.side,
[HodlHodlApi.FILTERS_ASSET_CODE]: HodlHodlApi.FILTERS_ASSET_CODE_VALUE_BTC,
[HodlHodlApi.FILTERS_INCLUDE_GLOBAL]: this.state.country === HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL,
[HodlHodlApi.FILTERS_ONLY_WORKING_NOW]: true, // so there wont be any offers which user tries to open website says 'offer not found'
};
+ if (this.state.country !== HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL) {
+ filters[HodlHodlApi.FILTERS_COUNTRY] = this.state.country;
+ }
+
if (this.state.currency) {
filters[HodlHodlApi.FILTERS_CURRENCY_CODE] = this.state.currency;
}
@@ -363,7 +110,7 @@ export default class HodlHodl extends Component {
[HodlHodlApi.SORT_BY]: HodlHodlApi.SORT_BY_VALUE_PRICE,
[HodlHodlApi.SORT_DIRECTION]: HodlHodlApi.SORT_DIRECTION_VALUE_ASC,
};
- const offers = await HodlApi.getOffers(pagination, filters, sort);
+ const offers = await this.state.HodlApi.getOffers(pagination, filters, sort);
this.setState({
offers,
@@ -371,7 +118,7 @@ export default class HodlHodl extends Component {
}
async fetchMyCountry() {
- const myCountryCode = await HodlApi.getMyCountryCode();
+ const myCountryCode = await this.state.HodlApi.getMyCountryCode();
this.setState({
myCountryCode,
country: myCountryCode, // we start with orders from current country
@@ -384,7 +131,7 @@ export default class HodlHodl extends Component {
* @returns {Promise}
**/
async fetchListOfCountries() {
- const countries = await HodlApi.getCountries();
+ const countries = await this.state.HodlApi.getCountries();
this.setState({ countries });
}
@@ -394,7 +141,7 @@ export default class HodlHodl extends Component {
* @returns {Promise}
**/
async fetchListOfCurrencies() {
- const currencies = await HodlApi.getCurrencies();
+ const currencies = await this.state.HodlApi.getCurrencies();
this.setState({ currencies });
}
@@ -404,7 +151,7 @@ export default class HodlHodl extends Component {
* @returns {Promise}
**/
async fetchListOfMethods() {
- const methods = await HodlApi.getPaymentMethods(this.state.country || HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL);
+ const methods = await this.state.HodlApi.getPaymentMethods(this.state.country || HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL);
this.setState({ methods });
}
@@ -412,6 +159,13 @@ export default class HodlHodl extends Component {
console.log('wallets/hodlHodl - componentDidMount');
A(A.ENUM.NAVIGATED_TO_WALLETS_HODLHODL);
+ const hodlApiKey = await BlueApp.getHodlHodlApiKey();
+ const displayLoginButton = !hodlApiKey;
+
+ const HodlApi = new HodlHodlApi(hodlApiKey);
+ this.setState({ HodlApi, hodlApiKey });
+ this.props.navigation.setParams({ displayLoginButton });
+
try {
await this.fetchMyCountry();
await this.fetchOffers();
@@ -429,22 +183,15 @@ export default class HodlHodl extends Component {
this.fetchListOfMethods();
}
- async _refresh() {
- this.setState(
- {
- isLoading: true,
- },
- async () => {
- await this.fetchOffers();
- this.setState({
- isLoading: false,
- });
- },
- );
- }
-
_onPress(item) {
- Linking.openURL('https://hodlhodl.com/offers/' + item.id);
+ const offers = this.state.offers.filter(value => value.id === item.id);
+ if (offers && offers[0]) {
+ NavigationService.navigate('HodlHodlViewOffer', {
+ offerToDisplay: offers[0],
+ });
+ } else {
+ Linking.openURL('https://hodlhodl.com/offers/' + item.id);
+ }
}
_onCountryPress(item) {
@@ -565,7 +312,7 @@ export default class HodlHodl extends Component {
return (
{
Keyboard.dismiss();
this.setState({ isChooseSideModalVisible: false });
@@ -575,8 +322,8 @@ export default class HodlHodl extends Component {
}
+ style={styles.modalFlatList}
+ ItemSeparatorComponent={() => }
data={[
{ code: HodlHodlApi.FILTERS_SIDE_VALUE_SELL, name: "I'm buying bitcoin" },
{ code: HodlHodlApi.FILTERS_SIDE_VALUE_BUY, name: "I'm selling bitcoin" },
@@ -588,10 +335,8 @@ export default class HodlHodl extends Component {
onHideUnderlay={separators.unhighlight}
onPress={() => this._onSidePress(item)}
>
-
-
- {item.name}
-
+
+ {item.name}
)}
@@ -606,7 +351,7 @@ export default class HodlHodl extends Component {
return (
{
if (this.state.openNextModal) {
const openNextModal = this.state.openNextModal;
@@ -625,8 +370,8 @@ export default class HodlHodl extends Component {
}
+ style={styles.modalFlatList}
+ ItemSeparatorComponent={() => }
data={[
{ code: 'currency', native_name: 'Currency' },
{ code: 'method', native_name: 'Payment method' },
@@ -641,16 +386,19 @@ export default class HodlHodl extends Component {
if (item.code === 'method') this.setState({ isFiltersModalVisible: false, openNextModal: 'isChooseMethodVisible' });
}}
>
-
-
-
- {item.native_name}
-
+
+
+
+ {item.native_name}
+
{item.code === 'currency' && (
- {this.state.currency ? this.state.currency + ' ❯' : 'Detail ❯'}
+
+ {' '}
+ {this.state.currency ? this.state.currency + ' ❯' : 'Detail ❯'}{' '}
+
)}
{item.code === 'method' && (
-
+
{' '}
{this.state.method ? this.getMethodName(this.state.method) + ' ❯' : 'Detail ❯'}
@@ -707,7 +455,7 @@ export default class HodlHodl extends Component {
return (
{
Keyboard.dismiss();
this.setState({ isChooseCountryModalVisible: false });
@@ -715,20 +463,20 @@ export default class HodlHodl extends Component {
>
-
+
this.setState({ countrySearchInput: text })}
placeholder="Search.."
placeholderTextColor="#9AA0AA"
value={this.state.countrySearchInput || ''}
numberOfLines={1}
- style={styles.modalSearchText2}
+ style={styles.searchTextInput}
/>
-
+
}
+ style={styles.modalFlatList}
+ ItemSeparatorComponent={() => }
data={countries2render}
keyExtractor={(item, index) => item.code}
renderItem={({ item, index, separators }) => (
@@ -737,10 +485,10 @@ export default class HodlHodl extends Component {
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
>
-
-
-
-
+
+
+
+
{item.native_name}
@@ -783,7 +531,7 @@ export default class HodlHodl extends Component {
return (
{
Keyboard.dismiss();
this.setState({ isChooseCurrencyVisible: false });
@@ -791,20 +539,20 @@ export default class HodlHodl extends Component {
>
-
+
this.setState({ currencySearchInput: text })}
placeholder="Search.."
placeholderTextColor="#9AA0AA"
value={this.state.currencySearchInput || ''}
numberOfLines={1}
- style={styles.modalSearchText}
+ style={styles.curSearchInput}
/>
-
+
}
+ style={styles.modalFlatList}
+ ItemSeparatorComponent={() => }
data={currencies2render}
keyExtractor={(item, index) => item.code}
renderItem={({ item, index, separators }) => (
@@ -813,17 +561,10 @@ export default class HodlHodl extends Component {
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
>
-
-
-
-
+
+
+
+
{item.name} {item.code !== CURRENCY_CODE_ANY && '[' + item.code + ']'}
@@ -866,7 +607,7 @@ export default class HodlHodl extends Component {
return (
{
Keyboard.dismiss();
this.setState({ isChooseMethodVisible: false });
@@ -874,20 +615,20 @@ export default class HodlHodl extends Component {
>
-
+
this.setState({ methodSearchInput: text })}
placeholder="Search.."
placeholderTextColor="#9AA0AA"
value={this.state.methodSearchInput || ''}
numberOfLines={1}
- style={styles.modalSearchText}
+ style={styles.mthdSearchInput}
/>
-
+
}
+ style={styles.modalFlatList}
+ ItemSeparatorComponent={() => }
data={methods2render}
keyExtractor={(item, index) => item.id}
renderItem={({ item, index, separators }) => (
@@ -896,16 +637,15 @@ export default class HodlHodl extends Component {
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
>
-
-
-
+
+
+
{item.name} {item.id !== METHOD_ANY && '[' + item.type + ']'}
@@ -921,108 +661,154 @@ export default class HodlHodl extends Component {
);
};
- render() {
+ _onRefreshOffers = async () => {
+ this.setState({
+ showShowFlatListRefreshControl: true,
+ });
+
+ try {
+ await this.fetchOffers();
+ } catch (_) {}
+
+ this.setState({
+ showShowFlatListRefreshControl: false,
+ });
+ };
+
+ renderHeader = () => {
return (
-
-
-
- Powered by HodlHodl®
- Local Trader
+
+
+ Powered by HodlHodl®
+
+ Local Trader
{
this.setState({ isChooseSideModalVisible: true });
}}
>
- {this.state.side === HodlHodlApi.FILTERS_SIDE_VALUE_SELL ? 'Buying' : 'Selling'}
-
+ {this.state.side === HodlHodlApi.FILTERS_SIDE_VALUE_SELL ? 'Buying' : 'Selling'}
+
-
-
- {/* All offers */}
-
-
- this.setState({ isChooseCountryModalVisible: true }) /* this.changeCountry() */}>
- {this.getNativeCountryName()}
-
-
+
+
+
+ {this.state.isLoading ? (
+
+ ) : (
+ this.setState({ isChooseCountryModalVisible: true })}>
+ {this.getNativeCountryName()}
+
+ )}
{
this.setState({ isFiltersModalVisible: true });
}}
>
- Filters
+ Filters
-
+
-
- {(this.state.isLoading && ) || (
-
- this._refresh()}
- refreshing={this.state.isLoading}
- style={styles.offersList}
- contentContainerStyle={styles.offersListContent}
- ItemSeparatorComponent={() => }
- data={this.state.offers}
- ListEmptyComponent={() => No offers. Try to change "Near me" to Global offers!}
- renderItem={({ item, index, separators }) => (
- this._onPress(item)}
- onShowUnderlay={separators.highlight}
- onHideUnderlay={separators.unhighlight}
- >
-
-
-
-
-
-
- {item.trader.login}
-
- {item.trader.trades_count > 0 ? Math.round(item.trader.rating * 100) + '%' : 'No rating'}
-
-
-
+
+
+ );
+ };
- {this.getItemText(item)}
-
-
-
- {this.getItemPrice(item)}
-
-
-
- Min/Max: {item.min_amount.replace('.00', '')} - {item.max_amount.replace('.00', '')} {item.currency_code}
-
-
+ renderItem = ({ item, index, separators }) => {
+ return (
+
+ this._onPress(item)}
+ onShowUnderlay={separators.highlight}
+ onHideUnderlay={separators.unhighlight}
+ >
+
+
+
+
+ {item.trader.online_status === 'online' && (
+
+
-
- )}
- />
-
- )}
+ )}
+
+
+
+ {item.trader.strong_hodler && (
+
+ )}
+ {item.trader.login}
+
+
+ {item.trader.trades_count > 0
+ ? Math.round(item.trader.rating * 100) + '% / ' + item.trader.trades_count + ' trades'
+ : 'No rating'}
+
+
+
+ {this.getItemText(item)}
+
+
+
+ {this.getItemPrice(item)}
+
+
+
+ Min/Max: {item.min_amount.replace('.00', '')} - {item.max_amount.replace('.00', '')} {item.currency_code}
+
+
+
+
+
+ );
+ };
+
+ sectionListKeyExtractor = (item, index) => {
+ return `${item}${index}}`;
+ };
+
+ renderSectionFooter = () => {
+ return this.state.offers.length <= 0 ? (
+
+ No offers. Try to change "Near me" to Global offers!
+
+ ) : undefined;
+ };
+
+ render() {
+ return (
+
+ }
+ renderItem={this.renderItem}
+ keyExtractor={this.sectionListKeyExtractor}
+ renderSectionHeader={this.renderHeader}
+ contentInset={{ top: 0, left: 0, bottom: 60, right: 0 }}
+ sections={[{ key: HodlHodlListSections.OFFERS, data: this.state.offers }]}
+ style={styles.offersSectionList}
+ ItemSeparatorComponent={() => }
+ renderSectionFooter={this.renderSectionFooter}
+ />
{this.renderChooseSideModal()}
-
{this.renderChooseContryModal()}
-
{this.renderFiltersModal()}
-
{this.renderChooseCurrencyModal()}
-
{this.renderChooseMethodModal()}
);
@@ -1030,9 +816,212 @@ export default class HodlHodl extends Component {
}
HodlHodl.propTypes = {
- route: PropTypes.shape({
- params: PropTypes.shape({
- wallet: PropTypes.object,
- }),
+ navigation: PropTypes.shape({
+ navigate: PropTypes.func,
+ setParams: PropTypes.func,
}),
};
+
+HodlHodl.navigationOptions = ({ navigation, route }) => ({
+ ...BlueNavigationStyle(navigation, true),
+ title: '',
+ headerLeft: () => {
+ return route.params.displayLoginButton ? (
+
+ ) : (
+
+ );
+ },
+});
+
+const styles = StyleSheet.create({
+ grayDropdownText: {
+ fontSize: 15,
+ fontWeight: '600',
+ color: '#9AA0AA',
+ },
+ modalContent: {
+ backgroundColor: '#FFFFFF',
+ padding: 22,
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ borderColor: 'rgba(0, 0, 0, 0.1)',
+ minHeight: 400,
+ height: 400,
+ },
+ modalContentShort: {
+ backgroundColor: '#FFFFFF',
+ padding: 22,
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ borderColor: 'rgba(0, 0, 0, 0.1)',
+ minHeight: 200,
+ height: 200,
+ },
+ bottomModal: {
+ justifyContent: 'flex-end',
+ margin: 0,
+ },
+ Title: {
+ fontWeight: 'bold',
+ fontSize: 30,
+ color: '#0c2550',
+ },
+ BottomLine: {
+ fontSize: 10,
+ color: '#0c2550',
+ },
+ grayDropdownTextContainer: {
+ backgroundColor: '#EEF0F4',
+ borderRadius: 20,
+ height: 35,
+ top: 3,
+ paddingLeft: 16,
+ paddingRight: 8,
+ paddingVertical: 6,
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexDirection: 'row',
+ },
+ grayTextContainerContainer: {
+ backgroundColor: '#EEF0F4',
+ borderRadius: 20,
+ height: 44,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 15,
+ },
+ grayTextContainer: {
+ width: '100%',
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ },
+ blueText: {
+ color: '#2f5fb3',
+ fontSize: 15,
+ fontWeight: '600',
+ },
+ allOffersText: {
+ fontSize: 12,
+ color: '#9AA0AA',
+ position: 'absolute',
+ top: 0,
+ left: 15,
+ },
+ locationText: {
+ top: 0,
+ left: 5,
+ color: '#0c2550',
+ fontSize: 20,
+ fontWeight: '500',
+ },
+ nicknameText: {
+ color: '#0c2550',
+ fontSize: 18,
+ fontWeight: '600',
+ },
+ blueTextContainer: {
+ backgroundColor: '#CCDDF9',
+ borderRadius: 20,
+ width: 110,
+ flex: 1,
+ flexDirection: 'row',
+ height: 36,
+ paddingLeft: 8,
+ justifyContent: 'center',
+ alignItems: 'center',
+ right: 4,
+ position: 'absolute',
+ },
+ searchInputContainer: {
+ flexDirection: 'row',
+ borderColor: '#EEF0F4',
+ borderBottomColor: '#EEF0F4',
+ borderWidth: 1.0,
+ borderBottomWidth: 0.5,
+ backgroundColor: '#EEF0F4',
+ minHeight: 48,
+ height: 48,
+ marginHorizontal: 20,
+ alignItems: 'center',
+ marginVertical: 8,
+ borderRadius: 26,
+ width: '100%',
+ },
+ circleWhite: {
+ position: 'absolute',
+ bottom: 4,
+ right: 3,
+ backgroundColor: 'white',
+ width: 9,
+ height: 9,
+ borderRadius: 4,
+ },
+ circleGreen: {
+ position: 'absolute',
+ bottom: 1,
+ right: 1,
+ backgroundColor: '#00d327',
+ width: 7,
+ height: 7,
+ borderRadius: 4,
+ },
+ itemPrice: { fontWeight: '600', fontSize: 14, color: '#9AA0AA' },
+ minmax: { color: '#9AA0AA', fontSize: 12, paddingLeft: 10 },
+ noOffersWrapper: { height: '100%', justifyContent: 'center' },
+ noOffersText: { textAlign: 'center', color: '#9AA0AA', paddingHorizontal: 16 },
+ modalFlatList: { width: '100%' },
+ itemSeparator: { height: 0.5, width: '100%', backgroundColor: '#C8C8C8' },
+ itemNameWrapper: { backgroundColor: 'white', flex: 1, flexDirection: 'row', paddingTop: 20, paddingBottom: 20 },
+ itemNameBold: { fontSize: 20, color: '#0c2550', fontWeight: 'bold' },
+ itemNameNormal: { fontSize: 20, color: '#0c2550', fontWeight: 'normal' },
+ whiteBackground: { backgroundColor: 'white' },
+ filterCurrencyText: { fontSize: 16, color: '#9AA0AA' },
+ filteCurrencyTextWrapper: { color: '#9AA0AA', right: 0, position: 'absolute' },
+ currencyNativeName: { fontSize: 20, color: '#0c2550' },
+ currencyWrapper: { paddingLeft: 10, flex: 1, flexDirection: 'row' },
+ methodNameText: { fontSize: 16, color: '#9AA0AA' },
+ searchTextInput: { fontSize: 17, flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 },
+ iconWithOffset: { left: -10 },
+ paddingLeft10: { paddingLeft: 10 },
+ countryNativeNameBold: { fontSize: 20, color: '#0c2550', fontWeight: 'bold' },
+ countryNativeNameNormal: { fontSize: 20, color: '#0c2550', fontWeight: 'normal' },
+ curSearchInput: { flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 },
+ mthdSearchInput: { flex: 1, marginHorizontal: 8, minHeight: 33, paddingLeft: 6, paddingRight: 6 },
+ currencyTextBold: {
+ fontSize: 20,
+ color: '#0c2550',
+ fontWeight: 'bold',
+ },
+ currencyTextNormal: {
+ fontSize: 20,
+ color: '#0c2550',
+ fontWeight: 'normal',
+ },
+ noPaddingLeftOrRight: { paddingLeft: 0, paddingRight: 0 },
+ flexRow: { flexDirection: 'row' },
+ traderRatingText2: { color: '#9AA0AA' },
+ itemPriceWrapper: {
+ backgroundColor: '#EEF0F4',
+ borderRadius: 20,
+ paddingLeft: 8,
+ paddingRight: 8,
+ height: 26,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ itemText: { color: '#9AA0AA', paddingTop: 10 },
+ itemPriceWrapperWrapper: { flex: 1, flexDirection: 'row', paddingTop: 10, paddingBottom: 10, alignItems: 'center' },
+ offersSectionList: { marginTop: 24, flex: 1 },
+ marginHorizontal20: { marginHorizontal: 20 },
+ avatarImage: { width: 40, height: 40, borderRadius: 40 },
+ avatarWrapper: { backgroundColor: 'white', flex: 1, flexDirection: 'row' },
+ avatarWrapperWrapper: { backgroundColor: 'white', paddingTop: 16, paddingBottom: 16 },
+ headerWrapper: { marginHorizontal: 20, marginBottom: 8 },
+ verifiedIcon: { marginTop: 5, marginRight: 5 },
+});
diff --git a/screen/wallets/hodlHodlLogin.js b/screen/wallets/hodlHodlLogin.js
new file mode 100644
index 000000000..adf434478
--- /dev/null
+++ b/screen/wallets/hodlHodlLogin.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react';
+import { WebView } from 'react-native-webview';
+import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
+import PropTypes from 'prop-types';
+
+const url = 'https://accounts.hodlhodl.com/accounts/request_access?attributes=api_key,api_signature_key';
+
+let lastTimeIvebeenHere = 0;
+
+const INJECTED_JAVASCRIPT = `(function() {
+
+ window.postMessage = function (data) {
+ window.ReactNativeWebView && window.ReactNativeWebView.postMessage(data);
+ }
+
+})();`;
+
+export default class HodlHodlLogin extends Component {
+ static navigationOptions = ({ navigation }) => ({
+ ...BlueNavigationStyle(navigation, true),
+ title: 'Login',
+ headerLeft: null,
+ });
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ url: url,
+ };
+ }
+
+ render() {
+ return (
+
+ (this.webview = ref)}
+ source={{ uri: this.state.url }}
+ onMessage={e => {
+ // this is a handler which receives messages sent from within the browser
+
+ if (lastTimeIvebeenHere && +new Date() - lastTimeIvebeenHere < 5000) return;
+ lastTimeIvebeenHere = +new Date();
+ // page can post messages several times, and that can confuse our react navigation, so we have protection
+ // against that
+
+ let json = false;
+ try {
+ json = JSON.parse(e.nativeEvent.data);
+ } catch (_) {}
+
+ if (json && json.allowed && json.data && json.data.api_key) {
+ this.props.route.params.cb(json.data.api_key, json.data.api_signature_key);
+ this.props.navigation.pop();
+ }
+ }}
+ />
+
+ );
+ }
+}
+
+HodlHodlLogin.propTypes = {
+ route: PropTypes.shape({
+ params: PropTypes.shape({
+ cb: PropTypes.func.isRequired,
+ }),
+ }),
+ navigation: PropTypes.shape({
+ getParam: PropTypes.func,
+ navigate: PropTypes.func,
+ pop: PropTypes.func,
+ }),
+};
diff --git a/screen/wallets/hodlHodlMyContracts.js b/screen/wallets/hodlHodlMyContracts.js
new file mode 100644
index 000000000..99b9e2eed
--- /dev/null
+++ b/screen/wallets/hodlHodlMyContracts.js
@@ -0,0 +1,418 @@
+/* global alert */
+import React, { Component } from 'react';
+import {
+ Alert,
+ FlatList,
+ Keyboard,
+ KeyboardAvoidingView,
+ Linking,
+ Platform,
+ StyleSheet,
+ Text,
+ TouchableHighlight,
+ View,
+} from 'react-native';
+import { BlueButton, BlueLoading, BlueNavigationStyle, BlueSpacing10, BlueSpacing20, SafeBlueArea } from '../../BlueComponents';
+import { AppStorage } from '../../class';
+import { HodlHodlApi } from '../../class/hodl-hodl-api';
+import Modal from 'react-native-modal';
+import NavigationService from '../../NavigationService';
+
+const BlueApp: AppStorage = require('../../BlueApp');
+
+export default class HodlHodlMyContracts extends Component {
+ static navigationOptions = ({ navigation }) => ({
+ ...BlueNavigationStyle(navigation, true),
+ title: 'My contracts',
+ headerLeft: null,
+ });
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ contracts: [],
+ isLoading: true,
+ };
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.state.inverval);
+ }
+
+ async componentDidMount() {
+ const hodlApiKey = await BlueApp.getHodlHodlApiKey();
+ const hodlApi = new HodlHodlApi(hodlApiKey);
+ this.setState({ hodlApi: hodlApi, contracts: [] });
+
+ const inverval = setInterval(async () => {
+ await this.refetchContracts();
+ }, 60 * 1000);
+
+ this.setState({ inverval });
+ await this.refetchContracts();
+ }
+
+ render() {
+ if (this.state.isLoading) return ;
+ return (
+
+ {
+ return item.id;
+ }}
+ ListEmptyComponent={() => You dont have any contracts in progress}
+ style={styles.flatList}
+ ItemSeparatorComponent={() => }
+ data={this.state.contracts}
+ renderItem={({ item: contract, index, separators }) => (
+ this._onContractPress(contract)}
+ >
+
+
+
+ {contract.status}
+
+
+
+
+
+
+ {contract.volume_breakdown.goes_to_buyer} {contract.asset_code}
+
+ {contract.your_role === 'buyer' ? 'buying' : 'selling'}
+
+
+
+ {contract.statusText}
+
+
+
+
+ )}
+ />
+ {this.renderContract()}
+
+ );
+ }
+
+ async refetchContracts() {
+ this.setState({
+ isLoading: true,
+ });
+
+ const hodlApi = this.state.hodlApi;
+ const contracts = [];
+ let contractToDisplay = this.state.contractToDisplay;
+
+ const contractIds = await BlueApp.getHodlHodlContracts();
+
+ /*
+ * Initiator sends “Getting contract” request once every 1-3 minutes until contract.escrow.address is not null (thus, waiting for offer’s creator to confirm his payment password in case he uses the website)
+ * Each party verifies the escrow address locally
+ * Each party sends “Confirming contract’s escrow validity” request to the server
+ */
+ for (const id of contractIds) {
+ let contract;
+ try {
+ contract = await hodlApi.getContract(id);
+ } catch (_) {
+ continue;
+ }
+ if (contract.status === 'canceled') continue;
+ if (contract.escrow && contract.escrow.address && hodlApi.verifyEscrowAddress()) {
+ await hodlApi.markContractAsConfirmed(id);
+ contract.isDepositedEnought =
+ contract.escrow.confirmations >= contract.confirmations && +contract.escrow.amount_deposited >= +contract.volume;
+ // technically, we could fetch balance of escrow address ourselved and verify, but we are relying on api here
+
+ contract.statusText = 'Waiting for seller to deposit bitcoins to escrow...';
+ if (contract.isDepositedEnought && contract.status !== 'paid')
+ contract.statusText = 'Bitcoins are in escrow! Please pay seller\nvia agreed payment method';
+ if (contract.status === 'paid') contract.statusText = 'Waiting for seller to release coins from escrow';
+ if (contract.status === 'in_progress' && contract.your_role === 'buyer')
+ contract.statusText = 'Coins are in escrow, please pay seller';
+
+ if (contract.status === 'completed') contract.statusText = 'All done!';
+ }
+
+ contracts.push(contract);
+
+ if (contractToDisplay && contract.id === this.state.contractToDisplay.id) {
+ // refreshing contract that is currently being displayed
+ contractToDisplay = contract;
+ }
+ }
+
+ this.setState({ hodlApi: hodlApi, contracts, contractToDisplay, isLoading: false });
+ }
+
+ _onContractPress(contract) {
+ this.setState({
+ contractToDisplay: contract,
+ isRenderContractVisible: true,
+ });
+ }
+
+ renderContract = () => {
+ if (!this.state.contractToDisplay) return;
+
+ return (
+ {
+ Keyboard.dismiss();
+ this.setState({ isRenderContractVisible: false });
+ }}
+ >
+
+
+
+
+ {this.state.contractToDisplay.volume_breakdown.goes_to_buyer} {this.state.contractToDisplay.asset_code}
+
+
+
+
+ {this.state.contractToDisplay.price} {this.state.contractToDisplay.currency_code}
+
+
+
+
+ To
+
+
+ Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.release_address}`)}
+ >
+ {this.state.contractToDisplay.release_address}
+
+
+
+
+
+ Escrow
+
+
+ Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.escrow.address}`)}
+ >
+ {this.state.contractToDisplay.escrow.address}
+
+
+
+
+
+ {this.isAllowedToMarkContractAsPaid() ? (
+
+ How to pay
+
+
+ {this.state.contractToDisplay.payment_method_instruction.details}
+
+
+
+ ) : (
+
+ )}
+
+
+
+ {this.isAllowedToMarkContractAsPaid() ? (
+
+ this._onMarkContractAsPaid()} />
+
+
+ ) : (
+
+ )}
+
+
+
+ {this.state.contractToDisplay.can_be_canceled && (
+ this._onCancelContract()} style={styles.cancelContractText}>
+ Cancel contract
+
+ )}
+
+ this._onOpenContractOnWebsite()} style={styles.openChatText}>
+ Open chat with counterparty
+
+
+
+
+ );
+ };
+
+ /**
+ * If you are the buyer, DO NOT SEND PAYMENT UNTIL CONTRACT STATUS IS "in_progress".
+ */
+ _onMarkContractAsPaid() {
+ if (!this.state.contractToDisplay) return;
+
+ Alert.alert(
+ 'Are you sure you want to mark this contract as paid?',
+ `Do this only if you sent funds to the seller via agreed payment method`,
+ [
+ {
+ text: 'Yes',
+ onPress: async () => {
+ const hodlApi = this.state.hodlApi;
+ try {
+ await hodlApi.markContractAsPaid(this.state.contractToDisplay.id);
+ this.setState({ isRenderContractVisible: false });
+ await this.refetchContracts();
+ } catch (Error) {
+ alert(Error);
+ }
+ },
+ style: 'default',
+ },
+ {
+ text: 'Cancel',
+ onPress: () => {},
+ style: 'cancel',
+ },
+ ],
+ { cancelable: true },
+ );
+ }
+
+ async _onOpenContractOnWebsite() {
+ if (!this.state.contractToDisplay) return;
+ const hodlApi = this.state.hodlApi;
+ const sigKey = await BlueApp.getHodlHodlSignatureKey();
+ if (!sigKey) {
+ alert('Error: signature key not set'); // should never happen
+ return;
+ }
+
+ const autologinKey = await hodlApi.requestAutologinToken(sigKey);
+ const uri = 'https://hodlhodl.com/contracts/' + this.state.contractToDisplay.id + '?sign_in_token=' + autologinKey;
+ this.setState({ isRenderContractVisible: false }, () => {
+ NavigationService.navigate('HodlHodlWebview', { uri });
+ });
+ }
+
+ _onCancelContract() {
+ if (!this.state.contractToDisplay) return;
+
+ Alert.alert(
+ 'Are you sure you want to cancel this contract?',
+ ``,
+ [
+ {
+ text: 'Yes, cancel contract',
+ onPress: async () => {
+ const hodlApi = this.state.hodlApi;
+ try {
+ await hodlApi.cancelContract(this.state.contractToDisplay.id);
+ this.setState({ isRenderContractVisible: false });
+ await this.refetchContracts();
+ } catch (Error) {
+ alert(Error);
+ }
+ },
+ style: 'default',
+ },
+ {
+ text: 'No',
+ onPress: () => {},
+ style: 'cancel',
+ },
+ ],
+ { cancelable: true },
+ );
+ }
+
+ isAllowedToMarkContractAsPaid() {
+ return this.state.contractToDisplay.status === 'in_progress' && this.state.contractToDisplay.your_role === 'buyer';
+ }
+}
+
+const styles = StyleSheet.create({
+ bottomModal: {
+ justifyContent: 'flex-end',
+ margin: 0,
+ },
+ modalContent: {
+ backgroundColor: '#FFFFFF',
+ padding: 22,
+ borderTopLeftRadius: 16,
+ borderTopRightRadius: 16,
+ borderColor: 'rgba(0, 0, 0, 0.1)',
+ minHeight: 425,
+ },
+ modalContentCentered: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ statusGreenWrapper: {
+ backgroundColor: '#d2f8d5',
+ borderRadius: 20,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center',
+ margin: 15,
+ paddingLeft: 15,
+ paddingRight: 15,
+ },
+ statusGreenText: {
+ fontSize: 12,
+ color: '#37bfa0',
+ },
+ statusGrayWrapper: {
+ backgroundColor: '#ebebeb',
+ borderRadius: 20,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center',
+ margin: 15,
+ paddingLeft: 15,
+ paddingRight: 15,
+ },
+ statusGrayText: {
+ fontSize: 12,
+ color: 'gray',
+ },
+ statusGrayWrapper2: {
+ backgroundColor: '#f8f8f8',
+ borderRadius: 5,
+ height: 28,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingLeft: 15,
+ paddingRight: 15,
+ },
+ statusGrayText2: {
+ fontSize: 12,
+ color: 'gray',
+ },
+ btcText: {
+ fontWeight: 'bold',
+ fontSize: 18,
+ color: '#0c2550',
+ },
+ subheaderText: {
+ fontSize: 12,
+ fontWeight: 'bold',
+ color: '#0c2550',
+ },
+ emptyComponentText: { textAlign: 'center', color: '#9AA0AA', paddingHorizontal: 16 },
+ itemSeparatorComponent: { height: 0.5, width: '100%', backgroundColor: '#C8C8C8' },
+ flexDirectionRow: { flexDirection: 'row' },
+ flexDirectionColumn: { flexDirection: 'column' },
+ volumeBreakdownText: { fontSize: 18, color: '#0c2550' },
+ contractStatusText: { fontSize: 14, color: 'gray', fontWeight: 'normal' },
+ cancelContractText: { color: '#d0021b', fontSize: 15, paddingTop: 20, fontWeight: '500', textAlign: 'center' },
+ openChatText: { color: '#1b02d0', fontSize: 15, paddingTop: 20, fontWeight: '500', textAlign: 'center' },
+ flatList: { paddingTop: 30 },
+ roleText: { fontSize: 14, color: 'gray', padding: 5 },
+});
diff --git a/screen/wallets/hodlHodlViewOffer.js b/screen/wallets/hodlHodlViewOffer.js
new file mode 100644
index 000000000..8395efc99
--- /dev/null
+++ b/screen/wallets/hodlHodlViewOffer.js
@@ -0,0 +1,351 @@
+/* global alert */
+import React, { Component } from 'react';
+import { Alert, FlatList, Image, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, Text, View } from 'react-native';
+import { BlueButton, BlueLoading, BlueNavigationStyle, BlueSpacing10, SafeBlueArea } from '../../BlueComponents';
+import PropTypes from 'prop-types';
+import { HodlHodlApi } from '../../class/hodl-hodl-api';
+import { Icon } from 'react-native-elements';
+import { AppStorage } from '../../class';
+import NavigationService from '../../NavigationService';
+
+const BlueApp: AppStorage = require('../../BlueApp');
+const prompt = require('../../prompt');
+
+export default class HodlHodlViewOffer extends Component {
+ static navigationOptions = ({ navigation }) => ({
+ ...BlueNavigationStyle(),
+ title: '',
+ });
+
+ constructor(props) {
+ super(props);
+
+ const offerToDisplay = props.route.params.offerToDisplay;
+
+ const horizontalScrollData = [];
+ horizontalScrollData.push({ id: 'window', body: offerToDisplay.payment_window_minutes + ' min' });
+ horizontalScrollData.push({
+ id: 'min / max',
+ body:
+ offerToDisplay.min_amount.replace('.00', '') +
+ ' - ' +
+ offerToDisplay.max_amount.replace('.00', '') +
+ ' ' +
+ offerToDisplay.currency_code,
+ });
+ offerToDisplay.first_trade_limit &&
+ horizontalScrollData.push({
+ id: '1st trade',
+ body: offerToDisplay.first_trade_limit.replace('.00', '') + ' ' + offerToDisplay.currency_code,
+ });
+
+ for (const paymentInstruction of offerToDisplay.payment_method_instructions || []) {
+ horizontalScrollData.push({
+ id: paymentInstruction.id + paymentInstruction.version,
+ header: paymentInstruction.payment_method_type,
+ body: paymentInstruction.payment_method_name,
+ });
+ }
+
+ horizontalScrollData.push({ id: 'confirmations', body: offerToDisplay.confirmations });
+
+ this.state = {
+ hodlApi: false,
+ isLoading: true,
+ horizontalScrollData,
+ offerToDisplay,
+ };
+ }
+
+ async componentDidMount() {
+ console.log('wallets/hodlHodlViewOffer - componentDidMount');
+
+ const hodlApiKey = await BlueApp.getHodlHodlApiKey();
+ const hodlApi = new HodlHodlApi(hodlApiKey);
+ this.setState({ hodlApi, hodlApiKey });
+
+ this.setState({
+ isLoading: false,
+ });
+ }
+
+ async _onAcceptOfferPress(offer) {
+ if (!this.state.hodlApiKey) {
+ alert('Please login to HodlHodl to accept offers');
+ return;
+ }
+ const myself = await this.state.hodlApi.getMyself();
+ if (!myself.encrypted_seed || myself.encrypted_seed.length < 10) {
+ const buttons = [
+ {
+ text: 'Yes',
+ onPress: async a => {
+ const sigKey = await BlueApp.getHodlHodlSignatureKey();
+ if (!sigKey) {
+ alert('Error: signature key not set'); // should never happen
+ return;
+ }
+
+ const autologinKey = await this.state.hodlApi.requestAutologinToken(sigKey);
+ const uri = 'https://hodlhodl.com/dashboards/settings?sign_in_token=' + autologinKey;
+ NavigationService.navigate('HodlHodlWebview', { uri });
+ },
+ },
+ {
+ text: 'Cancel',
+ onPress: async a => {},
+ },
+ ];
+ Alert.alert('HodlHodl', `Looks like you didn't finish setting up account on HodlHodl, would you like to finish setup now?`, buttons, {
+ cancelable: true,
+ });
+ return;
+ }
+
+ let fiatValue;
+ try {
+ fiatValue = await prompt('How much ' + offer.currency_code + ' do you want to buy?', 'For example 100', true, 'numeric');
+ } catch (_) {
+ return;
+ }
+ if (!fiatValue) return;
+
+ const buttons = [];
+ for (const paym of offer.payment_method_instructions) {
+ buttons.push({
+ text: paym.payment_method_name + ' (' + paym.payment_method_type + ')',
+ onPress: async a => {
+ let noError = true;
+ this.setState({ isLoading: true });
+ let contract;
+ try {
+ contract = await this.state.hodlApi.acceptOffer(offer.id, offer.version, paym.id, paym.version, fiatValue);
+ } catch (Error) {
+ noError = false;
+ alert(Error);
+ }
+ this.setState({ isLoading: false });
+
+ if (noError && contract.id) {
+ await BlueApp.addHodlHodlContract(contract.id);
+ NavigationService.navigate('HodlHodlMyContracts');
+ }
+ },
+ });
+ }
+ Alert.alert('Choose payment method', ``, buttons, { cancelable: true });
+ }
+
+ _renderHorizontalScrollItem(item) {
+ return (
+
+ {item.item.header || item.item.id}
+ {item.item.body}
+
+ );
+ }
+
+ render() {
+ return this.state.isLoading ? (
+
+ ) : (
+
+
+
+
+ {this.state.offerToDisplay.title}
+
+ {/* horizontal panel with bubbles */}
+
+
+
+
+ {this.state.offerToDisplay.country}
+
+
+
+
+
+ {this.state.offerToDisplay.price} {this.state.offerToDisplay.currency_code}
+
+
+
+ {/* end */}
+
+ {this.state.offerToDisplay.description}
+
+
+
+
+
+
+
+ {/* avatar and rating */}
+
+
+
+ {this.state.offerToDisplay.trader.online_status === 'online' && (
+
+
+
+ )}
+
+
+
+ {this.state.offerToDisplay.trader.strong_hodler && (
+
+ )}
+ {this.state.offerToDisplay.trader.login}
+
+
+ {this.state.offerToDisplay.trader.trades_count > 0
+ ? Math.round(this.state.offerToDisplay.trader.rating * 100) +
+ '%' +
+ ' / ' +
+ this.state.offerToDisplay.trader.trades_count +
+ ' trades'
+ : 'No rating'}
+
+
+
+ {/* end */}
+
+ {this.state.offerToDisplay.side === 'sell' ? (
+
+
+
+ {
+ await this._onAcceptOfferPress(this.state.offerToDisplay);
+ }}
+ />
+
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+ }
+}
+
+HodlHodlViewOffer.propTypes = {
+ route: PropTypes.shape({
+ params: PropTypes.shape({
+ offerToDisplay: PropTypes.object,
+ }),
+ }),
+};
+
+const styles = StyleSheet.create({
+ modalContent: {
+ backgroundColor: '#FFFFFF',
+ padding: 22,
+ },
+ Title: {
+ fontWeight: '600',
+ fontSize: 24,
+ color: '#0c2550',
+ },
+ circleWhite: {
+ position: 'absolute',
+ bottom: 0,
+ right: 3,
+ backgroundColor: 'white',
+ width: 13,
+ height: 13,
+ borderRadius: 6,
+ },
+ circleGreen: {
+ position: 'absolute',
+ bottom: 1,
+ right: 1,
+ backgroundColor: '#00d327',
+ width: 10,
+ height: 10,
+ borderRadius: 5,
+ },
+ grayTextContainerContainer: {
+ backgroundColor: '#EEF0F4',
+ borderRadius: 20,
+ height: 30,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 15,
+ marginRight: 10,
+ paddingRight: 20,
+ },
+ greenTextContainerContainer: {
+ backgroundColor: '#d2f8d5',
+ borderRadius: 20,
+ height: 30,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 15,
+ paddingLeft: 15,
+ paddingRight: 15,
+ },
+ grayTextContainer: {
+ width: '100%',
+ alignItems: 'center',
+ flex: 1,
+ flexDirection: 'row',
+ },
+ priceText: {
+ top: 0,
+ color: '#37bfa0',
+ fontSize: 14,
+ fontWeight: '500',
+ },
+ descriptionText: {
+ top: 0,
+ color: '#818893',
+ fontSize: 14,
+ paddingTop: 20,
+ paddingBottom: 20,
+ fontWeight: '500',
+ minHeight: 150,
+ lineHeight: 23,
+ },
+ nicknameText: {
+ color: '#0c2550',
+ fontSize: 16,
+ fontWeight: 'bold',
+ },
+ traderRatingText: {
+ color: '#9AA0AA',
+ fontSize: 12,
+ },
+ locationText: {
+ color: '#9BA0A9',
+ },
+ horizontalScrollIemHeader: { fontSize: 12, color: '#9AA0AA' },
+ horizontalScrollItemBody: { fontSize: 14, fontWeight: 'bold', color: '#0c2550' },
+ horizontalScrollWrapper: { flexDirection: 'column', paddingTop: 20, paddingBottom: 20, paddingRight: 40 },
+ flexDirRow: { flexDirection: 'row' },
+ iconWithPadding: { paddingLeft: 16 },
+ _hr: {
+ borderWidth: 0,
+ borderBottomWidth: 1,
+ borderColor: '#ebebeb',
+ },
+ avatarImg: { width: 60, height: 60, borderRadius: 60 },
+ avatarWrapper: { backgroundColor: 'white', flex: 1, flexDirection: 'column', alignItems: 'center', marginTop: 32 },
+ verifiedIcon: { marginTop: 3, marginRight: 5 },
+ traderWrapper: { alignItems: 'center', marginTop: 8 },
+ acceptOfferButtonWrapper: { width: '70%', alignItems: 'center' },
+ acceptOfferButtonWrapperWrapper: { marginTop: 24, alignItems: 'center' },
+});
diff --git a/screen/wallets/hodlHodlWebview.js b/screen/wallets/hodlHodlWebview.js
new file mode 100644
index 000000000..9d2d5f6ee
--- /dev/null
+++ b/screen/wallets/hodlHodlWebview.js
@@ -0,0 +1,38 @@
+import React, { Component } from 'react';
+import { WebView } from 'react-native-webview';
+import { BlueNavigationStyle, SafeBlueArea } from '../../BlueComponents';
+import PropTypes from 'prop-types';
+
+export default class HodlHodlWebview extends Component {
+ static navigationOptions = ({ navigation }) => ({
+ ...BlueNavigationStyle(navigation, true),
+ title: '',
+ headerLeft: null,
+ });
+
+ constructor(props) {
+ super(props);
+
+ const uri = props.route.params.uri;
+
+ this.state = {
+ uri,
+ };
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+}
+
+HodlHodlWebview.propTypes = {
+ route: PropTypes.shape({
+ params: PropTypes.shape({
+ uri: PropTypes.string.isRequired,
+ }),
+ }),
+};
diff --git a/screen/wallets/list.js b/screen/wallets/list.js
index 3cb29c37d..eae81f4da 100644
--- a/screen/wallets/list.js
+++ b/screen/wallets/list.js
@@ -436,7 +436,7 @@ export default class WalletsList extends Component {
return (
{
- this.props.navigation.navigate('HodlHodl', { fromWallet: this.state.wallet });
+ this.props.navigation.navigate('HodlHodlRoot', { params: { wallet: this.state.wallet }, screen: 'HodlHodl' });
}}
style={styles.ltRoot}
>
diff --git a/tests/integration/HodlHodl.test.js b/tests/integration/HodlHodl.test.js
index 89c2ef76e..0b43d3f15 100644
--- a/tests/integration/HodlHodl.test.js
+++ b/tests/integration/HodlHodl.test.js
@@ -5,6 +5,20 @@ import { HodlHodlApi } from '../../class/hodl-hodl-api';
const bitcoin = require('bitcoinjs-lib');
const assert = require('assert');
+it.skip('can verify escrow address', () => {
+ const encryptedSeed =
+ 'ES1:b2dc8bd89782f70ef11ff1d1c6bf6adde0bea78fb959391de48f49acbf7f9766ca128b89c1a9a013d158b6c4dabee77997f8a15764d1b083f213b1d6aa9fb3a14a1edb406930a25423a1df3be72306f120b08972cea669dba1284bd8:bf5af8737529b419cc20935a1c05c742:pbkdf2:10000';
+ const encryptPassword = 'Qwert12345';
+ const address = '34n3rBtPA16BQYWycphnhK7C9DoucWb527';
+ const index = 10298;
+ const witnessScript =
+ '522103dc0edfea797214be15a69148bfb1dffa1c8295c05300b7632143a77d918b4a0821031fec42b60942633616aff7e245796b5caae6bf59ef5ba688b0a59f33f08b2896210351fd6e52d38a37b9834909e3f8345c471346e1f5990ec00dafcc53e238d3c7c553ae';
+
+ const Hodl = new HodlHodlApi();
+ assert.ok(Hodl.verifyEscrowAddress(encryptedSeed, encryptPassword, index, address, witnessScript));
+ assert.ok(!Hodl.verifyEscrowAddress(encryptedSeed, encryptPassword, index, '3QDf45WU88t2kEBJTHcTPvtrXZx88SkmKC', witnessScript));
+});
+
it('can create escrow address', () => {
const keyPairServer = bitcoin.ECPair.fromPrivateKey(
Buffer.from('9a8cfd0e33a37c90a46d358c84ca3d8dd089ed35409a6eb1973148c0df492288', 'hex'),
@@ -109,12 +123,58 @@ describe('HodlHodl API', function () {
assert.ok(offers[0].asset_code === 'BTC');
assert.ok(offers[0].country_code);
assert.ok(offers[0].side === HodlHodlApi.FILTERS_SIDE_VALUE_SELL);
- assert.ok(offers[0].title || offers[0].description || offers[1].title || offers[1].description, JSON.stringify(offers[0], null, 2));
+ assert.ok(typeof offers[0].title !== 'undefined', JSON.stringify(offers[0], null, 2));
+ assert.ok(typeof offers[0].description !== 'undefined', JSON.stringify(offers[0], null, 2));
assert.ok(offers[0].price);
assert.ok(offers[0].payment_method_instructions);
assert.ok(offers[0].trader);
});
+ it('can get offer', async () => {
+ if (!process.env.HODLHODL_OFFER_ID) return;
+ const Hodl = new HodlHodlApi();
+ const offer = await Hodl.getOffer(process.env.HODLHODL_OFFER_ID);
+ assert.ok(offer.id);
+ assert.ok(offer.version);
+ });
+
+ it('can accept offer', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
+ if (!process.env.HODLHODL_OFFER_ID) return;
+ const Hodl = new HodlHodlApi();
+ const offer = await Hodl.getOffer(process.env.HODLHODL_OFFER_ID);
+ assert.strictEqual(offer.side, 'sell');
+ const paymentMethodInstructionId = offer.payment_method_instructions[0].id;
+ const paymentMethodInstructionVersion = offer.payment_method_instructions[0].version;
+ const fiatValue = 100;
+ const contract = await Hodl.acceptOffer(
+ offer.id,
+ offer.version,
+ paymentMethodInstructionId,
+ paymentMethodInstructionVersion,
+ fiatValue,
+ );
+ console.warn({ contract });
+ });
+
+ it('can get contract', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
+ if (!process.env.HODLHODL_CONTRACT_ID) return;
+ const Hodl = new HodlHodlApi();
+ const contract = await Hodl.getContract(process.env.HODLHODL_CONTRACT_ID);
+ assert.ok(contract.your_role);
+ assert.ok(contract.volume);
+ assert.ok(contract.escrow);
+ });
+
+ it('can mark contract as confirmed', async () => {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000;
+ if (!process.env.HODLHODL_CONTRACT_ID) return;
+ const Hodl = new HodlHodlApi();
+ const result = await Hodl.markContractAsConfirmed(process.env.HODLHODL_CONTRACT_ID);
+ console.warn(result);
+ });
+
it('can get payment methods', async () => {
const Hodl = new HodlHodlApi();
const methods = await Hodl.getPaymentMethods(HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL);
@@ -132,4 +192,20 @@ describe('HodlHodl API', function () {
assert.ok(currencies[0].name);
assert.ok(currencies[0].type);
});
+
+ it('cat get myself', async () => {
+ const Hodl = new HodlHodlApi();
+ const myself = await Hodl.getMyself();
+ assert.ok(myself.encrypted_seed);
+ });
+
+ it('can create signature for autologin', async () => {
+ const Hodl = new HodlHodlApi('');
+ const sig = Hodl.createSignature(
+ 'iqZC7uUmx4sVeIwFQN2YqGT5SyrXNLhxVX7QMGUeJK1CDdy87OcrOt3QvPE5LFC56Lgu7WLlg12U55Vy',
+ 'cce14197a08ebab7cfbb41cfce9fe91e0f31d572d3f48571ca3c30bfd516f769',
+ 1589980224,
+ );
+ assert.strictEqual(sig, '1d2a51ca2c54ff9107a3460b22f01bc877e527a9a719d81b32038741332159fc');
+ });
});