# Zoe
Zoe provides a framework for deploying and working with smart contracts. It is accessed
as a long-lived and well-trusted service that enforces offer safety for the contracts that use it. Zoe has a single invitationIssuer for the entirety of its lifetime. By having a reference to Zoe, a user can get the invitationIssuer and thus validate any invitation they receive from someone else.
Zoe is accessed asynchronously
The Zoe service is accessed asynchronously, using a standards-track library extension
to JavaScript that uses promises as remote references. In code, the Zoe service instance
is referred to via zoe, which only supports asynchronous invocation. Operations are
invoked asynchronously using the E helper for async messaging (opens new window).
All such operations immediately return a promise for their result. That may eventually fulfill to a local value, or to a Presence for another remote object (e.g. in another contract or service, running on another chain, etc.). Async messages can be sent using E with either promises or presences.
For more information about using E, see the section on it in Agoric's JavaScript Distributed Programming Guide.
# E(zoe).getBrands(instance)
instance{Instance}- Returns:
{Promise<BrandKeywordRecord>}
Returns a BrandKeywordRecord containing all brands defined in the contract instance.
A BrandKeywordRecord is a record where the keys are keywords,
and the values are the brands for particular issuers.
// Call example
const brandKeywordRecord = await E(zoe).getBrands(instance);
// Record example
const brandKeywordRecord = {
Asset: quatloosBrand,
Price: moolaBrand,
};
# E(zoe).getIssuers(instance)
instance{Instance}- Returns:
{Promise<IssuerKeywordRecord>}
Returns a IssuerKeywordRecord containing all issuers defined in the instance argument.
An IssuerKeywordRecord is a record where the keys are keywords,
and the values are issuers.
// Call example
const issuerKeywordRecord = await E(zoe).getIssuers(instance);
// Record example
const issuerKeywordRecord = {
Asset: quatloosIssuer,
Price: moolaIssuer,
};
# E(zoe).getTerms(instance)
instance{Instance}- Returns:
{Object}
Returns the terms of the instance argument, including its issuers, brands, and any
custom terms. The returned values look like:
{ brands, issuers, customTermA, customTermB ... }
//where brands and issuers are keywordRecords, like:
{
brands: { A: moolaKit.brand, B: simoleanKit.brand },
issuers: { A: moolaKit.issuer, B: simoleanKit.issuer },
customTermA: 'something',
customTermB: 'something else',
};
const terms = await E(zoe).getTerms(instance);
# E(zoe).getPublicFacet(instance)
instance{Instance}- Returns:
{Promise<PublicFacet>}
A contract instance's publicFacet is an object available via Zoe to anyone knowing that instance.
You use it for general queries and actions, such as getting a current price or creating public invitations.
Since a facet is defined just as any other object, the contract adds methods to the publicFacet just like you would
any object.
Returns a publicFacet containing the public facet defined for instance.
const ticketSalesPublicFacet = await E(zoe).getPublicFacet(sellItemsInstance);
# E(zoe).getInvitationIssuer()
- Returns
{Issuer}
Zoe has a single invitationIssuer for its entire
lifetime. By having a reference to Zoe, a user can get the invitationIssuer. This lets them claim any
invitation they receive from someone else by calling E(invitationIssuer).claim() with the
untrusted invitation as the argument. During the claiming process, the invitationIssuer validates
the invitation.
The mint associated with the invitationIssuer
creates invitations in the form of ERTP payments that represent the right to interact with
a smart contract in particular ways.
The invitationIssuer has two methods, both of which take an invitation as an argument.
Remember, an invitation is just a special case of an ERTP payment, so claim() and
getAmountOf() are the same as for other issuers.
A successful call of invitationIssuer.claim(invitation) means you are assured the invitation
is recognized as valid by the invitationIssuer. You are also assured the invitation is exclusively yours
and no one else has access to it.
const invitationIssuer = await E(zoe).getInvitationIssuer();
// Here a user, Bob, has received an untrusted invitation from Alice.
// Bob uses the trusted `invitationIssuer` from Zoe to
// transform the untrusted invitation to a trusted one
const invitation = await invitationIssuer.claim(untrustedInvitation);
const { value: invitationValue } =
await E(invitationIssuer).getAmountOf(invitation);
# E(zoe).getInvitationDetails(invitation)
invitation{Invitation}- Returns
{Promise<Object>}
Takes an invitation as an argument and returns an object containing the following
details about the invitation:
installation{Installation}: The contract's installation in Zoe.instance{Instance}: The contract instance this invitation is for.invitationHandle{Handle}: A handle used to refer to this invitation.description{String}: describes the purpose of thisinvitation. Use it to match the invitation to the role it plays in the contract.
const invitation = await invitationIssuer.claim(untrustedInvitation);
const invitationValue = await E(zoe).getInvitationDetails(invitation);
# E(zoe).install(bundle)
bundle{SourceBundle}- Returns:
{Promise<Installation>}
Takes bundled source code for a Zoe contract as an argument and installs the code on Zoe.
Returns an installation object.
An installation is an object with one property:
bundle: The contract source code, accessible viabundle.source, and other info.
// bundleSource takes source code files and
// bundles them together in the format install expects.
import bundleSource from '@endo/bundle-source';
const bundle = await bundleSource(pathResolve(`./src/contract.js`));
const installationP = await E(zoe).install(bundle);
# E(zoe).getInstance(invitation)
invitation{Invitation}- Returns:
{Promise<Instance>}
Returns a Promise for the contract instance the invitation is part of.
While instances are opaque objects, you can get information about them via
these methods:
getBrands()getTerms()getIssuers()getPublicFacet()
const instance = await E(zoe).getInstance(invitation);
# E(zoe).getInstallation(invitation)
invitation{Invitation}- Returns:
{Promise<Installation>}
Returns a Promise for the contract installation the invitation's contract instance uses.
An installation is an object with one property:
bundle: The contract source code, accessible viabundle.source, and other info.
const installation = await E(zoe).getInstallation(invitation);
# E(zoe).getInstallationForInstance(instance)
instance{Instance}- Returns
{Promise<Installation>}
Returns a Promise for the contract installation used by the
instance. An instance is the unique identifier for the running,
executing contract. The installation is the unique identifier for
the underlying code. This method can be used as part of a process to
inspect the underlying code for a running contract instance.
const installation = await E(zoe).getInstallationForInstance(instance);
# E(zoe).startInstance(installation, issuerKeywordRecord, terms)
installation{ERef<Installation>}issuerKeywordRecord{IssuerKeywordRecord}- optionalterms{Object}- optionalprivateArgs{Object}- optional- Returns:
{Promise<StartInstanceResult>}
Create an instance of the installed smart contract (specified by
the installation argument). You must also specify the
instance's issuers (as key-value pairs) and terms
for the contract.
The issuerKeywordRecord is an optional record mapping string names (keywords)
to issuers, such as { Asset: quatlooIssuer}. Keywords must begin
with a capital letter and must be ASCII. Parties to the contract will
use the keywords to index their proposal and their payments.
The terms are values used by this contract instance, such as the
number of bids an auction will wait for before closing. These values may
be different for different instances of the same contract, but the contract
defines what variables need their values passed in as terms.
privateArgs are optional. Pass an object record here with any values
that need to be made available to the contract code, but which should
not be in the public terms. For example, to share minting authority
among multiple contracts, pass in the following as privateArgs:
{ externalMint: myExternalMint }
It returns a promise for a StartInstanceResult object. The object consists of:
adminFacet{AdminFacet}creatorFacet{any}publicFacet{any}instance{Instance}creatorInvitation{Payment | undefined}
The adminFacet has one method:
getVatShutdownPromise()- Returns a promise that resolves to reason (the value passed to
fail(reason)) or completion (the value passed toexit(completion)) when this newly started instance terminates.
- Returns a promise that resolves to reason (the value passed to
A publicFacet is an object available via Zoe to anyone knowing
the instance they are associated with. The publicFacet is used for general queries
and actions, such as getting a current price or creating public invitations. Since a
facet is defined just as any other object,
the contract developer can add methods to them just like they would any object.
The creatorFacet is only available in this return value (i.e. only when starting
a contract instance). The contract designer
should use it to encapsulate things that the contract runner might not want to share,
or might want to control the distribution of. The party who starts the contract
should carefully consider the impact before sharing access to the creatorFacet.
creatorInvitation is an invitation that the contract instance creator can use.
It is usually used in contracts where the creator immediately sells
something (auctions, swaps, etc.), so it's helpful for the creator to have
an invitation to escrow and sell goods. Remember that Zoe invitations are
represented as a Payment.
const issuerKeywordRecord = {
'Asset': moolaIssuer,
'Price': quatlooIssuer
};
const terms = { numBids: 3 };
const { creatorFacet, publicFacet, creatorInvitation } = await E(zoe).startInstance(
installation, issuerKeywordRecord, terms);
# E(Zoe).offer(invitation, proposal, paymentKeywordRecord, offerArgs)
invitation{Invitation|Promise<Invitation>}proposal{Proposal}- optionalpaymentKeywordRecord{PaymentKeywordRecord}- optionalofferArgs{Object}- optional- Returns:
{Promise<UserSeat>}
Used to make an offer to the contract that created the invitation that is
provided as the first argument.
Vat per Contract
All contracts each run in a new vat with their own version of the Zoe Contract Facet. There is one vat that contains the Zoe Service.
# Proposals and payments
The invocation normally includes a proposal (the
rules under which they want to exercise the offer) and payments that correspond
to the give property of the proposal. The payments will be escrowed by Zoe. If
either the proposalor payments are empty, indicate this by
omitting that argument or passing undefined, instead of passing an
empty record.
The optional exit's value should be an exitRule, an object with three possible keys for
key:value pairs:
onDemand:null: (Default) The user can cancel on demand.waived:null: The user can't cancel and relies entirely on the smart contract to promptly finish their offer.afterDeadline: The offer is automatically cancelled after a deadline, as determined by itstimeranddeadlineproperties. The timer is a timer, and thedeadlineis with respect to the timer. Some example timers use Unix epoch time, while others count block height. Note thatdeadline's value is aBigInt, not aNumber(just append an "n" to the number you want to use to get itsBigInt)
const myProposal = harden({
give: { Asset: quatloos(4n)},
want: { Price: moola(15n) },
exit: { afterDeadline: {
timer,
deadline: 100n,
}}
})
paymentKeywordRecord is a record with keywords as keys, with
values of the actual payments to be escrowed. A payment is
expected for every entry under give.
offer() returns a promise for a UserSeat.
const paymentKeywordRecord = {
'Asset' : quatloosPayment,
'Price' : moolaPayment
};
# OfferArgs
offerArgs is an optional object record. It can be used to pass
additional arguments to the offerHandler contract code associated
with the invitation. It is up to the contract code whether it chooses
to handle any offerArgs passed to it or whether it drops them.
Zoe checks that offerArgs is a copyRecord, but the contract code
should be careful interacting with the values of the offerArgs
object. These values need input validation before being used by the
contract code since they are coming directly from the user and may
have malicious behavior.
# UserSeat Object
Zoe uses seats to access or manipulate offers. They let contracts and users interact
with them. Zoe has two kinds of seats. ZCFSeats
are used within contracts and with zcf methods. UserSeats represent offers external to
Zoe and the contract. The party who exercises an invitation and sends the offer() message
to Zoe gets a UserSeat that can check payouts' status or retrieve the result of
processing the offer in the contract. This varies, but examples
are a string and an invitation for another seat.
Also, a UserSeat can be handed to an agent outside Zoe and the contract, letting
them query or monitor the current state, access the payouts and result,
and, if it's allowed for this seat, call tryExit().
Since anyone can attempt to exit the seat if they have a reference to it,
you should only share a UserSeat with trusted parties.
UserSeat includes queries for the associated offer's current state
and an operation to request that the offer exit, as follows:
# E(UserSeat).getCurrentAllocation()
Returns:
{ Promise<Allocation> }An
Allocationis anAmountKeywordRecordof key-value pairs where the key is a keyword such asAssetorPriceapplicable to the contract. The value is anamount.Allocationsrepresent theamountsto be paid out to eachseaton exit. (Afterexit()has been called, the final allocation balances, which were transferred to payouts, continue to be reported.) Normal reasons for exiting are the user requesting to exit or the contract explicitly choosing to close out theseat. The guarantees also hold if the contract encounters an error or misbehaves. There are several methods for finding out whatamounta currentallocationis.An
Allocationexample:{ Asset: AmountMath.make(quatloosBrand, 5n), Price: AmountMath.make(moolaBrand, 9n) }
# E(UserSeat).getProposal()
Returns:
{ Promise<ProposalRecord> }A
Proposalis represented by aProposalRecord. It is the rules accompanying the escrow ofpaymentsdictating what the user expects to get back from Zoe. It has keysgive,want, andexit.giveandwantare records with keywords as keys andamountsas values. If it is compatible with the contract, the contract tries to satisfy it. If not, the contract kicks theseatout.Offer safety is always enforced; if kicked out, the user gets back what they put in. If the contract attempts to satisfy it, they either get what they asked for or Zoe ensures they get back their deposit.
Example:
const { want, give, exit } = sellerSeat.getProposal();
# E(UserSeat).getPayouts()
- Returns:
{ Promise<PaymentPKeywordRecord> } - A
payoutis apaymentthat goes to a party in a successful transaction, redirecting escrowed assets in accordance with the result of the transaction. Returns a record containing all thepayoutpaymentsassociated with theseat's offers.
# E(UserSeat).getPayout(keyword)
- Returns:
{ Promise<Payment> } - A
payoutis apaymentthat goes to a party in a successful transaction, redirecting escrowed assets in accordance with the result of the transaction. Returns thepayoutpaymentassociated with thekeywordargument.
# E(UserSeat).getOfferResult()
- Returns:
{ Promise<OfferResult> } - The returned
OfferResultcan be literally anything. For example, in tests for the Automated Refund Dapp, it's the string "The offer was accepted". In the Covered Call example, it's a call option, which is an assayableinvitationto buy the underlying asset. Strings and invitations are the most common things returned. The value is set by the returned result of theofferHandlersfunction passed as an argument tozcf.makeInvitation().
# E(UserSeat).getNotifier()
- Returns:
{ Promise<Notifier> } - You use a
notifierwherever some piece of code has changing state that other code wants updates on. The updates can be anything the contract wants to publish. For example, you could notify about price changes, new currency pools, etc. See also Notifiers and Subscriptions
# E(UserSeat).hasExited()
- Returns:
{ Promise<Boolean> } - Returns
trueif the seat has exited,falseif it is still active.
# E(UserSeat).tryExit()
- Returns
{ Void } - Note: Only works if the
seat'sproposalhas anOnDemandexitclause. Zoe's offer-safety guarantee applies no matter how aseat's interaction with a contract ends. Under normal circumstances, the participant might be able to calltryExit(), or the contract might do something explicitly. On exiting, the seat holder gets its currentallocationand theseatcan no longer interact with the contract.