Reactive DOT: funding request for continuation of development

4mos ago
5 Comments

NOTE: Polkassembly doesn't do code highlighting, so a much easier to read version can be found here instead.

Introduction

This proposal seeks continued funding for the development and refinement of the Reactive DOT library, building upon the progress made in proposal #812.

Reactive DOT is a library designed to:

  • Simplify development: Provide a set of intuitive front-end functions and utilities to facilitate Substrate chain interactions, making development accessible for developers of all skill levels.
  • Enhance developer experience: Reduce boilerplate code and complexity involved in integrating DApps with Substrate chains, allowing developers to focus on building robust applications.
  • Promote adoption: Lower the entry barrier for developers through improved tooling and familiar patterns, encouraging the adoption of Polkadot.

The initial phase successfully achieved all outlined goals and stretch objectives, significantly enhancing the development experience for Substrate DApps.

Achievements

Main goals

The library was developed to work with Polkadot-API instead of Polkadot.js, achieving the following main goals:

  • Reading chain constants, API calls, and storages:
    • Strong TypeScript IntelliSense and autocompletion.
    • Caching, persistent storage, and de-duplication of read states.
    • Async and error handling via React concurrency primitives (Suspense & Error Boundary).
  • Multichain support.
  • Documentation and walkthroughs:
  • Community engagement:
    • Announcement post and feedback gathering here.

Stretch goals

Leveraging the extremely well-built and lean core provided by Polkadot-API, multiple stretch goals were achieved within the first development phase:

  • Transaction submission:
    • Fully typed, utilising return type from PAPI typed API tx.
    • Overridable signer via context, hook, and callback options.
  • Wallet management:
    • Support for all browser-injected wallets (Talisman, Nova, Polkadot.js, SubWallet).
    • Support WalletConnect via QR code scanning (Nova, SubWallet).
    • Developed an accompanying drop-in UI library (WIP) for wallet connection management: dot-connect.
  • Account management:
    • Dynamic retrieval from all connected wallets.
    • Multichain support with automatic filtering based on the chain each account can submit transactions to.
    • Unique signer attached to each account, supporting multiple sources and allowing signer selection for transactions.
  • Utility to handle planck units:
    • Compatible with native number locale string conversion.
    • Uses monadic transformation for arithmetic, supporting both BigInt and Number.

Summary of achievements

Reactive DOT now provides all the essential building blocks for developing a Substrate DApp, with walkthroughs guiding developers on:

  • Connecting wallets.
  • Displaying connected accounts.
  • Reading states from the chain.
  • Submitting transactions.

This is achieved with complete type safety, autocompletion from TypeScript, centralised async/error handling (set once and forget), and behind-the-scenes caching, de-duplication, and persistence management.

Demonstration

The following tutorial demonstrates how quickly a simple DApp can be set up. The DApp will:

  • Have a UI dialog for managing wallet connections from injected wallets (extensions) and WalletConnect wallets (mobile).
  • Display all accounts' free balances.
  • Include a button to submit a simple remark extrinsic.
Install dependencies and sync metadata
yarn add @reactive-dot/react dot-connect polkadot-api
npx papi add dot -n polkadot
npx papi
Configure chains and wallets
import type { polkadot } from "@polkadot-api/descriptors";

declare module "@reactive-dot/core" {
  export interface Chains {
    polkadot: typeof polkadot;
  }
}

const config: Config = {
  chains: {
    polkadot: {
      descriptor: polkadot,
      provider: WebSocketProvider("wss://polkadot-rpc.publicnode.com"),
    },
  },
  wallets: [
    new InjectedWalletAggregator(),
    new WalletConnect({
      projectId: "WALLET_CONNECT_PROJECT_ID",
      providerOptions: {
        metadata: {
          name: "APP_NAME",
          description: "APP_DESCRIPTION",
          url: "APP_URL",
          icons: ["APP_ICON"],
        },
      },
      chainIds: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
    }),
  ],
};

export default config;
Add top-level context providers
// Register dot-connect custom elements and configure supported wallets
// this is the companion UI library for managing wallet connections
registerDotConnect({
  wallets: config.wallets,
});

function App() {
  return (
    <ReDotProvider config={config}>
      <ReDotChainProvider chainId="polkadot">
        <ErrorBoundary
          fallbackRender={() => (
            <article>
              <p>Sorry, something went wrong!</p>
            </article>
          )}
        >
          <Suspense fallback={<progress />}>
            <DApp />
          </Suspense>
        </ErrorBoundary>
      </ReDotChainProvider>
    </ReDotProvider>
  );
}
Implement DApp logic
function FreeBalance(props: { address: string }) {
  const chainSpec = useChainSpecData();
  const {
    data: { free },
  } = useLazyLoadQuery((builder) =>
    builder.readStorage("System", "Account", [props.address]),
  );

  const freeBalance = new DenominatedNumber(
    free,
    chainSpec.properties.tokenDecimals,
    chainSpec.properties.tokenSymbol,
  );

  return (
    <div>
      {props.address}: {freeBalance.toLocaleString()}
    </div>
  );
}

