Skip to content

Commit

Permalink
Added OrderInstructions message (#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
thehenrytsai authored Aug 8, 2024
1 parent cad5c4e commit 8908f85
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 29 deletions.
5 changes: 5 additions & 0 deletions .changeset/green-beans-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tbdex/protocol": minor
---

Added OrderInstructions message.
2 changes: 2 additions & 0 deletions packages/protocol/build/compile-validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import OfferingSchema from '../../../tbdex/hosted/json-schemas/offering.schema.j
import BalanceSchema from '../../../tbdex/hosted/json-schemas/balance.schema.json' assert { type: 'json' }
import MessageSchema from '../../../tbdex/hosted/json-schemas/message.schema.json' assert { type: 'json' }
import OrderSchema from '../../../tbdex/hosted/json-schemas/order.schema.json' assert { type: 'json' }
import OrderInstructionsSchema from '../../../tbdex/hosted/json-schemas/orderinstructions.schema.json' assert { type: 'json' }
import OrderstatusSchema from '../../../tbdex/hosted/json-schemas/orderstatus.schema.json' assert { type: 'json' }
import QuoteSchema from '../../../tbdex/hosted/json-schemas/quote.schema.json' assert { type: 'json' }
import ResourceSchema from '../../../tbdex/hosted/json-schemas/resource.schema.json' assert { type: 'json' }
Expand All @@ -36,6 +37,7 @@ const schemas = {
balance : BalanceSchema,
message : MessageSchema,
order : OrderSchema,
orderinstructions : OrderInstructionsSchema,
orderstatus : OrderstatusSchema,
quote : QuoteSchema,
resource : ResourceSchema,
Expand Down
12 changes: 9 additions & 3 deletions packages/protocol/src/exchange.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Close, Order, OrderStatus, Quote, Rfq } from './message-kinds/index.js'
import { Close, Order, OrderInstructions, OrderStatus, Quote, Rfq } from './message-kinds/index.js'
import { Message } from './message.js'
import { MessageKind } from './types.js'

Expand All @@ -18,9 +18,11 @@ export class Exchange {
rfq: Rfq | undefined
/** Message sent by the PFI in response to an RFQ */
quote: Quote | undefined
/** Message sent by Alice to the PFI to accept a quote*/
/** Message sent by Alice to the PFI to accept a quote */
order: Order | undefined
/** Message sent by the PFI to Alice to convet the current status of the order */
/** Message sent by the PFI to Alice to give payin and payout instructions */
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 */
close: Close | undefined
Expand Down Expand Up @@ -83,6 +85,8 @@ export class Exchange {
this.close = message
} else if (message.isOrder()) {
this.order = message
} else if (message.isOrderInstructions()) {
this.orderInstructions = message
} else if (message.isOrderStatus()) {
this.orderstatus.push(message)
} else {
Expand All @@ -107,6 +111,7 @@ export class Exchange {
get latestMessage(): Message | undefined {
return this.close ??
this.orderstatus[this.orderstatus.length - 1] ??
this.orderInstructions ??
this.order ??
this.quote ??
this.rfq
Expand Down Expand Up @@ -134,6 +139,7 @@ export class Exchange {
this.rfq,
this.quote,
this.order,
this.orderInstructions,
...this.orderstatus,
this.close
]
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/message-kinds/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './rfq.js'
export * from './quote.js'
export * from './order.js'
export * from './order-instructions.js'
export * from './order-status.js'
export * from './close.js'
73 changes: 73 additions & 0 deletions packages/protocol/src/message-kinds/order-instructions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { MessageKind, MessageModel, OrderInstructionsData, OrderInstructionsMetadata } from '../types.js'
import { Message } from '../message.js'
import { Parser } from '../parser.js'

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

/**
* Sent by the PFI to Alice to convey payment instructions.
* @beta
*/
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'])

/** The message kind `orderinstructions`. */
readonly kind = 'orderinstructions'

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

/** OrderInstructions' specific data containing payin and payout instructions */
readonly data: OrderInstructionsData

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

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

const orderInstructions = new OrderInstructions(
jsonMessage.metadata as OrderInstructionsMetadata,
jsonMessage.data as OrderInstructionsData,
jsonMessage.signature
)

await orderInstructions.verify()
return orderInstructions
}

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

const orderInstructions = new OrderInstructions(metadata, opts.data)
orderInstructions.validateData()
return orderInstructions
}
}
4 changes: 3 additions & 1 deletion packages/protocol/src/message-kinds/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ 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>(['orderstatus'])
readonly validNext = new Set<MessageKind>(['orderinstructions', 'close'])

/** The message kind (order) */
readonly kind = 'order'

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

/** Order's data */
readonly data: OrderData

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, OrderStatus, Close } from './message-kinds/index.js'
import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close } from './message-kinds/index.js'

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

