Overtorment
5 years ago
12 changed files with 1234 additions and 11 deletions
@ -0,0 +1,132 @@ |
|||||
|
import Frisbee from 'frisbee'; |
||||
|
|
||||
|
export class HodlHodlApi { |
||||
|
static PAGINATION_LIMIT = 'limit'; // int
|
||||
|
static PAGINATION_OFFSET = 'offset'; // int
|
||||
|
|
||||
|
static FILTERS_ASSET_CODE = 'asset_code'; |
||||
|
static FILTERS_ASSET_CODE_VALUE_BTC = 'BTC'; |
||||
|
static FILTERS_ASSET_CODE_VALUE_BTCLN = 'BTCLN'; |
||||
|
|
||||
|
static FILTERS_SIDE = 'side'; |
||||
|
static FILTERS_SIDE_VALUE_BUY = 'buy'; |
||||
|
static FILTERS_SIDE_VALUE_SELL = 'sell'; |
||||
|
|
||||
|
static FILTERS_INCLUDE_GLOBAL = 'include_global'; // bool
|
||||
|
static FILTERS_ONLY_WORKING_NOW = 'only_working_now'; // bool
|
||||
|
static FILTERS_COUNTRY = 'country'; // code or name (or "Global")
|
||||
|
static FILTERS_COUNTRY_VALUE_GLOBAL = 'Global'; // code or name
|
||||
|
static FILTERS_CURRENCY_CODE = 'currency_code'; |
||||
|
static FILTERS_PAYMENT_METHOD_ID = 'payment_method_id'; |
||||
|
static FILTERS_PAYMENT_METHOD_TYPE = 'payment_method_type'; |
||||
|
static FILTERS_PAYMENT_METHOD_NAME = 'payment_method_name'; |
||||
|
static FILTERS_VOLUME = 'volume'; |
||||
|
static FILTERS_PAYMENT_WINDOW_MINUTES_MAX = 'payment_window_minutes_max'; // in minutes
|
||||
|
static FILTERS_USER_AVERAGE_PAYMENT_TIME_MINUTES_MAX = 'user_average_payment_time_minutes_max'; // in minutes
|
||||
|
static FILTERS_USER_AVERAGE_RELEASE_TIME_MINUTES_MAX = 'user_average_release_time_minutes_max'; // in minutes
|
||||
|
|
||||
|
static SORT_DIRECTION = 'direction'; |
||||
|
static SORT_DIRECTION_VALUE_ASC = 'asc'; |
||||
|
static SORT_DIRECTION_VALUE_DESC = 'desc'; |
||||
|
|
||||
|
static SORT_BY = 'by'; |
||||
|
static SORT_BY_VALUE_PRICE = 'price'; |
||||
|
static SORT_BY_VALUE_PAYMENT_WINDOW_MINUTES = 'payment_window_minutes'; |
||||
|
static SORT_BY_VALUE_USER_AVERAGE_PAYMENT_TIME_MINUTES = 'user_average_payment_time_minutes'; |
||||
|
static SORT_BY_VALUE_USER_AVERAGE_RELEASE_TIME_MINUTES = 'user_average_release_time_minutes'; |
||||
|
static SORT_BY_VALUE_RATING = 'rating'; |
||||
|
|
||||
|
constructor(apiKey = false) { |
||||
|
this.baseURI = 'https://hodlhodl.com/'; |
||||
|
this.apiKey = apiKey || 'cmO8iLFgx9wrxCe9R7zFtbWpqVqpGuDfXR3FJB0PSGCd7EAh3xgG51vBKgNTAF8fEEpS0loqZ9P1fDZt'; |
||||
|
this._api = new Frisbee({ baseURI: this.baseURI }); |
||||
|
} |
||||
|
|
||||
|
_getHeaders() { |
||||
|
return { |
||||
|
headers: { |
||||
|
'Access-Control-Allow-Origin': '*', |
||||
|
'Content-Type': 'application/json', |
||||
|
Authorization: 'Bearer ' + this.apiKey, |
||||
|
}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
async getCountries() { |
||||
|
let response = await this._api.get('/api/v1/countries', this._getHeaders()); |
||||
|
|
||||
|
let json = response.body; |
||||
|
if (!json || !json.countries || json.status === 'error') { |
||||
|
throw new Error('API failure: ' + JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
return (this._countries = json.countries); |
||||
|
} |
||||
|
|
||||
|
async getMyCountryCode() { |
||||
|
let _api = new Frisbee({ baseURI: 'https://ifconfig.co/' }); |
||||
|
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': '*' }, |
||||
|
}); |
||||
|
|
||||
|
let body = response.body; |
||||
|
if (typeof body === 'string') body = body.replace('\n', ''); |
||||
|
if (!body || body.length !== 2) { |
||||
|
allowedTries--; |
||||
|
await (async () => new Promise(resolve => setTimeout(resolve, 3000)))(); // sleep
|
||||
|
} else { |
||||
|
return (this._myCountryCode = body); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
throw new Error('API failure after several tries: ' + JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
async getPaymentMethods(country) { |
||||
|
let response = await this._api.get('/api/v1/payment_methods?filters[country]=' + country, this._getHeaders()); |
||||
|
|
||||
|
let json = response.body; |
||||
|
if (!json || !json.payment_methods || json.status === 'error') { |
||||
|
throw new Error('API failure: ' + JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
return (this._payment_methods = json.payment_methods.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))); |
||||
|
} |
||||
|
|
||||
|
async getCurrencies() { |
||||
|
let response = await this._api.get('/api/v1/currencies', this._getHeaders()); |
||||
|
|
||||
|
let json = response.body; |
||||
|
if (!json || !json.currencies || json.status === 'error') { |
||||
|
throw new Error('API failure: ' + JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
return (this._currencies = json.currencies.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1))); |
||||
|
} |
||||
|
|
||||
|
async getOffers(pagination = {}, filters = {}, sort = {}) { |
||||
|
let uri = []; |
||||
|
for (let key in sort) { |
||||
|
uri.push('sort[' + key + ']=' + sort[key]); |
||||
|
} |
||||
|
for (let key in filters) { |
||||
|
uri.push('filters[' + key + ']=' + filters[key]); |
||||
|
} |
||||
|
for (let key in pagination) { |
||||
|
uri.push('pagination[' + key + ']=' + pagination[key]); |
||||
|
} |
||||
|
let response = await this._api.get('/api/v1/offers?' + uri.join('&'), this._getHeaders()); |
||||
|
|
||||
|
let json = response.body; |
||||
|
if (!json || !json.offers || json.status === 'error') { |
||||
|
throw new Error('API failure: ' + JSON.stringify(response)); |
||||
|
} |
||||
|
|
||||
|
return (this._offers = json.offers); |
||||
|
} |
||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,135 @@ |
|||||
|
/* global it, jasmine, describe */ |
||||
|
import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../../class'; |
||||
|
import { HodlHodlApi } from '../../class/hodl-hodl-api'; |
||||
|
|
||||
|
const bitcoin = require('bitcoinjs-lib'); |
||||
|
const assert = require('assert'); |
||||
|
|
||||
|
it('can create escrow address', () => { |
||||
|
const keyPairServer = bitcoin.ECPair.fromPrivateKey( |
||||
|
Buffer.from('9a8cfd0e33a37c90a46d358c84ca3d8dd089ed35409a6eb1973148c0df492288', 'hex'), |
||||
|
); |
||||
|
const keyPairSeller = bitcoin.ECPair.fromPrivateKey( |
||||
|
Buffer.from('ab4163f517bfac01d7acd3a1e398bfb28b53ebd162cb1dd767cc63ae8069ef37', 'hex'), |
||||
|
); |
||||
|
const keyPairBuyer = bitcoin.ECPair.fromPrivateKey( |
||||
|
Buffer.from('b4ab9ed098b6d4b308deaefce5079f4203c43cfb51b699dd35dcc0f1ae5906fd', 'hex'), |
||||
|
); |
||||
|
|
||||
|
const pubkeys = [ |
||||
|
keyPairServer.publicKey, // '03141024b18929bfec5b567c12b1693d4ae02783873e2e3aa444f0d6950cb97dee', // server
|
||||
|
keyPairSeller.publicKey, // '0208137b6cb23cef02c0529948a2ed12fbeed0813cce555de073319f56e215ee1b', // seller
|
||||
|
keyPairBuyer.publicKey, // '035ed5825258d4f1685df804f21296b9957cd319cf5949ace92fa5767eb7a946f2', // buyer
|
||||
|
].map(hex => Buffer.from(hex, 'hex')); |
||||
|
|
||||
|
const p2shP2wshP2ms = bitcoin.payments.p2sh({ |
||||
|
redeem: bitcoin.payments.p2wsh({ |
||||
|
redeem: bitcoin.payments.p2ms({ m: 2, pubkeys }), |
||||
|
}), |
||||
|
}); |
||||
|
const address = p2shP2wshP2ms.address; |
||||
|
// console.warn(p2sh_p2wsh_p2ms);
|
||||
|
|
||||
|
assert.strictEqual(address, '391ygT71qeF7vbYjxsUZPzH6oDc7Rv4vTs'); |
||||
|
|
||||
|
let signedByServerReleaseTransaction = |
||||
|
'01000000000101356493a6b93bf17e66d7ec12f1a54e279da17f669f41bf11405a6f2617e1022501000000232200208ec72df31adaa132e40a5f5033589c0e18b67a64cdc65e9c75027fe1efd10f4cffffffff02227e010000000000160014b1c61a73a529c315a1f2b87df12c7948d86ba10c26020000000000001976a914d0b77eb1502c81c4093da9aa6eccfdf560cdd6b288ac040047304402205a447563db8e74177a1fbcdcfe7b7b22556c39d68c17ffe0a4a02609d78c83130220772fbf3261b6031a915eca7e441092df3fe6e4c7d4f389c4921c1f18661c20f401460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000069522103141024b18929bfec5b567c12b1693d4ae02783873e2e3aa444f0d6950cb97dee210208137b6cb23cef02c0529948a2ed12fbeed0813cce555de073319f56e215ee1b21035ed5825258d4f1685df804f21296b9957cd319cf5949ace92fa5767eb7a946f253ae00000000'; |
||||
|
|
||||
|
let txDecoded = bitcoin.Transaction.fromHex(signedByServerReleaseTransaction); |
||||
|
// console.warn(txDecoded.ins[0].witness);
|
||||
|
|
||||
|
// we always expect only one input:
|
||||
|
const psbt = new bitcoin.Psbt().addInput({ |
||||
|
hash: txDecoded.ins[0].hash, |
||||
|
index: txDecoded.ins[0].index, |
||||
|
witnessUtxo: { |
||||
|
script: p2shP2wshP2ms.redeem.output, |
||||
|
value: 100000, |
||||
|
}, |
||||
|
// redeemScript,
|
||||
|
witnessScript: p2shP2wshP2ms.redeem.redeem.output, |
||||
|
}); |
||||
|
|
||||
|
for (let out of txDecoded.outs) { |
||||
|
let scripthex = out.script.toString('hex'); |
||||
|
let address = |
||||
|
LegacyWallet.scriptPubKeyToAddress(scripthex) || |
||||
|
SegwitP2SHWallet.scriptPubKeyToAddress(scripthex) || |
||||
|
SegwitBech32Wallet.scriptPubKeyToAddress(scripthex); |
||||
|
psbt.addOutput({ |
||||
|
address, |
||||
|
value: out.value, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// psbt.signInput(0, keyPairServer);
|
||||
|
psbt.signInput(0, keyPairSeller); |
||||
|
|
||||
|
// console.warn('signature = ', psbt.data.inputs[0].partialSig[0].signature.toString('hex'));
|
||||
|
|
||||
|
// let tx = psbt.finalizeAllInputs().extractTransaction();
|
||||
|
// console.log(tx.toHex());
|
||||
|
}); |
||||
|
|
||||
|
describe('HodlHodl API', function() { |
||||
|
it('can fetch countries & and own country code', async () => { |
||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 * 1000; |
||||
|
let Hodl = new HodlHodlApi(); |
||||
|
const countries = await Hodl.getCountries(); |
||||
|
assert.ok(countries[0]); |
||||
|
assert.ok(countries[0].code); |
||||
|
assert.ok(countries[0].name); |
||||
|
assert.ok(countries[0].native_name); |
||||
|
assert.ok(countries[0].currency_code); |
||||
|
assert.ok(countries[0].currency_name); |
||||
|
|
||||
|
let countryCode = await Hodl.getMyCountryCode(); |
||||
|
assert.strictEqual(countryCode.length, 2); |
||||
|
}); |
||||
|
|
||||
|
it('can get offers', async () => { |
||||
|
let Hodl = new HodlHodlApi(); |
||||
|
const offers = await Hodl.getOffers( |
||||
|
{ |
||||
|
[HodlHodlApi.PAGINATION_LIMIT]: 10, |
||||
|
}, |
||||
|
{ |
||||
|
[HodlHodlApi.FILTERS_COUNTRY]: HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL, |
||||
|
[HodlHodlApi.FILTERS_SIDE]: HodlHodlApi.FILTERS_SIDE_VALUE_SELL, |
||||
|
[HodlHodlApi.FILTERS_ASSET_CODE]: HodlHodlApi.FILTERS_ASSET_CODE_VALUE_BTC, |
||||
|
[HodlHodlApi.FILTERS_INCLUDE_GLOBAL]: true, |
||||
|
}, |
||||
|
{ |
||||
|
[HodlHodlApi.SORT_BY]: HodlHodlApi.SORT_BY_VALUE_PRICE, |
||||
|
[HodlHodlApi.SORT_DIRECTION]: HodlHodlApi.SORT_DIRECTION_VALUE_ASC, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
assert.ok(offers[0]); |
||||
|
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); |
||||
|
assert.ok(offers[0].price); |
||||
|
assert.ok(offers[0].payment_method_instructions); |
||||
|
assert.ok(offers[0].trader); |
||||
|
}); |
||||
|
|
||||
|
it('can get payment methods', async () => { |
||||
|
let Hodl = new HodlHodlApi(); |
||||
|
const methods = await Hodl.getPaymentMethods(HodlHodlApi.FILTERS_COUNTRY_VALUE_GLOBAL); |
||||
|
assert.ok(methods[0]); |
||||
|
assert.ok(methods[0].id); |
||||
|
assert.ok(methods[0].type); |
||||
|
assert.ok(methods[0].name); |
||||
|
}); |
||||
|
|
||||
|
it('cat get currencies', async () => { |
||||
|
let Hodl = new HodlHodlApi(); |
||||
|
const currencies = await Hodl.getCurrencies(); |
||||
|
assert.ok(currencies[0]); |
||||
|
assert.ok(currencies[0].code); |
||||
|
assert.ok(currencies[0].name); |
||||
|
assert.ok(currencies[0].type); |
||||
|
}); |
||||
|
}); |
Loading…
Reference in new issue