Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
 into sdk-373-add-method-to-return-rfq-bid-and-ask-amounts-for-structure
  • Loading branch information
Nagaprasadvr committed Sep 12, 2023
2 parents 256b233 + 3f5c5d0 commit 9441540
Show file tree
Hide file tree
Showing 7 changed files with 623 additions and 9 deletions.
12 changes: 3 additions & 9 deletions packages/js/src/plugins/rfqModule/RfqClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ import {
GetSettlementResultInput,
getSettlementResultOperation,
getSettlementResultHandler,
GetResponseStateAndActionInput,
getResponseStateAndActionOperation,
getResponseStateAndActionHandler,
RetrieveBidAndAskInput,
retrieveBidAndAskHandler,
retrieveBidAndAskOperation,
Expand Down Expand Up @@ -497,15 +500,6 @@ export class RfqClient {
);
}

/** {@inheritDoc retrieveBidAndAskOperation} */
retrieveBidAndAsk(input: RetrieveBidAndAskInput) {
return retrieveBidAndAskHandler.handle(
retrieveBidAndAskOperation(input),
this.convergence
);
}

/** {@inheritDoc getRfqStateAndAction} */
getRfqStateAndAction(input: GetRfqStateAndActionInput) {
return getRfqStateAndActionHandler.handle(
getRfqStateAndActionOperation(input),
Expand Down
7 changes: 7 additions & 0 deletions packages/js/src/plugins/rfqModule/models/ResponseSide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ export function toSolitaQuoteSide(responseSide: ResponseSide): SolitaQuoteSide {
}
}
}

