Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dapp-agoric-basics tutorial and takeaways #1067

Merged
merged 13 commits into from
May 2, 2024
Merged
30 changes: 30 additions & 0 deletions main/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,36 @@ export default defineConfig({
},
],
},
{
text: 'Tutorials',
collapsed: true,
items: [
{
text: 'dapp-agoric-basics',
link: '/guides/getting-started/tutorial-dapp-agoric-basics',
},
{
text: 'Takeaway 1: Sell Concert Tickets Contract Overview',
link: '/guides/getting-started/sell-concert-tickets-contract-explainer',
},


{
text: 'Takeaway 2: Swaparoo Contract Overview',
link: '/guides/getting-started/swaparoo-how-to-swap-assets-explainer',
},

{
text: 'Takeaway 3: Sending Invitation Payments using an Address',
link: '/guides/getting-started/swaparoo-making-a-payment-explainer',
},

{
text: 'Takeaway 4: Modifying the Swaparoo Smart Contract',
link: '/guides/getting-started/explainer-how-to-modify-swaparoo',
},
],
},
{
text: 'Support',
collapsed: true,
Expand Down
11 changes: 11 additions & 0 deletions main/.vitepress/themeConfig/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ export const nav = [
},
],
},

{
text: 'Tutorials',
items: [
{
text: 'dapp-agoric-basics',
link: '/guides/getting-started/tutorial-dapp-agoric-basics',
},
],
},

