Skip to content

Instantly share code, notes, and snippets.

@tgrecojs
Created December 2, 2022 01:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tgrecojs/6fcdeedd7d0cb0de2d8888f9a76d8c86 to your computer and use it in GitHub Desktop.
Save tgrecojs/6fcdeedd7d0cb0de2d8888f9a76d8c86 to your computer and use it in GitHub Desktop.
zoe fungible faucet contract
// @ts-check
/* global harden */
import '@agoric/zoe/exported.js';
import { AmountMath } from '@agoric/ertp';
import { assert } from '@agoric/assert';
import { Far } from '@endo/marshal';
import { assertIssuerKeywords, swap } from '@agoric/zoe/src/contractSupport';
/**
* This is a very simple contract that creates a new issuer and mints payments
* from it, in order to give an example of how that can be done. This contract
* sends new tokens to anyone who has an invitation.
*
* The expectation is that most contracts that want to do something similar
* would use the ability to mint new payments internally rather than sharing
* that ability widely as this one does.
*
* To pay others in tokens, the creator of the instance can make
* invitations for them, which when used to make an offer, will payout
* the specified amount of tokens.
*
* @type {ContractStartFn}
*/
const start = async (zcf) => {
// Create the internal token mint for a fungible digital asset. Note
// that 'Tokens' is both the keyword and the allegedName.
const zcfMint = await zcf.makeZCFMint('Tokens');
assertIssuerKeywords(zcf, ['Tokens', 'Payment']);
// AWAIT
// Now that ZCF has saved the issuer, brand, and local amountMath, they
// can be accessed synchronously.
const { issuer, brand } = zcfMint.getIssuerRecord();
const { zcfSeat: sellerSeat } = zcf.makeEmptySeatKit();
/** @type {OfferHandler} */
const mintPayment = (seat) => {
const { give, want } = seat.getProposal();
assert(give.Payment.value > 50n, 'Payment value must be greater than 50');
/**
* Not sure using the want.Token.value this is giving an intellisene error:
*
* Argument of type 'bigint | SetValue | CopyTagged | CopyBagValue' is not assignable to parameter of type 'bigint'.
* Type 'SetValue' is not assignable to type 'bigint'
*/
const amount = AmountMath.make(brand, want.Tokens.value);
// Synchronously mint and allocate amount to seat.
zcfMint.mintGains(harden({ Token: amount }), seat);
/**
* swap is shorthand for
* sellerSeat.incrementBy(seat.decrementBy({ Payment: give.Payment }))
*/
swap(zcf, sellerSeat, seat);
// Exit the seat so that the user gets a payout.
seat.exit();
// Since the user is getting the payout through Zoe, we can
// return anything here. Let's return some helpful instructions.
return 'Offer completed. You should receive a payment from Zoe';
};
const creatorFacet = Far('creatorFacet', {
// The creator of the instance can send invitations to anyone
// they wish to.
makeInvitation: () => zcf.makeInvitation(mintPayment, 'mint a payment'),
getTokenIssuer: () => issuer,
});
const publicFacet = Far('publicFacet', {
// Make the token issuer public. Note that only the mint can
// make new digital assets. The issuer is ok to make public.
getTokenIssuer: () => issuer,
});
// Return the creatorFacet to the creator, so they can make
// invitations for others to get payments of tokens. Publish the
// publicFacet.
return harden({ creatorFacet, publicFacet });
};
harden(start);
export { start };
// @ts-check
/* eslint-disable import/order -- https://github.com/endojs/endo/issues/1235 */
import { test } from './prepare-test-env-ava.js';
import path from 'path';
import bundleSource from '@endo/bundle-source';
import { E } from '@endo/eventual-send';
import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js';
import { makeZoeKit } from '@agoric/zoe';
import { AmountMath, makeIssuerKit } from '@agoric/ertp';
const filename = new URL(import.meta.url).pathname;
const dirname = path.dirname(filename);
const contractPath = `${dirname}/../src/contract.js`;
const setupTestMints = () => {
const dollarKit = makeIssuerKit('dollars');
const {
brand: dollarBrand,
issuer: dollarIssuer,
mint: dollarMint,
} = dollarKit;
const euroKit = makeIssuerKit('euros');
const { brand: euroBrand, issuer: euroIssuer, mint: euroMint } = euroKit;
const dollars = (x) => AmountMath.make(dollarBrand, x);
const euros = (x) => AmountMath.make(euroBrand, x);
return {
dollarKit,
dollarBrand,
dollarIssuer,
dollarMint,
dollars,
euroKit,
euroBrand,
euroIssuer,
euroMint,
euros,
};
};
const { dollarIssuer, dollarMint, dollars } = setupTestMints();
test('mint payments - called with less than the required amount', async (t) => {
const { zoeService } = makeZoeKit(makeFakeVatAdmin().admin);
const feePurse = E(zoeService).makeFeePurse();
const zoe = E(zoeService).bindDefaultFeePurse(feePurse);
// pack the contract
const bundle = await bundleSource(contractPath);
// install the contract
const installation = E(zoe).install(bundle);
const { creatorFacet, instance } = await E(zoe).startInstance(installation, {
Payment: dollarIssuer,
});
// Let's get the tokenIssuer from the contract so we can evaluate
// what we get as our payout
const publicFacet = E(zoe).getPublicFacet(instance);
const tokenIssuer = E(publicFacet).getTokenIssuer();
const tokenBrand = await E(tokenIssuer).getBrand();
const tokens = (x) => AmountMath.make(tokenBrand, x);
// Alice makes an invitation for Bob that will give him 1000 tokens
const invitation = E(creatorFacet).makeInvitation();
const proposal = {
give: { Payment: dollars(5n) },
want: { Tokens: tokens(5n) },
};
// Bob makes an offer using the invitation
const seat = E(zoe).offer(invitation, proposal, {
Payment: dollarMint.mintPayment(dollars(5n)),
});
const offerResult = E(seat).getOfferResult();
await t.throwsAsync(offerResult, {
message: 'Payment value must be greater than 50',
});
});
test('mint payments - happy path', async (t) => {
const { zoeService } = makeZoeKit(makeFakeVatAdmin().admin);
const feePurse = E(zoeService).makeFeePurse();
const zoe = E(zoeService).bindDefaultFeePurse(feePurse);
// pack the contract
const bundle = await bundleSource(contractPath);
// install the contract
const installation = E(zoe).install(bundle);
const { creatorFacet, instance } = await E(zoe).startInstance(installation, {
Payment: dollarIssuer,
});
// Let's get the tokenIssuer from the contract so we can evaluate
// what we get as our payout
const publicFacet = E(zoe).getPublicFacet(instance);
const tokenIssuer = E(publicFacet).getTokenIssuer();
const tokenBrand = await E(tokenIssuer).getBrand();
const tokens = (x) => AmountMath.make(tokenBrand, x);
// Alice makes an invitation for Bob that will give him 1000 tokens
const invitation = E(creatorFacet).makeInvitation();
const proposal = {
give: { Payment: dollars(1000n) },
want: { Tokens: tokens(1000n) },
};
// Bob makes an offer using the invitation
const seat = E(zoe).offer(invitation, proposal, {
Payment: dollarMint.mintPayment(dollars(1000n)),
});
const paymentP = E(seat).getPayout('Token');
const tokenPayoutAmount = await E(tokenIssuer).getAmountOf(paymentP);
// Bob got 1000 tokens
t.deepEqual(tokenPayoutAmount, tokens(1000n));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment