Skip to content

Commit

Permalink
Added Cancel message support (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
thehenrytsai authored Aug 13, 2024
1 parent 8908f85 commit 0a9394c
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-horses-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tbdex/protocol": minor
---

Added Cancel message support
2 changes: 2 additions & 0 deletions packages/protocol/build/compile-validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import standaloneCode from 'ajv/dist/standalone/index.js'

import { mkdirp } from 'mkdirp'

import CancelSchema from '../../../tbdex/hosted/json-schemas/cancel.schema.json' assert { type: 'json' }
import CloseSchema from '../../../tbdex/hosted/json-schemas/close.schema.json' assert { type: 'json' }
import DefinitionsSchema from '../../../tbdex/hosted/json-schemas/definitions.json' assert { type: 'json' }
import OfferingSchema from '../../../tbdex/hosted/json-schemas/offering.schema.json' assert { type: 'json' }
Expand All @@ -31,6 +32,7 @@ import RfqPrivateSchema from '../../../tbdex/hosted/json-schemas/rfq-private.sch
import RfqSchema from '../../../tbdex/hosted/json-schemas/rfq.schema.json' assert { type: 'json' }

const schemas = {
cancel : CancelSchema,
close : CloseSchema,
definitions : DefinitionsSchema,
offering : OfferingSchema,
Expand Down
16 changes: 11 additions & 5 deletions packages/protocol/src/exchange.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Close, Order, OrderInstructions, OrderStatus, Quote, Rfq } from './message-kinds/index.js'
import { Cancel, Close, Order, OrderInstructions, OrderStatus, Quote, Rfq } from './message-kinds/index.js'
import { Message } from './message.js'
import { MessageKind } from './types.js'

Expand All @@ -24,8 +24,10 @@ export class Exchange {
orderInstructions: OrderInstructions | undefined
/** Message sent by the PFI to Alice to convey the current status of the order */
orderstatus: OrderStatus[]
/** Message sent by either the PFI or Alice to terminate an exchange */
/** Message sent by the PFI to terminate an exchange */
close: Close | undefined
/** Message sent by Alice to indicate that she does not wish to further propagate the exchange, and get a refund if applicable */
cancel: Cancel | undefined

constructor() {
this.orderstatus = []
Expand Down Expand Up @@ -81,7 +83,9 @@ export class Exchange {
this.rfq = message
} else if (message.isQuote()) {
this.quote = message
} else if (message.isClose()) {
} else if (message.isCancel()) {
this.cancel = message
} else if (message.isClose()) {
this.close = message
} else if (message.isOrder()) {
this.order = message
Expand Down Expand Up @@ -109,7 +113,8 @@ export class Exchange {
* Latest message in an exchange if there are any messages currently
*/
get latestMessage(): Message | undefined {
return this.close ??
return this.cancel ??
this.close ??
this.orderstatus[this.orderstatus.length - 1] ??
this.orderInstructions ??
this.order ??
Expand Down Expand Up @@ -141,7 +146,8 @@ export class Exchange {
this.order,
this.orderInstructions,
...this.orderstatus,
this.close
this.close,
this.cancel
]
return allPossibleMessages.filter((message): message is Message => message !== undefined)
}
Expand Down
73 changes: 73 additions & 0 deletions packages/protocol/src/message-kinds/cancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { MessageKind, MessageModel, CancelData, CancelMetadata } from '../types.js'
import { Message } from '../message.js'
import { Parser } from '../parser.js'

/**
* Options passed to {@link Cancel.create}
* @beta
*/
export type CreateCancelOptions = {
data: CancelData
metadata: Omit<CancelMetadata, 'id' |'kind' | 'createdAt' | 'protocol'> & { protocol?: CancelMetadata['protocol'] }
}

/**
* Sent by Alice to indicate that she does not wish to further propagate the exchange, and get a refund if applicable.
* @beta
*/
export class Cancel extends Message {
/** The message kind `cancel`. */
readonly kind = 'cancel'

/** A set of valid Message kinds that can come after a Cancel */
readonly validNext = new Set<MessageKind>(['orderstatus', 'close'])

/** Metadata such as sender, recipient, date created, and ID */
readonly metadata: CancelMetadata

/** Cancel's data containing a reason why the exchange was canceled */
readonly data: CancelData

constructor(metadata: CancelMetadata, data: CancelData, signature?: string) {
super(metadata, data, signature)
this.metadata = metadata
this.data = data
}

/**
* Parses a JSON message into a Cancel.
* @param rawMessage - The Cancel to parse.
* @throws Error if the Cancel could not be parsed or is not a valid Cancel.
* @returns The parsed Cancel.
*/
static async parse(rawMessage: MessageModel | string): Promise<Cancel> {
const jsonMessage = Parser.rawToMessageModel(rawMessage)

const cancel = new Cancel(
jsonMessage.metadata as CancelMetadata,
jsonMessage.data as CancelData,
jsonMessage.signature
)

await cancel.verify()
return cancel
}

/**
* Creates an Cancel with the given options.
* @param opts - Options to create an Cancel.
*/
static create(opts: CreateCancelOptions): Cancel {
const metadata: CancelMetadata = {
...opts.metadata,
kind : 'cancel',
id : Message.generateId('cancel'),
createdAt : new Date().toISOString(),
protocol : opts.metadata.protocol ?? '1.0'
}

const cancel = new Cancel(metadata, opts.data)
cancel.validateData()
return cancel
}
}
13 changes: 5 additions & 8 deletions packages/protocol/src/message-kinds/close.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,19 @@ export type CreateCloseOptions = {
}

/**
* A Close can be sent by Alice or the PFI as a reply to an RFQ or a Quote
* A Close can only be sent the PFI as a reply to an RFQ, Quote, Order, OrderInstructions, OrderStatus, or Cancel
* @beta
*/
export class Close extends Message {
/** A set of valid Message kinds that can come after a close */
readonly validNext = new Set<MessageKind>([])
/** The message kind (close) */
readonly kind = 'close'

/** A set of valid Message kinds that can come after a close */
readonly validNext = new Set<MessageKind>([])

/** Metadata such as sender, recipient, date created, and ID */
readonly metadata: CloseMetadata

/** Close's data containing a reason why the exchange was closed */
readonly data: CloseData

Expand Down Expand Up @@ -68,9 +70,4 @@ export class Close extends Message {
close.validateData()
return close
}

/** an explanation of why the exchange is being closed */
get reason() {
return this.data.reason
}
}
1 change: 1 addition & 0 deletions packages/protocol/src/message-kinds/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './order.js'
export * from './order-instructions.js'
export * from './order-status.js'
export * from './close.js'
export * from './cancel.js'
2 changes: 1 addition & 1 deletion packages/protocol/src/message-kinds/order-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type CreateOrderInstructionsOptions = {
*/
export class OrderInstructions extends Message {
/** A set of valid Message kinds that can come after an Order Instructions */
readonly validNext = new Set<MessageKind>(['orderstatus', 'close'])
readonly validNext = new Set<MessageKind>(['orderstatus', 'close', 'cancel'])

/** The message kind `orderinstructions`. */
readonly kind = 'orderinstructions'
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/src/message-kinds/order-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type CreateOrderStatusOptions = {
*/
export class OrderStatus extends Message {
/** a set of valid Message kinds that can come after an order status */
readonly validNext = new Set<MessageKind>(['orderstatus', 'close'])
readonly validNext = new Set<MessageKind>(['orderstatus', 'close', 'cancel'])
/** The message kind (orderstatus) */
readonly kind = 'orderstatus'

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/src/message-kinds/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type CreateOrderOptions = {
*/
export class Order extends Message {
/** a set of valid Message kinds that can come after an order */
readonly validNext = new Set<MessageKind>(['orderinstructions', 'close'])
readonly validNext = new Set<MessageKind>(['orderinstructions', 'close', 'cancel'])

/** The message kind (order) */
readonly kind = 'order'
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/src/message-kinds/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type CreateQuoteOptions = {
*/
export class Quote extends Message {
/** a set of valid Message kinds that can come after a quote */
readonly validNext = new Set<MessageKind>(['order', 'close'])
readonly validNext = new Set<MessageKind>(['order', 'close', 'cancel'])
/** The message kind (quote) */
readonly kind = 'quote'

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/src/message-kinds/rfq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type ParseRfqOptions = {
*/
export class Rfq extends Message {
/** a set of valid Message kinds that can come after an rfq */
readonly validNext = new Set<MessageKind>(['quote', 'close'])
readonly validNext = new Set<MessageKind>(['quote', 'close', 'cancel'])
/** The message kind (rfq) */
readonly kind = 'rfq'

Expand Down
7 changes: 6 additions & 1 deletion packages/protocol/src/message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MessageKind, MessageModel, MessageMetadata, MessageData } from './types.js'
import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close } from './message-kinds/index.js'
import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close, Cancel } from './message-kinds/index.js'

import { Crypto } from './crypto.js'
import { typeid } from 'typeid-js'
Expand Down Expand Up @@ -188,6 +188,11 @@ export abstract class Message {
return this.metadata.kind === 'close'
}

/** Cancel type guard */
isCancel(): this is Cancel {
return this.metadata.kind === 'cancel'
}

/**
* returns the message as a json object. Automatically used by `JSON.stringify` method.
*/
Expand Down
12 changes: 10 additions & 2 deletions packages/protocol/src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { MessageModel, ResourceModel, RfqMetadata, RfqData, QuoteData, QuoteMetadata, OrderData, OrderMetadata, OrderStatusMetadata, OrderStatusData, CloseMetadata, CloseData, OfferingMetadata, OfferingData, BalanceMetadata, BalanceData, OrderInstructionsMetadata, OrderInstructionsData } from './types.js'
import type { MessageModel, ResourceModel, RfqMetadata, RfqData, QuoteData, QuoteMetadata, OrderData, OrderMetadata, OrderStatusMetadata, OrderStatusData, CancelData, CancelMetadata, CloseMetadata, CloseData, OfferingMetadata, OfferingData, BalanceMetadata, BalanceData, OrderInstructionsMetadata, OrderInstructionsData } from './types.js'

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Resource } from './resource.js'
import type { Message } from './message.js'

import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close } from './message-kinds/index.js'
import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close, Cancel } from './message-kinds/index.js'
import { Balance, Offering } from './resource-kinds/index.js'

/**
Expand Down Expand Up @@ -77,6 +77,14 @@ export class Parser {
)
break

case 'cancel':
message = new Cancel(
jsonMessage.metadata as CancelMetadata,
jsonMessage.data as CancelData,
jsonMessage.signature
)
break

default:
throw new Error(`Unrecognized message kind (${jsonMessage.metadata.kind})`)
}
Expand Down
21 changes: 18 additions & 3 deletions packages/protocol/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,23 @@ export type OrderStatusMetadata = MessageMetadata & { kind: 'orderstatus' }
*/
export type CloseMetadata = MessageMetadata & { kind: 'close' }

/**
* Cancel's metadata
* @beta
*/
export type CancelMetadata = MessageMetadata & { kind: 'cancel' }

/**
* Type alias to represent a set of message kind string keys
* @beta
*/
export type MessageKind = 'rfq' | 'quote' | 'order' | 'orderinstructions' | 'orderstatus' | 'close'
export type MessageKind = 'rfq' | 'quote' | 'order' | 'orderinstructions' | 'orderstatus' | 'close' | 'cancel'

/**
* Message's data
* @beta
*/
export type MessageData = RfqData | QuoteData | OrderData | OrderInstructionsData | OrderStatusData | CloseData
export type MessageData = RfqData | QuoteData | OrderData | OrderInstructionsData | OrderStatusData | CloseData | CancelData

/**
* Data contained in a RFQ message
Expand Down Expand Up @@ -478,7 +484,7 @@ export enum OrderStatusEnum {
}

/**
* A Close can be sent by Alice or the PFI as a reply to an RFQ or a Quote
* A Close, which only the PFI can send, can be submitted at any point during the exchange to indicate that the transaction has reached a terminal state.
* @beta
*/
export type CloseData = {
Expand All @@ -487,3 +493,12 @@ export type CloseData = {
/** indicates whether or not the exchange successfully completed */
success?: boolean
}

/**
* A Cancel can only be sent by Alice to indicate that she does not wish to further propagate the exchange, and get a refund if applicable.
* @beta
*/
export type CancelData = {
/** an explanation of why the exchange is being canceled */
reason?: string
}
Loading

0 comments on commit 0a9394c

Please sign in to comment.