UNPKG

@agoric/zoe

Version:

Zoe: the Smart Contract Framework for Offer Enforcement

133 lines (116 loc) 3.76 kB
import { Far } from '@endo/marshal'; import { makeLegacyMap } from '@agoric/store'; // Eventually will be importable from '@agoric/zoe-contract-support' import { satisfies } from '../contractSupport/index.js'; /** * This Barter Exchange accepts offers to trade arbitrary goods for other * things. It doesn't require registration of Issuers. If two offers satisfy * each other, it exchanges the specified amounts in each side's want clause. * https://agoric.com/documentation/zoe/guide/contracts/barter-exchange.html * * The Barter Exchange only accepts offers that look like * { give: { In: amount }, want: { Out: amount} } * The want amount will be matched, while the give amount is a maximum. Each * successful trader gets their `want` and may trade with counter-parties who * specify any amount up to their specified `give`. * * @param {ZCF} zcf */ const start = zcf => { // bookOrders is a Map of Maps. The first key is the brand of the offer's // GIVE, and the second key is the brand of its WANT. For each offer, we // store its handle and the amounts for `give` and `want`. // // Legacy Because we're storing a genuine JS Map const bookOrders = makeLegacyMap('bookOrders'); function lookupBookOrders(brandIn, brandOut) { if (!bookOrders.has(brandIn)) { bookOrders.init(brandIn, new Map()); } const ordersMap = bookOrders.get(brandIn); let ordersArray = ordersMap.get(brandOut); if (!ordersArray) { ordersArray = []; ordersMap.set(brandOut, ordersArray); } return ordersArray; } function findMatchingTrade(newDetails, orders) { return orders.find(order => { return ( satisfies(zcf, newDetails.seat, { Out: order.amountIn }) && satisfies(zcf, order.seat, { Out: newDetails.amountIn }) ); }); } function removeFromOrders(offerDetails) { const orders = lookupBookOrders( offerDetails.amountIn.brand, offerDetails.amountOut.brand, ); orders.splice(orders.indexOf(offerDetails), 1); } function tradeWithMatchingOffer(offerDetails) { const orders = lookupBookOrders( offerDetails.amountOut.brand, offerDetails.amountIn.brand, ); const matchingTrade = findMatchingTrade(offerDetails, orders); if (matchingTrade) { // reallocate by giving each side what it wants zcf.atomicRearrange( harden([ [ offerDetails.seat, matchingTrade.seat, { In: matchingTrade.amountOut }, { Out: matchingTrade.amountOut }, ], [ matchingTrade.seat, offerDetails.seat, { In: offerDetails.amountOut }, { Out: offerDetails.amountOut }, ], ]), ); removeFromOrders(matchingTrade); offerDetails.seat.exit(); matchingTrade.seat.exit(); return true; } return false; } function addToBook(offerDetails) { const orders = lookupBookOrders( offerDetails.amountIn.brand, offerDetails.amountOut.brand, ); orders.push(offerDetails); } function extractOfferDetails(seat) { const { give: { In: amountIn }, want: { Out: amountOut }, } = seat.getProposal(); return { seat, amountIn, amountOut, }; } /** @type {OfferHandler} */ const exchangeOfferHandler = seat => { const offerDetails = extractOfferDetails(seat); if (!tradeWithMatchingOffer(offerDetails)) { addToBook(offerDetails); } return 'Trade completed.'; }; const publicFacet = Far('publicFacet', { makeInvitation: () => zcf.makeInvitation(exchangeOfferHandler, 'exchange'), }); return { publicFacet }; }; harden(start); export { start };