Skip to content

Commit

Permalink
feat: Export getFloatDetails, createWithdrawal and `getWithdrawal…
Browse files Browse the repository at this point in the history
…Details` (#11)
  • Loading branch information
sbp-rib authored Dec 16, 2020
1 parent a116602 commit e5aa1f2
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 185 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export { buildContext } from './modules/context';
export { isAddressValid } from './modules/validate-address';
export { buildExplorerLink } from './modules/explorer-link';
export { getChainFor, isSkybridgeChain } from './modules/chains';
export { createFloat } from './modules/pool';
export { createFloat, getFloatDetails } from './modules/pool';
export { createWithdrawal, getWithdrawalDetails } from './modules/withdrawal';
export { isSkybridgeBridge } from './modules/bridges';
export { isSkybridgeMode } from './modules/modes';
export { isSkybridgeResource } from './modules/resources';
115 changes: 115 additions & 0 deletions src/modules/generic-create/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { getBridgeFor } from '../context';
import { fetch } from '../fetch';
import { logger } from '../logger';
import { SkybridgeMode } from '../modes';
import { SkybridgeParams } from '../common-params';
import { runProofOfWork } from '../pow';
import { SkybridgeResource } from '../resources';

export type CreateParams<R extends SkybridgeResource, M extends SkybridgeMode> = {
resource: R;
/** Time in milliseconds that this method will retry in case of error before throwing. Default: `120000` (2 min.). */
timeout?: number;
} & Pick<
SkybridgeParams<R, M>,
'context' | 'addressUserIn' | 'currencyIn' | 'currencyOut' | 'amountUser'
>;

export type CreateResult<R extends SkybridgeResource, M extends SkybridgeMode> = R extends 'pool'
? Pick<
SkybridgeParams<R, M>,
| 'addressSwapIn'
| 'addressUserIn'
| 'amountIn'
| 'currencyIn'
| 'currencyOut'
| 'nonce'
| 'timestamp'
| 'hash'
>
: Pick<
SkybridgeParams<R, M>,
| 'addressSwapIn'
| 'addressUserIn'
| 'amountIn'
| 'currencyIn'
| 'currencyOut'
| 'nonce'
| 'timestamp'
| 'hash'
| 'amountOut'
>;

const INTERVAL = 2000;

export const create = async <R extends SkybridgeResource, M extends SkybridgeMode>({
timeout = 2 * 60 * 1000,
...params
}: CreateParams<R, M>): Promise<CreateResult<R, M>> =>
createRec({ ...params, startedAt: Date.now(), timeout });

const createRec = async <R extends SkybridgeResource, M extends SkybridgeMode>({
resource,
startedAt,
timeout,
...params
}: CreateParams<R, M> & { startedAt: number; timeout: number }): Promise<CreateResult<R, M>> => {
logger('Will execute create(%O).', { ...params, resource, startedAt, timeout });

const apiPathResource = resource === 'pool' ? 'floats' : 'swaps';
const { amountIn, nonce } = await runProofOfWork(params);

type ApiResponse = Pick<
SkybridgeParams<R, M>,
'amountIn' | 'amountOut' | 'currencyIn' | 'currencyOut' | 'nonce' | 'hash'
> & { timestamp: number; addressDeposit: string; addressOut: string };

const bridge = getBridgeFor(params);
const result = await fetch<ApiResponse>(
`${params.context.servers.swapNode[bridge]}/api/v1/${apiPathResource}/create`,
{
method: 'post',
body: JSON.stringify({
address_to: params.addressUserIn,
amount: amountIn,
currency_from: params.currencyIn,
currency_to: params.currencyOut,
nonce,
}),
},
);

logger(`/${apiPathResource}/create has replied: %O`, result);

if (result.ok) {
return {
amountIn: result.response.amountIn,
amountOut: result.response.amountOut,
currencyIn: result.response.currencyIn,
currencyOut: result.response.currencyOut,
hash: result.response.hash,
nonce: result.response.nonce,
addressSwapIn: result.response.addressDeposit,
addressUserIn: result.response.addressOut,
timestamp: new Date(result.response.timestamp * 1000),
} as CreateResult<R, M>;
}

if (!/the send amount does not contain a valid proof of work/.test(result.response)) {
throw new Error(`${result.status}: ${result.response}`);
}

if (Date.now() - startedAt > timeout) {
logger('PoW has been failing for more than %dms. Will throw error.', timeout);
throw new Error(`${result.status}: ${result.response}`);
}

logger('PoW failed. Will try again in %dms.', INTERVAL);
return new Promise((resolve, reject) => {
setTimeout(() => {
createRec({ ...params, startedAt, timeout, resource })
.then(resolve)
.catch(reject);
}, INTERVAL);
});
};
2 changes: 1 addition & 1 deletion src/modules/pool/createFloat/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ it.each<Pick<SkybridgeParams<'pool', 'test'>, 'addressUserIn' | 'currencyIn' | '
addressUserIn: '0x3F4341a0599f63F444B6f1e0c7C5cAf81b5843Cc',
currencyIn: 'BTC',
},
])('"/swaps/create" succeeds with %O', async ({ addressUserIn, currencyIn, amountUser }) => {
])('"/floats/create" succeeds with %O', async ({ addressUserIn, currencyIn, amountUser }) => {
jest.setTimeout(180000);
expect.assertions(1);

Expand Down
95 changes: 5 additions & 90 deletions src/modules/pool/createFloat/index.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,7 @@
import { getBridgeFor } from '../../context';
import { fetch } from '../../fetch';
import { logger } from '../../logger';
import type { SkybridgeMode } from '../../modes';
import type { SkybridgeParams } from '../../common-params';
import { runProofOfWork } from '../../pow';
import { create, CreateParams, CreateResult } from '../../generic-create';

type Params<M extends SkybridgeMode> = Pick<
SkybridgeParams<'pool', M>,
'context' | 'addressUserIn' | 'currencyIn' | 'amountUser'
>;

type Result<M extends SkybridgeMode> = Pick<
SkybridgeParams<'pool', M>,
| 'addressSwapIn'
| 'addressUserIn'
| 'amountIn'
| 'currencyIn'
| 'currencyOut'
| 'nonce'
| 'timestamp'
| 'hash'
>;

const INTERVAL = 2000;

export const createFloat = async <M extends SkybridgeMode>({
timeout = 2 * 60 * 1000,
...params
}: Params<M> & {
/** Time in milliseconds that this method will retry in case of error before throwing. Default: `120000` (2 min.). */
timeout?: number;
}): Promise<Result<M>> => createSwapRec({ ...params, startedAt: Date.now(), timeout });

const createSwapRec = async <M extends SkybridgeMode>({
startedAt,
timeout,
...params
}: Params<M> & { startedAt: number; timeout: number }): Promise<Result<M>> => {
logger('Will execute createSwap(%O).', params);

const { amountIn, nonce } = await runProofOfWork({ ...params, currencyOut: 'sbBTC' });

type ApiResponse = Pick<
SkybridgeParams<'pool', M>,
'amountIn' | 'amountOut' | 'currencyIn' | 'currencyOut' | 'nonce' | 'hash'
> & { timestamp: number; addressDeposit: string; addressOut: string };

const bridge = getBridgeFor({ ...params, currencyOut: 'sbBTC' });
const result = await fetch<ApiResponse>(
`${params.context.servers.swapNode[bridge]}/api/v1/floats/create`,
{
method: 'post',
body: JSON.stringify({
address_to: params.addressUserIn,
amount: amountIn,
currency_from: params.currencyIn,
nonce,
}),
},
);

logger('/swaps/create has replied: %O', result);

if (result.ok) {
console.log('heyyy', result.response);
return {
...result.response,
addressSwapIn: result.response.addressDeposit,
addressUserIn: result.response.addressOut,
timestamp: new Date(result.response.timestamp * 1000),
};
}

if (!/the send amount does not contain a valid proof of work/.test(result.response)) {
throw new Error(`${result.status}: ${result.response}`);
}

if (Date.now() - startedAt > timeout) {
logger('PoW has been failing for more than %dms. Will throw error.', timeout);
throw new Error(`${result.status}: ${result.response}`);
}

logger('PoW failed. Will try again in %dms.', INTERVAL);
return new Promise((resolve, reject) => {
setTimeout(() => {
createSwapRec({ ...params, startedAt, timeout })
.then(resolve)
.catch(reject);
}, INTERVAL);
});
};
export const createFloat = async <M extends SkybridgeMode>(
params: Omit<CreateParams<'pool', M>, 'resource' | 'currencyOut'>,
): Promise<CreateResult<'pool', M>> =>
create({ ...params, resource: 'pool', currencyOut: 'sbBTC' });
2 changes: 1 addition & 1 deletion src/modules/pool/getFloatDetails/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const getFloatDetails = async <M extends SkybridgeMode>({
}
} catch (e) {}

throw new Error(`Could not find swap with hash "${hash}"`);
throw new Error(`Could not find float with hash "${hash}"`);
})();

