ADD: HodlHodl accept offers (#1162)

This commit is contained in:
Overtorment 2020-06-15 19:47:54 +01:00 committed by GitHub
parent 35b4a30bdf
commit ddd1255e27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 2031 additions and 539 deletions

View file

@ -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 = () => (
</LNDCreateInvoiceStack.Navigator>
);
const HodlHodlStack = createStackNavigator();
const HodlHodlRoot = () => (
<HodlHodlStack.Navigator>
<HodlHodlStack.Screen name="HodlHodl" component={HodlHodl} options={HodlHodl.navigationOptions} />
<HodlHodlStack.Screen name="HodlHodlViewOffer" component={HodlHodlViewOffer} options={HodlHodlViewOffer.navigationOptions} />
<HodlHodlStack.Screen name="HodlHodlLogin" component={HodlHodlLogin} options={HodlHodlLogin.navigationOptions} />
<HodlHodlStack.Screen name="HodlHodlMyContracts" component={HodlHodlMyContracts} options={HodlHodlMyContracts.navigationOptions} />
<HodlHodlStack.Screen name="HodlHodlWebview" component={HodlHodlWebview} options={HodlHodlWebview.navigationOptions} />
</HodlHodlStack.Navigator>
);
// LightningScanInvoiceStackNavigator === ScanLndInvoiceStack
const ScanLndInvoiceStack = createStackNavigator();
const ScanLndInvoiceRoot = () => (
@ -236,6 +251,7 @@ const Navigation = () => (
<RootStack.Screen name="LNDCreateInvoiceRoot" component={LNDCreateInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="ScanLndInvoiceRoot" component={ScanLndInvoiceRoot} options={{ headerShown: false }} />
<RootStack.Screen name="AztecoRedeemRoot" component={AztecoRedeemRoot} options={{ headerShown: false }} />
<RootStack.Screen name="HodlHodlRoot" component={HodlHodlRoot} options={{ headerShown: false }} />
<RootStack.Screen
name="ScanQRCodeRoot"
component={ScanQRCodeRoot}

View file

@ -29,6 +29,9 @@ export class AppStorage {
static PREFERRED_CURRENCY = 'preferredCurrency';
static ADVANCED_MODE_ENABLED = 'advancedmodeenabled';
static DELETE_WALLET_AFTER_UNINSTALL = 'deleteWalletAfterUninstall';
static HODL_HODL_API_KEY = 'HODL_HODL_API_KEY';
static HODL_HODL_SIGNATURE_KEY = 'HODL_HODL_SIGNATURE_KEY';
static HODL_HODL_CONTRACTS = 'HODL_HODL_CONTRACTS';
constructor() {
/** {Array.<AbstractWallet>} */
@ -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[]>} 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));

View file

@ -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; // <api_key>:<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<string>} 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 contracts 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

467
package-lock.json generated
View file

@ -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"
}
}
}
}

View file

@ -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",

View file

