Skip to content

Commit

Permalink
Adds a basic call builder for the /liquidity_pools endpoint. (#682)
Browse files Browse the repository at this point in the history
* Add call builder for liquidity pools, w/ filters by reserve asset
  • Loading branch information
Shaptic committed Aug 28, 2021
1 parent ede9372 commit e43ab21
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 3 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

A breaking change will get clearly marked in this log.


## Unreleased


## [v9.0.0](https://github.com/stellar/js-stellar-sdk/compare/v8.2.5...v9.0.0)

- Update `stellar-base` version to `6.0.1`.
### Add
- Introduced a `LiquidityPoolCallBuilder` to make calls to the new `/liquidity_pools` endpoint, including filtering by reserve asset ([#682](https://github.com/stellar/js-stellar-sdk/pull/682)).

### Update
- Update `stellar-base` version to `6.0.1` ([#681](https://github.com/stellar/js-stellar-sdk/pull/681)).

### Fix
- Updated various developer dependencies to secure versions ([#671](https://github.com/stellar/js-stellar-sdk/pull/671)).
Expand Down
4 changes: 4 additions & 0 deletions src/horizon_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ export namespace Horizon {
num_sponsored: number;
}

export enum LiquidityPoolType {
constantProduct = "constant_product",
}

export enum OperationResponseType {
createAccount = "create_account",
payment = "payment",
Expand Down
39 changes: 39 additions & 0 deletions src/liquidity_pool_call_builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Asset } from "stellar-base";

import { CallBuilder } from "./call_builder";
import { ServerApi } from "./server_api";

/**
* Creates a new {@link LiquidityPoolCallBuilder} pointed to server defined by serverUrl.
* Do not create this object directly, use {@link Server#liquidityPools}.
*
* @class LiquidityPoolCallBuilder
* @extends CallBuilder
* @constructor
* @param {string} serverUrl Horizon server URL.
*/
export class LiquidityPoolCallBuilder extends CallBuilder<
ServerApi.CollectionPage<ServerApi.LiquidityPoolRecord>
> {
constructor(serverUrl: URI) {
super(serverUrl);
this.url.segment("liquidity_pools");
}

/**
* Filters out liquidity pools whose reserves aren't in this list of assets.
*
* @see Asset
* @param {Asset[]} assets
* @returns {LiquidityPoolCallBuilder} current LiquidityPoolCallBuilder instance
*/
public forAssets(...assets: Asset[]) {
const commaSeparatedAssets: string = assets
.map((asset: Asset) => {
return asset.toString();
})
.join(",");
this.url.setQuery("reserves", commaSeparatedAssets);
return this;
}
}
9 changes: 9 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { EffectCallBuilder } from "./effect_call_builder";
import { FriendbotBuilder } from "./friendbot_builder";
import { Horizon } from "./horizon_api";
import { LedgerCallBuilder } from "./ledger_call_builder";
import { LiquidityPoolCallBuilder } from "./liquidity_pool_call_builder";
import { OfferCallBuilder } from "./offer_call_builder";
import { OperationCallBuilder } from "./operation_call_builder";
import { OrderbookCallBuilder } from "./orderbook_call_builder";
Expand Down Expand Up @@ -577,6 +578,14 @@ export class Server {
return new OperationCallBuilder(URI(this.serverURL as any));
}

/**
* @returns {LiquidityPoolCallBuilder} New {@link LiquidityPoolCallBuilder}
* object configured to the current Horizon server settings.
*/
public liquidityPools(): LiquidityPoolCallBuilder {
return new LiquidityPoolCallBuilder(URI(this.serverURL));
}

/**
* The Stellar Network allows payments to be made between assets through path
* payments. A strict receive path payment specifies a series of assets to
Expand Down
12 changes: 12 additions & 0 deletions src/server_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ export namespace ServerApi {
payments: CallCollectionFunction<PaymentOperationRecord>;
trades: CallCollectionFunction<TradeRecord>;
}
export interface LiquidityPoolRecord extends Horizon.BaseResponse {
id: string;
paging_token: string;
fee_bp: number;
type: Horizon.LiquidityPoolType;
total_trustlines: string;
total_shares: string;
reserves: {
amount: string;
asset: string;
};
}
interface EffectRecordMethods {
operation?: CallFunction<OperationRecord>;
precedes?: CallFunction<EffectRecord>;
Expand Down
2 changes: 0 additions & 2 deletions test/unit/call_builders_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const URI = require("urijs");
const CallBuilder = require('../../lib/call_builder').CallBuilder;

describe('CallBuilder functions', function() {

it('doesn\'t mutate the constructor passed url argument (it clones it instead)', function() {
let arg = URI('https://onedom.ain/');
const builder = new CallBuilder(arg);
Expand All @@ -12,6 +11,5 @@ describe('CallBuilder functions', function() {
expect(arg.toString()).not.to.be.equal('https://onedom.ain/one_segment'); // https://onedom.ain/
expect(builder.url.toString()).to.be.equal('https://onedom.ain/one_segment');
});

});

144 changes: 144 additions & 0 deletions test/unit/liquidity_pool_endpoints_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Helper function to deep-copy JSON responses.
function copyJson(js) {
return JSON.parse(JSON.stringify(js));
}

const BASE_URL = "https://horizon-live.stellar.org:1337";
const LP_URL = BASE_URL + "/liquidity_pools"


describe('/liquidity_pools tests', function() {
beforeEach(function() {
this.server = new StellarSdk.Server(BASE_URL);
this.axiosMock = sinon.mock(HorizonAxiosClient);
StellarSdk.Config.setDefault();
});

afterEach(function() {
this.axiosMock.verify();
this.axiosMock.restore();
});

it('can create a LiquidityPoolCallBuilder', function() {
expect(this.server.liquidityPools()).not.to.be.undefined;
});

const rootResponse = {
"_links": {
"self": {
"href": "https://private-33c60-amm3.apiary-mock.com/liquidity_pools?cursor=113725249324879873&limit=10&order=asc"
},
"next": {
"href": "https://private-33c60-amm3.apiary-mock.com/liquidity_pools?cursor=113725249324879873&limit=10&order=asc"
},
"prev": {
"href": "https://private-33c60-amm3.apiary-mock.com/liquidity_pools?cursor=113725249324879873&limit=10&order=desc"
}
},
"_embedded": {
"records": [
{
"id": "1",
"paging_token": "113725249324879873",
"fee_bp": 30,
"type": "constant_product",
"total_trustlines": "300",
"total_shares": "5000",
"reserves": [
{
"amount": "1000.0000005",
"asset": "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S"
},
{
"amount": "2000.0000000",
"asset": "PHP:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S"
}
]
},
{
"id": "2",
"paging_token": "113725249324879874",
"fee_bp": 30,
"type": "constant_product",
"total_trustlines": "200",
"total_shares": "3500",
"reserves": [
{
"amount": "1000.0000005",
"asset": "EURT:GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S"
},
{
"amount": "1200.0000000",
"asset": "USDC:GC5W3BH2MQRQK2H4A6LP3SXDSAAY2W2W64OWKKVNQIAOVWSAHFDEUSDC"
}
]
}
]
}
}

let emptyResponse = copyJson(rootResponse);
emptyResponse._embedded.records = [];

let phpResponse = copyJson(rootResponse);
phpResponse._embedded.records.pop(); // last elem doesn't have PHP asset

const EURT = new StellarSdk.Asset("EURT", "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S")
const PHP = new StellarSdk.Asset("PHP", "GAP5LETOV6YIE62YAM56STDANPRDO7ZFDBGSNHJQIYGGKSMOZAHOOS2S")

it('returns the right root response', function(done) {
this.axiosMock
.expects('get')
.withArgs(sinon.match(LP_URL))
.returns(Promise.resolve({ data: rootResponse }));

this.server
.liquidityPools()
.call()
.then((pools) => {
expect(pools.records).to.deep.equal(rootResponse._embedded.records);
done();
})
.catch(done);
});

describe('filtering by asset', function() {
const testCases = [
{
assets: [StellarSdk.Asset.native()],
response: emptyResponse,
}, {
assets: [EURT],
response: rootResponse,
}, {
assets: [PHP],
response: phpResponse,
}, {
assets: [EURT, PHP],
response: rootResponse,
},
];

testCases.forEach((testCase) => {
const queryStr = testCase.assets.map(asset => asset.toString()).join(',');
const description = testCase.assets.map(asset => asset.getCode()).join(' + ');

it('filters by asset(s) ' + description, function(done) {
this.axiosMock
.expects('get')
.withArgs(sinon.match(`${LP_URL}?reserves=${encodeURIComponent(queryStr)}`))
.returns(Promise.resolve({ data: testCase.response }))

this.server
.liquidityPools()
.forAssets(...testCase.assets)
.call()
.then((pools) => {
expect(pools.records).to.deep.equal(testCase.response._embedded.records);
done();
})
.catch(done);
});
});
});
});

0 comments on commit e43ab21

Please sign in to comment.