return {
Expand Down
97 changes: 5 additions & 92 deletions src/modules/swap/createSwap/index.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,6 @@
import { getBridgeFor } from '../../context';
import { fetch } from '../../fetch';
import { logger } from '../../logger';
import { SkybridgeMode } from '../../modes';
import { SkybridgeParams } from '../../common-params';
import { runProofOfWork } from '../../pow';
import type { SkybridgeMode } from '../../modes';
import { create, CreateParams, CreateResult } from '../../generic-create';

type Params<M extends SkybridgeMode> = Pick<
SkybridgeParams<'swap', M>,
'context' | 'addressUserIn' | 'currencyIn' | 'currencyOut' | 'amountUser'
>;

type Result<M extends SkybridgeMode> = Pick<
SkybridgeParams<'swap', M>,
| 'addressSwapIn'
| 'addressUserIn'
| 'amountIn'
| 'amountOut'
| 'currencyIn'
| 'currencyOut'
| 'nonce'
| 'timestamp'
| 'hash'
>;

const INTERVAL = 2000;

export const createSwap = async <M extends SkybridgeMode>({
timeout = 2 * 60 * 1000,
...params
}: Params<M> & {
/** Time in milliseconds that this method will retry in case of error before throwing. Default: `120000` (2 min.). */
timeout?: number;
}): Promise<Result<M>> => createSwapRec({ ...params, startedAt: Date.now(), timeout });

const createSwapRec = async <M extends SkybridgeMode>({
startedAt,
timeout,
...params
}: Params<M> & { startedAt: number; timeout: number }): Promise<Result<M>> => {
logger('Will execute createSwap(%O).', params);

const { amountIn, nonce } = await runProofOfWork(params);

type ApiResponse = Pick<
SkybridgeParams<'swap', M>,
'amountIn' | 'amountOut' | 'currencyIn' | 'currencyOut' | 'nonce' | 'hash'
> & { timestamp: number; addressDeposit: string; addressOut: string };

const bridge = getBridgeFor(params);
const result = await fetch<ApiResponse>(
`${params.context.servers.swapNode[bridge]}/api/v1/swaps/create`,
{
method: 'post',
body: JSON.stringify({
address_to: params.addressUserIn,
amount: amountIn,
currency_from: params.currencyIn,
currency_to: params.currencyOut,
nonce,
}),
},
);

logger('/swaps/create has replied: %O', result);

if (result.ok) {
return {
...result.response,
addressSwapIn: result.response.addressDeposit,
addressUserIn: result.response.addressOut,
timestamp: new Date(result.response.timestamp * 1000),
};
}

if (!/the send amount does not contain a valid proof of work/.test(result.response)) {
throw new Error(`${result.status}: ${result.response}`);
}

if (Date.now() - startedAt > timeout) {
logger('PoW has been failing for more than %dms. Will throw error.', timeout);
throw new Error(`${result.status}: ${result.response}`);
}

logger('PoW failed. Will try again in %dms.', INTERVAL);
return new Promise((resolve, reject) => {
setTimeout(() => {
createSwapRec({ ...params, startedAt, timeout })
.then(resolve)
.catch(reject);
}, INTERVAL);
});
};
export const createSwap = async <M extends SkybridgeMode>(
params: Omit<CreateParams<'swap', M>, 'resource'>,
): Promise<CreateResult<'swap', M>> => create({ ...params, resource: 'swap' });
44 changes: 44 additions & 0 deletions src/modules/withdrawal/createWithdrawal/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { buildContext } from '../../context';
import type { SkybridgeParams } from '../../common-params';

import { createWithdrawal } from '.';

jest.mock('../../context/buildContext');

it.skip.each<
Pick<SkybridgeParams<'withdrawal', 'test'>, 'addressUserIn' | 'currencyOut' | 'amountUser'>
>([
{
amountUser: '1',
addressUserIn: '0x3F4341a0599f63F444B6f1e0c7C5cAf81b5843Cc',
currencyOut: 'BTC',
},
{
amountUser: '1',
addressUserIn: '0x3F4341a0599f63F444B6f1e0c7C5cAf81b5843Cc',
currencyOut: 'BTC',
},
])('"/swaps/create" succeeds with %O', async ({ addressUserIn, currencyOut, amountUser }) => {
jest.setTimeout(180000);
expect.assertions(1);

try {
const context = await buildContext({ mode: 'test' });
const result = await createWithdrawal({
context,
addressUserIn,
currencyOut,
amountUser,
});
return expect(result).toMatchObject({
addressSwapIn: expect.any(String),
addressUserIn,
amountIn: expect.stringContaining('0.99'),
currencyIn: 'sbBTC',
currencyOut,
timestamp: expect.any(Date),
});
} catch (e) {
expect(e.message).toMatch(/The KVStore key \d+ already exists in epoch bucket \d+/);
}
});
7 changes: 7 additions & 0 deletions src/modules/withdrawal/createWithdrawal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { SkybridgeMode } from '../../modes';
import { create, CreateParams, CreateResult } from '../../generic-create';

export const createWithdrawal = async <M extends SkybridgeMode>(
params: Omit<CreateParams<'withdrawal', M>, 'resource' | 'currencyIn'>,
): Promise<CreateResult<'withdrawal', M>> =>
create({ ...params, resource: 'withdrawal', currencyIn: 'sbBTC' });
Loading

0 comments on commit e5aa1f2

Please sign in to comment.