UNPKG

@agoric/zoe

Version:

Zoe: the Smart Contract Framework for Offer Enforcement

144 lines (132 loc) 5.14 kB
import { assert } from '@endo/errors'; import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { offerTo, saveAllIssuers, assertProposalShape, atomicTransfer, } from '../contractSupport/index.js'; /** * This contract is inspired by the description of an OTC Desk smart * contract in this article: * https://medium.com/dragonfly-research/unbundling-uniswap-the-future-of-on-chain-market-making-1c7d6948d570 * * The creator of this contract instance can take three actions: add * inventory, remove inventory, and make quotes for potential trading * partners. * * To add inventory, the creator of the contract instance can call * `E(creatorFacet).makeAddInventoryInvitation(issuerKeywordRecord)` * and receive an invitation to add inventory. In this call, they must * pass in an issuerKeywordRecord of any issuers they wish to add * inventory for, if these issuers have not yet been saved to ZCF. * When actually escrowing the inventory as an offer, the proposal * must not `want` anything. All of the newly escrowed inventory is * taken and reallocated to the marketMakerSeat in the contract. * * To remove inventory, the creator of the contract instance can call * `E(creatorFacet).makeRemoveInventoryInvitation()` and receive an * invitation to remove inventory. When making an offer to remove * inventory, the proposal should specify the `want`, which will be * removed, but should not give anything. * * To make a quote, the creator can call * `E(creatorFacet).makeQuote(price, assets, timeAuthority, * deadline)`. `price` and `assets` are amountKeywordRecords that will * be used in the coveredCall. The assets are the underlyingAssets in * the call option, and the price is used as the strikePrice. The * timeAuthority should be a timer, and the deadline can be any time * understood by the timer. The quote will be cancelled after the * deadline. `makeQuote` returns a covered call option that can be * given away for free or sold. Importantly, if the recipient chooses * to exercise the option, they can verify that the goods being * offered are already escrowed, and the trade is guaranteed to * succeed if their proposal matches the quote. * * @param {ZCF<Record<string, any>>} zcf */ const start = zcf => { const { coveredCallInstallation } = zcf.getTerms(); const { zcfSeat: marketMakerSeat } = zcf.makeEmptySeatKit(); const zoe = zcf.getZoeService(); /** * Make a quote using the current inventory and receive a covered * call option that can be freely given or sold to someone else. * * @param {AmountKeywordRecord} price * @param {AmountKeywordRecord} assets * @param {import('@agoric/time').TimerService} timeAuthority * @param {any} deadline * @returns {Promise<Payment>} */ const makeQuote = async (price, assets, timeAuthority, deadline) => { /** @type {{ creatorInvitation: Invitation<Payment>} } */ const { creatorInvitation } = await E(zoe).startInstance( coveredCallInstallation, zcf.getTerms().issuers, ); const shouldBeInvitationMsg = `The covered call instance should return a creatorInvitation`; assert(creatorInvitation, shouldBeInvitationMsg); const proposal = harden({ give: assets, want: price, exit: { afterDeadline: { deadline, timer: timeAuthority, }, }, }); /** @type {{ userSeatPromise: Promise<UserSeat<Payment>>}} */ const { userSeatPromise: coveredCallUserSeat } = await offerTo( zcf, creatorInvitation, undefined, proposal, marketMakerSeat, ); const option = E(coveredCallUserSeat).getOfferResult(); return option; }; const addInventory = seat => { assertProposalShape(seat, { want: {} }); // Take everything in this seat and add it to the marketMakerSeat atomicTransfer(zcf, seat, marketMakerSeat, seat.getCurrentAllocation()); seat.exit(); return 'Inventory added'; }; const removeInventory = seat => { assertProposalShape(seat, { give: {} }); const { want } = seat.getProposal(); atomicTransfer(zcf, marketMakerSeat, seat, want); seat.exit(); return 'Inventory removed'; }; const creatorFacet = Far('creatorFacet', { /** * The inventory can be added in bulk before any quotes are made * or can be added immediately before a quote. * * @param {IssuerKeywordRecord} [issuerKeywordRecord] * @returns {Promise<Payment>} */ makeAddInventoryInvitation: async (issuerKeywordRecord = harden({})) => { await saveAllIssuers(zcf, issuerKeywordRecord); return zcf.makeInvitation(addInventory, 'addInventory'); }, /** * The inventory can be removed at any time, since the inventory * used for active quotes is escrowed separately within the coveredCall * instance. * * @returns {Promise<Payment>} */ makeRemoveInventoryInvitation: () => { return zcf.makeInvitation(removeInventory, 'removeInventory'); }, makeQuote, }); return harden({ creatorFacet }); }; export { start };