npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ada-anvil/weld

v0.2.1

Published

<div align="center"> <a href="https://github.com/othneildrew/Best-README-Template"> <img src="images/logo.png" alt="Logo" width="150" height="120"> </a>

Downloads

389

Readme

Table of Contents

About

Weld is a universal wallet connector library designed to make managing Web3 wallet connections seamless and intuitive. With a focus on flexibility and developer experience, Weld stands out among wallet connectors due to its innovative features:

  • Universal Reactivity System
    Weld's reactivity system is framework-agnostic, making it possible to integrate seamlessly with any frontend framework or Vanilla JavaScript. Weld's reactive stores enable developers to subscribe to specific state properties, minimizing unnecessary re-renders and improving performance, especially in complex applications.

  • Unified Cross-Chain Support
    Weld provides a single, unified API to interact with wallet extensions across multiple blockchain ecosystems, including Cardano, Ethereum, Polygon, and Solana. This means developers can manage wallet connections and user interactions consistently, regardless of the underlying blockchain.

  • Extensive TypeScript Support
    Weld is built with TypeScript from the ground up, offering comprehensive type definitions for all hooks and APIs. This provides a robust developer experience with powerful autocomplete and type inference.

Getting Started

Installation

npm install @ada-anvil/weld

Examples

Note: A recent version of Node.js is required to run the demo server and view the examples.

To get started with the examples, navigate to the project's root directory, install dependencies with npm install, and then start the demo server by running npm run dev.

Alternatively, you can explore the example code directly by browsing the examples folder.

Usage

Since React.js is the most widely used frontend framework in the Web3 sphere, Weld provides bindings that make its integration straightforward.

If you're looking to integrate Weld with another framework, please refer to the section below.

First, make sure all required dependencies are installed:

npm install @ada-anvil/weld react

Then, wrap your entire application within the WeldProvider:

import { WeldProvider } from "@ada-anvil/weld/react";
import { App } from "./app";

export function Index() {
  return (
    <WeldProvider>
      <App />
    </WeldProvider>
  );
}

Now, you can interact with the library through custom hooks from anywhere in your application.

import { useWallet, useExtensions } from "@ada-anvil/weld/react";

Connecting a Wallet

useWallet exposes two wallet connection functions: connect and connectAsync.

connect doesn't return anything and is guaranteed to never throw. You can pass callbacks to handle success and error cases:

const connect = useWallet("connect");

connect("eternl", {
  onSuccess: wallet => {
    console.log("Connected to", wallet.displayName);
  },
  onError: error => {
    console.error("Failed to connect wallet", error);
  },
});

connectAsync returns a promise that resolves with the connected wallet or rejects in case of an error:

const connectAsync = useWallet("connectAsync");

try {
  const wallet = await connectAsync("eternl"):
} catch (error) {
  console.error("Failed to connect wallet", error);
}

Weld has been thoroughly tested with the following wallet extensions:

| key | Name | Website | | ----------- | ---------- | ------------------------------------------------------------------------------------------------ | | eternl | Eternl | https://chrome.google.com/webstore/detail/eternl/kmhcihpebfmpgmihbkipmjlmmioameka?hl=en-US | | nami | Nami | https://chrome.google.com/webstore/detail/nami/lpfcbjknijpeeillifnkikgncikgfhdo?hl=en-US | | tokeo | Tokeo | https://tokeopay.io | | flint | Flint | https://chrome.google.com/webstore/detail/flint-wallet/hnhobjmcibchnmglfbldbfabcgaknlkj?hl=en-US | | gerowallet | Gero | https://chrome.google.com/webstore/detail/gerowallet/bgpipimickeadkjlklgciifhnalhdjhe/overview | | typhoncip30 | Typhon | https://chrome.google.com/webstore/detail/typhon-wallet/kfdniefadaanbjodldohaedphafoffoh | | nufi | NuFi | https://chrome.google.com/webstore/detail/nufi/gpnihlnnodeiiaakbikldcihojploeca?hl=en-US | | nufiSnap | MetaMask | https://chrome.google.com/webstore/detail/nufi/gpnihlnnodeiiaakbikldcihojploeca?hl=en-US | | lace | Lace | https://chrome.google.com/webstore/detail/lace/gafhhkghbfjjkeiendhlofajokpaflmk?hl=en-US | | vespr | VESPR | https://www.vespr.xyz/ |

