diff --git a/main/guides/ui-tutorial/agoric-provider/index.md b/main/guides/ui-tutorial/agoric-provider/index.md index 9b717cb607..4553e457fb 100644 --- a/main/guides/ui-tutorial/agoric-provider/index.md +++ b/main/guides/ui-tutorial/agoric-provider/index.md @@ -14,7 +14,7 @@ This makes it easy to handle all the platform-specific implementation details, s Install the following dependencies: ``` -yarn add -D @agoric/react-components cosmos-kit@2.8.5 @interchain-ui/react@1.22.11 +yarn add -D @agoric/react-components@0.1.1-dev-1329752.0 cosmos-kit@2.8.5 @interchain-ui/react@1.22.11 ``` The `cosmos-kit` dependency is used to provide different wallets in the wallet connection modal. diff --git a/main/guides/ui-tutorial/conclusion/index.md b/main/guides/ui-tutorial/conclusion/index.md new file mode 100644 index 0000000000..8a449f5c8f --- /dev/null +++ b/main/guides/ui-tutorial/conclusion/index.md @@ -0,0 +1,20 @@ +## Conclusion + +Throughout this tutorial, you've accomplished all the basics of building an Agoric Dapp UI: + +- Scaffolding a new React App. +- Setting up `AgoricProvider` and connecting to a wallet. +- Reading and rendering purse balances. +- Reading contract data with `chainStorageWatcher`. +- Making an offer to exchange some on-chain assets. + +With these basics in place, the world of Agoric UI development has opened up to you. To expand on your +newly acquired knowledge, consider trying out the following tasks: + +- Spruce up your UI with `interchain-ui` components and try customizing the theme. +- Add automated e2e testing using https://github.com/agoric-labs/synpress +- Add a new item to the "Offer Up" contract and implement UI support for it. +- Deploying the contract to a testnet and adding the `NetworkDropdown` component to your dapp to switch between networks. +- Write your own dapp and implement a custom UI for it. + +Check out the #dev channel in the Discord with any questions or feedback for future improvements on this guide. diff --git a/main/guides/ui-tutorial/connect-wallet/index.md b/main/guides/ui-tutorial/connect-wallet/index.md index 21e8849ccc..dcb3952af2 100644 --- a/main/guides/ui-tutorial/connect-wallet/index.md +++ b/main/guides/ui-tutorial/connect-wallet/index.md @@ -107,9 +107,7 @@ const Purses = () => { {istPurse ? ( stringifyAmountValue( istPurse.currentAmount, - // @ts-expect-error displayInfo missing type istPurse.displayInfo.assetKind, - // @ts-expect-error displayInfo missing type istPurse.displayInfo.decimalPlaces ) ) : ( diff --git a/main/guides/ui-tutorial/index.md b/main/guides/ui-tutorial/index.md index 42109491af..7a1e2aea95 100644 --- a/main/guides/ui-tutorial/index.md +++ b/main/guides/ui-tutorial/index.md @@ -1,6 +1,7 @@ # UI Tutorial -In this tutorial you will build your own UI from scratch. Along the way, you'll learn how to connect to a wallet, query the blockchain and smart contract state, sign transactions, and more! +In this tutorial you will build your own UI from scratch. Along the way, you'll learn how to connect to a wallet, query the blockchain and smart contract state, sign transactions, and more! To follow along, check your work, and help if you get stuck, you can use the https://github.com/agoric-labs/ui-tutorial repo as a reference. Each section of the tutorial has an associated branch in the repo +to act as a checkpoint. ## Prerequisites diff --git a/main/guides/ui-tutorial/making-an-offer/index.md b/main/guides/ui-tutorial/making-an-offer/index.md index e69de29bb2..85eeddd385 100644 --- a/main/guides/ui-tutorial/making-an-offer/index.md +++ b/main/guides/ui-tutorial/making-an-offer/index.md @@ -0,0 +1,280 @@ +## Making an Offer + +If you've made it this far, you've created a React app that connects to the wallet, renders +the IST purse balance of the user, and reads the chain with `chainStorageWatcher`. If you've +run into an issues, you can check out the `checkpoint-4` branch for reference. + +In this final tutorial, you'll see how to make offers from your app, tying everything together +in a basic end-to-end experience for the user. + +### Building out the UI + +Before we can submit an offer, we'll need to build out some basic inputs so the user can specify +the Items they want. There's 3 types of items available for sale in the contract, so start by +creating an array to list them in `Trade.tsx`: + +```ts +const allItems = ['scroll', 'map', 'potion']; +``` + +Next, add another component to `Trade.tsx` for letting the user choose the amount of each item in the offer: + +```tsx +const Item = ({ + label, + value, + onChange, + inputStep, +}: { + label: string; + value: number | string; + onChange: React.ChangeEventHandler; + inputStep?: string; +}) => ( +
+

{label}