export const inverseResponseSide = (side: ResponseSide): ResponseSide => {
if (side === Bid) {
return Ask;
}
return Bid;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { DefaultingParty } from '@convergence-rfq/rfq';
import {
Rfq,
Response,
ResponseSide,
AuthoritySide,
inverseResponseSide,
} from '../models';
import { Operation, SyncOperationHandler, useOperation } from '../../../types';
import { RfqTimers } from '@/utils/classes';
const Key = 'GetResponseStateAndAction' as const;
export type ResponseState =
| 'Active'
| 'Cancelled'
| 'Expired'
| 'MakerDefaulted'
| 'TakerDefaulted'
| 'BothDefaulted'
| 'Settled'
| 'SettlingPreparations'
| 'ReadyForSettling'
| 'WaitingForLastLook'
| 'OnlyMakerPrepared'
| 'OnlyTakerPrepared'
| 'Rejected';

export type ResponseAction =
| 'Cancel'
| 'UnlockCollateral'
| 'Cleanup'
| 'Settle'
| 'Approve'
| 'Settle One Party Defaulted'
| 'Settle Both Party Defaulted'
| null;

/**
* getResponseStateAndAction.
*
* ```ts
* const result = await convergence.rfqs().getResponseStateAndAction({
* response,
* rfq,
* caller: 'taker'| 'maker,
* responseSide: 'ask' | 'bid'
* })
* ```
*
* @group Operations
* @category Constructors
*/
export const getResponseStateAndActionOperation =
useOperation<GetResponseStateAndAction>(Key);

/**
* @group Operations
* @category Types
*/
export type GetResponseStateAndAction = Operation<
typeof Key,
GetResponseStateAndActionInput,
GetResponseStateAndActionOutput
>;

/**
* @group Operations
* @category Inputs
*/
export type GetResponseStateAndActionInput = {
response: Response;
rfq: Rfq;
caller: 'taker' | 'maker';
responseSide: 'ask' | 'bid';
};

/**
* @group Operations
* @category Outputs
*/
export type GetResponseStateAndActionOutput = {
responseState: ResponseState;
responseAction: ResponseAction;
};

/**
* @group Operations
* @category Handlers
*/
export const getResponseStateAndActionHandler: SyncOperationHandler<GetResponseStateAndAction> =
{
handle: (
operation: GetResponseStateAndAction
): GetResponseStateAndActionOutput => {
const { response, caller, rfq, responseSide } = operation.input;
if (!response.rfq.equals(rfq.address)) {
throw new Error('Response does not match RFQ');
}
const rfqTimers = new RfqTimers(rfq);
const responseState = getResponseState(
response,
rfq,
rfqTimers,
responseSide
);
const responseAction = getResponseAction(response, responseState, caller);
return { responseState, responseAction };
},
};

const getResponseState = (
response: Response,
rfq: Rfq,
rfqTimers: RfqTimers,
responseSide: ResponseSide
): ResponseState => {
const rfqExpired = rfqTimers.isRfqExpired();
const settlementWindowElapsed = rfqTimers.isRfqSettlementWindowElapsed();
const confirmedInverseResponseSide =
response?.confirmed?.side === inverseResponseSide(responseSide);
const responseConfirmed = response?.confirmed !== null;
const makerPrepared = hasMakerPrepared(response, rfq);
const takerPrepared = hasTakerPrepared(response, rfq);
const defaultingParty = getDefautingParty(
response,
settlementWindowElapsed,
makerPrepared,
takerPrepared
);

if (responseConfirmed && confirmedInverseResponseSide) return 'Rejected';
switch (response.state) {
case 'active':
if (!rfqExpired) return 'Active';
return 'Expired';
case 'canceled':
return 'Cancelled';
case 'waiting-for-last-look':
if (!rfqExpired) return 'WaitingForLastLook';
return 'Expired';
case 'settling-preparations':
if (!settlementWindowElapsed) {
if (makerPrepared) return 'OnlyMakerPrepared';
if (takerPrepared) return 'OnlyTakerPrepared';
return 'SettlingPreparations';
}
switch (defaultingParty) {
case DefaultingParty.Maker:
return 'MakerDefaulted';
case DefaultingParty.Taker:
return 'TakerDefaulted';
case DefaultingParty.Both:
return 'BothDefaulted';
}
case 'ready-for-settling':
return 'ReadyForSettling';
case 'settled':
return 'Settled';
case 'defaulted':
switch (defaultingParty) {
case DefaultingParty.Maker:
return 'MakerDefaulted';
case DefaultingParty.Taker:
return 'TakerDefaulted';
case DefaultingParty.Both:
return 'BothDefaulted';
}
default:
throw new Error('Invalid Response state');
}
};

const getResponseAction = (
response: Response,
responseState: ResponseState,
caller: AuthoritySide
): ResponseAction => {
const responseConfirmed = response?.confirmed !== null;
switch (caller) {
case 'maker':
switch (responseState) {
case 'Active':
if (!responseConfirmed) return 'Cancel';
break;
case 'Expired':
case 'Cancelled':
case 'Settled':
if (response.makerCollateralLocked > 0) return 'UnlockCollateral';
if (response.makerCollateralLocked === 0) return 'Cleanup';
case 'SettlingPreparations':
case 'OnlyMakerPrepared':
case 'OnlyTakerPrepared':
case 'ReadyForSettling':
return 'Settle';
case 'MakerDefaulted':
case 'TakerDefaulted':
return 'Settle One Party Defaulted';
case 'BothDefaulted':
return 'Settle Both Party Defaulted';
case 'Rejected':
return null;
}
break;

case 'taker':
switch (responseState) {
case 'Active':
if (!responseConfirmed) return 'Approve';
break;
case 'SettlingPreparations':
case 'OnlyMakerPrepared':
case 'OnlyTakerPrepared':
case 'ReadyForSettling':
return 'Settle';
case 'MakerDefaulted':
return 'Settle One Party Defaulted';
case 'TakerDefaulted':
return 'Settle One Party Defaulted';
case 'BothDefaulted':
return 'Settle Both Party Defaulted';
case 'Settled':
case 'Expired':
case 'Cancelled':
if (response.takerCollateralLocked > 0) return 'UnlockCollateral';
if (response.takerCollateralLocked === 0) return 'Cleanup';
case 'Rejected':
return null;
}
break;
}
return null;
};

const getDefautingParty = (
response: Response,
settlementWindowElapsed: boolean,
makerPrepared: boolean,
takerPrepared: boolean
): DefaultingParty | null => {
const { defaultingParty } = response;
if (defaultingParty) return defaultingParty;
const defaulted =
settlementWindowElapsed && (!makerPrepared || !takerPrepared);

if (defaulted && defaultingParty === null) {
if (!makerPrepared && !takerPrepared) return DefaultingParty.Both;
if (!makerPrepared) return DefaultingParty.Maker;
if (!takerPrepared) return DefaultingParty.Taker;
}

return null;
};

const hasMakerPrepared = (response: Response, rfq: Rfq) => {
return response.makerPreparedLegs === rfq.legs.length;
};

const hasTakerPrepared = (response: Response, rfq: Rfq) => {
return response.takerPreparedLegs === rfq.legs.length;
};
1 change: 1 addition & 0 deletions packages/js/src/plugins/rfqModule/operations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export * from './unlockResponsesCollateral';
export * from './unlockRfqsCollateral';
export * from './getSettlementResult';
export * from './retrieveBidAndAsk';
export * from './getResponseStateAndAction';
6 changes: 6 additions & 0 deletions packages/js/src/plugins/rfqModule/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import {
cleanUpRfqsOperationHandler,
getSettlementResultOperation,
getSettlementResultHandler,
getResponseStateAndActionOperation,
getResponseStateAndActionHandler,
retrieveBidAndAskHandler,
retrieveBidAndAskOperation,
} from './operations';
Expand Down Expand Up @@ -188,6 +190,10 @@ export const rfqModule = (): ConvergencePlugin => ({
unlockRfqsCollateralOperationHandler
);
op.register(getSettlementResultOperation, getSettlementResultHandler);
op.register(
getResponseStateAndActionOperation,
getResponseStateAndActionHandler
);
op.register(getRfqStateAndActionOperation, getRfqStateAndActionHandler);

op.register(retrieveBidAndAskOperation, retrieveBidAndAskHandler);
Expand Down
25 changes: 25 additions & 0 deletions packages/js/src/utils/classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Rfq } from '@/plugins/rfqModule';

export class RfqTimers {
public timestampExpiry: Date;
public timestampStart: Date;
public timeStampSettlement: Date;

constructor(rfq: Rfq) {
this.timestampStart = new Date(Number(rfq.creationTimestamp));
this.timestampExpiry = new Date(
this.timestampStart.getTime() + Number(rfq.activeWindow) * 1000
);
this.timeStampSettlement = new Date(
this.timestampExpiry.getTime() + Number(rfq.settlingWindow) * 1000
);
}

isRfqExpired(): boolean {
return Date.now() >= this.timestampExpiry.getTime();
}

isRfqSettlementWindowElapsed(): boolean {
return Date.now() >= this.timeStampSettlement.getTime();
}
}
Loading

0 comments on commit 9441540

Please sign in to comment.