function Remark() {
  const accounts = useAccounts();
  const [selectedAccountIndex, setSelectedAccountIndex] = useState(0);
  const selectedAccount = accounts.at(selectedAccountIndex);

  const [remarkState, remark] = useMutation(
    (tx) =>
      tx.System.remark({ remark: Binary.fromText("Hello from reactive-dot!") }),
    { signer: selectedAccount.signer },
  );

  return (
    <section>
      <select
        onChange={(event) =>
          setSelectedAccountIndex(Number(event.target.value))
        }
      >
        {accounts.map((account, index) => (
          <option key={index} value={index}>
            {account.address}
          </option>
        ))}
      </select>
      <button onClick={() => remark()} disabled={remarkState === PENDING}>
        Hello{remarkState === PENDING ? "..." : ""}
      </button>
    </section>
  );
}

function DApp() {
  const accounts = useAccounts();
  return (
    <div>
      {/* Button for managing wallet connection from `dot-connect` */}
      {/* it will pop up a modal for you to connect and disconnect wallets */}
      <dc-connection-button />
      {accounts.map((account, index) => (
        <FreeBalance key={index} address={account.address} />
      ))}
      {accounts.length > 0 and <Remark />}
    </div>
  );
}
Walkthrough recap

Achieving the same outcome without this library would require substantially more code, while being much harder to maintain and reason with.

Next steps

This proposal requests continued funding for Reactive DOT development, focusing on:

Refining existing features

  • More documentation.
  • Improvements and optimisations based on user feedback.
  • Increased testing and refactoring to maintain development velocity and smooth integration of future features.
  • Implementation of an automated versioning and publishing strategy.

Adding new features

React 19 support

React 19 will introduce the use API, enabling components to conditionally consume promise results. Reactive DOT currently lazy loads queries (fetch-on-render) and will need to expose a promise-centric API to fully utilise React 19 features.

Additional framework support

The library's goal is to support building Substrate front-ends beyond just React. Based on the latest State of JavaScript figures, the next frameworks for support, in order of importance, are:

  1. Vue
  2. Angular
  3. Svelte

Existing work has accounted for this by splitting primitives and functionalities into @reactive-dot/core and @reactive-dot/react to prepare for future integrations.

Research on server-side rendering (SSR)

Server-side rendering (SSR) is increasingly important, with applications like Polkassembly and SubSquare both built using Next.js. However, SSR traditionally presents challenges for Substrate DApps, which rely heavily on live WebSocket data. Potential strategies include:

  • Fully opt-in SSR: Faster initial load (render-as-you-fetch) but static values.
  • Client-side Substrate requests: Live data but potential request waterfalls due to the fetch-on-render pattern.

image

A hybrid approach is proposed, fetching initial values server-side to hydrate the client cache, which then updates with live chain data:

async function ServerComponent() {
  const preparedQuery = await prepareQuery((builder) =>
    builder
      .readStorage("SomePallet", "SomeStorage")
      .readStorage("SomeOtherPallet", "SomeOtherStorage"),
  );
  return <ClientComponent query={preparedQuery} />;
}
"use client";

function ClientComponent(props: { query: PreparedQuery }) {
  const resultThatIsInstantAndLive = usePreparedQuery(props.query);
  // ...
}

Note: The above example illustrates the internal workings of the idea. Ideally, a logic split isn't explicitly required by developers.

Community-driven features

In addition to the concrete goals above, feedback is welcomed to determine the focus of future work.

Summary

Build upon

  • Maintenance.
  • Improvements of existing features pending feedback.

Expand upon

  • React 19 support.
  • Support for additional frameworks, starting with Vue.
  • Research and development into hybrid SSR and client updates.
  • Features requested by the community.

Fuelled by positive feedback and a clearly defined path forward, I am confident that we can not only match but surpass the frontend developer experience of other ecosystems (i.e. EVM), setting a new industry standard.

Timeline and budget

The initial proposal requested funding for 2 months, which proved inadequate as a buffer due to the proposal process itself taking 1-2 months to conclude. During this time, I continued working without knowing the outcome. Based on that experience, I am now requesting a budget for 4 months, enabling me to fully dedicate my attention to research and development.

  • Period: August to November 2024.
  • Duration: 4 months / 87 workdays / 696 hours.
  • Requested amount: 15,378 DOT (based on Subscan's EMA7 of $6.12 USD per DOT).
  • Estimated rate: ~22.09 DOT or ~$134.96 USD per hour.
Up
Comments
No comments here