# Introduction to Zoe
Beta status
Zoe is currently at Beta status. It has not yet been formally tested or hardened. It is not yet of production quality.
Work in Progress: Fees and Metering
We recently started charging fees for using Zoe. If you are experiencing problems running dapps, please make sure your fee purse has enough RUN to pay fees. For more information about how fees are charged and how some fees are used to pay for code execution, please see our work-in-progress documentation. We expect that this model will change in the upcoming few months.
# What is Zoe?
Zoe is Agoric's smart contract framework. Use Zoe to:
- Run your code on-chain
- Mint new digital assets
- Credibly trade assets
Zoe relies on Agoric's Electronic Rights Transfer Protocol (ERTP), our token standard.
# Why use Zoe?
# For Users
Zoe is safer. Traditionally, putting digital assets in a smart contract has risked losing them. But Zoe guarantees you get either what you wanted or a full refund of the assets you put in. You will never leave a contract empty-handed, even if the smart contract is buggy or malicious.
# For Developers
Zoe is easier. Traditionally, writing a smart contract meant learning a new, untried language. And don't make any mistakes - if you do, your users might lose millions.
However, you write Zoe contracts in a secure subset of JavaScript. Moreover, Zoe automatically escrows all user digital assets and handles their subsequent payout. Even a buggy contract can't cause users to lose their assets.
# Contracts on Zoe
Agoric has written a number of example contracts that you can use, including:
- an Automated Market Maker (AMM) implementation
- a covered call option contract
- an OTC Desk market maker contract (opens new window)
- contracts for minting fungible (opens new window) and non-fungible tokens (opens new window)
# Using an example Zoe smart contract
You must have a Zoe invitation to a specific contract instance to join
and participate in it. Let's imagine your friend Alice has sent you a
Zoe invitation
for a contract instance to your
wallet.
Compare this to a smart contract on Ethereum. On Ethereum, the smart contract developer must guard against malicious calls and store an internal access control list to check whether the message sender is allowed to send such a message. Zoe, built on Agoric's object capability security model, is just easier.
This particular invitation is for an AtomicSwap contract (opens new window). In an AtomicSwap, one party puts up digital assets they want to exchange and sends an invitation to a second party for them to possibly complete the exchange. In this example, Alice has already escrowed the assets she wants to swap and is asking you to pay a specified price to receive her digital assets.
# Inspecting an invitation
So you have an invitation, but how do you use it? First, you use Zoe to inspect and validate the invitation.
const invitationDetails = await E(zoe).getInvitationDetails(invitation);
const { installation, asset, price } = invitationDetails;
Note
E() is part of the Agoric platform and is used to call methods on remote objects and receive a promise for the result. Code on the Agoric platform is put in separate environments, called vats, for security. Zoe is in a different vat, making it a remote object, so we must use E().
Invitations include information about their contract's installation. Essentially, this is the contract's source code as installed on Zoe. From this overall contract installation, people use Zoe to create and run specific instances of the contract. For example, if a real estate company has a contract for selling a house, they would create an instance of the contract for each individual house they have up for sale.
You use object identity comparison to quickly check that you recognize this contract installation, without having to check source code line-by-line to ensure the code is the same. In other words, you're sure this invitation is for participating in an instance of the contract it says it is, and not an unknown and possibly malicious one.
const isCorrectCode = installation === atomicSwapInstallation;
However, if you don't recognize the installation, you can inspect the code directly by calling:
const bundledCode = await E(installation).getBundle();
In many cases, the bundled source is a single reviewable string. In others, the bundle contains to base 64 encoded zip file that you can extract for review.
jq -r .endoZipBase64 bundle.json | base64 -d > bundle.zip
unzip bundle.zip
Contracts can add their own specific information to invitations. In
this case, the Atomic Swap contract adds information about what is
being traded: the asset
, the amount Alice has escrowed, and the
price
, what you must pay to get the asset. Let's say asset
is an
amount
of 3 moola, and price
is an amount
of 7 simoleans. (Moola
and simoleans are made-up currencies for this example.) Amounts are
descriptions of digital assets, but have no value themselves. Please
see the ERTP guide for more on
amounts.
# Making an offer
You've successfully checked out the invitation, so now you can make an offer.
An offer has three required parts:
- a Zoe invitation
- a proposal
- the digital assets you're offering to swap
The proposal
states what you want from the offer, and what you will
give in return. Zoe uses the proposal as an invariant to ensure you
don't lose your assets in the trade. This invariant is known as offer
safety.
You use the invitation's asset
and price
amounts to make your
proposal:
const proposal = {
want: { Asset: asset }, // asset: 3 moola
give: { Price: price }, // price: 7 simoleans
};
Proposals must use Keywords, which are capitalized ASCII keys. Here,
the specific keywords, Asset
and Price
, are determined by the
contract
code (opens new window).
You said you would give 7 simoleans, so you must send 7 simoleans as
an ERTP payment. (ERTP
payments are how the Agoric
platform transfers fungible and nonfungible digital assets.) You
happen to have some simoleans lying around in a simolean
purse (used to hold digital
assets of a specific type). You withdraw a payment of 7 simoleans from
the purse for your offer, and construct an object using the same
Keyword as your proposal.give
:
const simoleanPayment = await E(purse).withdraw(price);
const payments = { Price: simoleanPayment };
Now you need to harden (opens new window) your
just created proposal
and payments
objects. Hardening is
transitively freezing an object. For security reasons, we must harden
any objects that will be passed to a remote object like Zoe.
harden(proposal);
harden(payments);
You've put the required pieces together, so now you can make an offer:
const userSeat = await E(zoe).offer(invitation, proposal, payments);
At this point, Zoe burns your invitation and confirms its validity.
Zoe also escrows all of your payments, representing their value in
amounts as your current allocation
in the contract.
# Using your UserSeat
Making an offer as a user returns a UserSeat
. This seat represents
your position in the ongoing contract instance (your "seat at the
table"). You can use this seat to:
- Exit the contract.
- Get information about your position such as your current allocation.
- Get your payouts from Zoe.
Check that your offer was successful:
const offerResult = await E(userSeat).getOfferResult();
In response to your offer, the AtomicSwap contract returns the message: "The offer has been accepted. Once the contract has been completed, please check your payout." Other contracts and offers may return something different. The offer's result is entirely up to the contract.
# Getting payouts
Because this was an AtomicSwap contract, it is over once the second party escrows the correct assets. You can get your payout of moola with the Keyword you used ('Asset'):
const moolaPayment = await E(userSeat).getPayout('Asset');
Alice also receives her payouts:
const aliceSimoleanPayment = await E(aliceSeat).getPayout('Price');
# Writing and installing a contract
Now that you've seen how to participate in a contract instance, let's look at how you'd create a contract and its instances.
Let's pretend Alice wrote that contract from scratch, even though AtomicSwap is one of Agoric's example contracts (see the full AtomicSwap code here (opens new window)). Note: All Zoe contracts must have this format:
Show contract format
// @ts-check
// Checks the types as defined in JSDoc comments
// Add imports here
// Optional: you may wish to use the Zoe helpers in
// @agoric/zoe/src/contractSupport/index.js
import { swap as _ } from '@agoric/zoe/src/contractSupport/index.js';
// Import the Zoe types
import '@agoric/zoe/exported.js';
/**
* [Contract Description Here]
*
* @type {ContractStartFn}
*/
const start = (zcf, _privateArgs) => {
// ZCF: the Zoe Contract Facet
// privateArgs: any arguments to be made available to the contract
// code by the contract owner that should not be in the public
// terms.
// Add contract logic here, including the
// handling of offers and the making of invitations.
// Example: This is an example of an offerHandler
// which just gives a refund payout automatically.
const myOfferHandler = zcfSeat => {
zcfSeat.exit();
const offerResult = 'success';
return offerResult;
};
// Example: This is an invitation that, if used to make
// an offer will trigger `myOfferHandler`, giving a
// refund automatically.
const invitation = zcf.makeInvitation(myOfferHandler, 'myInvitation');
// Optional: Methods added to this object are available
// to the creator of the instance.
const creatorFacet = {};
// Optional: Methods added to this object are available
// to anyone who knows about the contract instance.
// Price queries and other information requests can go here.
const publicFacet = {};
return harden({
creatorInvitation: invitation, // optional
creatorFacet, // optional
publicFacet, // optional
});
};
harden(start);
export { start };
Alice fills in this code template with AtomicSwap's particulars (opens new window). To install this particular code, Alice first must bundle it off-chain, meaning the code and its imports are flattened together:
import bundleSource from '@endo/bundle-source';
const atomicSwapUrl = await importMetaResolve(
'@agoric/zoe/src/contracts/atomicSwap.js',
import.meta.url,
);
const atomicSwapPath = url.fileURLToPath(atomicSwapUrl);
const atomicSwapBundle = await bundleSource(atomicSwapPath);
Then Alice must install it on Zoe:
const atomicSwapInstallation = await E(zoe).install(atomicSwapBundle);
The return value is an installation
, which we saw earlier. It is an
object identifying a particular piece of code installed on Zoe. It can
be compared to other installations, and you can call
E(atomicSwapInstallation).getBundle()
to see the code itself.
# Creating an instance
Now Alice uses the installation to create a new instance. She must
also tell Zoe about the ERTP issuers she wants to use, by specifying
their role with Keywords. Alice was escrowing moola, so she uses the
keyword Asset
to label the moolaIssuer
. She wanted simoleans, so
she uses the keyword Price
to label the simoleanIssuer
. When you
create a new instance of the contract (see permalink to line
58 (opens new window))
by calling startInstance()
, it returns a creatorInvitation
. Alice
uses this to make her offer; even the instance's creator needs to have
an invitation to it to participate in it.
const issuerKeywordRecord = harden({
Asset: moolaKit.issuer,
Price: simoleanKit.issuer,
});
const { creatorInvitation } = await E(zoe).startInstance(
atomicSwapInstallation,
issuerKeywordRecord,
);
As per the Atomic Swap contract code (opens new window), Alice gets an invitation as a result of her offer. This is the invitation she sends to the counter-party.
const aliceSeat = await E(zoe).offer(
creatorInvitation,
aliceProposal,
alicePayments,
);
const invitation = await E(aliceSeat).getOfferResult();
# Zoe's two sides: Zoe Service and Zoe Contract Facet (ZCF)
You may have noticed the contract code's start
method had a zcf
parameter. This is the Zoe Contract Facet. Zoe has two sides: the Zoe
Service, which you've seen users interact with, and the Zoe Contract
Facet (ZCF), which is accessible to the contract code. Note that users
have access to the Zoe Service, but do not have access to ZCF.
Contract code has access to ZCF and can get access to the Zoe
Service.
To learn more about the Zoe Service, Zoe Contract Facet, and Zoe Helper APIs, see our Zoe API documentation.
# Next steps
If you want to dive deeper into how Zoe works and what you can do, go to the Zoe Guide.
To learn more about the AtomicSwap contract, you can read its documentation and look at its source code (opens new window). There are several other example contracts for different transaction types in the Contracts folder
To start building Zoe contracts and applications (dapps), follow the instructions in Starting a Project after installing the prerequisites.
To explore the Zoe Service and Zoe Contract Facet APIs, see the Zoe API documentation here.