Skip to content

Commit

Permalink
chore: define swap module interface
Browse files Browse the repository at this point in the history
  • Loading branch information
neuodev authored and stackchain committed Jul 26, 2023
1 parent 91ddff8 commit f88e661
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 1,350 deletions.
46 changes: 35 additions & 11 deletions packages/swap/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
import { Swap } from '@yoroi/types';
import { SwapOrdersApi } from './orders';
import { SwapPoolsApi } from './pools';
import { SwapTokensApi } from './tokens';
import { cancelOrder, createOrder, getOrders } from './orders';
import { getPools } from './pools';
import { getTokens } from './tokens';

export class SwapApi {
public readonly orders: SwapOrdersApi;
public readonly pools: SwapPoolsApi;
public readonly tokens: SwapTokensApi;
export class SwapApi implements Swap.ISwapApi {
constructor(public readonly network: Swap.Netowrk) {}

constructor(network: Swap.Netowrk) {
this.orders = new SwapOrdersApi(network);
this.pools = new SwapPoolsApi(network);
this.tokens = new SwapTokensApi(network);
public async createOrder(
order: Swap.CreateOrderData
): ReturnType<typeof createOrder> {
return createOrder(this.network, order);
}

public async cancelOrder(
orderUTxO: string,
collateralUTxO: string,
walletAddress: string
): ReturnType<typeof cancelOrder> {
return cancelOrder(this.network, orderUTxO, collateralUTxO, walletAddress);
}

public async getOrders(stakeKeyHash: string): ReturnType<typeof getOrders> {
return getOrders(this.network, stakeKeyHash);
}

public async getPools(
tokenA: Swap.BaseTokenInfo,
tokenB: Swap.BaseTokenInfo
): ReturnType<typeof getPools> {
return getPools(this.network, tokenA, tokenB);
}

public getTokens(
policyId = '',
assetName = ''
): ReturnType<typeof getTokens> {
return getTokens(this.network, policyId, assetName);
}
}
27 changes: 10 additions & 17 deletions packages/swap/api/orders.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi, Mocked } from 'vitest';
import { SwapOrdersApi } from './orders';
import { createOrder, cancelOrder, getOrders } from './orders';
import axios from 'axios';
import { axiosClient } from './config';

Expand All @@ -18,16 +18,6 @@ const GENS_TOKEN = {
const mockAxios = axiosClient as Mocked<typeof axios>;

describe('SwapOrdersApi', () => {
const api: SwapOrdersApi = new SwapOrdersApi('mainnet');

it('should be initialized for mainnet or testnet', () => {
const mainnet = new SwapOrdersApi('mainnet');
expect(mainnet.network).to.eq('mainnet');

const preprod = new SwapOrdersApi('preprod');
expect(preprod.network).to.eq('preprod');
});

describe('getOrders', () => {
it('Should return orders list using staking key hash', async () => {
mockAxios.get.mockImplementationOnce(() =>
Expand All @@ -36,7 +26,8 @@ describe('SwapOrdersApi', () => {
status: 200,
})
);
const result = await api.getOrders(
const result = await getOrders(
'preprod',
'24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7'
);
expect(result).to.have.lengthOf(1);
Expand All @@ -52,7 +43,7 @@ describe('SwapOrdersApi', () => {
})
);

const order = await api.createOrder(createOrderParams);
const order = await createOrder('mainnet', createOrderParams);

expect(order.contractAddress).to.eq(mockedCreateOrderResult.address);
expect(order.datum).to.eq(mockedCreateOrderResult.datum);
Expand All @@ -67,7 +58,7 @@ describe('SwapOrdersApi', () => {
data: { status: 'failed', reason: 'error_message' },
})
);
await api.createOrder(createOrderParams);
await createOrder('preprod', createOrderParams);
}).rejects.toThrowError(/^error_message$/);
});

Expand All @@ -76,7 +67,7 @@ describe('SwapOrdersApi', () => {
mockAxios.get.mockImplementationOnce(() =>
Promise.resolve({ status: 400 })
);
await api.createOrder(createOrderParams);
await createOrder('mainnet', createOrderParams);
}).rejects.toThrow('Failed to construct swap datum');
});
});
Expand All @@ -90,7 +81,8 @@ describe('SwapOrdersApi', () => {
})
);