Note: While these extensions are fully compatible with Weld, you can pass any key that corresponds to a valid extension API (i.e., window.cardano[key]) to the connect function. However, functionality is only guaranteed with the supported extensions listed above.

Tip: Use the extensions store to dynamically retrieve all wallet extensions installed on the user’s machine.

Retrieve Connected Wallet Info

Info about the connection state and the currently connected wallet can be obtained from the useWallet hook:

const wallet = useWallet("isConnected", "displayName", "balanceAda");

wallet.displayName; // string | undefined
wallet.balanceAda; // number | undefined

// Because of Weld's powerful type inference, `isConnected` can be used
// as a type guard to narrow down the other properies' type!
if (wallet.isConected) {
  wallet.displayName; // string
  wallet.balanceAda; // number
}

const displayedBalance = useWallet(s => s.balanceAda?.toFixed(2) ?? "-");

Interacting with the Wallet

You can interact with the currently connected wallet through the handler instance:

const wallet = useWallet("isConnected", "handler");

const interact = async () => {
  if (!wallet.isConnected) {
    return;
  }
  const utxos = await wallet.handler.getUtxos();
  const res = await wallet.handler.signTx("<CBOR>");
};

Disconnecting the Wallet

const disconnect = useWallet("disconnect");
const onDisconnect = () => disconnect();

Retrieving Wallet Extensions

You can retrieve the user's installed wallet extensions by using the useExtensions hook.

const supportedArr = useExtensions("supportedArr");
const names = supportedArr.map(ext => ext.info.displayName);

const supportedMap = useExtensions("supportedArr");
const name = supportedMap.get("eternl")?.info.displayName;

const hasInstalledExtensions = useExtensions(s => s.allArr.length > 0);

const state = useExtensions("isLoading", "allArr");
if (state.isLoading) return "Loading...";
return (
  <ul>
    {state.allArr.map(ext => (
      <li key={ext.info.key}>{ext.info.displayName}</li>
    ))}
  </ul>
);

Updating Wallet Extensions

Extensions are automatically updated following Weld's configuration options.

These options can be changed through the Weld provider:

return (
  <WeldProvider
    extensions={{
      updateOnWindowFocus: false,
      updateInterval: 30_000,
    }}
  >
    {children}
  </WeldProvider>
);

You can also trigger an update manually through the store's API:

const updateExtensions = useExtensions("update");
const onWalletPickerOpen = () => updateExtensions();

Concepts

Universal Reactive Stores

Weld's reactive system is built to prevent useless work by allowing you to subscribe to specific parts of a store's state.

When using React.js bindings, this is done by passing predicate functions or attribute selectors to the different hooks:

// A single property can be passed to `useWallet` to be returned directly
const isConnected = useWallet("isConnected");

// Multiple properties can be passed to `useWallet` to be returned in an object
const wallet = useWallet("isConnected", "balanceAda");

// The wallet store has smart type definitions that provide a nice DX
wallet.balanceAda; // inferred as `number | undefined`
if (wallet.isConnected) {
  wallet.balanceAda; // inferred as `number`
}

// A predicate function can be passed to `useWallet` to derive values from the store state.
// Note: Predicate functions are executed on every store state change but the result
//       is memoized and re-renders occur only when this result changes
const displayedBalance = useWallet(s => s.balanceAda?.toFixed(2) ?? "-");

