diff --git a/main/.vitepress/config.mjs b/main/.vitepress/config.mjs index f3d13b40a..aff88d3a6 100644 --- a/main/.vitepress/config.mjs +++ b/main/.vitepress/config.mjs @@ -436,10 +436,28 @@ export default defineConfig({ { text: 'Cross-Chain Unbond Example', link: '/guides/orchestration/getting-started/contract-walkthrough/cross-chain-unbond', + } + ] + }, + { + text: 'Dapp Orchestration Basics', + link: '/guides/orchestration/dapp-orchestration-basics/', + items: [ + { + text: 'Installation', + link: '/guides/orchestration/dapp-orchestration-basics/installation/', + }, + { + text: 'Contract', + link: '/guides/orchestration/dapp-orchestration-basics/contract/', + }, + { + text: 'User Interface', + link: '/guides/orchestration/dapp-orchestration-basics/ui/', }, { - text: 'Orchestration Basics', - link: '/guides/orchestration/getting-started/contract-walkthrough/orchestration-basics', + text: 'Testing', + link: '/guides/orchestration/dapp-orchestration-basics/testing/', } ] }, diff --git a/main/guides/getting-started/index.md b/main/guides/getting-started/index.md index c071420dd..b6ef751ca 100644 --- a/main/guides/getting-started/index.md +++ b/main/guides/getting-started/index.md @@ -140,12 +140,31 @@ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin <details> <summary>Installing Docker on MacOS</summary> + +You can install Docker using their official documentation or Homebrew. + +**Using Docker's Website:** + +Follow the [official Docker installation guide for Mac](https://docs.docker.com/desktop/install/mac-install/). + + +**Using Homebrew:** + Previously, you installed brew on your machine. You can install docker using the same command. + ```sh brew cask install docker ``` +or +```sh +brew install docker --cask +``` +Now, you can open Docker and let it run in the background +```sh +open -a Docker +``` </details> Now that Docker has been installed you'll need to add your user account to the Docker group. @@ -193,6 +212,8 @@ For more examples and ideas, visit: </details> + + ## Creating Your Dapp From a Template Now you'll use yarn to pull down the sample dapp. The sample dapp will be placed in a subfolder named `demo`. diff --git a/main/guides/orchestration/assets/docker-kube.png b/main/guides/orchestration/assets/docker-kube.png new file mode 100644 index 000000000..2f3859e96 Binary files /dev/null and b/main/guides/orchestration/assets/docker-kube.png differ diff --git a/main/guides/orchestration/assets/docker-resources.png b/main/guides/orchestration/assets/docker-resources.png new file mode 100644 index 000000000..aac888ca0 Binary files /dev/null and b/main/guides/orchestration/assets/docker-resources.png differ diff --git a/main/guides/orchestration/assets/ui.png b/main/guides/orchestration/assets/ui.png new file mode 100644 index 000000000..97b4c3b2f Binary files /dev/null and b/main/guides/orchestration/assets/ui.png differ diff --git a/main/guides/orchestration/dapp-orchestration-basics/contract/index.md b/main/guides/orchestration/dapp-orchestration-basics/contract/index.md new file mode 100644 index 000000000..f33115a4d --- /dev/null +++ b/main/guides/orchestration/dapp-orchestration-basics/contract/index.md @@ -0,0 +1,292 @@ +# Overview of `orca.contract.js` +Here, we will walkthrough the orchestration basics demo contract. + +## Table of Contents +- [Overview of `orca.contract.js`](#overview-of-orcacontractjs) + - [Table of Contents](#table-of-contents) + - [Directory Structure](#directory-structure) + - [`createAccountsFn` Orhestration Handler function](#createaccountsfn-orhestration-handler-function) + - [Parameters](#parameters) + - [Workflow](#workflow) + - [The `start` Function](#the-start-function) + - [Parameters](#parameters-1) + - [Creating Offer Handler](#creating-offer-handler) + - [Public Facet](#public-facet) + - [Return Value](#return-value) + + + +The `orca.contract.js` file defines the smart contract that orchestrates interactions with various blockchains using the Ochestration API, facilitating the creation and management of accounts on these chains. The primary components of this contract are the `createAccountsFn` function and the `start` function. + +## Directory Structure + +<details> + +```console +tree contract -I 'node_modules' +contract +├── Makefile +├── jsconfig.json +├── package.json +├── patch-bn.js +├── rollup.config.mjs +├── scripts +│ ├── deploy-contract.js +│ └── run-chain.sh +├── src +│ ├── @types +│ │ └── zoe-contract-facet.d.ts +│ ├── collectFees.js +│ ├── debug.js +│ ├── facade.js +│ ├── fixHub.js +│ ├── objectTools.js +│ ├── orc.js +│ ├── orca.contract.js +│ ├── orca.proposal.js +│ ├── platform-goals +│ │ ├── README.md +│ │ ├── board-aux.core.js +│ │ ├── endo1.core.js +│ │ ├── start-contract.js +│ │ ├── start-governed-contract.js +│ │ └── types.js +│ ├── tools +│ │ └── debug.js +│ └── types.js +├── test +│ ├── boot-tools.js +│ ├── build-proposal.test.js +│ ├── bundle-source.test.js +│ ├── lib-gov-test +│ │ └── puppet-gov.js +│ ├── market-actors.js +│ ├── mintStable.js +│ ├── orca-contract.test.js +│ ├── prepare-test-env-ava.js +│ ├── snapshots +│ │ ├── test-postalSvc.js.md +│ │ ├── test-postalSvc.js.snap +│ │ ├── test-swap-wallet.js.md +│ │ ├── test-swap-wallet.js.snap +│ │ ├── test-vote-by-committee.js.md +│ │ └── test-vote-by-committee.js.snap +│ ├── test-build-proposal.js +│ ├── test-bundle-source.js +│ ├── test-deploy-tools.js +│ └── wallet-tools.js +├── tools +│ ├── README.md +│ ├── agd-lib.js +│ ├── bundle-tools.js +│ ├── e2e-tools.js +│ ├── rollup-plugin-core-eval.js +│ ├── startOrch.js +│ └── ui-kit-goals +│ ├── README.md +│ ├── batchQuery.js +│ ├── makeHttpClient.js +│ ├── marshalTables.js +│ ├── name-service-client.js +│ ├── queryKit.js +│ └── test-nameProxy.js +└── tsconfig.json + +12 directories, 61 files + +``` + +</details> + +## `createAccountsFn` Orhestration Handler function + +The `createAccountsFn` function is an asynchronous operation designed to create an account on a specified Cosmos chain. This function not only creates the account but also returns a continuing offer that allows the user to perform further actions like Delegation, WithdrawRewards, and Transfers. Let's break down the key components of this function: + +### Parameters +- **`orch`**: + - **Type**: `Orchestrator` + - **Purpose**: The `orch` parameter represents an instance of the Orchestrator, a powerful abstraction that manages interactions with the blockchain. It provides methods to interact with different chains, create accounts, and fetch chain information. + +- **`_ctx`**: + - **Type**: `undefined` + - **Purpose**: This context parameter is not used in this particular function but is often included in functions to pass additional information or tools that might be needed for processing. (unused for now in the first release) + +- **`seat`**: + - **Type**: `ZCFSeat` + - **Purpose**: The [`seat`](/glossary/#seat) parameter represents the user's position from within the contract. It holds the proposal made by the user, which includes the assets they are willing to give or receive. + +- **`offerArgs`**: + - **Type**: `{ chainName: string }` + - **Purpose**: The `offerArgs` object contains arguments related to the user's offer, specifically the `chainName` that identifies which chain the orchestration account should be created on. + +### Workflow + +```javascript +const createAccountsFn = async (orch, _ctx, seat, { chainName }) => { + const { give } = seat.getProposal(); + trace('version 0.1.36'); + trace('give'); + trace(give); +``` + +- **Extracting Proposal**: The function begins by extracting the user's proposal from the `seat`. The `give` object represents what the user is willing to offer in the transaction. +- **Tracing Information**: Several `trace` calls are made to log the current state of the function. This is useful for debugging and ensuring that the function's inputs and intermediate states are correct. + +```javascript + trace('inside createAccounts'); + trace('orch'); + trace(orch); + trace('seat'); + trace(seat); + trace(chainName); + seat.exit(); +``` + +When we run the contract, we will see these log results, as a sanity check of our contract having the dependencies it needs to perform the work we want. + +Next, we simply exit the seat with `seat.exit()` because we are not intending to perform any asset reallocation in this example contract. + +```javascript +try { + const chain = await orch.getChain(chainName); + trace('chain object'); + trace(chain); +``` + +**Fetching Chain Object**: The function then retrieves the chain object using `orch.getChain(chainName)`. This object contains all the necessary information and methods to interact with the specified chain. We pass the `chainName` provided by the `offerArgs`. + +```javascript + const info = await chain.getChainInfo(); + trace('chain info', info); +``` + +**Fetching Chain Information**: The chain information is fetched using `chain.getChainInfo()`. This step is crucial as it provides details about the chain, such as its denomination, address prefixes, and other chain-specific data that might be required for account creation. + +```javascript + const chainAccount = await chain.makeAccount(); + console.log("chainAccount") + console.log(chainAccount) +``` + +**Creating Account**: The core operation of this function is to create an account on the specified chain. This is done by calling `chain.makeAccount()`. The returned `chainAccount` object represents the newly created account and includes methods to interact with the account, such as sending tokens or delegating stake. + +```javascript + return await chainAccount.asContinuingOffer(); +} catch (error) { + console.error('Error in createAccounts:', error); +} +``` + +**Returning Continuing Offer**: Once the account is created, the function returns a continuing offer by calling `chainAccount.asContinuingOffer()`. This allows the user to perform further actions on the account, such as delegating tokens or withdrawing rewards, through subsequent offers. + +**Error Handling**: We wrap the function in a `try...catch` block to handle any errors that might occur during the process. If an error is encountered, it is logged to the console for us to diagnose issues during development and testing. + + +## The `start` Function + +The `start` function is the entry point of the contract, responsible for initializing the contract and setting up the public interface that external users can interact with. Let's break down the function line by line. + +### Parameters +- `zcf`: **Zoe Contract Facet** - Provides essential utilities for contract execution, including making invitations, accessing state, and managing offers. See [zcf](/glossary/#zoe-contract-facet-zcf) for more. +- `privateArgs`: A collection of services and utilities that are provided privately to the contract. These include: + - `orchestrationService`: Manages orchestration operations across multiple chains. + - `marshaller`: Handles serialization and deserialization of data. + - `storageNode`: The storage node in vstorade that our contract can write to, and write children to. + - `timer`: Provides access to timing-related functionalities. This is not used yet in this example. + - `localchain`: Manages local chain interactions. + - `agoricNames`: A service that manages name lookups for Agoric chains and connections. +- `baggage`: Used for persisting contract-related data across reboots/upgrades, ensuring that the contract's state is preserved. + +```javascript +export const start = async (zcf, privateArgs, baggage) => { + trace('inside start function'); + trace('privateArgs', privateArgs); +``` + + The `start` function begins by tracing the function entry and the provided `privateArgs` to make sure that all necessary services are available in our logs. + +```javascript + const { + orchestrationService: orchestration, + marshaller, + storageNode, + timer, + localchain, + agoricNames, + } = privateArgs; +``` + +**Destructuring `privateArgs`**: The relevant services from `privateArgs` are unpacked and assigned to local variables for easier access within the function. + +```javascript + trace('orchestration: ', orchestration); + trace('marshaller: ', marshaller); + trace('storageNode: ', storageNode); + trace('timer: ', timer); + trace('localchain: ', localchain); + trace('agoricNames: ', agoricNames); +``` + +**Tracing Services**: Each service is logged for debugging purposes, ensuring that all services are correctly initialized. + +```javascript + const { orchestrate, zone } = provideOrchestration( + zcf, + baggage, + privateArgs, + privateArgs.marshaller, + ); +``` + +**Orchestration Setup**: The `provideOrchestration` function is invoked, which sets up the orchestration tooling for us, as a convenience. This function returns two key elements: + - `orchestrate`: the orchestration offer handler for `createAccountsFn`. + - `zone`: The [zone](/glossary/#zone) we use to allocate exposed remotable objects, or [Exos](/glossary/#exo). + +### Creating Offer Handler + +```javascript + /** @type {OfferHandler} */ + const makeAccount = orchestrate( + 'makeAccount', + undefined, + createAccountsFn, + ); +``` + +**OfferHandler Definition**: An `OfferHandler` we name `makeAccount` is created using the `orchestrate` method. This links the handler to the `createAccountsFn` function, which is responsible for creating accounts on a chain. + +### Public Facet + +The `publicFacet` is the part of the contract that is exposed to external users, allowing them to interact with it. See [`facet`](/glossary/#facet) + +```javascript + const publicFacet = zone.exo( + 'Orca Public Facet', + M.interface('Orca PF', { + makeAccountInvitation: M.callWhen().returns(InvitationShape), + }), + { + makeAccountInvitation() { + return zcf.makeInvitation( + makeAccount, + 'Make an Orchestration Account', + ); + }, + }, + ); +``` + +**Creating Public Facet**: The `zone.exo` method is used to create an isolated object (exo) that defines the public interface of the contract. + +**Interface Guard Definition**: The public facet exposes a single method, `makeAccountInvitation`, which returns an invitation for creating an orchestration account. The invitation is created using `zcf.makeInvitation` and is tied to the `makeAccount` offer handler. + +Here, our interface guard enforces that when `makeAccountInvitation` is invoked, it expects no input parameters, and expects to return an `InvitationShape`. Otherwise, the interface guard will reject the offer before it reaches the contract. + +### Return Value + +```javascript + return { publicFacet }; +}; +``` + +**Returning Public Facet**: The function returns an object containing the `publicFacet`. This makes the public interface accessible to users and other contracts, enabling them to interact with the contract's, all while adhering to the [object-capabilities model](/glossary/#object-capabilities). diff --git a/main/guides/orchestration/dapp-orchestration-basics/index.md b/main/guides/orchestration/dapp-orchestration-basics/index.md new file mode 100644 index 000000000..7de72aad0 --- /dev/null +++ b/main/guides/orchestration/dapp-orchestration-basics/index.md @@ -0,0 +1,18 @@ +# Orchestration Basics Dapp Template +To get started with developing using the Orchestration API, developers can make use of our [dapp-orchestration-basics](https://github.com/Agoric/dapp-orchestration-basics) template. + +Following the dApp pattern of our existing dapp templates, `dapp-orchestration-basics` contains both ui & contract folders within a single yarn workspace. + +This dapp template can be used to become familiar with using the API, build on top of it, and obtain hands-on experience with how the API works. + +## Project Structure +Here is the directory structure for `ui/` and `contract`: + +```console +tree dapp-orchestration-basics +├── ui +├── contract +``` + +## User Interface Preview +<img src="../assets/ui.png" width="100%" /> \ No newline at end of file diff --git a/main/guides/orchestration/dapp-orchestration-basics/installation/index.md b/main/guides/orchestration/dapp-orchestration-basics/installation/index.md new file mode 100644 index 000000000..808bed6d7 --- /dev/null +++ b/main/guides/orchestration/dapp-orchestration-basics/installation/index.md @@ -0,0 +1,218 @@ +# Agoric Orchestration Basics Installation Guide +Here, we will walk through installing the Orchestration-Basics demo project onto your local machine. + +## Table of Contents + +- [Agoric Orchestration Basics Installation Guide](#agoric-orchestration-basics-installation-guide) + - [Table of Contents](#table-of-contents) + - [Setting Up the Local Environment](#setting-up-the-local-environment) + - [Overriding the Chain Registry for use with `agoric-sdk/multichain-testing`](#overriding-the-chain-registry-for-use-with-agoric-sdkmultichain-testing) + - [Multichain-Testing Makefile Helpers](#multichain-testing-makefile-helpers) + - [Troubleshooting Docker](#troubleshooting-docker) + - [Enabling Kubernetes](#enabling-kubernetes) + - [Increasing resources allocated to docker](#increasing-resources-allocated-to-docker) + - [Adding a New Address](#adding-a-new-address) + - [Funding the Account](#funding-the-account) + - [Building \& Deploying the dApp](#building--deploying-the-dapp) + - [Running Tests](#running-tests) + - [End-to-End Build \& Deploy](#end-to-end-build--deploy) + - [Funding on Osmosis (for testing)](#funding-on-osmosis-for-testing) + - [Example RPC query for Balances](#example-rpc-query-for-balances) + - [Temporary Funding of ICA](#temporary-funding-of-ica) + +The Orchestration Basics dApp demonstrates key features of the orchestration API within a fully functional end-to-end environment. The user interface enables interaction with the orchestration components from client-side javascript. + +## Setting Up the Local Environment + +To set up your local environment, refer to the `agoric-sdk/multichain-testing/README.md` for detailed setup instructions. Here’s a brief overview of the key steps: + +Once it is setup, you want to be able to run: +```console +kubectl get pods +``` + +We expect to see an output like this: + +```console +NAME READY STATUS RESTARTS AGE +agoriclocal-genesis-0 1/2 Running 0 68s +gaialocal-genesis-0 2/3 Running 0 68s +hermes-agoric-gaia-0 2/2 Running 0 68s +hermes-agoric-osmosis-0 2/2 Running 0 68s +hermes-osmosis-gaia-0 2/2 Running 0 68s +osmosislocal-genesis-0 3/3 Running 0 68s +registry-79cf44b64-xct5b 1/1 Running 0 68s +``` + +This assures us that our environment with the following resources is running: +- an Agoric node +- a Gaia node +- an Osmosis node +- an Agoric <-> Gaia hermes relayer +- an Agoric <-> Osmosis hermes relayer + +**Check Agoric Daemon Status** +Verify that the Agoric daemon is running with the following command: +```bash +agd status +``` +If the daemon isn't reachable, but the node is running, you may need to make sure the ports are forwarded properly. You can do this by running this command from inside `multichain-testing/`: + +```bash +make port-forward +``` + + +### Overriding the Chain Registry for use with `agoric-sdk/multichain-testing` + +To use this dapp with the kubernetes environment spun-up by `multchain-testing` & starship, we have to make sure vstorage reflects the chain information relevent to our locally-maintained environment. + +Run the following commands from the `agoric-sdk/multichain-testing/` directory: + +```bash +make override-chain-registry +``` + +## Multichain-Testing Makefile Helpers +To streamline your workflow, you can add the following commands to the bottom of your `multichain-testing/Makefile`: + +```makefile +teardown: stop-forward stop clean delete + +corepack-setup: + corepack prepare yarn@4 --activate + +corepack-enable: + corepack enable + +test: + yarn test test/install-contracts.test.ts + +all: setup install + sleep 3 + make port-forward + sleep 120 + make fund-provision-pool + sleep 10 + make add-address + echo "done running" + +hermes-update: + kubectl exec -i hermes-agoric-osmosis-0 -c relayer -- hermes update client --host-chain agoriclocal --client 07-tendermint-1 # or 07-tendermint-0 + sleep 60 + make hermes-update +``` + +**Restart the Environment** +If you need to restart your environment for any reason, use the following command from `agoric-sdk/multichain-testing`: +```bash +make teardown ; make stop; make stop-forward; make clean; make; make port-forward +``` + +## Troubleshooting Docker +During setting up the environment, you may run into a few issues with docker. Here are two common setup requirements. + +### Enabling Kubernetes +Inside of "settings", navigate to the "kubernetes" section. Here, you want to ensure that you select the "Enable Kubernetes" checkbox. You will have to restart docker to make these changes take effect. This is something to keep this in mind if you have any containers that are currently running. + +<img src="../../assets/docker-kube.png" width="100%" /> + +### Increasing resources allocated to docker +From inside of "settings", navigate to the "Resources" section. By default docker doesn't optimize the resources allocated for running resource-intensive environments. For this environment, we want to ensure our `CPU limit`, `Memory Limit`, and `Swap` are all bumped up to 75% their max. You can always increase/decrease after you get everything setup, and determine how much resources are needed for your machine. + +<img src="../../assets/docker-resources.png" width="100%" /> + + +## Adding a New Address + +Before deploying the contract, we want to generate a new address using the keyring inside of the localchain environment. To add a new address to the keychain inside the Kubernetes pod, run the following command from the top-level directory: + +```bash +make add-address +``` + +Now, inside of `contract/`, paste the resulting `agoric1` prefixed address into the `Makefile` under the `ADDR`variable. + +## Funding the Account + +To now fund the account we just generated, we run the following command from the top-level directory. + +```bash +make fund +``` + +This funds the pool, provisions the smart wallet, and also funds `CLIENTADDR` and `CLIENT_OSMO_ADDR`. You can use `CLIENTADDR` and `CLIENT_OSMO_ADDR` to set your browser wallet addreses that you would like to fund during this step. + +**CLIENTADDR:** Your browser wallet address for interacting with the orchestration dApp. + +**CLIENT_OSMO_ADDR:** Your Osmosis account address. + +## Building & Deploying the dApp + +To build and deploy the dApp, run the following command from the top-level directory: + +```bash +make +``` + +This is a convenient step that will build, and deploy `/contract/src/orca.contract.js`, using `scripts/deploy-contract.js`. + + +## Running Tests + +To run tests, use the following commands from the top-level directory: + +```bash +make test-orca +``` + +For more on testing the project, see the testing guide under `dapp-orchestration-basics`. + + +## End-to-End Build & Deploy + +For an end-to-end build and deploy, use the following command if the contract deployment fails -- this will simply retry submitting the proposal: + +```bash +make redeploy +``` + +## Funding on Osmosis (for testing) + +To fund an account on Osmosis for any reason, we can get access to the node's environment with this command: + +```console +kubectl -it osmosislocal-genesis-0 -- bash +``` + +and now we are inside the container, and can interact with the keychain accounts, etc. For example, we can now look at the accounts in the keychain, without having to have the osmosis executable on our local filesystem directly. + +```console +osmosisd keys list +[{"name":"faucet","type":"local","address":"osmo1vhdew4wqu3tp8l2d55aqcc73aqvr0rr9vdxded","pubkey":"{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"A5fdOBMxJrfNN0qRmA+Ewzamh1u/4AA1eKhEJHp+lWXr\"}"},{"name":"genesis","type":"local","address":"osmo1qjtcxl86z0zua2egcsz4ncff2gzlcndz2jeczk","pubkey":"{\"@type\":\"/cosmos.crypto.secp256k1.PubKey\",\"key\":\"AmPsa3aqarITEk9NM7T9guOi9VdyrHkqEdqVABLIcDew\"}"}]osmosislocal-genesis-0:/opt# +``` + + +use the following command to fund any address from the faucet account: + +```console +osmosisd tx bank send faucet osmo1dw3nep8yqy5szzxn6hmma6j2z77vp4wz8tkh0w3gyrruwny0w03s070kaa 299999999uosmo --chain-id osmosislocal --gas-adjustment 2 --gas auto --from faucet --gas-prices 0.0025uosmo +``` + +## Example RPC query for Balances + +To query balances via RPC, use the following URL structure: + +```console +http://127.0.0.1:26657/abci_query?path=%22/cosmos.bank.v1beta1.Query/AllBalances%22&data=%22%5Cn-agoric12j5kzvrwunqvrga5vm4zpy3mkeh3lvyld0amz5%22 +``` + +## Temporary Funding of ICA + +To temporarily fund an ICA, use the following command for example: + +```console +agd tx bank send keplr1 agoric15ch7da0d8nvqc8hk6dguq4ext0lvskpjcwm3patf8sygm63chmpqjlzt74 1000uist -y --chain-id agoriclocal +``` + +This is just a native bank transfer of **1000IST** (`uist`) to an arbitrary account using `agd` diff --git a/main/guides/orchestration/dapp-orchestration-basics/testing/index.md b/main/guides/orchestration/dapp-orchestration-basics/testing/index.md new file mode 100644 index 000000000..3bf972f6f --- /dev/null +++ b/main/guides/orchestration/dapp-orchestration-basics/testing/index.md @@ -0,0 +1,376 @@ +# Orchestration Basics Contract Testing Guide +Here, we will walkthrough the testing setup for the Orchestration Basics demo contract, `orca.contract.js`. + +## Table of Contents + +- [Orchestration Basics Contract Testing Guide](#orchestration-basics-contract-testing-guide) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Import Statements](#import-statements) + - [Setup and Context](#setup-and-context) + - [Initializing Zoe and Bundling Contracts](#initializing-zoe-and-bundling-contracts) + - [Mocking Dummy Storage Node](#mocking-dummy-storage-node) + - [Mocking Dummy Marshaller](#mocking-dummy-marshaller) + - [Setting Up Agoric Names](#setting-up-agoric-names) + - [Creating Cosmos Interchain Service](#creating-cosmos-interchain-service) + - [Final Context Setup](#final-context-setup) + - [Installing the Contract](#installing-the-contract) + - [Starting the Contract](#starting-the-contract) + - [Orchestration Account Scenario](#orchestration-account-scenario) + - [Macro Definition](#macro-definition) + - [Chain Configuration Validation](#chain-configuration-validation) + - [Setting Up the Testing Context](#setting-up-the-testing-context) + - [Starting the Zoe Instance](#starting-the-zoe-instance) + - [Creating an Account Invitation](#creating-an-account-invitation) + - [Making the Account Offer](#making-the-account-offer) + - [Retrieving and Verifying the Offer Result](#retrieving-and-verifying-the-offer-result) + - [Querying vStorage for Verification](#querying-vstorage-for-verification) + - [Continuing the Offer](#continuing-the-offer) + - [Test Execution](#test-execution) + +This document provides a detailed walkthrough of the `orca-contract.test.js` file. This test script is designed to validate the functionality of the Orca contract within the Agoric platform using AVA as the testing framework. + +## Overview + +The `orca-contract.test.js` script performs various tests, including: +- Installing the contract on Zoe. +- Starting the contract. +- Registering chains. +- Orchestrating accounts. +- Verifying results via vstorage queries. + +## Import Statements + +The test script begins by importing necessary modules and setting up the test environment: + +```javascript +import { test as anyTest } from './prepare-test-env-ava.js'; +import { createRequire } from 'module'; +import { E, Far } from '@endo/far'; +import { makeNodeBundleCache } from '@endo/bundle-source/cache.js'; +import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { startOrcaContract } from '../src/orca.proposal.js'; +import { makeMockTools } from './boot-tools.js'; +import { getBundleId } from '../tools/bundle-tools.js'; +import { startOrchCoreEval } from '../tools/startOrch.js'; +``` + +## Setup and Context + +The `makeTestContext` function sets up the testing environment by creating necessary mocks, bundling contracts, and initializing services: + +### Initializing Zoe and Bundling Contracts + +```javascript +const makeTestContext = async t => { +const { zoeService: zoe, feeMintAccess } = makeZoeKitForTest(); +const bundleCache = await makeNodeBundleCache('bundles/', {}, s => import(s)); +const bundle = await bundleCache.load(contractPath, 'orca'); +const tools = await makeMockTools(t, bundleCache); +``` + +- **Zoe Initialization**: `makeZoeKitForTest()` sets up a test environment with Zoe +- **Contract Bundling**: The contract is bundled using `makeNodeBundleCache`, which caches the compiled contract for faster testing. + +### Mocking Dummy Storage Node + +```javascript +const makeDummyStorageNode = nodeName => { + return Far('DummyStorageNode', { + makeChildNode: async (childName) => { + console.log(`makeChildNode called with name: ${childName}`); + return makeDummyStorageNode(childName); + }, + getPath: () => `/${nodeName}`, + setValue: (value) => { + console.log(`setValue called on node: ${nodeName} with value: ${value}`); + return value; + }, + }); +}; +``` + +**Storage Node Mock**: A mock storage node is created to simulate interactions with vstorage. This node can create child nodes and store values. + +### Mocking Dummy Marshaller + +```javascript +const makeDummyMarshaller = () => { + return Far('DummyMarshaller', { + toCapData: (data) => ({}), + fromCapData: (capData) => ({}), + }); +}; +``` + +**Marshaller Mock**: A dummy marshaller is created to handle data serialization and deserialization. + +### Setting Up Agoric Names + +```javascript +const agoricNames = Far('DummyAgoricNames', { + lookup: async (key, name) => { + if (key === 'chain' && (name === 'agoric' || name === 'osmosis')) { + const state = { + name, + chainId: `${name}local`, + denom: name === 'agoric' ? 'ubld' : 'uosmo', + expectedAddressPrefix: name === 'agoric' ? 'agoric' : 'osmo', + details: `${name} chain details`, + }; + + return { + ...state, + makeAccount: Far('Account', { + getChainId: () => state.chainId, + getAccountAddress: () => `${state.name}AccountAddress`, + getBalance: () => `1000${state.denom}`, + }), + }; + } else if (key === 'chainConnection' && (name.includes('agoric') || name.includes('osmosis'))) { + return { + connectionName: name, + sourceChain: name.split('_')[0], + destinationChain: name.split('_')[1], + transferChannel: { + version: '1', + state: 'open', + portId: 'transfer', + counterPartyPortId: 'transfer', + counterPartyChannelId: 'channel-1', + channelId: 'channel-0', + }, + }; + } + throw Error(`Chain or connection not found: ${name}`); + }, +}); +``` + +**Agoric Names Mock**: A mock service for `agoricNames` is set up to simulate looking up chain information and connection details. This is crucial for testing how the contract interacts with different chains. + +### Creating Cosmos Interchain Service + +```javascript +const cosmosInterchainService = Far('DummyCosmosInterchainService', { + getChainHub: async () => ({ + registerChain: async (name, details) => console.log(`chain registered: ${name}`, details), + getChain: async (name) => { + if (name.includes('agoric') || name.includes('osmosis')) { + return { + name, + chainId: `${name}local`, + denom: name === 'agoric' ? 'ubld' : 'uosmo', + expectedAddressPrefix: name === 'agoric' ? 'agoric' : 'osmo', + }; + } + throw Error(`chain not found: ${name}`); + }, + }), + }); +``` + +**Interchain Service Mock**: This service simulates interchain operations, such as registering chains and retrieving chain information. + +### Final Context Setup + +```javascript +return { + zoe, + bundle, + bundleCache, + feeMintAccess, + cosmosInterchainService, + agoricNames, + storageNode: makeDummyStorageNode(), + marshaller: makeDummyMarshaller(), + ...tools +}; +``` + +**Returning Context**: The function returns an object containing all the initialized services, mocks, and tools required for the test environment. + +## Installing the Contract + +The first test installs the Orca contract using `zoe`: + +```javascript +test('Install the contract', async t => { + const { zoe, bundle } = t.context; + const installation = await E(zoe).install(bundle); + t.log('installed:', installation); + t.is(typeof installation, 'object'); +}); +``` + +## Starting the Contract +This test starts the Orca contract using the Zoe service and mock services we created earlier: + +```javascript +test('Start Orca contract', async t => { + const { zoe, bundle, cosmosInterchainService, agoricNames, storageNode, marshaller } = t.context; + const installation = E(zoe).install(bundle); + + const privateArgs = { + cosmosInterchainService, + orchestrationService: cosmosInterchainService, + storageNode, + marshaller, + agoricNames + }; + + const { instance } = await E(zoe).startInstance(installation, {}, {}, privateArgs); + t.log('started:', instance); + t.truthy(instance); +}); +``` + + + +## Orchestration Account Scenario + +The `orchestrationAccountScenario` macro for testing the orchestration of accounts across different chains. This macro handles everything from account creation to verifying results using vstorage. + +### Macro Definition + +```javascript +const orchestrationAccountScenario = test.macro({ + title: (_, chainName) => + `orchestrate - ${chainName} makeAccount returns a ContinuingOfferResult`, +``` + +**Purpose**: This defines a test macro, `orchestrationAccountScenario`, that takes a chain name as a parameter and tests the orchestration process. + +**Title Function**: Here, `title` generates a descriptive name for the test, including the chain name, so we can easily identify it in test logs. + +### Chain Configuration Validation + +```javascript +exec: async (t, chainName) => { + const config = chainConfigs[chainName]; + if (!config) { + return t.fail(`unknown chain: ${chainName}`); + } +``` + +**Configuration Check**: This test begins by validating whether the provided `chainName` has a corresponding configuration in the `chainConfigs` object. If not, the test fails immediately. + +**Purpose**: This makes sure that the test is only run for recognized chains, avoiding unnecessary errors. + +### Setting Up the Testing Context + +```javascript +const { zoe, bundle, cosmosInterchainService, agoricNames, storageNode, marshaller } = t.context; +t.log('installing the contract...'); +const installation = E(zoe).install(bundle); +``` + +**Extracting Context**: The test destructures several key elements from the testing context we set up, including Zoe, the contract bundle, and services like `cosmosInterchainService`. + +**Contract Installation**: Using [`E`](/glossary/#E), we invoke the install method on `zoe`, passing the `bundleId` as an argument. This is the first step in the 2-step, contract deployment process. Next, we need to start the contract instance. + +### Starting the Zoe Instance + +```javascript +const privateArgs = { + cosmosInterchainService, + orchestrationService: cosmosInterchainService, + storageNode, + marshaller, + agoricNames, +}; + +const { instance } = await E(zoe).startInstance(installation, {}, {}, privateArgs); +t.truthy(instance); +``` + +**Private Arguments**: The `privateArgs` object is prepared with essential services and dependencies that the contract may require. + +**Instance Creation**: Zoe is used to start an instance of the contract with these arguments. The test verifies that the instance is successfully created. + +### Creating an Account Invitation + +```javascript +const publicFacet = await E(zoe).getPublicFacet(instance); +const initialInvitation = await E(publicFacet).makeAccountInvitation(); +``` + +**Public Facet Access**: The test retrieves the public facet of the contract instance, which provides access to public methods. + +**Account Invitation**: An invitation is created using the `makeAccountInvitation` method. This invitation will be used to create an account on the specified chain. + +### Making the Account Offer + +```javascript +const makeAccountOffer = { + give: {}, + want: {}, + exit: { onDemand: null }, +}; + +const offerId = 'offerId'; +const initialUserSeat = await E(zoe).offer(initialInvitation, makeAccountOffer, undefined, { id: offerId }); +``` + +**Offer Structure**: The `makeAccountOffer` object is defined, detailing what the user is offering and what they want in return. In this case, both `give` and `want` are empty. + +**Making the Offer**: The offer is submitted using Zoe's `offer` method, and an `offerId` is assigned to track the offer. The result is captured in `initialUserSeat`. + +### Retrieving and Verifying the Offer Result + +```javascript +const offerResult = await E(initialUserSeat).getOfferResult(); +t.truthy(offerResult, 'Offer result should exist'); +``` + +**Result Retrieval**: The test retrieves the result of the offer using `getOfferResult`. + +**Verification**: It asserts that the result is valid, ensuring that the account creation process was successful. + +### Querying vStorage for Verification + +```javascript +const qt = makeQueryTool(); +const wallet = 'test-wallet'; +const { address, currentWalletRecord } = await queryVstorage(t, qt, wallet, offerId); +``` + +**vStorage Query**: The test uses a query tool, `qt`, to check the state of vstorage, verifying that the wallet's state reflects the new account's creation and that the offer ID is correctly recorded. + +**Address Validation**: The retrieved address is checked to verify it matches the expected format for the chain. + +```js +t.regex(address, new RegExp(`^${config.expectedAddressPrefix}1`), `Address for ${chainName} is valid`); +``` + +### Continuing the Offer + +```javascript +const continuingInvitation = await E(publicFacet).makeAccountInvitation(); +t.truthy(continuingInvitation, 'continuing invitation should be created'); + +const continuingOffer = { + give: {}, + want: {}, + exit: { onDemand: null }, +}; + +const continuingUserSeat = await E(zoe).offer(continuingInvitation, continuingOffer, undefined, { previousOffer: offerId }); +const continuingOfferResult = await E(continuingUserSeat).getOfferResult(); + +t.truthy(continuingOfferResult, 'continuing offer should produce a result'); +t.log('continuing offer result', continuingOfferResult); +``` + +**Creating a Continuing Invitation**: The test generates a new invitation for a subsequent offer, ensuring the contract can handle repeated interactions. + +**Making and Verifying the Continuing Offer**: The test submits a new offer and verifies that the result is as expected. The `previousOffer` parameter links this offer to the previous one, simulating an ongoing interaction. + +### Test Execution + +```javascript +test(orchestrationAccountScenario, 'osmosis'); +``` + +**Running the Test**: The test is finally executed for the 'osmosis' chain, validating the entire orchestration process from account creation to offer continuation on that chain. + diff --git a/main/guides/orchestration/dapp-orchestration-basics/ui/index.md b/main/guides/orchestration/dapp-orchestration-basics/ui/index.md new file mode 100644 index 000000000..e7c070919 --- /dev/null +++ b/main/guides/orchestration/dapp-orchestration-basics/ui/index.md @@ -0,0 +1,527 @@ +# Overview of User Interface Components +Here, we will walkthrough the components making up the user interface for the Orchestration-Basics demo project. + +## Table of Contents + +- [Overview of User Interface Components](#overview-of-user-interface-components) + - [Table of Contents](#table-of-contents) + - [Installation](#installation) + - [`Orchestration.tsx`](#orchestrationtsx) + - [Purpose](#purpose) + - [Key Interactions](#key-interactions) + - [Imports and Setup](#imports-and-setup) + - [Component State and Refs](#component-state-and-refs) + - [Toggle Modal Visibility](#toggle-modal-visibility) + - [Effect Hook for Loading Balances](#effect-hook-for-loading-balances) + - [Opening Modal with Content](#opening-modal-with-content) + - [Effect Hook for Modal Display](#effect-hook-for-modal-display) + - [Handling Account Creation](#handling-account-creation) + - [Handling Deposit](#handling-deposit) + - [Executing Deposit Transaction](#executing-deposit-transaction) + - [Handling Withdraw](#handling-withdraw) + - [Handling Stake and Unstake](#handling-stake-and-unstake) + - [Rendering the Component](#rendering-the-component) + - [`makeAccountOffer` Function](#makeaccountoffer-function) + - [Function Signature](#function-signature) + - [Chain Selection Validation](#chain-selection-validation) + - [Contract Instance Retrieval](#contract-instance-retrieval) + - [Offer Preparation](#offer-preparation) + - [Submitting the Offer](#submitting-the-offer) + - [Handling Offer Updates](#handling-offer-updates) + - [Successful Offer Handling](#successful-offer-handling) + - [Refunded Offer Handling](#refunded-offer-handling) + - [`AccountList.tsx`](#accountlisttsx) + - [Purpose](#purpose-1) + - [Key Interactions](#key-interactions-1) + - [Key Interactions](#key-interactions-2) + - [`FetchBalances.tsx`](#fetchbalancestsx) + - [Purpose](#purpose-2) + - [Key Interactions](#key-interactions-3) + - [`RpcEndpoints.tsx`](#rpcendpointstsx) + - [Purpose](#purpose-3) + - [Key Interactions](#key-interactions-4) + - [`ChainSelector.tsx`](#chainselectortsx) + - [Purpose](#purpose-4) + - [Key Interactions](#key-interactions-5) + - [`CreateAccountButton.tsx`](#createaccountbuttontsx) + - [Purpose](#purpose-5) + +The UI for the orchestration dApp is divided into multiple components, each responsible for a specific piece of functionality. Below is an overview of the key components and their roles. + +You can find these components in `ui/src/components/Orchestration`. + +## Installation +From inside of the `ui/` folder, you can run `yarn install`. It is recommended to use node `v18`. + +From there, you can run `yarn dev` to start the ui. + +<img src="../../assets/ui.png" width="100%" /> + + +## `Orchestration.tsx` + +### Purpose +The `Orchestration.tsx` component serves as the main controller for the orchestration dApp's user interface. It manages the state and interactions required for users to create accounts, manage their balances, and perform various blockchain transactions such as deposits, withdrawals, staking, and unstaking. + +### Key Interactions +- **Account Management:** Utilizes components like `AccountList`, `CreateAccountButton`, and `ChainSelector` to allow users to view and manage their accounts across different chains. +- **Transaction Handling:** Interfaces with the Agoric wallet and the Keplr wallet for signing and broadcasting transactions. It supports various types of transactions, including deposits, withdrawals, staking, and unstaking. +- **Retrieving Balance:** Fetches and displays account balances in real-time for any native-denom the user holds. +- **Modal Interactions:** Manages modal dialogs for user actions such as creating accounts and making deposits. These dialogs provide feedback on transaction status, ensuring users are informed of the progress and outcome of their actions. +- **State Management:** Relies on hooks and context (e.g., `useContractStore`, `NotificationContext`) to maintain and update the application's state, ensuring a seamless user experience. + + + +#### Imports and Setup +```javascript +import React, { useState, useContext, useEffect, useRef } from 'react'; +import { useAgoric } from '@agoric/react-components'; +import { useContractStore } from '../../store/contract'; +import { NotificationContext } from '../../context/NotificationContext'; +import { Button } from 'react-daisyui'; +import AccountList from './AccountList'; +import ChainSelector from './ChainSelector'; +import CreateAccountButton from './CreateAccountButton'; +import { fetchBalances } from './FetchBalances'; +import { makeAccountOffer } from './MakeAccountOffer'; +import { initializeKeplr } from './KeplrInitializer'; +import RpcEndpoints from './RpcEndpoints'; +import { StargateClient, SigningStargateClient } from "@cosmjs/stargate"; +``` +- **React hooks:** `useState`, `useContext`, `useEffect`, and `useRef` are used for managing state, context, side effects, and references to DOM elements. +- **Custom hooks and context:** `useAgoric`, `useContractStore`, and `NotificationContext` are utilized to interact with the Agoric ecosystem, manage contract data, and handle notifications. +- **UI Components:** The component imports child components (`AccountList`, `ChainSelector`, `CreateAccountButton`) that handle specific parts of the UI. + + +#### Component State and Refs +```javascript +const Orchestration = () => { + const { walletConnection } = useAgoric(); + const { addNotification } = useContext(NotificationContext); + const [offerId, setOfferId] = useState(''); + const icas = useContractStore(state => state.icas); + const [balances, setBalances] = useState([]); + const [selectedChain, setSelectedChain] = useState(''); + const [loadingDeposit, setLoadingDeposit] = useState<{ [key: string]: boolean }>({}); + const [loadingWithdraw, setLoadingWithdraw] = useState<{ [key: string]: boolean }>({}); + const [loadingStake, setLoadingStake] = useState<{ [key: string]: boolean }>({}); + const [loadingUnstake, setLoadingUnstake] = useState<{ [key: string]: boolean }>({}); + const [loadingCreateAccount, setLoadingCreateAccount] = useState(false); + const [modalContent, setModalContent] = useState(''); + const [modalOpen, setModalOpen] = useState(false); + const [modalAddress, setModalAddress] = useState(''); + const [selectedDenom, setSelectedDenom] = useState('uist'); + const [amount, setAmount] = useState(0); + const [statusText, setStatusText] = useState(''); + const modalRef = useRef<HTMLDialogElement | null>(null); + const guidelines = false; +``` +- **State management:** Multiple states are used to track various aspects of the component, including selected chain, balances, loading states for different operations, modal content, and transaction status. +- **Refs:** `modalRef` is used to control the modal dialog, allowing it to be opened and closed programmatically. + +#### Toggle Modal Visibility +```javascript + const handleToggle = () => { + setModalOpen(prevState => !prevState); + }; +``` +**Modal toggling:** This function toggles the visibility of the modal dialog, changing its open/close state. + +#### Effect Hook for Loading Balances +```javascript + useEffect(() => { + const loadBalances = async () => { + try { + const fetchedBalances = await fetchBalances(icas, selectedChain); + setBalances(fetchedBalances); + } catch (error) { + console.error('failed to fetch balances:', error); + } + }; + if (icas && icas.length > 0) { + loadBalances(); + } + }, [icas, selectedChain]); +``` +**Loading balances:** This `useEffect` hook fetches the balances for the accounts when `icas` or `selectedChain` changes. It makes sure the balances are updated in real-time based on the selected chain. + +#### Opening Modal with Content +```javascript + const openModal = (content: string, address: string = '') => { + setModalContent(content); + setModalAddress(address); + handleToggle(); + }; +``` +**Modal initialization:** This function sets the content and address for the modal and then toggles it open. + +#### Effect Hook for Modal Display +```javascript + useEffect(() => { + if (modalRef.current) { + if (modalOpen) { + modalRef.current.showModal(); + } else { + modalRef.current.close(); + } + } + }, [modalOpen]); +``` +**Modal display:** This effect handles the actual opening and closing of the modal dialog by calling the appropriate methods on the `modalRef` when `modalOpen` changes. + +#### Handling Account Creation +```javascript + const handleCreateAccount = () => { + openModal('Create Account'); + setLoadingCreateAccount(true); + setStatusText('Submitted'); + + if (walletConnection) { + makeAccountOffer(walletConnection, addNotification!, selectedChain, setLoadingCreateAccount, handleToggle, setStatusText) + .catch((error) => { + addNotification!({ + text: `transaction failed: ${error.message}`, + status: 'error', + }); + setLoadingCreateAccount(false); + handleToggle(); + }); + } else { + addNotification!({ + text: 'error: please connect your wallet or check your connection to RPC endpoints', + status: 'error', + }); + setLoadingCreateAccount(false); + handleToggle(); + } + }; +``` +**Account creation:** This function initiates the account creation process by opening a modal and setting the loading state. It then attempts to make an account offer via the `makeAccountOffer` function, handling success or failure accordingly. + +#### Handling Deposit +```javascript + const handleDeposit = (address: string) => { + openModal('Deposit', address); + }; +``` +**Deposit handling:** This function prepares the modal for a deposit action by setting the modal content to 'Deposit' and passing the selected address. + +#### Executing Deposit Transaction +```javascript + if (result.code !== undefined && result.code !== 0) { + throw new Error(`Failed to send IBC transfer: ${result.log}`); + } + console.log("IBC transfer sent successfully"); + } +} catch (error) { + console.error("failed to deposit:", error); +} finally { + setLoadingDeposit(prevState => ({ ...prevState, [modalAddress]: false })); + handleToggle(); +} +``` +**Deposit execution:** This function handles the actual execution of a deposit transaction. It sets the loading state, interacts with the blockchain through Keplr and `StargateClient`, and manages different scenarios based on the address prefix, either using Cosmos SDK messages or IBC transfers (*for now, this will be handled by the contract in a newer dapp version*). If the deposit fails, it logs the error and resets the loading state. After the transaction, it toggles the modal to close it. + +#### Handling Withdraw +```javascript +const handleWithdraw = (address: string) => { +openModal('Withdraw', address); +setLoadingWithdraw(prevState => ({ ...prevState, [address]: false })); +}; +``` +**Withdraw handling:** This function initializes the withdrawal process by setting the modal content to 'Withdraw' and updating the loading state for the selected address. + +#### Handling Stake and Unstake +```javascript +const handleStake = (address: string) => { +openModal('Stake', address); +setLoadingStake(prevState => ({ ...prevState, [address]: false })); +}; + +const handleUnstake = (address: string) => { +openModal('Unstake', address); +setLoadingUnstake(prevState => ({ ...prevState, [address]: false })); +}; +``` +**Stake and Unstake handling:** These functions set up the modals for staking and unstaking operations by setting the relevant modal content and updating the loading state for the selected address. + + +#### Rendering the Component + +```js +return ( + <div className="flex w-full flex-col items-center"> + <div className="w-full p-4"> + <div className={`flex flex-row justify-between items-start space-x-10 border${guidelines ? "" : "-0"}`}> + + <AccountList balances={balances} handleDeposit={handleDeposit} handleWithdraw={handleWithdraw} handleStake={handleStake} handleUnstake={handleUnstake} loadingDeposit={loadingDeposit} loadingWithdraw={loadingWithdraw} loadingStake={loadingStake} loadingUnstake={loadingUnstake} guidelines={guidelines} /> + + <div className={`flex flex-col w-1/2 space-y-4 pl-4 rounded-lg p-4 border${guidelines ? "" : "-0"}`}> + <ChainSelector setSelectedChain={setSelectedChain} /> + <CreateAccountButton handleCreateAccount={handleCreateAccount} loadingCreateAccount={loadingCreateAccount} /> + </div> + </div> + </div> + + {/* modal */} + <dialog ref={modalRef} className="daisyui-modal"> + <div className="daisyui-modal-box w-full max-w-md"> + <button className="daisyui-btn daisyui-btn-sm daisyui-btn-circle daisyui-btn-neutral absolute right-2 top-2" onClick={handleToggle}>✕</button> + <h3 className="font-bold text-lg">{modalContent}</h3> + {modalContent === 'Create Account' && ( + <div className="py-4 flex flex-col items-center justify-center"> + <p>{statusText}</p> + {loadingCreateAccount && ( + <svg aria-hidden="true" role="status" className="inline w-4 h-4 me-3 text-gray-200 animate-spin dark:text-gray-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M100 50.6C100 78.2 77.6..." fill="currentColor" /> + <path d="M94 39C96.4 38.4 97.9..." fill="#1C64F2" /> + </svg> + )} + </div> + )} + {modalContent === 'Deposit' && ( + <div> + <label className="block"> + <span className="text-gray-700">Select Denom</span> + <select value={selectedDenom} onChange={e => setSelectedDenom(e.target.value)} className="form-select mt-1 block w-1/2"> + <option value="ubld">BLD</option> + <option value="uist">IST</option> + <option value="uosmo">OSMO</option> + {/* Add more options as needed */} + </select> + </label> + <label className="block mt-4"> + <span className="text-gray-700">Amount</span> + <input type="number" value={amount} onChange={e => setAmount(parseInt(e.target.value))} className="form-input mt-1 block w-1/2" /> + </label> + <div className="modal-action mt-4"> + <button className="daisyui-btn daisyui-btn-info daisyui-btn-sm mr-2" onClick={executeDeposit} disabled={loadingDeposit[modalAddress]}> + {loadingDeposit[modalAddress] ? ( + <svg aria-hidden="true" role="status" className="inline w-4 h-4 me-3 text-gray-200 animate-spin dark:text-gray-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M100 50.5908C..." fill="currentColor"/> + <path d="M93.9676 39.0409C96.393 38.4038..." fill="#1C64F2"/> + </svg> + ) : 'Confirm'} + </button> + <button className="daisyui-btn daisyui-btn-sm daisyui-btn-neutral" onClick={handleToggle}>Close</button> + </div> + </div> + )} + </div> + </dialog> + </div> + ); + ``` + + +**Main UI Structure:** The component's return statement builds the main UI structure. Here are the key elements to take-away from what we are rendering: + - **AccountList Component:** Displays the list of user accounts and allows interactions like deposit, withdraw, stake, and unstake. + - **ChainSelector and CreateAccountButton:** These components are used to select the blockchain network and create a new account, respectively. + - **Modal Dialog:** A modal dialog is used to provide user feedback and interaction for actions like creating accounts and performing transactions. The dialog content is dynamically set based on the current operation. + + +## `makeAccountOffer` Function + +The `makeAccountOffer` function is a key part of the user-interaction flow in the orchestration dApp. It handles the logic for constructing & submitted an offer on the blockchain by interacting with the Agoric wallet and managing the offer's lifecycle: + +### Function Signature + +```javascript +export const makeAccountOffer = async ( + wallet: AgoricWalletConnection, + addNotification: (arg0: DynamicToastChild) => void, + selectedChain: string, + setLoadingCreateAccount: React.Dispatch<React.SetStateAction<boolean>>, + handleToggle: () => void, + setStatusText: React.Dispatch<React.SetStateAction<string>> +) => { +``` + +**Parameters**: + - `wallet`: Represents the Agoric wallet connection used to submit the offer to the blockchain. + - `addNotification`: A function used to display notifications to the user, such as success or error messages. + - `selectedChain`: The name of the chain where the account is being created. + - `setLoadingCreateAccount`: A state update function to manage the loading state of the account creation process. + - `handleToggle`: A function to toggle the state of the UI, such as closing or opening modals. + - `setStatusText`: A state update function to set the status message displayed to the user. + +### Chain Selection Validation + +```javascript + if (!selectedChain) { + addNotification({ + text: `Please Select Chain`, + status: 'error', + }); + setLoadingCreateAccount(false); + handleToggle(); + return; + } +``` + +**Purpose**: + - The function begins by checking if a chain has been selected. If `selectedChain` is not provided, it triggers an error notification, stops the loading process, and exits the function early. + - **Why This Matters**: Chain selection is crucial because the dApp interacts with multiple chains, and knowing which chain to operate on is necessary for any blockchain transaction. + +### Contract Instance Retrieval + +```javascript + const { instances } = useContractStore.getState(); + const instance = instances?.['orca']; + + if (!instance) { + setLoadingCreateAccount(false); + handleToggle(); + throw Error('no contract instance'); + } +``` + +**Purpose**: + - The function retrieves the contract instance from the state using `useContractStore`. Specifically, it looks for the `orca` contract instance. + - **Why This Matters**: Without the correct contract instance, the dApp cannot interact with the blockchain to create an account. If the instance is not found, the function halts and an error is thrown. + +### Offer Preparation + +```javascript + const want = {}; + const give = {}; + + const makeAccountofferId = Date.now(); +``` + +**Purpose**: + - The `want` and `give` objects represent the assets that the user expects to receive or provide in the offer. In this case, they are empty because the offer is for account creation, not a typical trade. + - **Offer ID**: The `makeAccountofferId` is generated using the current timestamp. This ID uniquely identifies the offer and is used to track its progress. + +### Submitting the Offer + +```javascript + await wallet?.makeOffer( + { + source: 'contract', + instance, + publicInvitationMaker: 'makeAccountInvitation', + }, + { give, want }, + { chainName: selectedChain }, + async (update: { status: string; data?: unknown }) => { +``` + +**Purpose**: + - The `makeOffer` method on the `wallet` object submits the offer to the blockchain. The offer is tied to the `orca` contract instance and uses the `makeAccountInvitation` method, which is defined in the contract's public facet. + - **Why This Matters**: This is the core action that interacts with the blockchain, making the user's intent (in this case, account creation) known to the smart contract. + +### Handling Offer Updates + +```javascript + if (update.status === 'error') { + const msg = `offer update error: \${update.data}`; + addNotification({ + text: msg, + status: 'error', + }); + setLoadingCreateAccount(false); + handleToggle(); + console.log(update); + setStatusText(msg); + } +``` + +**Error Handling**: + - If the offer submission encounters an error, the status is checked. An error message is logged, a notification is displayed to the user, and the UI state is updated to reflect the failure. + - **Why This Matters**: Robust error handling to verify that users are kept informed and the UI can recover gracefully from failures during the submission of offers to the contract instance. + +### Successful Offer Handling + +```javascript + if (update.status === 'accepted') { + const msg = 'Account created successfully'; + addNotification({ + text: msg, + status: 'success', + }); + console.log(update); + setStatusText(msg); + + setTimeout(() => { + setLoadingCreateAccount(false); + handleToggle(); + setStatusText(msg); + }, 2000); + } +``` + +**Success Handling**: + - If the offer is accepted, a success notification is displayed, and the UI reflects the successful creation of the account. The function also uses a `setTimeout` to delay the reset of the loading state, allowing users to see the success message. + - **Why This Matters**: Providing clear feedback for successful actions allows users to know their actions were successful. + +### Refunded Offer Handling + +```javascript + if (update.status === 'refunded') { + addNotification({ + text: 'offer was refunded', + status: 'error', + }); + setLoadingCreateAccount(false); + handleToggle(); + console.log(update); + } + }, + makeAccountofferId + ); +}; +``` + +**Refund Handling**: + - If the offer is refunded (e.g., the account creation could not be completed), a notification is shown to the user, and the UI state is updated to reflect this. + - **Why This Matters**: Handling refunded offers is critical in ensuring users are informed of why their transaction did not go through, helping them understand what went wrong and what they can do next. + + +## `AccountList.tsx` + +### Purpose +The `AccountList.tsx` component is responsible for displaying the list of user accounts and their associated balances in various native denoms. It presents account information in a structured and user-friendly format, allowing users to view and interact with their [Orchestration Accounts](/glossary/#orchestration-account-TODO) directly. + +### Key Interactions +- **Balance Display:** Fetches and displays the balances for each account in real-time. Each account is shown with its corresponding balances in various denoms. +- **User Actions:** Provides a button group for users to perform actions such as depositing, withdrawing, staking, and unstaking assets. Each button triggers its respective handler function. +- **Visual Feedback:** The component updates the UI to reflect the loading state for each action, ensuring that users are informed of the transaction process. This includes disabling buttons and showing spinners when actions are in progress. +- **Address Handling:** Truncates and displays the account addresses to maintain a clean UI while still providing users with the essential account details. + +### Key Interactions +When clicked, this button initiates the account creation offer being submitted to the contract. It interacts with the `makeAccountOffer` function, described above. + +## `FetchBalances.tsx` + +### Purpose +The `FetchBalances.tsx` component is responsible for retrieving the balances of user accounts from different blockchains. It interacts with the local RPC endpoints to fetch balance data for addresses on supported chains (for Osmosis and Agoric). + +### Key Interactions +- **RPC Communication:** Connects to blockchain networks using the `StargateClient` from the `@cosmjs/stargate` package, communicating with the specified RPC endpoints to retrieve balance information. +- **Address Handling:** Determines the correct blockchain network based on the prefix of the provided address (e.g., `osmo1` for Osmosis, `agoric1` for Agoric) and fetches the corresponding balances. +- **Error Handling:** Implements error handling to manage cases where balance retrieval fails, ensuring that the UI is informed of such issues without crashing the application. +- **Asynchronous Processing:** Uses `Promise.all` to handle multiple asynchronous balance fetch requests simultaneously, optimizing the performance of balance retrieval across multiple accounts. + + +## `RpcEndpoints.tsx` +### Purpose +Provides a convenient place to manage RPC endpoints for different chains, including your local agoric instance, to be used by different components. + +### Key Interactions + +Used by other components, like `FetchBalances`, to interact with the correct blockchain network. + + +## `ChainSelector.tsx` + +### Purpose +Provides a basic UI element for users to select the Cosmos chain they want to interact with. Selecting a chain is crucial as different chains offer different services or have different orchestration rules. + +### Key Interactions +The selected chain is passed back to the parent component, `Orchestration.tsx`, and used in various transactions and interactions. + +## `CreateAccountButton.tsx` + +### Purpose +A demo button that triggers the process of creating a new account on a selected Cosmos chain. diff --git a/main/guides/orchestration/getting-started/contract-walkthrough/orchestration-basics.md b/main/guides/orchestration/getting-started/contract-walkthrough/orchestration-basics.md deleted file mode 100644 index 3585791c1..000000000 --- a/main/guides/orchestration/getting-started/contract-walkthrough/orchestration-basics.md +++ /dev/null @@ -1,5 +0,0 @@ -# dApp Orchestration Basics -To get started with developing using the Orchestration API, developers can make use of our [dapp-orchestration-basics](https://github.com/Agoric/dapp-orchestration-basics) template. - -Following the dApp pattern of our existing dapp templates, `dapp-orchestration-basics` contains both ui & contract folders within a single yarn workspace. - diff --git a/main/guides/orchestration/getting-started/contract-walkthroughs.md b/main/guides/orchestration/getting-started/contract-walkthroughs.md index a6c3a753d..dc5acf70a 100644 --- a/main/guides/orchestration/getting-started/contract-walkthroughs.md +++ b/main/guides/orchestration/getting-started/contract-walkthroughs.md @@ -6,7 +6,7 @@ In this section, we will cover three primary contracts: 1. `swapExample.contract.js`: A comprehensive guide to the process of swapping assets between different chains using the Agoric orchestration library. 2. `unbondExample.contract.js`: A detailed walkthrough of the unbonding and liquid staking process, highlighting the steps involved in managing cross-chain operations. -3. `orca.contract.js`: A dapp template serving as an introduction to the foundational concepts and implementation of basic orchestration within an Agoric dApp (currently under active development). + Each walkthrough will include line-by-line explanations of the contract code, providng insights into the mechanics and best practices of smart contract development on the Agoric platform. By the end of these walkthroughs, you should have a solid understanding of how to utilize Agoric’s tools and libraries to create robust and efficient cross-chain smart contracts. @@ -31,12 +31,3 @@ The Unbond Contract focuses on the process of unbonding staked assets and perfor [See Contract Overview](/guides/orchestration/getting-started/contract-walkthrough/cross-chain-unbond) - -## Dapp Orchestration Basics - -The Agoric Dapp Orchestration Basics walkthrough (currently under development) will provide an introduction to the core concepts and basic implementation of orchestrtion within an Agoric dApp. This guide aims to: - -- Explain the fundamental principles of orchestration. -- Show an end-to-end dApp using the Orchestration API - -[See Contract Overview](/guides/orchestration/getting-started/contract-walkthrough/orchestration-basics) diff --git a/yarn.lock b/yarn.lock index 5ae51cb4a..e38f303d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5681,4 +5681,4 @@ yargs@^17.5.1: yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" - integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== \ No newline at end of file