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