-
Notifications
You must be signed in to change notification settings - Fork 217
/
Copy pathsimpleExchange.js
148 lines (131 loc) · 4.3 KB
/
simpleExchange.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// @ts-check
import { makeNotifierKit } from '@agoric/notifier';
import '../../exported';
import {
swap,
satisfies,
assertProposalShape,
assertIssuerKeywords,
} from '../contractSupport/zoeHelpers';
/**
* SimpleExchange is an exchange with a simple matching algorithm, which allows
* an unlimited number of parties to create new orders or accept existing
* orders. The notifier allows callers to find the current list of orders.
* https://agoric.com/documentation/zoe/guide/contracts/simple-exchange.html
*
* The SimpleExchange uses Asset and Price as its keywords. The contract treats
* the two keywords symmetrically. New offers can be created and existing offers
* can be accepted in either direction.
*
* { give: { 'Asset', simoleans(5) }, want: { 'Price', quatloos(3) } }
* { give: { 'Price', quatloos(8) }, want: { 'Asset', simoleans(3) } }
*
* The Asset is treated as an exact amount to be exchanged, while the
* Price is a limit that may be improved on. This simple exchange does
* not partially fill orders.
*
* The publicFacet is returned from the contract.
*
* @type {ContractStartFn}
*/
const start = zcf => {
let sellSeats = [];
let buySeats = [];
// eslint-disable-next-line no-use-before-define
const { notifier, updater } = makeNotifierKit(getBookOrders());
assertIssuerKeywords(zcf, harden(['Asset', 'Price']));
function dropExit(p) {
return {
want: p.want,
give: p.give,
};
}
function flattenOrders(seats) {
const activeSeats = seats.filter(s => !s.hasExited());
return activeSeats.map(seat => dropExit(seat.getProposal()));
}
function getBookOrders() {
return {
buys: flattenOrders(buySeats),
sells: flattenOrders(sellSeats),
};
}
// Tell the notifier that there has been a change to the book orders
function bookOrdersChanged() {
updater.updateState(getBookOrders());
}
// If there's an existing offer that this offer is a match for, make the trade
// and return the seat for the matched offer. If not, return undefined, so
// the caller can know to add the new offer to the book.
function swapIfCanTrade(offers, seat) {
for (const offer of offers) {
const satisfiedBy = (xSeat, ySeat) =>
satisfies(zcf, xSeat, ySeat.getCurrentAllocation());
if (satisfiedBy(offer, seat) && satisfiedBy(seat, offer)) {
swap(zcf, seat, offer);
// return handle to remove
return offer;
}
}
return undefined;
}
// try to swap offerHandle with one of the counterOffers. If it works, remove
// the matching offer and return the remaining counterOffers. If there's no
// matching offer, add the offerHandle to the coOffers, and return the
// unmodified counterOfffers
function swapIfCanTradeAndUpdateBook(counterOffers, coOffers, seat) {
const offer = swapIfCanTrade(counterOffers, seat);
if (offer) {
// remove the matched offer.
counterOffers = counterOffers.filter(value => value !== offer);
} else {
// Save the order in the book
coOffers.push(seat);
}
bookOrdersChanged();
return counterOffers;
}
const sell = seat => {
assertProposalShape(seat, {
give: { Asset: null },
want: { Price: null },
});
buySeats = swapIfCanTradeAndUpdateBook(buySeats, sellSeats, seat);
return 'Order Added';
};
const buy = seat => {
assertProposalShape(seat, {
give: { Price: null },
want: { Asset: null },
});
sellSeats = swapIfCanTradeAndUpdateBook(sellSeats, buySeats, seat);
return 'Order Added';
};
/** @type {OfferHandler} */
const exchangeOfferHandler = seat => {
// Buy Order
if (seat.getProposal().want.Asset) {
return buy(seat);
}
// Sell Order
if (seat.getProposal().give.Asset) {
return sell(seat);
}
// Eject because the offer must be invalid
throw seat.fail(
new Error(`The proposal did not match either a buy or sell order.`),
);
};
const makeExchangeInvitation = () =>
zcf.makeInvitation(exchangeOfferHandler, 'exchange');
/** @type {SimpleExchangePublicFacet} */
const publicFacet = harden({
makeInvitation: makeExchangeInvitation,
getNotifier: () => notifier,
});
// set the initial state of the notifier
bookOrdersChanged();
return harden({ publicFacet });
};
harden(start);
export { start };