const txCbor = await api.cancelOrder(
const txCbor = await cancelOrder(
'mainnet',
'orderUtxo',
'collateralUtxo',
'addr1'
Expand All @@ -104,7 +96,8 @@ describe('SwapOrdersApi', () => {
mockAxios.get.mockImplementationOnce(() =>
Promise.resolve({ status: 400 })
);
await api.cancelOrder(
await cancelOrder(
'mainnet',
cancelOrderParams.utxo,
cancelOrderParams.collateralUTxOs,
cancelOrderParams.address
Expand Down
172 changes: 78 additions & 94 deletions packages/swap/api/orders.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,94 @@
import { Swap } from '@yoroi/types';
import { SWAP_API_ENDPOINTS, axiosClient } from './config';

// todo: use axios params
export async function createOrder(
network: Swap.Netowrk,
order: Swap.CreateOrderData
): Promise<Swap.CreateOrderResponse> {
const apiUrl = SWAP_API_ENDPOINTS[network].constructSwapDatum;
const response = await axiosClient.get<
| { status: 'failed'; reason?: string }
| { status: 'success'; hash: string; datum: string; address: string }
>('/', {
baseURL: apiUrl,
params: {
walletAddr: order.address,
protocol: order.protocol,
poolId: order.poolId,
sellTokenPolicyID: order.sell.policyId,
sellTokenNameHex: order.sell.assetName,
sellAmount: order.sell.amount,
buyTokenPolicyID: order.buy.policyId,
buyTokenNameHex: order.buy.assetName,
buyAmount: order.buy.amount,
},
});

export class SwapOrdersApi {
private readonly constructSwapDatumApiUrl: string;
private readonly cancelSwapTransactionApiUrl: string;
private readonly getOrdersApiUrl: string;

constructor(public readonly network: Swap.Netowrk) {
const { constructSwapDatum, cancelSwapTransaction, getOrders } =
SWAP_API_ENDPOINTS[network];
this.constructSwapDatumApiUrl = constructSwapDatum;
this.cancelSwapTransactionApiUrl = cancelSwapTransaction;
this.getOrdersApiUrl = getOrders;
}

/**
* @param order the order to construct the datum for and the address to send the order to.
* @returns the order datum, order datum hash, and address to send the order to.
*/
public async createOrder(
order: Swap.Order
): Promise<Record<'datumHash' | 'datum' | 'contractAddress', string>> {
const response = await axiosClient.get<
| { status: 'failed'; reason?: string }
| { status: 'success'; hash: string; datum: string; address: string }
>('/', {
baseURL: this.constructSwapDatumApiUrl,
params: {
walletAddr: order.address,
protocol: order.protocol,
poolId: order.poolId,
sellTokenPolicyID: order.sell.policyId,
sellTokenNameHex: order.sell.assetName,
sellAmount: order.sell.amount,
buyTokenPolicyID: order.buy.policyId,
buyTokenNameHex: order.buy.assetName,
buyAmount: order.buy.amount,
},
if (response.status !== 200) {
throw new Error('Failed to construct swap datum', {
cause: response.data,
});
}

if (response.status !== 200) {
throw new Error('Failed to construct swap datum', {
cause: response.data,
});
}

if (response.data.status === 'failed') {
throw new Error(response.data.reason || 'Unexpected error occurred');
}

return {
datumHash: response.data.hash,
datum: response.data.datum,
contractAddress: response.data.address,
};
if (response.data.status === 'failed') {
throw new Error(response.data.reason || 'Unexpected error occurred');
}

/**
* @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0"
* @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format.
* @param walletAddress address of the wallet that owns the order in cbor format.
* @returns an unsigned transaction to cancel the order.
*/
public async cancelOrder(
orderUTxO: string,
collateralUTxO: string,
walletAddress: string
): Promise<string> {
const response = await axiosClient.get('/', {
baseURL: this.cancelSwapTransactionApiUrl,
params: {
wallet: walletAddress,
utxo: orderUTxO,
collateralUTxO,
},
});
return {
datumHash: response.data.hash,
datum: response.data.datum,
contractAddress: response.data.address,
};
}

if (response.status !== 200) {
throw new Error('Failed to cancel swap transaction', {
cause: response.data,
});
}
/**
* @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0"
* @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format.
* @param walletAddress address of the wallet that owns the order in cbor format.
* @returns an unsigned transaction to cancel the order.
*/
export async function cancelOrder(
network: Swap.Netowrk,
orderUTxO: string,
collateralUTxO: string,
walletAddress: string
): Promise<string> {
const apiUrl = SWAP_API_ENDPOINTS[network].cancelSwapTransaction;
const response = await axiosClient.get('/', {
baseURL: apiUrl,
params: {
wallet: walletAddress,
utxo: orderUTxO,
collateralUTxO,
},
});

return response.data.cbor;
if (response.status !== 200) {
throw new Error('Failed to cancel swap transaction', {
cause: response.data,
});
}

/**
* @param stakeKeyHash the stake key hash of the wallet to get orders for.
* @returns all unfufilled orders for the given stake key hash.
*/
public async getOrders(stakeKeyHash: string): Promise<Swap.OpenOrder[]> {
const response = await axiosClient.get<Swap.OpenOrder[]>('/', {
baseURL: this.getOrdersApiUrl,
params: {
'stake-key-hash': stakeKeyHash,
},
});
return response.data.cbor;
}

if (response.status !== 200) {
throw new Error(`Failed to get orders for ${stakeKeyHash}`, {
cause: response.data,
});
}
export async function getOrders(
network: Swap.Netowrk,
stakeKeyHash: string
): Promise<Swap.OpenOrder[]> {
const apiUrl = SWAP_API_ENDPOINTS[network].getPools;
const response = await axiosClient.get<Swap.OpenOrder[]>('/', {
baseURL: apiUrl,
params: {
'stake-key-hash': stakeKeyHash,
},
});

return response.data;
if (response.status !== 200) {
throw new Error(`Failed to get orders for ${stakeKeyHash}`, {
cause: response.data,
});
}

return response.data;
}
11 changes: 7 additions & 4 deletions packages/swap/api/pools.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { describe, expect, it, vi, Mocked } from 'vitest';
import { SwapPoolsApi } from './pools';
import { getPools } from './pools';
import { axiosClient } from './config';

vi.mock('./config.ts');
const mockAxios = axiosClient as Mocked<typeof axiosClient>;

describe('SwapPoolsApi', () => {
const api = new SwapPoolsApi('preprod');
it('should get pools list for a given token pair', async () => {
mockAxios.get.mockImplementationOnce(() =>
Promise.resolve({
Expand All @@ -15,7 +14,11 @@ describe('SwapPoolsApi', () => {
})
);

const result = await api.getPools(getPoolsParams.sell, getPoolsParams.buy);
const result = await getPools(
'mainnet',
getPoolsParams.sell,
getPoolsParams.buy
);
expect(result).to.be.of.lengthOf(1);
});

Expand All @@ -24,7 +27,7 @@ describe('SwapPoolsApi', () => {
mockAxios.get.mockImplementationOnce(() =>
Promise.resolve({ status: 500 })
);
await api.getPools(getPoolsParams.sell, getPoolsParams.buy);
await getPools('preprod', getPoolsParams.sell, getPoolsParams.buy);
}).rejects.toThrow('Failed to fetch pools for token pair');
});
});
Expand Down
Loading

0 comments on commit f88e661

Please sign in to comment.