/** OrderInstructions type guard */
isOrderInstructions(): this is OrderInstructions {
return this.metadata.kind === 'orderinstructions'
}

/** OrderStatus type guard */
isOrderStatus(): this is OrderStatus {
return this.metadata.kind === 'orderstatus'
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 } from './types.js'
import type { MessageModel, ResourceModel, RfqMetadata, RfqData, QuoteData, QuoteMetadata, OrderData, OrderMetadata, OrderStatusMetadata, OrderStatusData, 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, OrderStatus, Close } from './message-kinds/index.js'
import { Rfq, Quote, Order, OrderInstructions, OrderStatus, Close } from './message-kinds/index.js'
import { Balance, Offering } from './resource-kinds/index.js'

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

case 'orderinstructions':
message = new OrderInstructions(
jsonMessage.metadata as OrderInstructionsMetadata,
jsonMessage.data as OrderInstructionsData,
jsonMessage.signature
)
break

case 'orderstatus':
message = new OrderStatus(
jsonMessage.metadata as OrderStatusMetadata,
Expand Down
34 changes: 32 additions & 2 deletions packages/protocol/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ export type QuoteMetadata = MessageMetadata & { kind: 'quote' }
*/
export type OrderMetadata = MessageMetadata & { kind: 'order' }

/**
* OrderInstructions' metadata
* @beta
*/
export type OrderInstructionsMetadata = MessageMetadata & { kind: 'orderinstructions' }

/**
* OrderStatus's metadata
* @beta
Expand All @@ -264,13 +270,13 @@ export type CloseMetadata = MessageMetadata & { kind: 'close' }
* Type alias to represent a set of message kind string keys
* @beta
*/
export type MessageKind = 'rfq' | 'quote' | 'order' | 'orderstatus' | 'close'
export type MessageKind = 'rfq' | 'quote' | 'order' | 'orderinstructions' | 'orderstatus' | 'close'

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

/**
* Data contained in a RFQ message
Expand Down Expand Up @@ -394,6 +400,18 @@ export type QuoteDetails = {
total: string
}

/**
* Describes the payment instructions with plain text and/or a link.
* @beta
*/
export type PaymentInstruction = {
/** Link to allow Alice to pay PFI, or be paid by the PFI. */
link?: string

/** Instruction on how Alice can pay PFI, or how Alice can be paid by the PFI. */
instruction?: string
}

/**
* Message sent by Alice to the PFI to accept a Quote. Order is currently an empty object
* @beta
Expand All @@ -402,6 +420,18 @@ export type OrderData = {
[key: string]: never
}

/**
* Message sent by the PFI to Alice to convey payment instructions.
* @beta
*/
export type OrderInstructionsData = {
/** Object that describes how to pay the PFI (e.g. BTC address, payment link). */
payin: PaymentInstruction

/** Object that describes how be paid by the PFI (e.g. BTC address, payment link). */
payout: PaymentInstruction
}

/**
* Message sent by the PFI to Alice to convey the current status of an order. There can be many OrderStatus
* messages in a given Exchange
Expand Down
Loading

0 comments on commit 8908f85

Please sign in to comment.