diff --git a/README.md b/README.md index 0316d6d..3956147 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,68 @@ # ethers-svelte -Use the [ethers.js library](https://docs.ethers.io/) as a -collection of [readable Svelte stores](https://svelte.dev/tutorial/readable-stores) -for Svelte, Sapper or SvelteKit. This package includes also a few basic Svelte components -for very typical usage of ethers.js: `Balance` `Identicon` `Jazzicon`. - -`ethers-svelte` support `ethers.js` version 6. If you want the same package for `ethers.js` version 5, please use the package [svelte-ethers-store](https://www.npmjs.com/package/svelte-ethers-store). - -If you also use the [web3.js library](https://web3js.readthedocs.io/) to interact -with EVM, you may be interested by the sister package [svelte-web3](https://www.npmjs.com/package/svelte-web3). +`ethers-svelte` is a package that integrates the [ethers.js v6 +library](https://docs.ethers.io/v6/) as a collection of [readable Svelte +stores](https://svelte.dev/tutorial/readable-stores) for Svelte or SvelteKit. It +provides a convenient and reactive way to interact with Ethereum blockchain +using ethers.js in your Svelte applications. + +**Key features:**** + +- A set of reactive Svelte stores, automatically updated when a new connection, + or when the chain or the selected account changes +- Support for ethers.js version 6, with compatibility for various EVM providers + (such as browser wallets like Metamask, WalletConnect, Web3Modal, web3-onboard and more) +- A few basic Svelte components for typical ethers.js usage, such as `Balance`, + `Identicon`, and `Jazzicon`, demonstrating how it is easy to build common UI + elements in your application. + +With `ethers-svelte`, you can quickly set up connections to Ethereum blockchain, +manage accounts and contracts, and create reactive UI components that respond to +changes in the underlying blockchain data. This package simplifies the process +of building decentralized applications using Svelte or SvelteKit (Sapper should +also works but it's official support is now deprecated). + +`ethers-svelte` support `ethers.js` version 6. If you want the same package for +`ethers.js` version 5, please use the package +[svelte-ethers-store](https://www.npmjs.com/package/svelte-ethers-store). + +If you also use the [web3.js library](https://web3js.readthedocs.io/) to +interact with EVM, you may be interested by the sister package +[svelte-web3](https://www.npmjs.com/package/svelte-web3). ### Community For additional help or discussion, join us [in our Discord](https://discord.gg/7yXuwDwaHF). + ## Installation -Add the `ethers-svelte` package +To use `ethers-svelte` in your Svelte or SvelteKit project, you need to add it +as a dependency: ```bash npm i ethers-svelte ``` -## Basic usage (default stores connected to one chain) +Once the package is installed, you can import and use the provided stores and +components in your application, as shown in the [Basic +Usage](#basic-usage-default-stores-connected-to-one-chain) and [Ethers Svelte +Components](#ethers-svelte-components) sections. + + +## Basic Usage (Default Stores Connected to One Chain) -### Derived stores +In this section, we will cover how to use the default stores provided by +`ethers-svelte` for managing a single chain connection. The default stores +include `connected`, `provider`, `chainId`, `chainData`, `signer`, +`signerAddress`, and `contracts`. -This library creates a set of readable Svelte stores that are -automatically updated when a new connection happens, or when the chain -or the selected account change. You can import them directly in any -Svelte or JavaScript files : +### Derived Stores + +`ethers-svelte` provides a set of readable Svelte stores that automatically +update when a new connection is established, or when the chain or selected +account changes. Import the required stores in your Svelte or JavaScript files: ```js import { @@ -41,7 +73,7 @@ import { signer, signerAddress, contracts, -} from "ethers-svelte"; +} from "ethers-svelte" ``` - connected: store value is true if a connection has been set up. @@ -52,104 +84,133 @@ import { - signerAddress: store value is a shortcut to get `$signer.getAddress()` when connected. - contract: store value is an Object for all ethers.Contract instances you need. -For these stores to be useful in your Svelte application, a connection -to an EVM blockchain first need to established . The abstract helper -`defaultEvmStores` can be used to initiate the connection and -automatically instanciate all stores. + +To make these stores useful in your Svelte application, you first need to +establish a connection to an EVM blockchain. Use the `defaultEvmStores` helper +to initiate the connection and instantiate all stores: ```js -import { defaultEvmStores } from "ethers-svelte"; +import { defaultEvmStores } from "ethers-svelte" ``` -### Connection with the browser provider (eg wallets like Metamask) +### Connecting with the Browser Provider (e.g., MetaMask) To enable a connection with the current [EIP-1193 provider](https://eips.ethereum.org/EIPS/eip-1193#appendix-i-consumer-facing-api-documentation) -injected in the browser `window` context, simply call `setProvider` on -the library abstract helper with no argument: +injected into the browser's `window` context, simply call the `setProvider()` +method on the `defaultEvmStores` helper without any arguments: ```js -defaultEvmStores.setProvider(); +defaultEvmStores.setProvider() ``` -Please note that `setProvider` can only to be called with no argument -in a browser context. So you may want to use `onMount` when using -Sapper or SvelteKit. Similarly, you cannot use `setProvider` with no -argument in SSR context. +Please note that using `setProvider()` without any arguments is only possible in +a browser context. When using SvelteKit, you may want to use the `onMount` +function. Similarly, you cannot use `setProvider` with no argument in SSR +context. ```js +import { onMount } from "svelte" + onMount(() => { - // add a test to return in SSR context - defaultEvmStores.setProvider(); -}); + // Add a test to return in SSR context + defaultEvmStores.setProvider() +}) ``` `ethers-svelte` will automatically update the stores when the network or -accounts change and remove listeners at disconnection. +accounts change, and it will remove listeners upon disconnection. -### Connection with non injected EIP-1193 providers +### Abbreviating the `defaultEvmStores` Helper -To connect to non injected EIP-1193 providers like : +It's common to abbreviate the `defaultEvmStores` helper in applications. For +example, you can use a `evm` shortcut alias as shown in the following example. +This documentation will use this convention going forward. + +```js +import { + defaultEvmStores as evm, +} from "ethers-svelte" + +// ... + +evm.setProvider() +``` + +By using the `evm` alias, you can simplify your code and make it easier to read +while still retaining the functionality provided by the `defaultEvmStores` +helper. This convention allows for cleaner code organization and improved +readability in your Ethereum-based applications. + + +### Connecting with Non-Injected EIP-1193 Providers + +For non-injected EIP-1193 providers like: - buidler.dev - ethers.js - eth-provider - WalletConnect - Web3Modal +- Web3 Onboard + -Call `setProvider` on the library abstract helper with the JavaScript provider -instance object of the library. For example with Web3Modal : +Call the `setProvider()` method on the `evm` helper with the +JavaScript provider instance object of the library. For example, with Web3Modal: ```js const web3Modal = new Web3Modal() const provider = await web3Modal.connect() -defaultEvmStores.setProvider(provider) +evm.setProvider(provider) ``` `ethers-svelte` will automatically update the stores when the network or -accounts change and remove listeners at disconnection. +accounts change, and it will remove listeners upon disconnection. -### Connection with other Ethers.js providers (ws, http, ipc, ...) +### Connecting with Other Ethers.js Providers (ws, http, ipc, ...) -You can instanciate many types of providers using Ethers.js, see the -relevant -[documentation](https://docs.ethers.io/v5/api/providers/other/) and -simply pass them as argument to `defaultEvmStores.setProvider()` to inititate the stores: +You can instantiate various types of providers using Ethers.js (see the +[relevant documentation](https://docs.ethers.org/v6/api/providers/thirdparty/)) +and pass them as an argument to `evm.setProvider()` to initiate the +stores: ```js -defaultEvmStores.setProvider(new ethers.providers.InfuraProvider()) +evm.setProvider(new ethers.InfuraProvider()) // or -defaultEvmStores.setProvider(new ethers.providers.EtherscanProvider()) +evm.setProvider(new ethers.EtherscanProvider()) // or -defaultEvmStores.setProvider(new ethers.providers.AlchemyProvider()) +evm.setProvider(new ethers.AlchemyProvider()) // etc... ``` -As a shortcut, if you pass an URL string or a valid connection object, a -[Ethers.js JsonRpcProvider](https://docs.ethers.io/v5/api/providers/jsonrpc-provider/) +As a shortcut, if you pass a URL string or a valid connection object, an +[Ethers.js +JsonRpcProvider](https://docs.ethers.org/v6/api/providers/jsonrpc/#JsonRpcProvider) will be automatically instantiated. -For provider that support the function `getSigner()`, a Signer Object will be automatically -associated with the `signer` store. You can also pass `addressOrIndex` as the second argument -of `setProvider()` to select another account than the default when possible. +For providers that support the `getSigner()` function, a Signer Object will be +automatically associated with the `signer` store. You can also pass +`addressOrIndex` as the second argument of `setProvider()` to select another +account than the default when possible. + ```js -defaultEvmStores.setProvider(, ) +evm.setProvider(, ) ``` -If you don't need a signer, you might also call `setProvider` with the -argument `addressOrIndex` as a `null` value and bypass any attempt to -detect an account. +If you don't need a signer, you might also call `setProvider()` with the +argument `addressOrIndex` set to `null`, which will bypass any attempt to detect +an account. -### Using the stores +### Using the Stores -After a connection has been established, you may import the stores -anywhere in your application. Most of the time, you should use the `$` -prefix Svelte notation to access the stores values. +After a connection has been established, you can import the stores anywhere in +your application. Most of the time, you should use the `$` prefix Svelte +notation to access the store values. ```html {#if !$connected} @@ -163,60 +224,72 @@ prefix Svelte notation to access the stores values. {/if} ``` +The example above allows you to display connection status, chain ID, and signer +address in your application based on the current connection state. + +As you build your application, you can use these stores to create reactive UI +components that respond to changes in the underlying blockchain data. + + ### Using the Ethers.js Providers and Signers API -Likewise use the `$` prefix Svelte notation to access Provider or Signer -read-only abstractions and use the whole Ethers.js API. (beware, in the Ethers -library documentation, Provider or Signer instances are always noted as `provider` -and `signer`, without `$`, but in the context of `ethers-svelte`, this naming -is used by the Svelte stores themselves encapsulating Provider or Signer instances). +To leverage the full functionality of Ethers.js Providers and Signers within +your Svelte application, use the `$` prefix Svelte notation to access the +`provider` and `signer` stores. This allows you to call methods from the +Ethers.js API directly. -```js - import { connected, provider, signer } from 'ethers-svelte' +For example, you can interact with the blockchain using the following Ethers.js methods: - // ... +```js +import { connected, provider, signer } from "ethers-svelte" - const { name, chainId } = await $provider.getNetwork() +// ... - const balance = await $signer.getBalance() +const { name, chainId } = await $provider.getNetwork() - $signer.sendTransaction({, , }); +const balance = await $signer.getBalance() +$signer.sendTransaction({ to: , value: , gasLimit: }) ``` -For providers that don't support `getSigner`, the value `$signer` will be `null`. +Remember that for providers that don't support `getSigner`, the value of +`$signer` will be `null`. + +By using the `$` notation to access the `provider` and `signer` instances, you +can ensure that your application's UI components will react to any changes in +the underlying blockchain data. This simplifies the process of creating and +managing decentralized applications using Svelte. -### Using the contracts store for reactive contract calls -To enjoy the same reactivity as using `$provider` and `$signer` but -with a contract instance, you first need to declare its address and -interface. To differenciate each `ethers.Contract` instance, you also -need to define a logical name. That's the function `attachContract`: +### Using the Contracts Store for Reactive Contract Calls + +The `contracts` store allows you to interact with smart contracts reactively by +declaring their address, ABI, and an optional logical name. To do this, use the +`attachContract()` function provided by `evm`. ```html ``` -`attachContract` only needs to be called once and can be called before -connection since `ethers.Contract` instances will only be created when -a connection becomes available. You may want to reattach new contract -definition or abi for example when you the current network change. For -the old definition will be overwritten and instance updated in the -`contracts` store, simply use the same logical name. +The `attachContract()` function only needs to be called once and can be called +before a connection is established. `Ethers.Contract` instances will be created +when a connection becomes available. If you want to reattach a new contract +definition or ABI when the current network changes, simply use the same logical +name. The old definition will be overwritten, and the instance updated in the +`contracts` store. -After a contract as be declared, you can use its instance anywhere -using the `$` notation and the logical name : +After declaring a contract, you can access its instance anywhere in your +application using the `$` notati*on and the logical name: ```html @@ -227,64 +300,94 @@ using the `$` notation and the logical name : {:then value} -result of contract call totalSupply on my contract : { value } +Result of contract call totalSupply on my contract: {value} {/await} ``` -By default, `ethers-svelte` build contract instances using the signer if -available and if not the provider. You may want to force using the current provider -by passing `false` as fourth argument. +By default, `ethers-svelte` builds contract instances using the signer if +available and the provider otherwise. You can force the use of the current +provider by passing `false` as the fourth argument to `attachContract()`. -```html -defaultEvmStores.attachContract('myContract', -
- , , ) -
+```js +evm.attachContract('myContract',
, , false) ``` -### Reading stores outside of Svelte files +By using the `$contracts` store, you can create reactive UI components that +interact with smart contracts and automatically update when the underlying +contract state changes. + +### Accessing Stores Outside of Svelte Files -The `$` prefix Svelte notation to access store values is only -available inside Svelte files. To directly access the instantiated -values in pure javascript library without subscribing to the store, -you can use a special getter on the library abstract helper: +When working with pure JavaScript files outside of Svelte components, you cannot +use the `$` prefix notation to access store values directly. Instead, you can +use the `$` method provided by the `defaultEvmStores/evm` helper to access +the instantiated values without subscribing to the store: ```js // this is not a Svelte file but a standard JavaScript file -import { defaultEvmStores } from "ethers-svelte"; +import { defaultEvmStores as evm } from "ethers-svelte" -if (defaultEvmStores.$selectedAccount) { - // do something if store selectedAccount is non null +if (evm.$selectedAccount)) { + // Do something if the store selectedAccount is non-null } ``` -### Forcing a disconnect (and removing all listeners) +You can use the `$` methods with any of the available store names, such +as `connected`, `provider`, `chainId`, `chainData`, `signer`, `signerAddress`, +and `contracts`. + + +### Forcing a Disconnect (and Removing All Listeners) -Simply call the function `disconnect` directly on the on the library -abstract helper: +There might be situations where you want to disconnect from the current provider +and remove all associated listeners. To achieve this, simply call the +`disconnect()` method on the `defaultEvmStores/evm` helper: ```js -defaultEvmStores.disconnect(); +evm.disconnect() ``` -## Human readable chain CAIP-2 information +This method will reset the connection state, remove all listeners, and update +the store values accordingly. You can use this method, for example, when your +application needs to switch between different providers or when the user logs +out. -`chainData` is a store returning the current JavaScript [CAIP-2 representation](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) object. +By disconnecting and removing listeners, you can ensure that your application +remains responsive to user actions and maintains a clean state when transitions +occur between providers or user sessions. -### Example -The information returned by the `chainData` store depends (like all -other ethers stores) on which chain the current provider is -connected. If the store has not yet been connected (with -`setProvider`), the store value will be `undefined`. +## Human Readable Chain CAIP-2 Information + +The `chainData` store provides human-readable information about the currently +connected chain in the +[CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) +format. This information can be useful for displaying details about the +connected chain and updating UI elements reactively. -This object is extremely useful to build components that reactively -update all variables elements that depends on the current active chain -or account. +### Accessing Chain Data -Below is the CAIP-2 formatted information when the default store is -connected with the Ethereum Mainnet : +To access the chain data, simply import and use the `chainData` store: + +```js +import { chainData } from "ethers-svelte" +``` + +The information returned by the `chainData` store depends on the connected +chain. If the store has not yet been connected (with `setProvider()`), the store +value will be an empty Object. + +### Example + +The following is an example of the CAIP-2 formatted information when the default +store is connected to the Ethereum Mainnet. The `chainData` store will return an +object containing various information about the connected chain, such as its +name, chain ID, native currency, and more. + +You can use this information to display relevant details about the connected +chain in your application and update your UI reactively based on the current +chain data. ```json { @@ -317,50 +420,125 @@ connected with the Ethereum Mainnet : } ``` -You might want to access all chains CAIP-2 data directly without using the -`chainData` store. In this case, use the getter `allChainsData`, it returns -the list of all CAIP-2 data available. + +### Accessing All Chains CAIP-2 Data Directly + +If you want to access all chains CAIP-2 data directly without using the +`chainData` store, you can use the `allChainsData` getter. It returns the list +of all available CAIP-2 data. ```js -import { allChainsData } from "ethers-svelte"; +import { allChainsData } from "ethers-svelte" -console.log(allChainsData); +console.log(allChainsData) ``` -Another solution is to use the helper function `getChainDataByChainId` -that takes the chainId as argument and returns -the CAIP-2 data or an empty object if not found. +Alternatively, you can use the `getChainDataByChainId` helper function, which +takes the `chainId` as an argument and returns the corresponding CAIP-2 data or +an empty object if not found. ```js -import { getChainDataByChainId } from "ethers-svelte"; +import { getChainDataByChainId } from "ethers-svelte" -console.log(getChainDataByChainId(5)); +console.log(getChainDataByChainId(5)) ``` -## Ethers Svelte components +These methods allow you to access chain-specific information without relying on +the reactive `chainData` store, making it easier to work with multiple chains or +retrieve data outside of Svelte components. + +## Ethers.js Svelte Components + +`ethers-svelte` includes several basic Svelte components designed to simplify +common tasks when building Ethereum-based applications. These components +demonstrate how to use the `ethers-svelte` library effectively and serve as +reusable and composable best practices components. Currently, the library +includes `Balance`, `Identicon`, and `Jazzicon` components. We encourage +community members to contribute and help develop additional components by +joining our discussions in our [Discord](https://discord.gg/7yXuwDwaHF). -We plan to export generic Svelte low level components both to -demonstrate the use of the `ethers-svelte` library and as -resuable and composable best practices components. A `Balance` and -`Identicon` components have been implemented for now. You are welcome -to help define and develop new components by joining our discussions -in our [Discord](https://discord.gg/7yXuwDwaHF). +You can find example usage of these components in the `components` route in the +example directory. -See also the `components` route in the example directory. +### Balance Component + +The `Balance` component displays the balance of a specified Ethereum address. To +use the `Balance` component, import it and pass the address as a prop: ```html + -

