Skip to content

juliettech13/aragonOSx-sdk-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aragon

Aragon OSx SDK Demo

This project is a dApp showcasing some of what's possible with the Aragon OSx SDK.

It shows how to:

  • Set up the Aragon SDK in a React project
  • Get a DAO's details
  • Deposit ETH into a DAO
  • Get members from a DAO
  • Display proposals published in a DAO
  • Vote in a DAO's proposals

To find additional documentation on the Aragon OSx SDK, go here.

Quickstart

There's 2 ways to go around this repository:

  1. You can clone it and build on top of it. At which point, you merely want to fork it and run:
npm install && npm run start
  1. You want to use it as a tutorial, at which point, follow the flow below 🙂. You can tap into each step through going into the branches.

Setup

  1. Bootstrap the project by using Create React App with Typescript
npx create-react-app aragon-sdk-demo --template typescript
  1. Install Rainbowkit, Wagmi, and Ethers to bootstrap connecting to the blockchain
npm install @rainbow-me/rainbowkit wagmi ethers
  1. Set up RainbowKit providers and blockchain config
// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';

import { getDefaultWallets, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { configureChains, createClient, WagmiConfig } from 'wagmi';
import { goerli } from 'wagmi/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy';
import { publicProvider } from 'wagmi/providers/public';

import App from './App';
import reportWebVitals from './reportWebVitals';

const { chains, provider } = configureChains(
  // Determine which chains you want for your app
  [goerli],
  [
    // Make sure to get your own API Key from Alchemy itself and store it within your .env file: https://dashboard.alchemy.com/
    alchemyProvider({ apiKey: process.env.REACT_APP_ALCHEMY_GOERLI_KEY || '' }),
    publicProvider()
  ]
);

const { connectors } = getDefaultWallets({
  appName: 'Aragon SDK demo',
  chains
});

const wagmiClient = createClient({
  autoConnect: true,
  connectors,
  provider
})

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <WagmiConfig client={wagmiClient}>
      <RainbowKitProvider chains={chains}>
        <App />
      </RainbowKitProvider>
    </WagmiConfig>
  </React.StrictMode>
);

reportWebVitals();

Install the Aragon SDK

In your terminal, install the Aragon SDK.

npm install @aragon/sdk-client

Set up the Aragon SDK in context

We want to create a context hook for the Aragon SDK so that we have access to it throughout the entire app.

  1. Let's create the context folder within the root of our app.
mkdir src/context && touch src/context/AragonSDK.tsx
  1. Inside the AragonSDK.tsx file, let's add the following code:
// src/context/AragonSDK.tsx

import { createContext, useEffect, useContext, useState } from 'react';

import { useSigner } from 'wagmi';
import { Context, ContextParams } from '@aragon/sdk-client';

const AragonSDKContext = createContext({});

export function AragonSDKWrapper({ children }: any): JSX.Element {
  const [context, setContext] = useState<Context | undefined>(undefined);
  const signer = useSigner().data || undefined;

  useEffect(() => {
    const aragonSDKContextParams: ContextParams = {
      network: 'goerli', // mainnet, mumbai, etc
      signer,
      daoFactoryAddress: '0x16B6c6674fEf5d29C9a49EA68A19944f5a8471D3', // the DAO Factory contract address from the Goerli network. You can find the daoFactoryAddress you need from the active_contracts file within the osx repository here: https://github.com/aragon/osx/blob/develop/active_contracts.json
      web3Providers: ['https://rpc.ankr.com/eth_goerli'], // feel free to use the provider of your choosing: Alchemy, Infura, etc.
      ipfsNodes: [
        {
          url: 'https://testing-ipfs-0.aragon.network/api/v0',
          headers: { 'X-API-KEY': process.env.REACT_APP_IPFS_KEY || '' } // make sure you have the key for your IPFS node within your .env file
        },
      ],
      graphqlNodes: [
        {
          url:
            'https://subgraph.satsuma-prod.com/aragon/osx-goerli/api' // this will change based on the chain you're using (osx-mainnet alternatively)
        },
      ],
    };

    setContext(new Context(aragonSDKContextParams));
  }, [signer]);

  return (
    <AragonSDKContext.Provider value={{ context }}>
      {children}
    </AragonSDKContext.Provider>
  )
}

export function useAragonSDKContext(): any {
  return useContext(AragonSDKContext);
}
  1. Then, in your src/index.tsx file, add the context hook provider for the Aragon SDK so you have access to it everywhere in your application.
// src/index.tsx

// import rainbowkit provider, wagmi config, other providers, etc..
import { AragonSDKWrapper } from './context/AragonSDK';