// `useWallet` can be called without any arguments in which case the entire
// store state gets returned.
// However, this means that any change to the store will cause a re-render,
// which is why it's preferred to select only the data you need
// using one of the previously mentioned strategies.
const wallet = useWallet();

When using stores directly, listeners can be registered by calling the subscribeWithSelector and subscribe functions.

Error handling

When using Weld, two types of errors can occur: synchronous errors and asynchronous ones.

Synchronous errors

Synchronous errors occur when you call wallet handler functions or connectAsync.

It's up to you to handle them using try/catch blocks.

Asynchronous errors

Asynchronous errors are the ones that occur during side effects like polling updates. Since they can occur anywhere and at any point, these errors cannot be caught by a try catch so we don't throw them as errors to prevent uncaught failure rejections.

The provider wraps the asynchronous error events that are related to the current wallet and allows you to pass callbacks to handle them when they occur:

<WeldProvider
  onUpdateError={(context, error) => handleError(context, error)}
  extensions={{ onUpdateError: error => handleError(error) }}
  wallet={{ onUpdateError: error => handleError(error) }}
>
  {children}
</WeldProvider>

Wallet Connection Persistence

Weld offers a flexible interface for managing wallet connection persistence. In most cases, you shouldn't need to use this feature as it's automatically handled by the wallet store.

weld.wallet.subscribeWithSelector(
  state => state.key,
  key => {
    if (key) {
      defaults.storage.set("connectedWallet", key);
    } else {
      defaults.storage.remove("connectedWallet");
    }
  }
);

Automatic reconnection

When using the library, an attempt will be made to reconnect the persisted wallet on first mount.

If you disable the persistence feature, you can still use the getPersistedValue helper function to retrieve the persisted wallet and connect it during the initialization of your app.

function initFunction() {
  const lastConnectedWallet = getPersistedValue("weld_connected-wallet");
  if (lastConnectedWallet) {
    weld.wallet.connect(lastConnectedWallet);
  }
}

Note: getPersistedValue always returns undefined when persistence is disabled.

Configuration

By default, the user's wallet connection is persisted in a cookie to allow support for SSR. This behavior can be customized by updating the configuration store to provide a different Storage interface.

Here's how to customize the persistence strategy to use localStorage instead of cookies:

import { WeldProvider } from "@ada-anvil/weld/react";

export default function RootLayout({ children }) {
  return (
    <WeldProvider
      storage={{
        get(key) {
          return window.localStorage.getItem(key) ?? undefined;
        },
        set(key, value) {
          if (typeof window !== "undefined") {
            window.localStorage.setItem(key, value);
          }
        },
        remove(key) {
          if (typeof window !== "undefined") {
            window.localStorage.removeItem(key);
          }
        },
      }}
    >
      {children}
    </WeldProvider>
  );
}

Here's an example using the default configuration.

weld.config.update({
  storage: {
    get(key) {
      if (typeof window !== "undefined") {
        return window.localStorage.getItem(key) ?? undefined;
      }
    },
    set(key, value) {
      if (typeof window !== "undefined") {
        window.localStorage.setItem(key, value);
      }
    },
    remove(key) {
      if (typeof window !== "undefined") {
        window.localStorage.removeItem(key);
      }
    },
  },
});

The persistence features can be disabled through the configuration store:

weld.config.update({
  enablePersistence: false,
});

Note: When using a SSR framework, make sure to set configuration options inside a client side file.

Cross-Chain Support

Weld supports managing wallets and extensions across multiple blockchains. Currently, Weld integrates with the following chains:

Usage with Ethereum

First, make sure all required dependencies are installed:

npm install @ada-anvil/weld ethers

To use the react bindings, you'll also need to make sure that react is installed and available

Then, wrap your entire application within the WeldEthProvider:

import { WeldEthProvider } from "@ada-anvil/weld/eth/react";
import { App } from "./app";

export function Index() {
  return (
    <WeldEthProvider>
      <App />
    </WeldEthProvider>
  );
}

Now, you can interact with the library through custom hooks from anywhere in your application.

import { useEthWallet, useEthExtensions } from "@ada-anvil/weld/eth/react";

To use Weld's ethereum stores outside of React, use the weldEth instance as you would the weld instance.

import { weldEth } from "@ada-anvil/weld/eth";
weldEth.wallet.subscribeWithSelector(
  s => s.isConnected,
  isConnected => {
    if (isConnected) {
      console.log("Eth wallet is connected");
    }
  }
);

Usage with Polygon

First, make sure all required dependencies are installed:

npm install @ada-anvil/weld ethers

To use the react bindings, you'll also need to make sure that react is installed and available

Then, wrap your entire application within the WeldPolyProvider:

import { WeldPolyProvider } from "@ada-anvil/weld/poly/react";
import { App } from "./app";

export function Index() {
  return (
    <WeldPolyProvider>
      <App />
    </WeldPolyProvider>
  );
}

Now, you can interact with the library through custom hooks from anywhere in your application.

import { usePolyWallet, usePolyExtensions } from "@ada-anvil/weld/poly/react";

To use Weld's polygon stores outside of React, use the weldPoly instance as you would the weld instance.

import { weldPoly } from "@ada-anvil/weld/poly";
weldPoly.wallet.subscribeWithSelector(
  s => s.isConnected,
  isConnected => {
    if (isConnected) {
      console.log("Poly wallet is connected");
    }
  }
);

Usage with Solana

First, make sure all required dependencies are installed:

npm install @ada-anvil/weld @solana/web3.js @solana/spl-token

To use the react bindings, you'll also need to make sure that react is installed and available

Then, wrap your entire application within the WeldSolProvider:

import { WeldSolProvider } from "@ada-anvil/weld/sol/react";
import { App } from "./app";

export function Index() {
  return (
    <WeldSolProvider>
      <App />
    </WeldSolProvider>
  );
}

Now, you can interact with the library through custom hooks from anywhere in your application.

import { useSolWallet, useSolExtensions } from "@ada-anvil/weld/sol/react";

To use Weld's solana stores outside of React, use the weldSol instance as you would the weld instance.

import { weldSol } from "@ada-anvil/weld/sol";
weldSol.wallet.subscribeWithSelector(
  s => s.isConnected,
  isConnected => {
    if (isConnected) {
      console.log("Sol wallet is connected");
    }
  }
);

Cross-Framework Support

Weld is built with flexibility in mind and as such can be used with any frontend framework or even Vanilla JavaScript.

Here are some examples of how Weld can be integrated with popular modern frameworks.

If you're looking to integrate Weld with React.js, please refer to our in depth tutorial above.

Usage with Svelte

You can easily integrate Weld with Svelte by leveraging the context API and by delegating fine-grained reactivity to the $state rune.

First, create a .svelte.ts file containing the context:

import { createWeldInstance, type WeldConfig } from "@ada-anvil/weld";
import { getContext, setContext } from "svelte";

export class Weld {
  weld = createWeldInstance();
  // Use the $state rune to create a reactive object for each Weld store
  config = $state(this.weld.config.getState());
  wallet = $state(this.weld.wallet.getState());
  extensions = $state(this.weld.extensions.getState());

  constructor(persist?: Partial<WeldConfig>) {
    this.weld.config.update({ updateInterval: 2000 });
    if (persist) this.weld.persist(persist);
    $effect(() => {
      this.weld.init();
      // Subscribe to Weld stores and update reactive objects when changse occur
      // Note: No need to use subscribeWithSelector as $state objects are deeply reactive
      this.weld.config.subscribe(s => (this.config = s));
      this.weld.wallet.subscribe(s => (this.wallet = s));
      this.weld.extensions.subscribe(s => (this.extensions = s));
      return () => this.weld.cleanup();
    });
  }
}

// Use the context API to scope weld stores and prevent unwanted sharing
// of data between clients when rendering on the server

const weldKey = Symbol("weld");

export function setWeldContext(persist?: Partial<WeldConfig>) {
  const value = new Weld(persist);
  setContext(weldKey, value);
  return value;
}

export function getWeldContext() {
  return getContext<ReturnType<typeof setWeldContext>>(weldKey);
}

Then, initialize the context once at the root of your app:

<script>
  import { setWeldContext } from "./weld.svelte";
  setWeldContext();
</script>

Finally, use the context anywhere in your application:

<script>
  import { getWeldContext } from "./weld.svelte";
  const weld = getWeldContext();
  const displayedBalance = $derived(weld.wallet.balanceAda?.toFixed(2) ?? "-");
</script>

<div>Balance: {displayedBalance}</div>

Usage with Vanilla JavaScript

Weld can be used without any framework. Here's an example of how you can leverage Weld's reactivity system in regular html and JavaScript:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Weld x Vanilla JavaScript</title>
  </head>

  <body>
    <main>
      <section>
        <h2>Wallets</h2>
        <ul id="wallets"></ul>
      </section>
      <section>
        <h2>Connection</h2>
        Connecting to <span id="connecting-to">-</span><br />
        Connected to <span id="connected-to">-</span><br />
        Balance <span id="balance">-</span><br />
        <button onclick="window.Weld.wallet.connect('nami')">Connect nami</button>
      </section>
    </main>

    <script>
      function init() {
        window.Weld.config.update({ debug: true });

        window.Weld.extensions.subscribeWithSelector(
          s => s.allArr,
          exts => {
            const list = document.querySelector("#wallets");
            for (const ext of exts) {
              const item = document.createElement("li");
              item.textContent = ext.info.displayName;
              list?.appendChild(item);
            }
          }
        );

        window.Weld.wallet.subscribeWithSelector(
          s => s.isConnectingTo,
          isConnectingTo => {
            document.querySelector("#connecting-to").textContent = isConnectingTo ?? "-";
          }
        );

        window.Weld.wallet.subscribeWithSelector(
          s => s.displayName,
          displayName => {
            document.querySelector("#connected-to").textContent = displayName ?? "-";
          }
        );

        window.Weld.wallet.subscribeWithSelector(
          s => s.balanceAda,
          balance => {
            document.querySelector("#balance").textContent = balance?.toFixed(2) ?? "-";
          }
        );

        window.addEventListener("load", () => {
          window.Weld.init();
        });

        window.addEventListener("unload", () => {
          window.Weld.cleanup();
        });
      }
    </script>

    <script onload="init()" src="https://unpkg.com/@ada-anvil/[email protected]/cdn.min.js" defer></script>
  </body>
</html>

Note: When using a build tool like Vite, we recommend using a package manager instead of the CDN version to install and manage Weld:

npm install @ada-anvil/weld

And then:

import { weld } from "@ada-anvil/weld";

weld.wallet.subscribeWithSelector(
  s => s.isConnected,
  isConnected => {
    // update your UI
  }
);

Server-Side Rendering

When pre-rendering your application on the server, you can inform Weld about the last connected wallet so the store state initializes with the correct values.

For example, when using Next.js, you can avoid hydration errors by retrieving the connected wallet cookie in a server component. You can then pass this value as an initial parameter to the Weld provider:

import { cookies } from "next/headers";
import { STORAGE_KEYS } from "@ada-anvil/weld/server";
import { WeldProvider } from "@ada-anvil/weld/react";

export default function RootLayout({ children }) {
  const lastConnectedWallet = cookies().get(STORAGE_KEYS.connectedWallet)?.value;
  return <WeldProvider wallet={{ tryToReconnectTo: lastConnectedWallet }}>{children}</WeldProvider>;
}

This setup ensures that the correct wallet connection state is available when the application first renders.

Note: This approach works only if you use cookies to store persisted data, as they are accessible on both the client and server— unlike window.localStorage, for example, which is only available in the browser.