+ +
+); +``` + +And add some styles to `App.css`: + +```css +.item-col { + display: flex; + flex-direction: column; + align-items: center; + padding: 0 15px 25px 15px; + margin: 5px; +} + +.row-center { + display: flex; + flex-direction: row; + align-items: center; +} + +.input { + border: none; + background: #242424; + text-align: center; + padding: 5px 10px; + border-radius: 8px; + font-size: 1.2rem; + width: 75px; +} + +.input[type='number']::-webkit-inner-spin-button { + opacity: 1; +} +``` + +Next, in your `Trade` component, render an `Item` for each item in the list: + +```tsx +const Trade = () => { + ... + + const [choices, setChoices] = useState>({ + map: 1n, + scroll: 2n, + }); + + const changeChoice = (ev: FormEvent) => { + if (!ev.target) return; + const elt = ev.target as HTMLInputElement; + const title = elt.title; + if (!title) return; + const qty = BigInt(elt.value); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [title]: _old, ...rest } = choices; + const newChoices = qty > 0 ? { ...rest, [title]: qty } : rest; + setChoices(newChoices); + }; + + return ( +
+

Want: Choose up to 3 items

+
+ {allItems.map(title => ( + + ))} +
+
+ ); +} +``` + +As you can see, you're storing `choices` with a `useState` hook. This way, as the user changes the inputs, +the number of items of each type is updated. Later on you'll use `choices` to specify the offer. + + +Next, you'll add an input to let the user specify the amount of IST they want to give in exchange for the items. +First, get a reference to the IST purse with the `usePurse` you created earlier, and create a state hook for the IST value: + +```tsx +const Trade = () => { + const istPurse = usePurse('IST'); + const [giveValue, setGiveValue] = useState(0n); + + ... +} +``` + +Next, use the `` component to add an input for the IST "give" amount: + +```tsx +import { AmountInput } from '@agoric/react-components'; + +... + +// In your 'Trade' component: +{istPurse && ( + <> +

Give: At least 0.25 IST

+
+ +
+ +)} +``` + +### Submitting the Offer + +With these components in place, the user is able to select their Item and IST amounts, and the app +is able to store those in its state. Now, you'll see how to use the `makeOffer` function to sign +a transaction and make an offer to the smart contract with the selected amounts. + +Recall the `useContract` hook you added to your `Trade` component previously. Now, you'll need the +brands and instance from that to submit the offer: + +```ts +const { brands, instance } = useContract(); +``` + +Next, get the `makeOffer` function from the `useAgoric()` hook: + +```ts +const { makeOffer } = useAgoric(); +``` + +Now, create a function to submit the offer. For more details on how this works, see [making an offer](../../getting-started/explainer-how-to-make-an-offer.md): + +```ts +import { makeCopyBag } from '@agoric/store'; + +// Inside your 'Trade' component: +const submitOffer = () => { + assert(brands && instance && makeOffer); + const value = makeCopyBag(Object.entries(choices)); + const want = { Items: { brand: brands.Item, value } }; + const give = { Price: { brand: brands.IST, value: giveValue } }; + + makeOffer( + { + source: 'contract', + instance, + publicInvitationMaker: 'makeTradeInvitation', + }, + { give, want }, + undefined, + (update: { status: string; data?: unknown }) => { + console.log('UPDATE', update); + if (update.status === 'error') { + alert(`Offer error: ${update.data}`); + } + if (update.status === 'accepted') { + alert('Offer accepted'); + } + if (update.status === 'refunded') { + alert('Offer rejected'); + } + } + ); +}; +``` + +This uses the `makeCopyBag` util to construct the Item amount in a way that the contract can understand. +Add it to your dependencies: + +``` +yarn add -D @agoric/store@0.9.2 +``` + +And add the type to `vite-env.d.ts`: + +```ts +declare module '@agoric/store' { + export const makeCopyBag; +} +``` + +Now, simply add a button to submit the offer: + +```tsx + {!!(brands && instance && makeOffer && istPurse) && ( + + )} +``` + +Upon clicking the offer, you should see a Keplr window pop up to approve the transaction with the "Give" and "Want" +you selected. Try selecting 3 items, and giving 0.25 IST, and the offer should be accepted. See what happens +if you select more than 3 items, or give less than 0.25 IST... it should reject the offer, and you should be +refunded your IST (see [offer safety](../../../guides/zoe/offer-safety.md)) + +### Rendering the Items Purse + +So, you've made a successful offer and acquired some items, but where are they? You can render the items purse +similarly to the IST purse. In `Purses.tsx`, add the following: + +```tsx +... + + const itemsPurse = usePurse('Item'); +... + +
+ Items: + {itemsPurse ? ( +
    + {itemsPurse.currentAmount.value.payload.map( + // @ts-expect-error ignore 'any' type + ([name, number]) => ( +
  • + {String(number)} {name} +
  • + ) + )} +
+ ) : ( + 'None' + )} +
+``` + +Now, make another offer, and see that your items purse automatically updates after the offer is accepted. To see the complete +solution for this example, check out the `checkpoint-5` branch. + +## Next + +[Conclusion](../conclusion/index.md) diff --git a/main/guides/ui-tutorial/querying-contract-data/index.md b/main/guides/ui-tutorial/querying-contract-data/index.md index 1f9dfe89db..fbb4bf5aca 100644 --- a/main/guides/ui-tutorial/querying-contract-data/index.md +++ b/main/guides/ui-tutorial/querying-contract-data/index.md @@ -48,6 +48,7 @@ And add some styling for it in `App.css` while we're at it: background: #171717; border-radius: 24px; margin-bottom: 16px; + padding: 16px; } ``` @@ -115,7 +116,7 @@ As you can see, this hook makes use of `chainStorageWatcher` to watch two vstora Go ahead and add this hook to the `Trade` component you made before this: -``` +```tsx import { useContract } from './hooks'; const Trade = () => {