// ......<Setup section above, Step 3>

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <WagmiConfig client={wagmiClient}>
      <RainbowKitProvider chains={chains}>
        <AragonSDKWrapper> // Adding the AragonSDKWrapper for the context hook
          <BrowserRouter>
            <App />
          </BrowserRouter>
        </AragonSDKWrapper>
      </RainbowKitProvider>
    </WagmiConfig>
  </React.StrictMode>
);

CAREFUL:_ Webpack 5 doesn’t have the polyfill node needed to run several crypto packages, so if you started your app with create-react-app or similar, you may stumble upon a polyfill problem. In order to solve it, follow this tutorial or start your app with a tool like Vite.js instead.

Set up a basic front-end to use the SDK

  1. Firstly, I will install a CSS Framework to bootstrap front-end components, as well as set up the CSS in JS framework. I will use React-Bootstrap and Aphrodite for this.
npm install react-bootstrap bootstrap aphrodite

In this basic front-end, we'll set up 4 main components:

  1. Navbar Contains the ConnectButton enabling users to connect their wallet into the dapp.

  2. DepositETH A Form enabling users to input the amount of ETH they want to deposit into the DAO.

  3. MembersList A table displaying all DAO token-holders. This is how we'll see the the address who deposited the ETH becoming a DAO member.

  4. DisplayProposals Cards displaying the proposals for that specific DAO. As a member, token-holders can now vote on these proposals.

Keep in mind, that we assume throughout this process that an Aragon DAO already exists. If you don't have one, feel free to create one here or create one through the SDK.

You can run npm run start in your terminal to see the code in the browser.

Using the SDK

Deposit ETH

Within the DepositETH component, we want to use the SDK so a user can deposit ETH into a DAO.

// src/components/DepositETH/index.tsx

import React, { useState } from 'react';
import { Button, Container } from 'react-bootstrap';
import { Client, DaoDepositSteps, DepositParams, TokenType } from '@aragon/sdk-client';

import { ETHToWei } from '../../helpers/crypto';
import { useAragonSDKContext } from '../../context/AragonSDK';
import { formatEther } from 'ethers/lib/utils.js';

export default function DepositETH(): JSX.Element {
  const [amountOfETH, setAmountOfETH] = useState<number>(0);

  const { context } = useAragonSDKContext();

  async function depositEthToDao() {
    const client = new Client(context);

    const depositParams: DepositParams = {
      type: TokenType.NATIVE, // means ETH
      amount: BigInt(ETHToWei(amountOfETH)), // amount is always in wei
      daoAddressOrEns: '0xff25e3d89995ea3b97cede27f00ec2281a89e960' // my-dao.dao.eth
    }

    const steps = client.methods.deposit(depositParams);

    for await (const step of steps) {
      try {
        switch(step.key) {
          case DaoDepositSteps.DEPOSITING:
            alert(`Depositing ETH into DAO... here's your transaction: https://goerli.etherscan.io/tx/${step.txHash}`);
            break;
          case DaoDepositSteps.DONE:
            alert(`Deposit of ${formatEther(amountOfETH)} ETH into DAO complete!`);
            break;
        }
      } catch (e) {
        console.error(e);
      }
    }
  }

  return (
    //.... front-end deposit form
  )
}

List all members

We want to display all members for our DAO. Members are defined as addresses who have interacted at least once with the specified DAO.

In order to do this, we will call into the getMembers function of the SDK, store the addresses on a state variable, then use that to map over it and return rows in a table.

However, in order to call getMembers, we need the plugin address we're getting our members from. One DAO may have several plugins installed with many different members within it, so we want to make sure we pick the plugin that works for our use case. The best way to do this is calling getDAO to get our DAO details, this will include the plugins we have installed.

Then, we can either iterate over them finding the one we need, OR simply calling it directly if we know what we want (as is the case below for simplicity).

// src/components/MembersList/index.tsx

import React, { useEffect, useState } from 'react';
import { Table } from 'react-bootstrap';
import { Client, ContextPlugin, DaoDetails, TokenVotingClient } from '@aragon/sdk-client';
import { useAragonSDKContext } from '../../context/AragonSDK';