balance =

+

Balance:

+``` + +### Identicon Component + +The `Identicon` component generates a unique identicon (a visual representation +of an Ethereum address) using the `ethereum-blockies-base64` library. To use the +`Identicon` component, import it and pass the address as a prop: + +```html + + ``` -## FAQ +### Jazzicon Component + +The `Jazzicon` component generates a unique and colorful identicon using the +`@metamask/jazzicon` library. To use the `Jazzicon` component, import it and +pass the address and size (optional) as props: + +```html + + + +``` + +These components serve as a starting point for building your Ethereum-based +applications using `ethers-svelte`. You can further customize them and create +additional components according to your application's requirements. By +leveraging the reactivity and simplicity of Svelte along with the powerful +features of ethers.js, you can create a seamless and efficient user experience +for your decentralized applications. + +## Frequently Asked Questions + +### 1. How can I automatically connect to a provider on page load? + +Auto-connecting on page load is outside the scope of this package. However, the +implementation depends on the type of provider you are using and a method to +store connection information between page loads (e.g., using localStorage). You +can create a custom function that connects to your desired provider and calls +`evm.setProvider()` on page load. + +### 2. Can I use ethers-svelte with multiple chains simultaneously? + +Yes, you can create multiple instances of EVM stores by calling +`makeEvmStores(name)` with a unique name for each instance. This allows you to +manage connections, accounts, and contracts for different chains independently. + +### 3. Can I use ethers-svelte with Sapper or SvelteKit? + +Yes, `ethers-svelte` works with both Sapper and SvelteKit. However, Sapper suppory +is not officially supported and when using `setProvider()` with no arguments in +a server-side rendering (SSR) context, you should use the `onMount` lifecycle +function to ensure it is called only in the browser context. + + +### 4. Is there an example of integrating `ethers-svelte` with Web3 Onboard + +Yes, you can refer to the Rouge Ticket application, which demonstrates the +integration of `ethers-svelte` and Web3 Onboard. The wallet configuration can be +found in the `wallet.js` file: -### _how to auto-connect on page load?_ +[https://github.com/TheRougeProject/ticket-dapp/blob/main/src/lib/wallet.js](https://github.com/TheRougeProject/ticket-dapp/blob/main/src/lib/wallet.js) -It is out of scope of this package to implement this function but it -generally depends on the type of provider you are using and a way to -store connection information between page loads (for example by using -localStorage). +This example will help you understand how to use `ethers-svelte` in conjunction +with Web3 Onboard to build a seamless and user-friendly Ethereum application. diff --git a/package.json b/package.json index 619000e..3cd1c3a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,15 @@ { "name": "ethers-svelte", "version": "3.1.2", - "description": "Ethers.js library as a collection of stores for Svelte, Sapper or SvelteKit & related UX components.", + "description": "Ethers.js library as a collection of stores for Svelte or SvelteKit & related UX components.", + "author": { + "name": "Christophe Le Bars", + "email": "" + }, "license": "MIT", + "engines": { + "node": ">=14.0.0" + }, "repository": "clbrge/ethers-svelte", "main": "dist/index.cjs", "module": "dist/index.mjs", @@ -21,10 +28,6 @@ "svelte": "./src/components/index.js" } }, - "author": { - "name": "Christophe Le Bars", - "email": "" - }, "scripts": { "update-chains": "node ./scripts/update-chains.mjs", "format": "prettier --plugin-search-dir . --write .",