@ -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
? [

View file

@ -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 {
<TouchableOpacity
onPress={() => {
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 ? (

File diff suppressed because one or more lines are too long

View file

@ -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 (
<SafeBlueArea>
<WebView
injectedJavaScript={INJECTED_JAVASCRIPT}
ref={ref => (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();
}
}}
/>
</SafeBlueArea>
);
}
}
HodlHodlLogin.propTypes = {
route: PropTypes.shape({
params: PropTypes.shape({
cb: PropTypes.func.isRequired,
}),
}),
navigation: PropTypes.shape({
getParam: PropTypes.func,
navigate: PropTypes.func,
pop: PropTypes.func,
}),
};

View file

@ -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 <BlueLoading />;
return (
<SafeBlueArea>
<FlatList
scrollEnabled={false}
keyExtractor={(item, index) => {
return item.id;
}}
ListEmptyComponent={() => <Text style={styles.emptyComponentText}>You dont have any contracts in progress</Text>}
style={styles.flatList}
ItemSeparatorComponent={() => <View style={styles.itemSeparatorComponent} />}
data={this.state.contracts}
renderItem={({ item: contract, index, separators }) => (
<TouchableHighlight
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
onPress={() => this._onContractPress(contract)}
>
<View style={styles.flexDirectionRow}>
<View style={['paid', 'completed'].includes(contract.status) ? styles.statusGreenWrapper : styles.statusGrayWrapper}>
<Text style={['paid', 'completed'].includes(contract.status) ? styles.statusGreenText : styles.statusGrayText}>
{contract.status}
</Text>
</View>
<View style={styles.flexDirectionColumn}>
<View style={styles.flexDirectionRow}>
<Text style={styles.volumeBreakdownText}>
{contract.volume_breakdown.goes_to_buyer} {contract.asset_code}
</Text>
<Text style={styles.roleText}>{contract.your_role === 'buyer' ? 'buying' : 'selling'}</Text>
</View>
<View>
<Text style={styles.contractStatusText}>{contract.statusText}</Text>
</View>
</View>
</View>
</TouchableHighlight>
)}
/>
{this.renderContract()}
</SafeBlueArea>
);
}
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 offers creator to confirm his payment password in case he uses the website)
* Each party verifies the escrow address locally
* Each party sends Confirming contracts 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 (
<Modal
isVisible={this.state.isRenderContractVisible}
style={styles.bottomModal}
onBackdropPress={() => {
Keyboard.dismiss();
this.setState({ isRenderContractVisible: false });
}}
>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={styles.modalContent}>
<View style={styles.modalContentCentered}>
<Text style={styles.btcText}>
{this.state.contractToDisplay.volume_breakdown.goes_to_buyer} {this.state.contractToDisplay.asset_code}
</Text>
<View style={styles.statusGreenWrapper}>
<Text style={styles.statusGreenText}>
{this.state.contractToDisplay.price} {this.state.contractToDisplay.currency_code}
</Text>
</View>
</View>
<Text style={styles.subheaderText}>To</Text>
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
<Text
style={styles.statusGrayText2}
onPress={() => Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.release_address}`)}
>
{this.state.contractToDisplay.release_address}
</Text>
</View>
</View>
<BlueSpacing10 />
<Text style={styles.subheaderText}>Escrow</Text>
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
<Text
style={styles.statusGrayText2}
onPress={() => Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.escrow.address}`)}
>
{this.state.contractToDisplay.escrow.address}
</Text>
</View>
</View>
<BlueSpacing20 />
{this.isAllowedToMarkContractAsPaid() ? (
<View>
<Text style={styles.subheaderText}>How to pay</Text>
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
<Text style={styles.statusGrayText2}>{this.state.contractToDisplay.payment_method_instruction.details}</Text>
</View>
</View>
</View>
) : (
<View />
)}
<BlueSpacing20 />
{this.isAllowedToMarkContractAsPaid() ? (
<View>
<BlueButton title="Mark contract as Paid" onPress={() => this._onMarkContractAsPaid()} />
<BlueSpacing20 />
</View>
) : (
<View />
)}
<BlueSpacing20 />
{this.state.contractToDisplay.can_be_canceled && (
<Text onPress={() => this._onCancelContract()} style={styles.cancelContractText}>
Cancel contract
</Text>
)}
<Text onPress={() => this._onOpenContractOnWebsite()} style={styles.openChatText}>
Open chat with counterparty
</Text>
</View>
</KeyboardAvoidingView>
</Modal>
);
};
/**
* 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 },
});

View file

@ -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 (
<View style={styles.horizontalScrollWrapper}>
<Text style={styles.horizontalScrollIemHeader}>{item.item.header || item.item.id}</Text>
<Text style={styles.horizontalScrollItemBody}>{item.item.body}</Text>
</View>
);
}
render() {
return this.state.isLoading ? (
<BlueLoading />
) : (
<SafeBlueArea>
<ScrollView>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'position' : null}>
<View style={styles.modalContent}>
<Text style={styles.Title}>{this.state.offerToDisplay.title}</Text>
{/* horizontal panel with bubbles */}
<View style={styles.flexDirRow}>
<View style={styles.grayTextContainerContainer}>
<View style={styles.grayTextContainer}>
<Icon name="place" type="material" size={16} color="#9BA0A9" containerStyle={styles.iconWithPadding} />
<Text style={styles.locationText}>{this.state.offerToDisplay.country}</Text>
</View>
</View>
<View style={styles.greenTextContainerContainer}>
<Text style={styles.priceText}>
{this.state.offerToDisplay.price} {this.state.offerToDisplay.currency_code}
</Text>
</View>
</View>
{/* end */}
<Text style={styles.descriptionText}>{this.state.offerToDisplay.description}</Text>
<View style={styles._hr} />
<FlatList horizontal data={this.state.horizontalScrollData} renderItem={this._renderHorizontalScrollItem} />
<View style={styles._hr} />
{/* avatar and rating */}
<View style={styles.avatarWrapper}>
<View>
<Image
style={styles.avatarImg}
source={
this.state.offerToDisplay.trader.avatar_url.endsWith('.svg')
? require('../../img/hodlhodl-default-avatar.png')
: {
uri: this.state.offerToDisplay.trader.avatar_url,
}
}
/>
{this.state.offerToDisplay.trader.online_status === 'online' && (
<View style={styles.circleWhite}>
<View style={styles.circleGreen} />
</View>
)}
</View>
<View style={styles.traderWrapper}>
<View style={styles.flexDirRow}>
{this.state.offerToDisplay.trader.strong_hodler && (
<Icon name="verified-user" type="material" size={14} color="#0071fc" containerStyle={styles.verifiedIcon} />
)}
<Text style={styles.nicknameText}>{this.state.offerToDisplay.trader.login}</Text>
</View>
<Text style={styles.traderRatingText}>
{this.state.offerToDisplay.trader.trades_count > 0
? Math.round(this.state.offerToDisplay.trader.rating * 100) +
'%' +
' / ' +
this.state.offerToDisplay.trader.trades_count +
' trades'
: 'No rating'}
</Text>
</View>
</View>
{/* end */}
{this.state.offerToDisplay.side === 'sell' ? (
<View style={styles.acceptOfferButtonWrapperWrapper}>
<View style={styles.acceptOfferButtonWrapper}>
<BlueSpacing10 />
<BlueButton
title="Accept offer"
onPress={async () => {
await this._onAcceptOfferPress(this.state.offerToDisplay);
}}
/>
</View>
</View>
) : (
<View />
)}
</View>
</KeyboardAvoidingView>
</ScrollView>
</SafeBlueArea>
);
}
}
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' },
});

View file

@ -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 (
<SafeBlueArea>
<WebView source={{ uri: this.state.uri }} incognito />
</SafeBlueArea>
);
}
}
HodlHodlWebview.propTypes = {
route: PropTypes.shape({
params: PropTypes.shape({
uri: PropTypes.string.isRequired,
}),
}),
};

View file

@ -436,7 +436,7 @@ export default class WalletsList extends Component {
return (
<TouchableOpacity
onPress={() => {
this.props.navigation.navigate('HodlHodl', { fromWallet: this.state.wallet });
this.props.navigation.navigate('HodlHodlRoot', { params: { wallet: this.state.wallet }, screen: 'HodlHodl' });
}}
style={styles.ltRoot}
>

View file

@ -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');
});
});