export default function MembersList(): JSX.Element {
  const [members, setMembers] = useState<string[]>([]);

  const { context } = useAragonSDKContext();

  useEffect(() => {
    async function getDaoMembers() {
      const client = new Client(context); // general purpose client allowing us to call getDao
      const daoAddressOrEns: string = '0xff25e3d89995ea3b97cede27f00ec2281a89e960'; // or my-dao.dao.eth

      const dao: DaoDetails | null = await client.methods.getDao(daoAddressOrEns); // returns details about our DAO
      const pluginAddress: string = dao?.plugins[0].instanceAddress || ''; // returns the pluginAddress we have installed

      const contextPlugin: ContextPlugin = ContextPlugin.fromContext(context); // enables us to create a TokenVotingClient so we can get our members
      const tokenVotingClient: TokenVotingClient = new TokenVotingClient(contextPlugin);

      const daoMembers: string[] | undefined = await tokenVotingClient.methods.getMembers(pluginAddress) || [];
      setMembers(daoMembers);
    };
    getDaoMembers();
  }, [context]);

  return (
    <>
      <h3 className="text-center pt-5">DAO Members</h3>
      <p className="text-center">AKA - anyone who owns a $PARK token.</p>
      <Table striped bordered hover className="mx-auto" style={{ width: '800px' }}>
        <thead>
          <tr>
            <th>#</th>
            <th>Address</th>
          </tr>
        </thead>
        <tbody>
        // We use the members state variable we just populated with members from the DAO
          {members.map((member, index) => (
            <tr key={index}>
              <td>{index + 1}</td>
              <td>{member}</td>
            </tr>
          ))}
        </tbody>
      </Table>
    </>
  )
}

Display and vote on proposals

Lastly, we want to display all proposals a specific DAO has and enable users to vote on them if they're able.

We do this by calling on the getProposals function, as well as the voteProposal function. Additionally, we could add a check canVote which verifies if the signer is able to vote in a specific proposal. However for simplicity, we won't do this here.

// src/components/DisplayProposals

import React, { useEffect, useState } from 'react';
import { Button, Card, Container, Row } from 'react-bootstrap';
import { ContextPlugin, IVoteProposalParams, TokenVotingClient, TokenVotingProposalListItem, VoteProposalStep, VoteValues } from '@aragon/sdk-client';

import { useAragonSDKContext } from '../../context/AragonSDK';

export default function DisplayProposals() {
  const [proposals, setProposals] = useState<TokenVotingProposalListItem[]>([]);

  const { context } = useAragonSDKContext();
  const contextPlugin: ContextPlugin = ContextPlugin.fromContext(context);
  const tokenVotingClient: TokenVotingClient = new TokenVotingClient(contextPlugin);

  useEffect(() => {
    async function fetchProposals() {
      const daoProposals: TokenVotingProposalListItem[] = await tokenVotingClient.methods.getProposals({ daoAddressOrEns: '0xff25e3d89995ea3b97cede27f00ec2281a89e960' });
      setProposals(daoProposals);
    }
    fetchProposals();
  });

  async function vote(proposalId: string, voteInput: VoteValues) {
    const voteParams: IVoteProposalParams = {
      proposalId,
      vote: voteInput
    };

    const steps = tokenVotingClient.methods.voteProposal(voteParams);

    for await (const step of steps) {
      try {
        switch (step.key) {
          case VoteProposalStep.VOTING:
            alert(`Voting... Review your transaction here: https://goerli.etherscan.io/tx/${step.txHash}`);
            break;
          case VoteProposalStep.DONE:
            alert(`Vote casted for proposal ${proposalId}!`);
            break;
        }
      } catch (err) {
        alert(err);
      }
    }
  }

  return (
    <Container className="mx-auto">
      <h3 className="text-center pt-5 pb-3">DAO Proposals</h3>
      <Row>
        // Display all proposals retrieved
        {proposals.map((proposal, index) => {
          const { id, metadata } = proposal;
          return (
            <Card border="success" className="mx-3 mb-5" style={{ width: 'auto' }} key={index}>
              <Card.Body>
                <Card.Title>{metadata.title}</Card.Title>
                <Card.Text>
                  {metadata.summary}
                </Card.Text>
                <div className="d-flex justify-content-between">
                  // Vote on the proposals
                  <Button variant="primary" onClick={() => vote(id, VoteValues.YES)}>Yay</Button>
                  <Button variant="warning" onClick={() => vote(id, VoteValues.NO)}>Nay</Button>
                </div>
              </Card.Body>
            </Card>
          )
          })}
      </Row>
    </Container>
  )
}

Learn more

Hope that helps you get started with using the Aragon OSx SDK!

If you want to read more on the SDK functionality, feel free to head over to Aragon's Developer Portal, Aragon's Discord server to interact with the vibrant Developer community or reach out to me directly through email.

Excited to see what you build!!

Stack

Additional available scripts

In the project directory, you can run:

npm start

Runs the app in the development mode.
Open http://localhost:3000 to view it in the browser.

The page will reload if you make edits.
You will also see any lint errors in the console.

npm test

Launches the test runner in the interactive watch mode.
See the section about running tests for more information.

npm run build

Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.

The build is minified and the filenames include the hashes.
Your app is ready to be deployed!

See the section about deployment for more information.

About

Basic React app demo for the aragon SDK

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published