Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Added OrderInstructions message #268

Merged
merged 7 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading