-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dapp-agoric-basics tutorial and takeaways (#1067)
* Create tutorial-dapp-agoric-basics.md * Delete main/guides/tutorial-dapp-agoric-basics.md * Add files via upload * Create tutorial-dapp-agoric-basics.md * Create swaparoo-making-a-payment-explainer.md * Update swaparoo-how-to-swap-assets-explainer.md * Create sell-concert-tickets-contract-explainer.md * Create explainer-how-to-modify-swaparoo.md * Update swaparoo-making-a-payment-explainer.md * Update swaparoo-how-to-swap-assets-explainer.md * Update config.mjs * Update nav.js * Update tutorial-dapp-agoric-basics.md
- Loading branch information
1 parent
acbb947
commit 1fc4d9f
Showing
12 changed files
with
389 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
33
main/guides/getting-started/explainer-how-to-modify-swaparoo.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
94 changes: 94 additions & 0 deletions
94
main/guides/getting-started/sell-concert-tickets-contract-explainer.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
105
main/guides/getting-started/swaparoo-how-to-swap-assets-explainer.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
41
main/guides/getting-started/swaparoo-making-a-payment-explainer.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
Oops, something went wrong.