From cdddd39fcee061db0e07d95ce2daa3cb859e0361 Mon Sep 17 00:00:00 2001 From: Adolfo Builes Date: Mon, 3 Feb 2020 17:39:47 -0500 Subject: [PATCH 1/3] Add support for top level offers end-point. --- src/offer_call_builder.ts | 29 ++++++------- src/server.ts | 22 ++++------ test/unit/server_test.js | 85 +++++++++++++++++++++++++-------------- 3 files changed, 77 insertions(+), 59 deletions(-) diff --git a/src/offer_call_builder.ts b/src/offer_call_builder.ts index 98e395a0d..6eb366571 100644 --- a/src/offer_call_builder.ts +++ b/src/offer_call_builder.ts @@ -1,32 +1,33 @@ import { CallBuilder } from "./call_builder"; -import { BadRequestError } from "./errors"; import { ServerApi } from "./server_api"; /** * Creates a new {@link OfferCallBuilder} pointed to server defined by serverUrl. * Do not create this object directly, use {@link Server#offers}. * - * @see [Offers for Account](https://www.stellar.org/developers/horizon/reference/endpoints/offers-for-account.html) + * @see [Offers](https://www.stellar.org/developers/horizon/reference/endpoints/offers.html) * @class OfferCallBuilder * @constructor * @extends CallBuilder * @param {string} serverUrl Horizon server URL. - * @param {string} resource Resource to query offers - * @param {...string} resourceParams Parameters for selected resource */ export class OfferCallBuilder extends CallBuilder< ServerApi.CollectionPage > { - constructor( - serverUrl: uri.URI, - resource: string, - ...resourceParams: string[] - ) { + constructor(serverUrl: uri.URI) { super(serverUrl); - if (resource === "accounts") { - this.url.segment([resource, ...resourceParams, "offers"]); - } else { - throw new BadRequestError("Bad resource specified for offer:", resource); - } + this.url.segment("offers"); + } + + /** + * Returns offers relating to a single account. + * + * @see [Offers](https://www.stellar.org/developers/horizon/reference/endpoints/offers.html) + * @param {string} id For example: `GDGQVOKHW4VEJRU2TETD6DBRKEO5ERCNF353LW5WBFW3JJWQ2BRQ6KDD` + * @returns {AccountCallBuilder} current AccountCallBuilder instance + */ + public forAccount(id: string): this { + this.url.setQuery("seller", id); + return this; } } diff --git a/src/server.ts b/src/server.ts index 902fc4794..3a1fd0872 100644 --- a/src/server.ts +++ b/src/server.ts @@ -469,27 +469,21 @@ export class Server { } /** - * People on the Stellar network can make offers to buy or sell assets. This endpoint represents all the offers a particular account makes. - * Currently this method only supports querying offers for account and should be used like this: + * People on the Stellar network can make offers to buy or sell assets. This endpoint represents all the offers on the DEX. + * + * You can query all offers for account using the function `.accountId`: + * * ``` - * server.offers('accounts', accountId).call() + * server.offers() + * .forAccount(accountId).call() * .then(function(offers) { * console.log(offers); * }); * ``` - * @param {string} resource Resource to query offers - * @param {...string} resourceParams Parameters for selected resource * @returns {OfferCallBuilder} New {@link OfferCallBuilder} object */ - public offers( - resource: string, - ...resourceParams: string[] - ): OfferCallBuilder { - return new OfferCallBuilder( - URI(this.serverURL as any), - resource, - ...resourceParams, - ); + public offers(): OfferCallBuilder { + return new OfferCallBuilder(URI(this.serverURL as any)); } /** diff --git a/test/unit/server_test.js b/test/unit/server_test.js index 12ccfbd0b..06c516156 100644 --- a/test/unit/server_test.js +++ b/test/unit/server_test.js @@ -1112,41 +1112,42 @@ describe('server.js non-transaction tests', function() { }); describe('OfferCallBuilder', function() { - let offersResponse = { - _embedded: { - records: [] - }, - _links: { - next: { - href: - '/accounts/GBCR5OVQ54S2EKHLBZMK6VYMTXZHXN3T45Y6PRX4PX4FXDMJJGY4FD42/offers?order=asc\u0026limit=10\u0026cursor=' - }, - prev: { - href: - '/accounts/GBCR5OVQ54S2EKHLBZMK6VYMTXZHXN3T45Y6PRX4PX4FXDMJJGY4FD42/offers?order=desc\u0026limit=10\u0026cursor=' - }, - self: { - href: - '/accounts/GBCR5OVQ54S2EKHLBZMK6VYMTXZHXN3T45Y6PRX4PX4FXDMJJGY4FD42/offers?order=asc\u0026limit=10\u0026cursor=' - } - } - }; + function buildOffersResponse(withAccount) { + const prefix = withAccount ? '/accounts/GBCR5OVQ54S2EKHLBZMK6VYMTXZHXN3T45Y6PRX4PX4FXDMJJGY4FD42' : ''; - it('requests the correct endpoint', function(done) { + return { + _embedded: { + records: [] + }, + _links: { + next: { + href: + `${prefix}/offers?order=asc\u0026limit=10\u0026cursor=` + }, + prev: { + href: + `${prefix}/offers?order=desc\u0026limit=10\u0026cursor=` + }, + self: { + href: + `${prefix}/offers?order=asc\u0026limit=10\u0026cursor=` + } + } + }; + } + + it('without params requests the correct endpoint', function(done) { + const offersResponse = buildOffersResponse(false); this.axiosMock .expects('get') .withArgs( sinon.match( - 'https://horizon-live.stellar.org:1337/accounts/GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K/offers?order=asc' + 'https://horizon-live.stellar.org:1337/offers?order=asc' ) ) .returns(Promise.resolve({ data: offersResponse })); - this.server - .offers( - 'accounts', - 'GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K' - ) + .offers() .order('asc') .call() .then(function(response) { @@ -1162,11 +1163,33 @@ describe('server.js non-transaction tests', function() { }); }); - it('rejects the wrong resource', function(done) { - expect(() => this.server.offers('ledgers', '123').call()).to.throw( - /Bad resource specified/ - ); - done(); + it('accountId requests the correct endpoint', function(done) { + const offersResponse = buildOffersResponse(true); + this.axiosMock + .expects('get') + .withArgs( + sinon.match( + 'https://horizon-live.stellar.org:1337/offers?seller=GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K&order=asc' + ) + ) + .returns(Promise.resolve({ data: offersResponse })); + + this.server + .offers() + .forAccount('GBS43BF24ENNS3KPACUZVKK2VYPOZVBQO2CISGZ777RYGOPYC2FT6S3K') + .order('asc') + .call() + .then(function(response) { + expect(response.records).to.be.deep.equal( + offersResponse._embedded.records + ); + expect(response.next).to.be.function; + expect(response.prev).to.be.function; + done(); + }) + .catch(function(err) { + done(err); + }); }); }); From 2effe8709fb95345828b26581c439750e5ed4d8b Mon Sep 17 00:00:00 2001 From: Adolfo Builes Date: Mon, 3 Feb 2020 18:18:43 -0500 Subject: [PATCH 2/3] Add support for offers filtered by selling and buying asset. --- src/offer_call_builder.ts | 41 +++++++++++++++++++- test/unit/server_test.js | 80 +++++++++++++++++++++++++++++++++------ 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/offer_call_builder.ts b/src/offer_call_builder.ts index 6eb366571..21287593c 100644 --- a/src/offer_call_builder.ts +++ b/src/offer_call_builder.ts @@ -1,3 +1,4 @@ +import { Asset } from "stellar-base"; import { CallBuilder } from "./call_builder"; import { ServerApi } from "./server_api"; @@ -20,14 +21,50 @@ export class OfferCallBuilder extends CallBuilder< } /** - * Returns offers relating to a single account. + * Returns all offers where the given account is the seller. * * @see [Offers](https://www.stellar.org/developers/horizon/reference/endpoints/offers.html) * @param {string} id For example: `GDGQVOKHW4VEJRU2TETD6DBRKEO5ERCNF353LW5WBFW3JJWQ2BRQ6KDD` - * @returns {AccountCallBuilder} current AccountCallBuilder instance + * @returns {OfferCallBuilder} current OfferCallBuilder instance */ public forAccount(id: string): this { this.url.setQuery("seller", id); return this; } + + /** + * Returns all offers buying an asset. + * @see [Offers](https://www.stellar.org/developers/horizon/reference/endpoints/offers.html) + * @see Asset + * @param {Asset} value For example: `new Asset('USD','GDGQVOKHW4VEJRU2TETD6DBRKEO5ERCNF353LW5WBFW3JJWQ2BRQ6KDD')` + * @returns {OfferCallBuilder} current OfferCallBuilder instance + */ + public buying(asset: Asset): this { + if (!asset.isNative()) { + this.url.setQuery("buying_asset_type", asset.getAssetType()); + this.url.setQuery("buying_asset_code", asset.getCode()); + this.url.setQuery("buying_asset_issuer", asset.getIssuer()); + } else { + this.url.setQuery("buying_asset_type", "native"); + } + return this; + } + + /** + * Returns all offers selling an asset. + * @see [Offers](https://www.stellar.org/developers/horizon/reference/endpoints/offers.html) + * @see Asset + * @param {Asset} value For example: `new Asset('EUR','GDGQVOKHW4VEJRU2TETD6DBRKEO5ERCNF353LW5WBFW3JJWQ2BRQ6KDD')` + * @returns {OfferCallBuilder} current OfferCallBuilder instance + */ + public selling(asset: Asset): this { + if (!asset.isNative()) { + this.url.setQuery("selling_asset_type", asset.getAssetType()); + this.url.setQuery("selling_asset_code", asset.getCode()); + this.url.setQuery("selling_asset_issuer", asset.getIssuer()); + } else { + this.url.setQuery("selling_asset_type", "native"); + } + return this; + } } diff --git a/test/unit/server_test.js b/test/unit/server_test.js index 06c516156..2a4d4d205 100644 --- a/test/unit/server_test.js +++ b/test/unit/server_test.js @@ -1112,32 +1112,27 @@ describe('server.js non-transaction tests', function() { }); describe('OfferCallBuilder', function() { - function buildOffersResponse(withAccount) { - const prefix = withAccount ? '/accounts/GBCR5OVQ54S2EKHLBZMK6VYMTXZHXN3T45Y6PRX4PX4FXDMJJGY4FD42' : ''; - - return { + const offersResponse = { _embedded: { records: [] }, _links: { next: { href: - `${prefix}/offers?order=asc\u0026limit=10\u0026cursor=` + '/offers' }, prev: { href: - `${prefix}/offers?order=desc\u0026limit=10\u0026cursor=` + '/offers' }, self: { href: - `${prefix}/offers?order=asc\u0026limit=10\u0026cursor=` + '/offers' } } }; - } it('without params requests the correct endpoint', function(done) { - const offersResponse = buildOffersResponse(false); this.axiosMock .expects('get') .withArgs( @@ -1163,8 +1158,7 @@ describe('server.js non-transaction tests', function() { }); }); - it('accountId requests the correct endpoint', function(done) { - const offersResponse = buildOffersResponse(true); + it('forAccount requests the correct endpoint', function(done) { this.axiosMock .expects('get') .withArgs( @@ -1191,6 +1185,70 @@ describe('server.js non-transaction tests', function() { done(err); }); }); + it('selling requests the correct endpoint', function(done) { + const selling = new StellarSdk.Asset( + 'USD', + 'GDVDKQFP665JAO7A2LSHNLQIUNYNAAIGJ6FYJVMG4DT3YJQQJSRBLQDG' + ); + + this.axiosMock + .expects('get') + .withArgs( + sinon.match( + 'https://horizon-live.stellar.org:1337/offers?selling_asset_type=credit_alphanum4&selling_asset_code=USD&selling_asset_issuer=GDVDKQFP665JAO7A2LSHNLQIUNYNAAIGJ6FYJVMG4DT3YJQQJSRBLQDG&order=asc' + ) + ) + .returns(Promise.resolve({ data: offersResponse })); + + this.server + .offers() + .selling(selling) + .order('asc') + .call() + .then(function(response) { + expect(response.records).to.be.deep.equal( + offersResponse._embedded.records + ); + expect(response.next).to.be.function; + expect(response.prev).to.be.function; + done(); + }) + .catch(function(err) { + done(err); + }); + }); + it('buying requests the correct endpoint', function(done) { + const buying = new StellarSdk.Asset( + 'COP', + 'GDVDKQFP665JAO7A2LSHNLQIUNYNAAIGJ6FYJVMG4DT3YJQQJSRBLQDG' + ); + + this.axiosMock + .expects('get') + .withArgs( + sinon.match( + 'https://horizon-live.stellar.org:1337/offers?buying_asset_type=credit_alphanum4&buying_asset_code=COP&buying_asset_issuer=GDVDKQFP665JAO7A2LSHNLQIUNYNAAIGJ6FYJVMG4DT3YJQQJSRBLQDG&order=asc' + ) + ) + .returns(Promise.resolve({ data: offersResponse })); + + this.server + .offers() + .buying(buying) + .order('asc') + .call() + .then(function(response) { + expect(response.records).to.be.deep.equal( + offersResponse._embedded.records + ); + expect(response.next).to.be.function; + expect(response.prev).to.be.function; + done(); + }) + .catch(function(err) { + done(err); + }); + }); }); describe('OrderbookCallBuilder', function() { From 761d581d9e074889214ea000ea36cef58366222b Mon Sep 17 00:00:00 2001 From: Adolfo Builes Date: Mon, 3 Feb 2020 18:18:57 -0500 Subject: [PATCH 3/3] Prettier. --- src/federation_server.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/federation_server.ts b/src/federation_server.ts index 94c9eea26..eae7d6e9f 100644 --- a/src/federation_server.ts +++ b/src/federation_server.ts @@ -239,9 +239,7 @@ export class FederationServer { } else { return Promise.reject( new BadResponseError( - `Server query failed. Server responded: ${response.status} ${ - response.statusText - }`, + `Server query failed. Server responded: ${response.status} ${response.statusText}`, response.data, ), );