From 7ecb390e98fa0d10822db92fda9063ceaf0e5e88 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Tue, 6 Aug 2024 17:03:40 -0700 Subject: [PATCH 1/7] Added OrderInstructions message --- packages/protocol/build/compile-validators.js | 2 + packages/protocol/src/message-kinds/index.ts | 1 + .../src/message-kinds/order-instructions.ts | 73 +++++++++++++++++++ packages/protocol/src/message-kinds/order.ts | 4 +- packages/protocol/src/message.ts | 5 ++ packages/protocol/src/parser.ts | 12 ++- packages/protocol/src/types.ts | 34 ++++++++- packages/protocol/tests/test-vectors.spec.ts | 26 +++++-- 8 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 packages/protocol/src/message-kinds/order-instructions.ts diff --git a/packages/protocol/build/compile-validators.js b/packages/protocol/build/compile-validators.js index eb595ac6..8591e7ec 100644 --- a/packages/protocol/build/compile-validators.js +++ b/packages/protocol/build/compile-validators.js @@ -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' } @@ -36,6 +37,7 @@ const schemas = { balance : BalanceSchema, message : MessageSchema, order : OrderSchema, + orderinstructions : OrderInstructionsSchema, orderstatus : OrderstatusSchema, quote : QuoteSchema, resource : ResourceSchema, diff --git a/packages/protocol/src/message-kinds/index.ts b/packages/protocol/src/message-kinds/index.ts index 5293e800..efb8af3f 100644 --- a/packages/protocol/src/message-kinds/index.ts +++ b/packages/protocol/src/message-kinds/index.ts @@ -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' diff --git a/packages/protocol/src/message-kinds/order-instructions.ts b/packages/protocol/src/message-kinds/order-instructions.ts new file mode 100644 index 00000000..08e0f0cf --- /dev/null +++ b/packages/protocol/src/message-kinds/order-instructions.ts @@ -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 & { 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(['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 { + 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 + } +} \ No newline at end of file diff --git a/packages/protocol/src/message-kinds/order.ts b/packages/protocol/src/message-kinds/order.ts index 2a153ea4..79434628 100644 --- a/packages/protocol/src/message-kinds/order.ts +++ b/packages/protocol/src/message-kinds/order.ts @@ -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(['orderstatus']) + readonly validNext = new Set(['orderinstructions', 'orderstatus']) + /** 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 diff --git a/packages/protocol/src/message.ts b/packages/protocol/src/message.ts index 242eb2c6..cafbd360 100644 --- a/packages/protocol/src/message.ts +++ b/packages/protocol/src/message.ts @@ -173,6 +173,11 @@ export abstract class Message { return this.metadata.kind === 'order' } + /** OrderInstructions type guard */ + isOrderInstructions(): this is OrderStatus { + return this.metadata.kind === 'orderinstructions' + } + /** OrderStatus type guard */ isOrderStatus(): this is OrderStatus { return this.metadata.kind === 'orderstatus' diff --git a/packages/protocol/src/parser.ts b/packages/protocol/src/parser.ts index 1a56d9d8..e5860a15 100644 --- a/packages/protocol/src/parser.ts +++ b/packages/protocol/src/parser.ts @@ -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' /** @@ -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, diff --git a/packages/protocol/src/types.ts b/packages/protocol/src/types.ts index cd115e3b..bb7c16e0 100644 --- a/packages/protocol/src/types.ts +++ b/packages/protocol/src/types.ts @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/packages/protocol/tests/test-vectors.spec.ts b/packages/protocol/tests/test-vectors.spec.ts index 5e7f7614..6be3ce5a 100644 --- a/packages/protocol/tests/test-vectors.spec.ts +++ b/packages/protocol/tests/test-vectors.spec.ts @@ -7,7 +7,8 @@ import ParseQuote from '../../../tbdex/hosted/test-vectors/protocol/vectors/pars import ParseRfq from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-rfq.json' assert { type: 'json' } import ParseOmitPrivateData from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-rfq-omit-private-data.json' assert { type: 'json' } import ParseBalance from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-balance.json' assert { type: 'json' } -import { Balance, Close, Message, Offering, Order, OrderStatus, Quote, Resource, Rfq } from '../src/main.js' +import vectorOrderInstructions from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-orderinstructions.json' assert { type: 'json' } +import { Balance, Close, Message, Offering, Order, OrderInstructions, OrderStatus, Quote, Resource, Rfq } from '../src/main.js' import { Parser } from '../src/parser.js' import Sinon from 'sinon' @@ -73,7 +74,7 @@ describe('TbdexTestVectorsProtocol', function () { expect(order.toJSON()).to.deep.eq(ParseOrder.output) }) - it('parse_orderstatus', async () => { + it('parse_order_status', async () => { // Parse with parseMessage() const message = await Parser.parseMessage(ParseOrderStatus.input) expect(messageVerifyStub.calledOnce).to.be.true @@ -82,10 +83,25 @@ describe('TbdexTestVectorsProtocol', function () { messageVerifyStub.resetHistory() // Parse with OrderStatus.parse() - const orderstatus = await OrderStatus.parse(ParseOrderStatus.input) + const orderStatus = await OrderStatus.parse(ParseOrderStatus.input) expect(messageVerifyStub.calledOnce).to.be.true - expect(orderstatus.isOrderStatus()).to.be.true - expect(orderstatus.toJSON()).to.deep.eq(ParseOrderStatus.output) + expect(orderStatus.isOrderStatus()).to.be.true + expect(orderStatus.toJSON()).to.deep.eq(ParseOrderStatus.output) + }) + + it('parse_order_instructions', async () => { + // Parse with parseMessage() + const message = await Parser.parseMessage(vectorOrderInstructions.input) + expect(messageVerifyStub.calledOnce).to.be.true + expect(message.isOrderInstructions()).to.be.true + expect(message.toJSON()).to.deep.eq(vectorOrderInstructions.output) + messageVerifyStub.resetHistory() + + // Parse with OrderInstructions.parse() + const orderInstructions = await OrderInstructions.parse(vectorOrderInstructions.input) + expect(messageVerifyStub.calledOnce).to.be.true + expect(orderInstructions.isOrderInstructions()).to.be.true + expect(orderInstructions.toJSON()).to.deep.eq(vectorOrderInstructions.output) }) it('parse_quote', async () => { From 9886edb649621835c495dd731ea44e25c34a8346 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Tue, 6 Aug 2024 17:38:11 -0700 Subject: [PATCH 2/7] consistency fix --- packages/protocol/tests/test-vectors.spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/protocol/tests/test-vectors.spec.ts b/packages/protocol/tests/test-vectors.spec.ts index 6be3ce5a..9df572b8 100644 --- a/packages/protocol/tests/test-vectors.spec.ts +++ b/packages/protocol/tests/test-vectors.spec.ts @@ -2,12 +2,12 @@ import { expect } from 'chai' import ParseClose from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-close.json' assert { type: 'json' } import ParseOffering from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-offering.json' assert { type: 'json' } import ParseOrder from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-order.json' assert { type: 'json' } +import ParseOrderInstructions from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-orderinstructions.json' assert { type: 'json' } import ParseOrderStatus from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-orderstatus.json' assert { type: 'json' } import ParseQuote from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-quote.json' assert { type: 'json' } import ParseRfq from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-rfq.json' assert { type: 'json' } import ParseOmitPrivateData from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-rfq-omit-private-data.json' assert { type: 'json' } import ParseBalance from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-balance.json' assert { type: 'json' } -import vectorOrderInstructions from '../../../tbdex/hosted/test-vectors/protocol/vectors/parse-orderinstructions.json' assert { type: 'json' } import { Balance, Close, Message, Offering, Order, OrderInstructions, OrderStatus, Quote, Resource, Rfq } from '../src/main.js' import { Parser } from '../src/parser.js' import Sinon from 'sinon' @@ -74,7 +74,7 @@ describe('TbdexTestVectorsProtocol', function () { expect(order.toJSON()).to.deep.eq(ParseOrder.output) }) - it('parse_order_status', async () => { + it('parse_orderstatus', async () => { // Parse with parseMessage() const message = await Parser.parseMessage(ParseOrderStatus.input) expect(messageVerifyStub.calledOnce).to.be.true @@ -89,19 +89,19 @@ describe('TbdexTestVectorsProtocol', function () { expect(orderStatus.toJSON()).to.deep.eq(ParseOrderStatus.output) }) - it('parse_order_instructions', async () => { + it('parse_orderinstructions', async () => { // Parse with parseMessage() - const message = await Parser.parseMessage(vectorOrderInstructions.input) + const message = await Parser.parseMessage(ParseOrderInstructions.input) expect(messageVerifyStub.calledOnce).to.be.true expect(message.isOrderInstructions()).to.be.true - expect(message.toJSON()).to.deep.eq(vectorOrderInstructions.output) + expect(message.toJSON()).to.deep.eq(ParseOrderInstructions.output) messageVerifyStub.resetHistory() // Parse with OrderInstructions.parse() - const orderInstructions = await OrderInstructions.parse(vectorOrderInstructions.input) + const orderInstructions = await OrderInstructions.parse(ParseOrderInstructions.input) expect(messageVerifyStub.calledOnce).to.be.true expect(orderInstructions.isOrderInstructions()).to.be.true - expect(orderInstructions.toJSON()).to.deep.eq(vectorOrderInstructions.output) + expect(orderInstructions.toJSON()).to.deep.eq(ParseOrderInstructions.output) }) it('parse_quote', async () => { From bf657cd556902a9bb6a3b469cc015ff51ac9b136 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Tue, 6 Aug 2024 17:38:59 -0700 Subject: [PATCH 3/7] lint --- packages/protocol/src/message.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/protocol/src/message.ts b/packages/protocol/src/message.ts index cafbd360..79c3506e 100644 --- a/packages/protocol/src/message.ts +++ b/packages/protocol/src/message.ts @@ -173,10 +173,10 @@ export abstract class Message { return this.metadata.kind === 'order' } - /** OrderInstructions type guard */ - isOrderInstructions(): this is OrderStatus { - return this.metadata.kind === 'orderinstructions' - } + /** OrderInstructions type guard */ + isOrderInstructions(): this is OrderStatus { + return this.metadata.kind === 'orderinstructions' + } /** OrderStatus type guard */ isOrderStatus(): this is OrderStatus { From a7c29206f832aab1f753b3363c6494d0d8e6f404 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Wed, 7 Aug 2024 15:57:19 -0700 Subject: [PATCH 4/7] 100% code coverage and fixes --- packages/protocol/src/exchange.ts | 12 ++- packages/protocol/src/message-kinds/order.ts | 2 +- packages/protocol/src/message.ts | 4 +- packages/protocol/tests/exchange.spec.ts | 82 ++++++++++++++++---- 4 files changed, 78 insertions(+), 22 deletions(-) diff --git a/packages/protocol/src/exchange.ts b/packages/protocol/src/exchange.ts index 247a3c15..8e6cd1d9 100644 --- a/packages/protocol/src/exchange.ts +++ b/packages/protocol/src/exchange.ts @@ -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' @@ -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 @@ -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 { @@ -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 @@ -134,6 +139,7 @@ export class Exchange { this.rfq, this.quote, this.order, + this.orderInstructions, ...this.orderstatus, this.close ] diff --git a/packages/protocol/src/message-kinds/order.ts b/packages/protocol/src/message-kinds/order.ts index 79434628..f5df94bf 100644 --- a/packages/protocol/src/message-kinds/order.ts +++ b/packages/protocol/src/message-kinds/order.ts @@ -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(['orderinstructions', 'orderstatus']) + readonly validNext = new Set(['orderinstructions']) /** The message kind (order) */ readonly kind = 'order' diff --git a/packages/protocol/src/message.ts b/packages/protocol/src/message.ts index 79c3506e..d2a5f28f 100644 --- a/packages/protocol/src/message.ts +++ b/packages/protocol/src/message.ts @@ -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' @@ -174,7 +174,7 @@ export abstract class Message { } /** OrderInstructions type guard */ - isOrderInstructions(): this is OrderStatus { + isOrderInstructions(): this is OrderInstructions { return this.metadata.kind === 'orderinstructions' } diff --git a/packages/protocol/tests/exchange.spec.ts b/packages/protocol/tests/exchange.spec.ts index de03bd57..4f285ccb 100644 --- a/packages/protocol/tests/exchange.spec.ts +++ b/packages/protocol/tests/exchange.spec.ts @@ -1,6 +1,6 @@ import { BearerDid, DidDht, DidJwk } from '@web5/dids' import { expect } from 'chai' -import { Close, DevTools, Exchange, Message, Order, OrderStatus, OrderStatusEnum, Quote, Rfq } from '../src/main.js' +import { Close, DevTools, Exchange, Message, Order, OrderInstructions, OrderStatus, OrderStatusEnum, Quote, Rfq } from '../src/main.js' describe('Exchange', () => { let aliceDid: BearerDid @@ -10,6 +10,7 @@ describe('Exchange', () => { let closeByAlice: Close let closeByPfi: Close let order: Order + let orderInstructions: OrderInstructions let orderStatus: OrderStatus beforeEach(async () => { @@ -68,6 +69,19 @@ describe('Exchange', () => { }) await order.sign(aliceDid) + orderInstructions = OrderInstructions.create({ + metadata: { + from : pfiDid.uri, + to : aliceDid.uri, + exchangeId : rfq.metadata.exchangeId + }, + data: { + payin: { }, + payout: { } + } + }) + await order.sign(pfiDid) + orderStatus = OrderStatus.create({ metadata: { from : pfiDid.uri, @@ -92,11 +106,12 @@ describe('Exchange', () => { const exchange = new Exchange() // Messages are listed out of order - exchange.addMessages([order, quote, orderStatus, rfq]) + exchange.addMessages([order, quote, orderInstructions, orderStatus, rfq]) expect(exchange.rfq).to.deep.eq(rfq) expect(exchange.quote).to.deep.eq(quote) expect(exchange.order).to.deep.eq(order) + expect(exchange.orderInstructions).to.deep.eq(orderInstructions) expect(exchange.orderstatus).to.deep.eq([orderStatus]) }) @@ -169,7 +184,7 @@ describe('Exchange', () => { describe('message sequence', () => { it('can add an Rfq first but not other message kinds first', async () => { const exchange = new Exchange() - for (const message of [quote, closeByAlice, closeByPfi, order, orderStatus]) { + for (const message of [quote, closeByAlice, closeByPfi, order, orderInstructions, orderStatus]) { try { exchange.addNextMessage(message) expect.fail() @@ -182,11 +197,11 @@ describe('Exchange', () => { expect(exchange.rfq).to.deep.eq(rfq) }) - it('cannot add an Order, OrderStatus, or Rfq after Rfq', async () => { + it('cannot add an Order, OrderInstructions, OrderStatus, or Rfq after Rfq', async () => { const exchange = new Exchange() exchange.addNextMessage(rfq) - for (const message of [rfq, order, orderStatus]) { + for (const message of [rfq, order, orderInstructions, orderStatus]) { try { exchange.addNextMessage(message) expect.fail() @@ -220,12 +235,12 @@ describe('Exchange', () => { expect(exchange.close).to.deep.eq(closeByPfi) }) - it('cannot add Rfq, Quote, Order, OrderStatus, or Close after Close', async () => { + it('cannot add Rfq, Quote, Order, OrderInstructions, OrderStatus, or Close after Close', async () => { const exchange = new Exchange() exchange.addMessages([rfq, quote]) exchange.addNextMessage(closeByAlice) - for (const message of [rfq, quote, order, orderStatus, closeByAlice]) { + for (const message of [rfq, quote, order, orderInstructions, orderStatus, closeByAlice]) { try { exchange.addNextMessage(message) expect.fail() @@ -244,11 +259,11 @@ describe('Exchange', () => { expect(exchange.order).to.deep.eq(order) }) - it('cannot add Rfq, Quote, or OrderStatus after Quote', async () => { + it('cannot add Rfq, Quote, OrderInstructions or OrderStatus after Quote', async () => { const exchange = new Exchange() exchange.addMessages([rfq, quote]) - for (const message of [rfq, quote, orderStatus]) { + for (const message of [rfq, quote, orderInstructions, orderStatus]) { try { exchange.addNextMessage(message) expect.fail() @@ -258,20 +273,43 @@ describe('Exchange', () => { } }) - it('can add an OrderStatus after Order', async () => { + it('can add an OrderInstructions after Order', async () => { const exchange = new Exchange() exchange.addMessages([rfq, quote, order]) + exchange.addNextMessage(orderInstructions) + expect(exchange.orderInstructions).to.deep.eq(orderInstructions) + }) + + it('cannot add Rfq, Quote, Order, OrderStatus, or Close after Order', async () => { + const exchange = new Exchange() + exchange.addMessages([rfq, quote, order]) + + for (const message of [rfq, quote, order, orderStatus, closeByAlice]) { + try { + exchange.addNextMessage(message) + expect.fail() + } catch (e) { + expect(e.message).to.contain('is not a valid next message') + } + } + }) + + it('can add an OrderStatus after OrderInstructions', async () => { + const exchange = new Exchange() + + exchange.addMessages([rfq, quote, order, orderInstructions]) + exchange.addNextMessage(orderStatus) expect(exchange.orderstatus).to.deep.eq([orderStatus]) }) - it('cannot add Rfq, Quote, Order, or Close after Order', async () => { + it('cannot add Rfq, Quote, Order, or OrderInstructions after OrderInstructions', async () => { const exchange = new Exchange() - exchange.addMessages([rfq, quote, order]) + exchange.addMessages([rfq, quote, order, orderInstructions]) - for (const message of [rfq, quote, order, closeByAlice]) { + for (const message of [rfq, quote, order, orderInstructions]) { try { exchange.addNextMessage(message) expect.fail() @@ -285,7 +323,6 @@ describe('Exchange', () => { const exchange = new Exchange() exchange.addNextMessage(rfq) - let quote = Quote.create({ metadata: { from : pfiDid.uri, @@ -365,6 +402,19 @@ describe('Exchange', () => { }) await order.sign(aliceDid) + const orderInstructions = OrderInstructions.create({ + metadata: { + from : aliceDid.uri, + to : pfiDid.uri, + exchangeId : rfq.metadata.exchangeId + }, + data: { + payin: { }, + payout: { } + } + }) + await order.sign(aliceDid) + const orderStatus = OrderStatus.create({ metadata: { from : pfiDid.uri, @@ -378,9 +428,9 @@ describe('Exchange', () => { await orderStatus.sign(pfiDid) const exchange = new Exchange() - exchange.addMessages([rfq, quote, order, orderStatus]) + exchange.addMessages([rfq, quote, order, orderInstructions, orderStatus]) - expect(exchange.messages).to.deep.eq([rfq, quote, order, orderStatus]) + expect(exchange.messages).to.deep.eq([rfq, quote, order, orderInstructions, orderStatus]) }) }) }) From da9b5a1f9aa1c70db6fcccbc1034537b28efbe12 Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Wed, 7 Aug 2024 19:20:22 -0700 Subject: [PATCH 5/7] lint --- packages/protocol/tests/exchange.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/protocol/tests/exchange.spec.ts b/packages/protocol/tests/exchange.spec.ts index 4f285ccb..16edfc0c 100644 --- a/packages/protocol/tests/exchange.spec.ts +++ b/packages/protocol/tests/exchange.spec.ts @@ -76,8 +76,8 @@ describe('Exchange', () => { exchangeId : rfq.metadata.exchangeId }, data: { - payin: { }, - payout: { } + payin : { }, + payout : { } } }) await order.sign(pfiDid) @@ -409,8 +409,8 @@ describe('Exchange', () => { exchangeId : rfq.metadata.exchangeId }, data: { - payin: { }, - payout: { } + payin : { }, + payout : { } } }) await order.sign(aliceDid) From ff61129bb7842b815bcd50f5e4962824e42504ec Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Thu, 8 Aug 2024 08:45:11 -0700 Subject: [PATCH 6/7] Create green-beans-rhyme.md --- .changeset/green-beans-rhyme.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/green-beans-rhyme.md diff --git a/.changeset/green-beans-rhyme.md b/.changeset/green-beans-rhyme.md new file mode 100644 index 00000000..060b0cea --- /dev/null +++ b/.changeset/green-beans-rhyme.md @@ -0,0 +1,5 @@ +--- +"@tbdex/protocol": minor +--- + +Added OrderInstructions message. From 535845424622ee90089dea346b064464ad74d53d Mon Sep 17 00:00:00 2001 From: Henry Tsai Date: Thu, 8 Aug 2024 13:00:57 -0700 Subject: [PATCH 7/7] Addressed PR comments --- packages/protocol/src/message-kinds/order.ts | 2 +- packages/protocol/tests/exchange.spec.ts | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/protocol/src/message-kinds/order.ts b/packages/protocol/src/message-kinds/order.ts index f5df94bf..68660960 100644 --- a/packages/protocol/src/message-kinds/order.ts +++ b/packages/protocol/src/message-kinds/order.ts @@ -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(['orderinstructions']) + readonly validNext = new Set(['orderinstructions', 'close']) /** The message kind (order) */ readonly kind = 'order' diff --git a/packages/protocol/tests/exchange.spec.ts b/packages/protocol/tests/exchange.spec.ts index 16edfc0c..434874b9 100644 --- a/packages/protocol/tests/exchange.spec.ts +++ b/packages/protocol/tests/exchange.spec.ts @@ -273,20 +273,27 @@ describe('Exchange', () => { } }) - it('can add an OrderInstructions after Order', async () => { + it('can add an OrderInstructions or a Close after Order', async () => { const exchange = new Exchange() - exchange.addMessages([rfq, quote, order]) exchange.addNextMessage(orderInstructions) expect(exchange.orderInstructions).to.deep.eq(orderInstructions) }) - it('cannot add Rfq, Quote, Order, OrderStatus, or Close after Order', async () => { + it('can add a Close after Order', async () => { + const exchange = new Exchange() + exchange.addMessages([rfq, quote, order]) + + exchange.addNextMessage(closeByPfi) + expect(exchange.close).to.deep.eq(closeByPfi) + }) + + it('cannot add Rfq, Quote, Order, or OrderStatus after Order', async () => { const exchange = new Exchange() exchange.addMessages([rfq, quote, order]) - for (const message of [rfq, quote, order, orderStatus, closeByAlice]) { + for (const message of [rfq, quote, order, orderStatus]) { try { exchange.addNextMessage(message) expect.fail()