{
text: 'Support',
items: [
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions main/guides/getting-started/explainer-how-to-modify-swaparoo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Modifying the Swaparoo Smart Contract

To modify the `swapWithFee` contract so that the fee is doubled, you need to use the parameter governance features provided by the Agoric platform. Here's how you can do it:

### Update the contract code to support parameter governance:
- Import the necessary functions from `@agoric/governance/src/contractHelper.js`.
- Define the parameter types for the governed parameters (in this case, `Fee`).
- Modify the `start` function to include `handleParamGovernance` and get the `publicMixin`, `makeDurableGovernorFacet`, and `params`.
- Update the `publicFacet` and `creatorFacet` to include the `publicMixin` and `makeDurableGovernorFacet`.
- Deploy the updated contract code and start the contract instance.

### Set up the committee (electorate) and election manager contracts:
- Deploy the `committee.js` contract from `@agoric/governance` for the electorate.
- Deploy the `econCommitteeCharter.js` contract from `@agoric/inter-protocol` for the election manager.
- Obtain invitations for the committee members and send them to their smart wallets.

### Committee members accept the charter and committee invitations:
- Each committee member uses their smart wallet to redeem the charter invitation and obtain the capability to put a question using `VoteOnParamChange`.
- Each committee member also accepts the committee invitation to obtain the capability to vote.

### A committee member puts a question to double the fee:
- The committee member creates an offer with the necessary `offerArgs`, including the new fee value (doubled) and the deadline.
- The offer is made using the `VoteOnParamChange` capability obtained earlier.

### Other committee members vote on the question:
- Each committee member retrieves the question details from `vstorage` using the `questionHandle`.
- They create an offer to vote on the question using the `makeVoteInvitation` capability obtained earlier.

### Once the deadline is reached and the question carries, the contract governor is notified:
- The contract governor instructs the `swapWithFee` contract to change the fee to the new value (doubled).
- The `swapWithFee` contract updates its parameters and publishes the changes to `vstorage`.

By following these steps and utilizing the parameter governance features provided by Agoric, you can modify the `swapWithFee` contract to double the fee without directly changing the contract code. The changes are made through a governed process involving the committee members and the election manager.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Sell Concert Tickets Smart Contract
This smart contract is designed to mint and sell event tickets as non-fungible tokens (NFTs) in the form of a semi-fungible asset. In this example there are three categories or classes of tickets:
- Tickets near the front are the most expensive
- Tickets in the middle rows are priced between expensive and cheap seats
- Tickets in the back are the lowest priced

## Contract Setup
The contract is parameterized with an `inventory` object, which contains information about the different types of tickets available for sale, their prices `tradePrice`, and the maximum number of tickets for each type `maxTickets`. For example:
```js
const inventory = {
frontRow: {
tradePrice: AmountMath.make(istBrand, 3n),
maxTickets: 3n,
},
middleRow: {
tradePrice: AmountMath.make(istBrand, 2n),
maxTickets: 5n,
}
};
```

After the contract is initialized, a new ERTP mint for the "Ticket" asset is created:
```js
const ticketMint = await zcf.makeZCFMint('Ticket', AssetKind.COPY_BAG);
const { brand: ticketBrand } = ticketMint.getIssuerRecord();
```

The entire inventory of tickets is minted and held by the `inventorySeat`:
```js
const inventoryBag = makeCopyBag(
Object.entries(inventory).map(([ticket, { maxTickets }], _i) => [
ticket,
maxTickets,
]),
);
const toMint = {
Tickets: {
brand: ticketBrand,
value: inventoryBag,
},
};
const inventorySeat = ticketMint.mintGains(toMint);
```

## Trading Tickets
Customers who wish to purchase event tickets first make an invitation to trade for tickets using `makeTradeInvitation`:
```js
const makeTradeInvitation = () =>
zcf.makeInvitation(tradeHandler, 'buy tickets', undefined, proposalShape);
```

The `tradeHandler` function is called when a purchaser makes an offer:
```js
const tradeHandler = buyerSeat => {
const { give, want } = buyerSeat.getProposal();
// ... checks and transfers
};
```

## Trade Handler
The `tradeHandler` function begins by checking to see if there are enough tickets in inventory to satisfy the trade:
```js
AmountMath.isGTE(
inventorySeat.getCurrentAllocation().Tickets,
want.Tickets,
) || Fail`Not enough inventory, ${q(want.Tickets)} wanted`;
```

Next, the total price is calcualted using `bagPrice`:
```js
const totalPrice = bagPrice(want.Tickets.value, inventory);
```

After that, a check is made to ensure the offered price is sufficient:
```js
AmountMath.isGTE(give.Price, totalPrice) ||
Fail`Total price is ${q(totalPrice)}, but ${q(give.Price)} was given`;
```

Finally, `atomicRearrange` can be called to exchange the requested tickets for the required payment:
```js
atomicRearrange(
zcf,
harden([
// price from buyer to proceeds
[buyerSeat, proceeds, { Price: totalPrice }],
// tickets from inventory to buyer
[inventorySeat, buyerSeat, want],
]),
);
```

As you're going through this tutorial it may be helpful to watch this video walkthrough:
<iframe width="560" height="315" src="https://www.youtube.com/embed/Wtq6dwsRdOQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
105 changes: 104 additions & 1 deletion main/guides/getting-started/swaparoo-how-to-swap-assets-explainer.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,108 @@
# Swaparoo Contract

## How to Swap Assets
This smart contract is designed to allow two parties to swap assets between themselves, with a fee charged to one of the parties. The contract is started with a `feeAmount` and a `namesByAddressAdmin` object, which is used to retrieve a deposit facet for the second party.

**NOTE:** *`namesByAddressAdmin` is actually excess authority in this scenario. Normally read-only access can be attained via `namesByAddress`. The use of `namesByAddressAdmin` in this example is due to a bug, which will be addressed in an upcoming release.*

Let's take a look at how this contract works:

## Setting up Fee Handling
The contract retrieves the `feeIssuer` from the Zoe service, which is an issuer for the stable token used for fees. It creates a `feeSeat` and a `feeShape` based on the `feeAmount` specified in the contract terms.
```js
const stableIssuer = await E(zcf.getZoeService()).getFeeIssuer();
const feeBrand = await E(stableIssuer).getBrand();
const { zcfSeat: feeSeat } = zcf.makeEmptySeatKit();
const feeShape = makeNatAmountShape(feeBrand, feeAmount.value);
```

## Making the First Invitation
The `makeFirstInvitation` function is called with an array of issuers. It verifies that these issuers are part of the contract terms and saves any new issuers to the contract. It then creates an invitation with a proposal shape that includes the `feeShape` in the give record.
```js
const makeFirstInvitation = issuers => {
mustMatch(issuers, M.arrayOf(IssuerShape));
for (const i of issuers) {
if (!Object.values(zcf.getTerms().issuers).includes(i)) {
zcf.saveIssuer(i, `Issuer${(issuerNumber += 1)}`);
}
}
const proposalShape = M.splitRecord({
give: M.splitRecord({ Fee: feeShape }),
});

const firstInvitation = zcf.makeInvitation(
makeSecondInvitation,
'create a swap',
undefined,
proposalShape,
);
return firstInvitation;
};
```

## Making the Second Invitation
When the first party accepts the invitation, the `makeSecondInvitation` function is called. This function retrieves the deposit facet for the second party using the `namesByAddressAdmin` object and the provided address.
```js
const makeSecondInvitation = async (firstSeat, offerArgs) => {
mustMatch(offerArgs, harden({ addr: M.string() }));
const { addr: secondPartyAddress } = offerArgs;

const secondDepositFacet = await E(depositFacetFromAddr).lookup(
secondPartyAddress,
'depositFacet',
);
// ...
};
```

From there a second invitation is created with an offer handler that checks if the second party's proposal matches the first party's want. If it does, it calls the `swapWithFee` function to perform the asset swap and collect the fee.
```js
const secondSeatOfferHandler = secondSeat => {
if (!matches(secondSeat.getProposal(), makeSecondProposalShape(want1))) {
// Handle mismatched proposals
return;
}

return swapWithFee(zcf, firstSeat, secondSeat, feeSeat, feeAmount);
};

const secondSeatInvitation = await zcf.makeInvitation(
secondSeatOfferHandler,
'matchOffer',
{ give: give1, want: want1 },
);
```

## Performing the swap
The `swapWithFee` function uses the `atomicRearrange` function from Zoe to perform the asset swap and collect the fee. It rearranges the assets between the first seat, second seat, and the feeSeat.
```js
export const swapWithFee = (zcf, firstSeat, secondSeat, feeSeat, feeAmount) => {
const { Fee: _, ...firstGive } = firstSeat.getProposal().give;

atomicRearrange(
zcf,
harden([
[firstSeat, secondSeat, firstGive],
[secondSeat, firstSeat, secondSeat.getProposal().give],
[firstSeat, feeSeat, { Fee: feeAmount }],
]),
);

firstSeat.exit();
secondSeat.exit();
return 'success';
};
```

## Collecting fees
The contract also provides a `creatorFacet` with a `makeCollectFeesInvitation` method, which creates an invitation to collect the fees accumulated in the feeSeat.
```js
const creatorFacet = Far('Creator', {
makeCollectFeesInvitation() {
return makeCollectFeesInvitation(zcf, feeSeat, feeBrand, 'Fee');
},
});
```

## Video Walkthrough
Watch this short video walk-through of the complete Swaparoo Smart Contract that allows any two parties to trade any digital assets with minimal risk.
<iframe width="560" height="315" src="https://www.youtube.com/embed/qHa7u8r62JQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
41 changes: 41 additions & 0 deletions main/guides/getting-started/swaparoo-making-a-payment-explainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Sending Invitation Payments using an Address
In this document, we'll explain how to send a payment to someone using their `agoric1...` address from an Agoric smart contract using a deposit facet.

## Using a depositFacet
Let's take a look at the following code snippet from the Swaparoo contract:
```js
const secondDepositFacet = await E(depositFacetFromAddr).lookup(
secondPartyAddress,
'depositFacet',
);

await E(secondDepositFacet).receive(secondSeatInvitation);

return 'invitation sent';
```

## Step-by-Step Explanation
### Retrieving the Deposit Facet:
- `depositFacetFromAddr` is an object that provides a lookup function for deposit facets associated with addresses. The Swaparoo contract is provided with a `namesByAddressAdmin` by the proposal (`swaparoo.proposal.js`). The contract makes `depositFacetFromAddr` using `fixHub()`.
- An example of an address might be `agoric1ydzxwh6f893jvpaslmaz6l8j2ulup9a7x8qvvq`.
- The lookup function is called with `secondPartyAddress` and `'depositFacet'` as arguments to retrieve the deposit facet associated with the `secondPartyAddress`.
- The resulting deposit facet is stored in the `secondDepositFacet` variable.

### Making the Payment:
- `secondDepositFacet` represents the deposit facet obtained in the previous step.
- The `receive` method is called on `secondDepositFacet`, passing `secondSeatInvitation` as an argument.
- `secondSeatInvitation` is an Invitation to participate in the second seat (recall that invitations are payments).
- Since `receive` is another asynchronous operation, the `await` keyword is again used to wait for it to complete.
- By calling `receive` on the deposit facet with `secondSeatInvitation`, the payment represented by `secondSeatInvitation` is transferred or deposited into a purse associated with `secondDepositFacet`.

### Returning a Result:
- After the payment has been successfully made by calling `receive`, the function returns the string `'invitation sent'` to indicate that the invitation has been sent.

## Deposit Facets in Agoric
In the Agoric smart contract framework, deposit facets are used as a way to transfer and manage digital assets and payments between parties. By calling the receive method on a deposit facet and passing in a payment or offer, the smart contract can deposit or transfer assets into the account associated with that facet.

Deposit facets provide an abstraction layer for handling payments and ensure that the transfers are performed securely and reliably within the smart contract.

## Video Walkthrough
As you're going through this tutorial it may be helpful to watch this video walkthrough.
<iframe width="560" height="315" src="https://www.youtube.com/embed/XeHBMO7SckU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
Loading
Loading