diff --git a/docusaurus.config.js b/docusaurus.config.js index 47aa57cbfca..0cec9dc65cd 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -122,7 +122,7 @@ const config = { markdown: { mermaid: true, hooks: { - onBrokenMarkdownLinks: 'throw', + onBrokenMarkdownLinks: 'warn', }, }, themes: ['@docusaurus/theme-mermaid'], diff --git a/sdk-sidebar.js b/sdk-sidebar.js index 20e3b20f1c7..02fd02cb832 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -49,76 +49,77 @@ const sdkSidebar = { ], evm: [ 'evm/index', + { + type: 'category', + label: 'Quickstart', + collapsible: false, + collapsed: false, + items: [ + 'evm/connect/quickstart/javascript', + 'evm/connect/quickstart/wagmi', + 'evm/connect/quickstart/rainbowkit', + 'evm/connect/quickstart/connectkit', + 'evm/connect/quickstart/react-native', + 'evm/connect/quickstart/dynamic', + 'evm/connect/quickstart/web3auth', + ], + }, { type: 'category', label: 'Guides', collapsible: false, collapsed: false, items: [ + 'evm/connect/guides/manage-user-accounts', + 'evm/connect/guides/manage-networks', { type: 'category', - label: 'JavaScript', + label: 'Send transactions', collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/guides/javascript/index" }, + link: { type: "doc", id: "evm/connect/guides/send-transactions/index" }, items: [ - 'evm/connect/guides/javascript/manage-user-accounts', - 'evm/connect/guides/javascript/manage-networks', - { - type: 'category', - label: 'Send transactions', - collapsible: true, - collapsed: true, - link: { type: "doc", id: "evm/connect/guides/javascript/send-transactions/index" }, - items: [ - 'evm/connect/guides/javascript/send-transactions/batch-transactions', - ], - }, - { - type: 'category', - label: 'Sign data', - collapsible: true, - collapsed: true, - link: { type: "doc", id: "evm/connect/guides/javascript/sign-data/index" }, - items: [ - 'evm/connect/guides/javascript/sign-data/siwe', - ], - }, - 'evm/connect/guides/javascript/batch-requests', - 'evm/connect/guides/javascript/interact-with-contracts', - 'evm/connect/guides/javascript/use-deeplinks', - 'evm/connect/guides/javascript/display-tokens', - { - type: 'category', - label: 'Best practices', - collapsible: true, - collapsed: true, - items: [ - 'evm/connect/guides/javascript/best-practices/display', - 'evm/connect/guides/javascript/best-practices/run-devnet', - 'evm/connect/guides/javascript/best-practices/production-readiness', - ], - }, + 'evm/connect/guides/send-transactions/batch-transactions', ], }, { type: 'category', - label: 'Wagmi', + label: 'Sign data', collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/guides/wagmi/index" }, + link: { type: "doc", id: "evm/connect/guides/sign-data/index" }, items: [ - 'evm/connect/guides/wagmi/manage-user-accounts', - 'evm/connect/guides/wagmi/manage-networks', - 'evm/connect/guides/wagmi/send-transactions', - 'evm/connect/guides/wagmi/interact-with-contracts', + 'evm/connect/guides/sign-data/siwe', ], }, - 'evm/connect/guides/rainbowkit', - 'evm/connect/guides/connectkit', - 'evm/connect/guides/react-native', - 'evm/connect/guides/dynamic', - 'evm/connect/guides/web3auth', + 'evm/connect/guides/interact-with-contracts', + { + type: 'category', + label: 'MetaMask Exclusive', + collapsible: true, + collapsed: true, + items: [ + 'evm/connect/guides/batch-requests', + 'evm/connect/guides/use-deeplinks', + 'evm/connect/guides/display-tokens', + 'evm/connect/guides/best-practices/display', + 'evm/connect/guides/best-practices/run-devnet', + 'evm/connect/guides/best-practices/production-readiness', + ], + }, + // { + // type: 'category', + // label: 'Wagmi', + // collapsible: true, + // collapsed: true, + // link: { type: "doc", id: "evm/connect/guides/wagmi/index" }, + // items: [ + // 'evm/connect/guides/wagmi/manage-user-accounts', + // 'evm/connect/guides/wagmi/manage-networks', + // 'evm/connect/guides/wagmi/send-transactions', + // 'evm/connect/guides/wagmi/interact-with-contracts', + // ], + // }, ], }, { @@ -132,7 +133,7 @@ const sdkSidebar = { label: "Create a wallet AI agent", href: "/tutorials/create-wallet-ai-agent" }, - { + { type: "link", label: "Upgrade an EOA to a smart account", href: "/tutorials/upgrade-eoa-to-smart-account" @@ -152,7 +153,7 @@ const sdkSidebar = { label: "JSON-RPC API", collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/reference/json-rpc-api/index" }, + link: { type: "doc", id: "evm/connect/reference/json-rpc-api/index" }, items: [ "evm/connect/reference/json-rpc-api/wallet_sendCalls", "evm/connect/reference/json-rpc-api/eth_signTypedData_v4", diff --git a/sdk/evm/connect/_assets/personal_sign.png b/sdk/evm/connect/_assets/personal_sign.png index 38687051fa9..8e243237e64 100644 Binary files a/sdk/evm/connect/_assets/personal_sign.png and b/sdk/evm/connect/_assets/personal_sign.png differ diff --git a/sdk/evm/connect/_assets/signTypedData.png b/sdk/evm/connect/_assets/signTypedData.png index 09cb166fda2..8991aeedbe6 100644 Binary files a/sdk/evm/connect/_assets/signTypedData.png and b/sdk/evm/connect/_assets/signTypedData.png differ diff --git a/sdk/evm/connect/_assets/siwe-bad-domain-2.png b/sdk/evm/connect/_assets/siwe-bad-domain-2.png index ef4a46e5e88..2c287235765 100644 Binary files a/sdk/evm/connect/_assets/siwe-bad-domain-2.png and b/sdk/evm/connect/_assets/siwe-bad-domain-2.png differ diff --git a/sdk/evm/connect/_assets/siwe-bad-domain.png b/sdk/evm/connect/_assets/siwe-bad-domain.png index 445dd352611..c1b1566c53d 100644 Binary files a/sdk/evm/connect/_assets/siwe-bad-domain.png and b/sdk/evm/connect/_assets/siwe-bad-domain.png differ diff --git a/sdk/evm/connect/_assets/siwe.png b/sdk/evm/connect/_assets/siwe.png index 0113aaa5587..7b75a79838f 100644 Binary files a/sdk/evm/connect/_assets/siwe.png and b/sdk/evm/connect/_assets/siwe.png differ diff --git a/sdk/evm/connect/guides/javascript/batch-requests.md b/sdk/evm/connect/guides/batch-requests.md similarity index 77% rename from sdk/evm/connect/guides/javascript/batch-requests.md rename to sdk/evm/connect/guides/batch-requests.md index ed1b777ea8d..5c1c72db042 100644 --- a/sdk/evm/connect/guides/javascript/batch-requests.md +++ b/sdk/evm/connect/guides/batch-requests.md @@ -10,7 +10,7 @@ These requests can be contract calls or other JSON-RPC methods (for example, sig Despite being batched into one HTTP request, each call still requires individual user approval, and if any request is rejected, the entire batch fails. :::info -"Batching" can also refer to [Wagmi contract read batching](../wagmi/interact-with-contracts.md#batch-contract-reads) or +"Batching" can also refer to [Wagmi contract read batching](./wagmi/interact-with-contracts.md#batch-contract-reads) or [sending atomic batch transactions](send-transactions/batch-transactions.md) in MetaMask. ::: @@ -29,44 +29,44 @@ When using `metamask_batch`, keep in mind the following: - Even though the requests are batched, each individual request still requires user approval. - If any request in the batch is rejected, the entire batch will fail. -::: + ::: The following is an example of batching JSON-RPC requests using `metamask_batch`: ```js -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() async function handleBatchRequests() { // Example batch: one personal_sign call and one eth_sendTransaction call. const requests = [ - { method: "personal_sign", params: ["Hello from batch!", "0x1234..."] }, + { method: 'personal_sign', params: ['Hello from batch!', '0x1234...'] }, { - method: "eth_sendTransaction", + method: 'eth_sendTransaction', params: [ { - from: "0x1234...", - to: "0xABCD...", + from: '0x1234...', + to: '0xABCD...', // Additional transaction parameters. }, ], }, - ]; + ] try { const results = await provider.request({ - method: "metamask_batch", + method: 'metamask_batch', params: [requests], - }); - console.log("Batch Results:", results); + }) + console.log('Batch Results:', results) } catch (err) { - console.error("Batch request failed:", err); + console.error('Batch request failed:', err) } } -document.getElementById("batchBtn").addEventListener("click", handleBatchRequests); +document.getElementById('batchBtn').addEventListener('click', handleBatchRequests) ``` The following HTML displays a **Send Batch** button: @@ -76,7 +76,8 @@ The following HTML displays a **Send Batch** button: ``` :::tip Tips + - For a better user experience, it's important to use reliable RPC providers instead of public nodes. We recommend using services like [MetaMask Developer](https://developer.metamask.io/) to ensure better reliability and performance. - Ensure that requests in a batch do not depend on one another's chain context, as mid-batch state changes can affect outcomes. -::: + ::: diff --git a/sdk/evm/connect/guides/javascript/best-practices/display.md b/sdk/evm/connect/guides/best-practices/display.md similarity index 100% rename from sdk/evm/connect/guides/javascript/best-practices/display.md rename to sdk/evm/connect/guides/best-practices/display.md diff --git a/sdk/evm/connect/guides/javascript/best-practices/production-readiness.md b/sdk/evm/connect/guides/best-practices/production-readiness.md similarity index 100% rename from sdk/evm/connect/guides/javascript/best-practices/production-readiness.md rename to sdk/evm/connect/guides/best-practices/production-readiness.md diff --git a/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md b/sdk/evm/connect/guides/best-practices/run-devnet.md similarity index 100% rename from sdk/evm/connect/guides/javascript/best-practices/run-devnet.md rename to sdk/evm/connect/guides/best-practices/run-devnet.md diff --git a/sdk/evm/connect/guides/javascript/display-tokens.md b/sdk/evm/connect/guides/display-tokens.md similarity index 80% rename from sdk/evm/connect/guides/javascript/display-tokens.md rename to sdk/evm/connect/guides/display-tokens.md index 63471b28168..2ea9eafeb38 100644 --- a/sdk/evm/connect/guides/javascript/display-tokens.md +++ b/sdk/evm/connect/guides/display-tokens.md @@ -38,22 +38,22 @@ extension (not on mobile). To prompt users to add an ERC-20 token, you can add something like the following to your project script: ```javascript -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() -const tokenAddress = "0xd00981105e61274c8a5cd5a88fe7e037d935b513" -const tokenSymbol = "TUT" +const tokenAddress = '0xd00981105e61274c8a5cd5a88fe7e037d935b513' +const tokenSymbol = 'TUT' const tokenDecimals = 18 -const tokenImage = "http://placekitten.com/200/300" +const tokenImage = 'http://placekitten.com/200/300' try { // 'wasAdded' is a boolean. Like any RPC method, an error can be thrown. const wasAdded = await provider.request({ - method: "wallet_watchAsset", + method: 'wallet_watchAsset', params: { - type: "ERC20", + type: 'ERC20', options: { // The address of the token. address: tokenAddress, @@ -68,9 +68,9 @@ try { }) if (wasAdded) { - console.log("Thanks for your interest!") + console.log('Thanks for your interest!') } else { - console.log("Your loss!") + console.log('Your loss!') } } catch (error) { console.log(error) @@ -83,7 +83,7 @@ We recommend [detecting the user's network chain ID](manage-networks.md) and prompting them to switch chains, if necessary. ::: -For another example, [WatchToken](https://vittominacori.github.io/watch-token/create/) is a +For another example, [WatchToken](https://vittominacori.github.io/watch-token/create/) is a live web dapp that lets you enter your token details and share them using a web link. ## Display NFTs @@ -101,10 +101,10 @@ The add NFT interfaces look like the following:
- NFT confirmation + NFT confirmation
- Multiple NFTs confirmation + Multiple NFTs confirmation
@@ -114,30 +114,30 @@ To prompt users to add a single NFT, add something like the following to your pr `wallet_watchAsset` supports both ERC-721 and ERC-1155 NFT standards. ```javascript -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() try { // wasAdded is a boolean. Like any RPC method, an error can be thrown. const wasAdded = await provider.request({ - method: "wallet_watchAsset", + method: 'wallet_watchAsset', params: { - type: "ERC721", // Or "ERC1155". + type: 'ERC721', // Or "ERC1155". options: { // The address of the token. - address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // ERC-721 or ERC-1155 token ID. - tokenId: "1", + tokenId: '1', }, }, }) if (wasAdded) { - console.log("User successfully added the token!") + console.log('User successfully added the token!') } else { - console.log("User did not add the token.") + console.log('User did not add the token.') } } catch (error) { console.log(error) diff --git a/sdk/evm/connect/guides/interact-with-contracts.md b/sdk/evm/connect/guides/interact-with-contracts.md new file mode 100644 index 00000000000..f17765474a5 --- /dev/null +++ b/sdk/evm/connect/guides/interact-with-contracts.md @@ -0,0 +1,617 @@ +--- +description: Interact with contracts with MM Connect in your JavaScript dapp. +keywords: [SDK, JavaScript, read, write, smart, contract, contracts, dapp] +sidebar_label: Interact with contracts +toc_max_heading_level: 3 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Interact with smart contracts + +Interact with smart contracts in your JavaScript dapp. +With MM Connect, you can: + +- **Read data** from smart contracts. +- **Write data** to smart contracts. +- **Handle contract events**. +- **Manage transaction states**. +- **Handle contract errors**. + +## Smart Contract + +### Solidity Contract + +In this example, we'll be demonstrating how to use MetaMask Connect SDK with viem, web3.js, ethers.js or with ETH APIs to interact with Solidity smart contracts. + +The simple Hello World contract allows anyone to read and write a message to it. + +```tsx +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.7.0 <0.9.0; + +contract HelloWorld { + + string public message; + + constructor(string memory initMessage) { + message = initMessage; + } + + function update(string memory newMessage) public { + message = newMessage; + } +} +``` + +### Read From Contract + + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { ethers } from 'ethers' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const ethersProvider = new ethers.BrowserProvider(provider) +const signer = await ethersProvider.getSigner() + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'message', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }], + name: 'update', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] +const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334' +const contract = new ethers.Contract( + contractAddress, + JSON.parse(JSON.stringify(contractABI)), + signer +) + +// Read message from smart contract +const message = await contract.message() +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { Web3 } from 'web3' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const web3 = new Web3(provider) + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'message', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }], + name: 'update', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] +const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334' +const contract = new web3.eth.Contract(contractABI, contractAddress) + +// Read message from smart contract +const message = await contract.methods.message().call() +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createPublicClient, custom } from 'viem' +import { sepolia } from 'viem/chains' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const publicClient = createPublicClient({ + chain: sepolia, + transport: custom(provider), +}) + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'message', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }], + name: 'update', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] + +const publicClient = createPublicClient({ + chain: sepolia, + transport: custom(provider), +}) + +const contractAddress = '0x8AA6820B3F197384874fAdb355361758258cb981' // On Sepolia testnet, replace with your contract address + +// Read message from smart contract +const message = await publicClient.readContract({ + address: contractAddress, + abi: contractABI, + functionName: 'message', +}) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createEVMClient } from '@metamask/connect/evm' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function getMessage(contractAddress, userAddress) { + try { + const functionSignature = '0x06fdde03' + const result = await provider.request({ + method: 'eth_call', + params: [ + { + to: contractAddress, + data: functionSignature + encodedAddress, + }, + ], + }) + return result + } catch (error) { + console.error('Error reading message:', error) + throw error + } +} + +// Example usage +async function displayMessage() { + const status = document.getElementById('status') + try { + const message = await getMessage('0xContractAddress', '0xUserAddress') + status.textContent = `Message: ${message}` + } catch (error) { + status.textContent = `Error: ${error.message}` + } +} +``` + + + + +### Write to Contract + + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { ethers } from 'ethers' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const ethersProvider = new ethers.BrowserProvider(provider) +const signer = await ethersProvider.getSigner() + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'message', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }], + name: 'update', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] +const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334' +const contract = new ethers.Contract( + contractAddress, + JSON.parse(JSON.stringify(contractABI)), + signer +) + +// Send transaction to smart contract to update message +const tx = await contract.update('NEW_MESSAGE') + +// Wait for transaction to finish +const receipt = await tx.wait() +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { Web3 } from 'web3' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const web3 = new Web3(provider) + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, +] +const contractAddress = '0x04cA407965D60C2B39d892a1DFB1d1d9C30d0334' +const contract = new web3.eth.Contract(contractABI, contractAddress) + +// Send transaction to smart contract to update message +const tx = await contract.methods.update('NEW_MESSAGE').send({ from: signer.getAddress() }) + +// Wait for transaction to finish +const receipt = await tx.wait() +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createPublicClient, custom } from 'viem' +import { sepolia } from 'viem/chains' + +const contractABI = [ + { + inputs: [{ internalType: 'string', name: 'initMessage', type: 'string' }], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'message', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'string', name: 'newMessage', type: 'string' }], + name: 'update', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] +const publicClient = createPublicClient({ + chain: sepolia, + transport: custom(this.provider), +}) + +const walletClient = createWalletClient({ + chain: sepolia, + transport: custom(this.provider), +}) + +const contractAddress = '0x8AA6820B3F197384874fAdb355361758258cb981' // On Sepolia, replace with your contract address +address = await walletClient.getAddresses() + +// Submit transaction to the blockchain +const hash = await walletClient.writeContract({ + account: address[0], + address: contractAddress, + abi: JSON.parse(JSON.stringify(contractABI)), + functionName: 'update', + args: ['NEW_MESSAGE'], +}) + +// Send transaction to smart contract to update message +const receipt = await publicClient.waitForTransactionReceipt({ hash }) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createEVMClient } from '@metamask/connect/evm' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function updateMessage(contractAddress, userAddress, newMessage) { + try { + const tx = await provider.request({ + method: 'update', + params: [contractAddress, newMessage], + }) + return tx + } catch (error) { + console.error('Error updating message:', error) + throw error + } +} + +// Example usage +async function updateMessageExample() { + const status = document.getElementById('status') + try { + const tx = await updateMessage('0xContractAddress', '0xUserAddress', 'NEW_MESSAGE') + status.textContent = `Transaction: ${tx}` + } catch (error) { + status.textContent = `Error: ${error.message}` + } +} +``` + + + + + +## Read and write to contracts + +You can implement smart contract interactions directly in JavaScript. + +The following example reads contract data using the [`eth_call`](../../reference/json-rpc-api/index.md) RPC method: + +```javascript +import { createEVMClient } from '@metamask/connect/evm' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function getBalance(contractAddress, userAddress) { + try { + // Create function signature for balanceOf(address) + const functionSignature = '0x70a08231' + // Pad address to 32 bytes + const encodedAddress = userAddress.slice(2).padStart(64, '0') + + const result = await provider.request({ + method: 'eth_call', + params: [ + { + to: contractAddress, + data: functionSignature + encodedAddress, + }, + ], + }) + + return BigInt(result) + } catch (error) { + console.error('Error reading balance:', error) + throw error + } +} + +// Example usage +async function displayBalance() { + const status = document.getElementById('status') + try { + const balance = await getBalance('0xContractAddress', '0xUserAddress') + status.textContent = `Balance: ${balance.toString()}` + } catch (error) { + status.textContent = `Error: ${error.message}` + } +} +``` + +The following example writes to contracts using the [`eth_requestAccounts`](../../reference/json-rpc-api/index.md), +[`eth_sendTransaction`](../../reference/json-rpc-api/index.md), and +[`eth_getTransactionReceipt`](../../reference/json-rpc-api/index.md) +RPC methods: + +```javascript +async function mintNFT(contractAddress, tokenId) { + try { + // Get user's account + const accounts = await provider.request({ + method: 'eth_requestAccounts', + }) + + // Create function signature for mint(uint256) + const functionSignature = '0x6a627842' + // Pad tokenId to 32 bytes + const encodedTokenId = tokenId.toString(16).padStart(64, '0') + + // Send transaction + const txHash = await provider.request({ + method: 'eth_sendTransaction', + params: [ + { + from: accounts[0], + to: contractAddress, + data: functionSignature + encodedTokenId, + }, + ], + }) + + return txHash + } catch (error) { + if (error.code === 4001) { + throw new Error('Transaction rejected by user') + } + throw error + } +} + +// Track transaction status +async function watchTransaction(txHash) { + return new Promise((resolve, reject) => { + const checkTransaction = async () => { + try { + const tx = await provider.request({ + method: 'eth_getTransactionReceipt', + params: [txHash], + }) + + if (tx) { + if (tx.status === '0x1') { + resolve(tx) + } else { + reject(new Error('Transaction failed')) + } + } else { + setTimeout(checkTransaction, 2000) + } + } catch (error) { + reject(error) + } + } + + checkTransaction() + }) +} +``` + +The following is an example implementation of contract interaction: + +```html +
+ +
+
+ + +``` + +## Best practices + +Follow these best practices when interacting with smart contracts. + +#### Contract validation + +- Always **verify contract addresses**. +- Double check **ABI correctness**. +- **Validate input data** before sending. +- Use **typed data** when possible (for example, using [Viem](https://viem.sh/)). + +#### Error handling + +- Handle [common errors](#common-errors) like **user rejection** and **contract reverts**. +- Provide **clear error messages** to users. +- Implement proper **error recovery** flows. +- Consider **gas estimation failures**. + +#### User experience + +- Show **clear loading states**. +- Display **transaction progress**. +- Provide **confirmation feedback**. +- Enable proper **error recovery**. + +## Common errors + +| Error code | Description | Solution | +| ---------- | --------------------------- | -------------------------------------------------------------- | +| `4001` | User rejected transaction | Show a retry option and a clear error message. | +| `-32000` | Invalid input | Validate the input data before sending. | +| `-32603` | Contract execution reverted | Check the contract conditions and handle the error gracefully. | +| `-32002` | Request already pending | Prevent multiple concurrent transactions. | + +## Next steps + +See the following guides to add more functionality to your dapp: + +- [Manage user accounts](manage-user-accounts.md) +- [Manage networks](manage-networks.md) +- [Send transactions](send-transactions/index.md) diff --git a/sdk/evm/connect/guides/javascript/interact-with-contracts.md b/sdk/evm/connect/guides/javascript/interact-with-contracts.md deleted file mode 100644 index a376bb60c04..00000000000 --- a/sdk/evm/connect/guides/javascript/interact-with-contracts.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -description: Interact with contracts with MM Connect in your JavaScript dapp. -keywords: [SDK, JavaScript, read, write, smart, contract, contracts, dapp] -sidebar_label: Interact with contracts -toc_max_heading_level: 2 ---- - -# Interact with smart contracts - -Interact with smart contracts in your JavaScript dapp. -With MM Connect, you can: - -- **Read data** from smart contracts. -- **Write data** to smart contracts. -- **Handle contract events**. -- **Manage transaction states**. -- **Handle contract errors**. - -## Read and write to contracts - -You can implement smart contract interactions directly in JavaScript. - -The following example reads contract data using the [`eth_call`](../../reference/json-rpc-api/index.md) RPC method: - -```javascript -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); - -async function getBalance(contractAddress, userAddress) { - try { - // Create function signature for balanceOf(address) - const functionSignature = "0x70a08231"; - // Pad address to 32 bytes - const encodedAddress = userAddress.slice(2).padStart(64, "0"); - - const result = await provider.request({ - method: "eth_call", - params: [{ - to: contractAddress, - data: functionSignature + encodedAddress, - }], - }); - - return BigInt(result); - } catch (error) { - console.error("Error reading balance:", error); - throw error; - } -} - -// Example usage -async function displayBalance() { - const status = document.getElementById("status"); - try { - const balance = await getBalance( - "0xContractAddress", - "0xUserAddress" - ); - status.textContent = `Balance: ${balance.toString()}`; - } catch (error) { - status.textContent = `Error: ${error.message}`; - } -} -``` - -The following example writes to contracts using the [`eth_requestAccounts`](../../reference/json-rpc-api/index.md), -[`eth_sendTransaction`](../../reference/json-rpc-api/index.md), and -[`eth_getTransactionReceipt`](../../reference/json-rpc-api/index.md) -RPC methods: - -```javascript -async function mintNFT(contractAddress, tokenId) { - try { - // Get user's account - const accounts = await provider.request({ - method: "eth_requestAccounts" - }); - - // Create function signature for mint(uint256) - const functionSignature = "0x6a627842"; - // Pad tokenId to 32 bytes - const encodedTokenId = tokenId.toString(16).padStart(64, "0"); - - // Send transaction - const txHash = await provider.request({ - method: "eth_sendTransaction", - params: [{ - from: accounts[0], - to: contractAddress, - data: functionSignature + encodedTokenId, - }], - }); - - return txHash; - } catch (error) { - if (error.code === 4001) { - throw new Error("Transaction rejected by user"); - } - throw error; - } -} - -// Track transaction status -async function watchTransaction(txHash) { - return new Promise((resolve, reject) => { - const checkTransaction = async () => { - try { - const tx = await provider.request({ - method: "eth_getTransactionReceipt", - params: [txHash], - }); - - if (tx) { - if (tx.status === "0x1") { - resolve(tx); - } else { - reject(new Error("Transaction failed")); - } - } else { - setTimeout(checkTransaction, 2000); - } - } catch (error) { - reject(error); - } - }; - - checkTransaction(); - }); -} -``` - -The following is an example implementation of contract interaction: - -```html -
- -
-
- - -``` - -## Best practices - -Follow these best practices when interacting with smart contracts. - -#### Contract validation - -- Always **verify contract addresses**. -- Double check **ABI correctness**. -- **Validate input data** before sending. -- Use **typed data** when possible (for example, using [Viem](https://viem.sh/)). - -#### Error handling - -- Handle [common errors](#common-errors) like **user rejection** and **contract reverts**. -- Provide **clear error messages** to users. -- Implement proper **error recovery** flows. -- Consider **gas estimation failures**. - -#### User experience - -- Show **clear loading states**. -- Display **transaction progress**. -- Provide **confirmation feedback**. -- Enable proper **error recovery**. - -## Common errors - -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected transaction | Show a retry option and a clear error message. | -| `-32000` | Invalid input | Validate the input data before sending. | -| `-32603` | Contract execution reverted | Check the contract conditions and handle the error gracefully. | -| `-32002` | Request already pending | Prevent multiple concurrent transactions. | - -## Next steps - -See the following guides to add more functionality to your dapp: - -- [Manage user accounts](manage-user-accounts.md) -- [Manage networks](manage-networks.md) -- [Send transactions](send-transactions/index.md) diff --git a/sdk/evm/connect/guides/javascript/sign-data/index.md b/sdk/evm/connect/guides/javascript/sign-data/index.md deleted file mode 100644 index 502f69c8ec6..00000000000 --- a/sdk/evm/connect/guides/javascript/sign-data/index.md +++ /dev/null @@ -1,254 +0,0 @@ ---- -description: Use RPC methods to request cryptographic signatures from users. ---- - -# Sign data - -You can use the following RPC methods to request cryptographic signatures from users: - -- [`eth_signTypedData_v4`](#use-eth_signtypeddata_v4) - Use this method to request the most human-readable - signatures that are efficient to process onchain. - We recommend this for most use cases. -- [`personal_sign`](#use-personal_sign) - Use this method for the easiest way to request human-readable - signatures that don't need to be efficiently processed onchain. - -:::caution -`eth_sign` is deprecated. -See [MIP-3](https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-3.md) for more information about this deprecation. -::: - -:::note -MetaMask supports signing transactions using Trezor and Ledger hardware wallets. -These wallets only support signing data using `personal_sign`. -If you can't log in to a dapp when using a Ledger or Trezor, the dapp might be requesting you to -sign data using an unsupported method, in which case we recommend using your standard MetaMask account. -::: - -## Use `eth_signTypedData_v4` - -[`eth_signTypedData_v4`](../../../reference/json-rpc-api/index.md) -provides the most human-readable signatures that are efficient to process onchain. -It follows the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) specification to allow users to sign -typed structured data that can be verified onchain. -It renders the structured data in a useful way (for example, displaying known -account names in place of addresses). - -

- -![eth_signTypedData_v4](../../../_assets/signTypedData.png) - -

- -An `eth_signTypedData_v4` payload uses a standard format of encoding structs, but has a different -format for the top-level struct that is signed, which includes some metadata about the verifying -contract to provide replay protection of these signatures between different contract instances. - -We recommend using [`eth-sig-util`](https://github.com/MetaMask/eth-sig-util) to generate and -validate signatures. -You can use [`eip712-codegen`](https://github.com/danfinlay/eip712-codegen#readme) to generate most -of the Solidity required to verify these signatures onchain. -It currently doesn't generate the top-level struct verification code, so you must write that part manually. -See -[this example implementation](https://github.com/delegatable/delegatable-sol/blob/fb34bb259890417285f7185bc6500fb0ab8bf86f/contracts/Delegatable.sol#L80). - -:::caution -Since the top-level struct type's name and the `domain.name` are presented to the user prominently -in the confirmation, consider your contract name, the top-level struct name, and the struct keys to -be a user-facing security interface. -Ensure your contract is as readable as possible to the user. -::: - -### Example - -The following is an example of using `eth_signTypedData_v4` with MetaMask: - -```javascript title="index.js" -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); - -signTypedDataV4Button.addEventListener("click", async function (event) { - event.preventDefault() - - // eth_signTypedData_v4 parameters. All of these parameters affect the resulting signature. - const msgParams = JSON.stringify({ - domain: { - // This defines the network, in this case, Mainnet. - chainId: 1, - // Give a user-friendly name to the specific contract you're signing for. - name: "Ether Mail", - // Add a verifying contract to make sure you're establishing contracts with the proper entity. - verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", - // This identifies the latest version. - version: "1", - }, - - // This defines the message you're proposing the user to sign, is dapp-specific, and contains - // anything you want. There are no required fields. Be as explicit as possible when building out - // the message schema. - message: { - contents: "Hello, Bob!", - attachedMoneyInEth: 4.2, - from: { - name: "Cow", - wallets: [ - "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF", - ], - }, - to: [ - { - name: "Bob", - wallets: [ - "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", - "0xB0B0b0b0b0b0B000000000000000000000000000", - ], - }, - ], - }, - // This refers to the keys of the following types object. - primaryType: "Mail", - types: { - // This refers to the domain the contract is hosted on. - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], - // Not an EIP712Domain definition. - Group: [ - { name: "name", type: "string" }, - { name: "members", type: "Person[]" }, - ], - // Refer to primaryType. - Mail: [ - { name: "from", type: "Person" }, - { name: "to", type: "Person[]" }, - { name: "contents", type: "string" }, - ], - // Not an EIP712Domain definition. - Person: [ - { name: "name", type: "string" }, - { name: "wallets", type: "address[]" }, - ], - }, - }) - - var from = await web3.eth.getAccounts() - - var params = [from[0], msgParams] - var method = "eth_signTypedData_v4" - - provider.sendAsync( - { - method, - params, - from: from[0], - }, - function (err, result) { - if (err) return console.dir(err) - if (result.error) { - alert(result.error.message) - } - if (result.error) return console.error("ERROR", result) - console.log("TYPED SIGNED:" + JSON.stringify(result.result)) - - const recovered = sigUtil.recoverTypedSignature_v4({ - data: JSON.parse(msgParams), - sig: result.result, - }) - - if ( - ethUtil.toChecksumAddress(recovered) === - ethUtil.toChecksumAddress(from) - ) { - alert("Successfully recovered signer as " + from) - } else { - alert( - "Failed to verify signer when comparing " + result + " to " + from - ) - } - } - ) -}) -``` - -The following HTML displays a sign button: - -```html title="index.html" -

Sign typed data v4

- -``` - -See the [live example](https://metamask.github.io/test-dapp/#signTypedDataV4) and -[test dapp source code](https://github.com/MetaMask/test-dapp) for more information. - -## Use `personal_sign` - -[`personal_sign`](../../../reference/json-rpc-api/index.md) is the -easiest way to request human-readable signatures that don't need to be efficiently processed onchain. -It's often used for signature challenges that are authenticated on a web server, such as -[Sign-In with Ethereum](siwe.md). - -

- -![Personal sign](../../../_assets/personal_sign.png) - -

- -:::caution important - -- Don't use this method to display binary data, because the user wouldn't be able to understand what - they're agreeing to. -- If using this method for a signature challenge, think about what would prevent a phisher from - reusing the same challenge and impersonating your site. - Add text referring to your domain, or the current time, so the user can easily verify if this - challenge is legitimate. - ::: - -### Example - -The following is an example of using `personal_sign` with MetaMask: - -```javascript title="index.js" -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); - -personalSignButton.addEventListener("click", async function (event) { - event.preventDefault() - const exampleMessage = "Example `personal_sign` message." - try { - const from = accounts[0] - // For historical reasons, you must submit the message to sign in hex-encoded UTF-8. - // This uses a Node.js-style buffer shim in the browser. - const msg = `0x${Buffer.from(exampleMessage, "utf8").toString("hex")}` - const sign = await provider.request({ - method: "personal_sign", - params: [msg, from], - }) - personalSignResult.innerHTML = sign - personalSignVerify.disabled = false - } catch (err) { - console.error(err) - personalSign.innerHTML = `Error: ${err.message}` - } -}) -``` - -The following HTML displays a sign button: - -```html title="index.html" -

Personal sign

- -``` - -`personal_sign` prepends the message with `\x19Ethereum Signed Message:\n` before -hashing and signing it. - -See the [live example](https://metamask.github.io/test-dapp/#personalSign) and -[test dapp source code](https://github.com/MetaMask/test-dapp) for more information. diff --git a/sdk/evm/connect/guides/javascript/manage-networks.md b/sdk/evm/connect/guides/manage-networks.md similarity index 57% rename from sdk/evm/connect/guides/javascript/manage-networks.md rename to sdk/evm/connect/guides/manage-networks.md index 6fe5cc64efd..93eeb83bd94 100644 --- a/sdk/evm/connect/guides/javascript/manage-networks.md +++ b/sdk/evm/connect/guides/manage-networks.md @@ -16,7 +16,7 @@ With MM Connect, you can:

- Switch Networks + Switch Networks

@@ -29,30 +29,30 @@ The following example detects the current network using the [`chainChanged`](../../reference/provider-api.md#chainchanged) provider event: ```javascript -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() // Get current chain ID async function getCurrentChain() { try { - const chainId = await provider.request({ - method: "eth_chainId" - }); - console.log("Current chain ID:", chainId); - return chainId; + const chainId = await provider.request({ + method: 'eth_chainId', + }) + console.log('Current chain ID:', chainId) + return chainId } catch (err) { - console.error("Error getting chain:", err); + console.error('Error getting chain:', err) } } // Listen for network changes -provider.on("chainChanged", (chainId) => { - console.log("Network changed to:", chainId); +provider.on('chainChanged', chainId => { + console.log('Network changed to:', chainId) // We recommend reloading the page - window.location.reload(); -}); + window.location.reload() +}) ``` The following example switches networks using the @@ -64,50 +64,52 @@ RPC methods: // Network configurations const networks = { mainnet: { - chainId: "0x1", - name: "Ethereum Mainnet" + chainId: '0x1', + name: 'Ethereum Mainnet', }, optimism: { - chainId: "0xA", - name: "Optimism", - rpcUrls: ["https://mainnet.optimism.io"], + chainId: '0xA', + name: 'Optimism', + rpcUrls: ['https://mainnet.optimism.io'], nativeCurrency: { - name: "Ethereum", - symbol: "ETH", - decimals: 18 + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, }, - blockExplorerUrls: ["https://optimistic.etherscan.io"] - } -}; + blockExplorerUrls: ['https://optimistic.etherscan.io'], + }, +} async function switchNetwork(networkKey) { - const network = networks[networkKey]; - + const network = networks[networkKey] + try { // Try to switch to the network await provider.request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: network.chainId }] - }); + method: 'wallet_switchEthereumChain', + params: [{ chainId: network.chainId }], + }) } catch (err) { // If the error code is 4902, the network needs to be added if (err.code === 4902) { try { await provider.request({ - method: "wallet_addEthereumChain", - params: [{ - chainId: network.chainId, - chainName: network.name, - rpcUrls: network.rpcUrls, - nativeCurrency: network.nativeCurrency, - blockExplorerUrls: network.blockExplorerUrls - }] - }); + method: 'wallet_addEthereumChain', + params: [ + { + chainId: network.chainId, + chainName: network.name, + rpcUrls: network.rpcUrls, + nativeCurrency: network.nativeCurrency, + blockExplorerUrls: network.blockExplorerUrls, + }, + ], + }) } catch (addError) { - console.error("Error adding network:", addError); + console.error('Error adding network:', addError) } } else { - console.error("Error switching network:", err); + console.error('Error switching network:', err) } } } @@ -144,11 +146,11 @@ Follow these best practices when managing networks. The following table lists common network management errors and their codes: -| Error code | Description | Solution | -|------------|-------------|----------| -| `4902` | Network not added | Use [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) to add the network first. | -| `4001` | User rejected request | Show a message asking the user to approve the network switch. | -| `-32002` | Request already pending | Disable the switch network button while the request is pending. | +| Error code | Description | Solution | +| ---------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| `4902` | Network not added | Use [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) to add the network first. | +| `4001` | User rejected request | Show a message asking the user to approve the network switch. | +| `-32002` | Request already pending | Disable the switch network button while the request is pending. | ## Next steps diff --git a/sdk/evm/connect/guides/javascript/manage-user-accounts.md b/sdk/evm/connect/guides/manage-user-accounts.md similarity index 62% rename from sdk/evm/connect/guides/javascript/manage-user-accounts.md rename to sdk/evm/connect/guides/manage-user-accounts.md index c133af023cb..69f6748f8e2 100644 --- a/sdk/evm/connect/guides/javascript/manage-user-accounts.md +++ b/sdk/evm/connect/guides/manage-user-accounts.md @@ -19,7 +19,7 @@ With MM Connect, you can:

- Connect to MetaMask + Connect to MetaMask

@@ -31,36 +31,36 @@ and [`accountsChanged`](../../reference/provider-api.md#accountschanged) provide For example: ```javascript -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() // Connect wallet async function connectWallet() { try { // Disable button while request is pending - document.getElementById("connectBtn").disabled = true; - - const accounts = await provider.request({ - method: "eth_requestAccounts" - }); - - const account = accounts[0]; - console.log("Connected:", account); - + document.getElementById('connectBtn').disabled = true + + const accounts = await provider.request({ + method: 'eth_requestAccounts', + }) + + const account = accounts[0] + console.log('Connected:', account) + // Update UI - document.getElementById("status").textContent = `Connected: ${account}`; - document.getElementById("connectBtn").style.display = "none"; - document.getElementById("disconnectBtn").style.display = "block"; + document.getElementById('status').textContent = `Connected: ${account}` + document.getElementById('connectBtn').style.display = 'none' + document.getElementById('disconnectBtn').style.display = 'block' } catch (err) { if (err.code === 4001) { - console.log("User rejected connection"); + console.log('User rejected connection') } else { - console.error(err); + console.error(err) } } finally { - document.getElementById("connectBtn").disabled = false; + document.getElementById('connectBtn').disabled = false } } @@ -69,22 +69,22 @@ async function disconnectWallet() { try { await evmClient.terminate() } catch (err) { - console.error("Error with disconnecting:", err) + console.error('Error with disconnecting:', err) } } // Handle account changes -provider.on("accountsChanged", (accounts) => { +provider.on('accountsChanged', accounts => { if (accounts.length === 0) { // User disconnected - document.getElementById("status").textContent = "Not connected"; - document.getElementById("connectBtn").style.display = "block"; - document.getElementById("disconnectBtn").style.display = "none"; + document.getElementById('status').textContent = 'Not connected' + document.getElementById('connectBtn').style.display = 'block' + document.getElementById('disconnectBtn').style.display = 'none' } else { // Account changed - document.getElementById("status").textContent = `Connected: ${accounts[0]}`; + document.getElementById('status').textContent = `Connected: ${accounts[0]}` } -}); +}) ``` Display connect and disconnect buttons in HTML: @@ -93,9 +93,7 @@ Display connect and disconnect buttons in HTML:
Not connected
- +
``` @@ -105,22 +103,20 @@ You can use MM Connect's [`connectAndSign`](../../reference/methods.md#connectan For example: ```js -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); +const evmClient = createEVMClient() async function handleConnectAndSign() { try { - const signature = await evmClient.connectAndSign({ msg: "Hello in one go!" }) - console.log("Signature:", signature) + const signature = await evmClient.connectAndSign({ msg: 'Hello in one go!' }) + console.log('Signature:', signature) } catch (err) { - console.error("Error with connectAndSign:", err) + console.error('Error with connectAndSign:', err) } } -document - .getElementById("connectSignBtn") - .addEventListener("click", handleConnectAndSign) +document.getElementById('connectSignBtn').addEventListener('click', handleConnectAndSign) ``` The following HTML displays a **Connect & Sign** button: @@ -168,11 +164,11 @@ Learn how to [manage networks](manage-networks.md). The following table lists common authentication errors and their codes: -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected request | Show a message asking the user to approve the connection. | -| `-32002` | Request already pending | Disable the connect button while the request is pending. | -| `-32603` | Internal JSON-RPC error | Check if MetaMask is properly installed. | +| Error code | Description | Solution | +| ---------- | ----------------------- | --------------------------------------------------------- | +| `4001` | User rejected request | Show a message asking the user to approve the connection. | +| `-32002` | Request already pending | Disable the connect button while the request is pending. | +| `-32603` | Internal JSON-RPC error | Check if MetaMask is properly installed. | ## Next steps diff --git a/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md b/sdk/evm/connect/guides/send-transactions/batch-transactions.md similarity index 100% rename from sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md rename to sdk/evm/connect/guides/send-transactions/batch-transactions.md diff --git a/sdk/evm/connect/guides/javascript/send-transactions/index.md b/sdk/evm/connect/guides/send-transactions/index.md similarity index 100% rename from sdk/evm/connect/guides/javascript/send-transactions/index.md rename to sdk/evm/connect/guides/send-transactions/index.md diff --git a/sdk/evm/connect/guides/sign-data/index.md b/sdk/evm/connect/guides/sign-data/index.md new file mode 100644 index 00000000000..84d0180dc44 --- /dev/null +++ b/sdk/evm/connect/guides/sign-data/index.md @@ -0,0 +1,551 @@ +--- +description: Use RPC methods to request cryptographic signatures from users. +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Sign data + +You can use the following RPC methods to request cryptographic signatures from users: + +- [`eth_signTypedData_v4`](#use-eth_signtypeddata_v4) - Use this method to request the most human-readable + signatures that are efficient to process onchain. + We recommend this for most use cases. +- [`personal_sign`](#use-personal_sign) - Use this method for the easiest way to request human-readable + signatures that don't need to be efficiently processed onchain. + +:::caution +`eth_sign` is deprecated. +See [MIP-3](https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-3.md) for more information about this deprecation. +::: + +:::note +MetaMask supports signing transactions using Trezor and Ledger hardware wallets. +These wallets only support signing data using `personal_sign`. +If you can't log in to a dapp when using a Ledger or Trezor, the dapp might be requesting you to +sign data using an unsupported method, in which case we recommend using your standard MetaMask account. +::: + +## Use `eth_signTypedData_v4` + +[`eth_signTypedData_v4`](../../../reference/json-rpc-api/index.md) +provides the most human-readable signatures that are efficient to process onchain. +It follows the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) specification to allow users to sign +typed structured data that can be verified onchain. +It renders the structured data in a useful way (for example, displaying known +account names in place of addresses). + +

+ +![eth_signTypedData_v4](../../_assets/signTypedData.png) + +

+ +An `eth_signTypedData_v4` payload uses a standard format of encoding structs, but has a different +format for the top-level struct that is signed, which includes some metadata about the verifying +contract to provide replay protection of these signatures between different contract instances. + +We recommend using [`eth-sig-util`](https://github.com/MetaMask/eth-sig-util) to generate and +validate signatures. +You can use [`eip712-codegen`](https://github.com/danfinlay/eip712-codegen#readme) to generate most +of the Solidity required to verify these signatures onchain. +It currently doesn't generate the top-level struct verification code, so you must write that part manually. +See +[this example implementation](https://github.com/delegatable/delegatable-sol/blob/fb34bb259890417285f7185bc6500fb0ab8bf86f/contracts/Delegatable.sol#L80). + +:::caution +Since the top-level struct type's name and the `domain.name` are presented to the user prominently +in the confirmation, consider your contract name, the top-level struct name, and the struct keys to +be a user-facing security interface. +Ensure your contract is as readable as possible to the user. +::: + +### Example + +The following is an example of using `eth_signTypedData_v4` with MetaMask: + + + + +```javascript +import { createEVMClient } from '@metamask/connect/evm' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function signTypedDataV4() { + // Get current account + const accounts = await provider.request({ + method: 'eth_requestAccounts', + }) + + const message = { + domain: { + // This defines the network, in this case, Mainnet. + chainId: 1, + // Give a user-friendly name to the specific contract you're signing for. + name: 'Ether Mail', + // Add a verifying contract to make sure you're establishing contracts with the proper entity. + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + // This identifies the latest version. + version: '1', + }, + + // This defines the message you're proposing the user to sign, is dapp-specific, and contains + // anything you want. There are no required fields. Be as explicit as possible when building out + // the message schema. + message: { + contents: 'Hello, Bob!', + attachedMoneyInEth: 4.2, + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + }, + // This refers to the keys of the following types object. + primaryType: 'Mail', + types: { + // This refers to the domain the contract is hosted on. + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + // Not an EIP712Domain definition. + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + // Refer to primaryType. + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + // Not an EIP712Domain definition. + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, + } + + const signature = await provider.request({ + method: 'eth_signTypedData_v4', + params: [accounts[0], JSON.stringify(message)], + }) + + return signature +} +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createPublicClient, createWalletClient, custom } from 'viem' +import { mainnet } from 'viem/chains' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const walletClient = createWalletClient({ chain: mainnet, transport: custom(provider) }) + +const address = await walletClient.getAddresses() + +const signature = await walletClient.signTypedData({ + account: address[0], + domain: { + // This defines the network, in this case, Mainnet. + chainId: 1, + // Give a user-friendly name to the specific contract you're signing for. + name: 'Ether Mail', + // Add a verifying contract to make sure you're establishing contracts with the proper entity. + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + // This identifies the latest version. + version: '1', + }, + + // This defines the message you're proposing the user to sign, is dapp-specific, and contains + // anything you want. There are no required fields. Be as explicit as possible when building out + // the message schema. + message: { + contents: 'Hello, Bob!', + attachedMoneyInEth: 4.2, + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + }, + // This refers to the keys of the following types object. + primaryType: 'Mail', + types: { + // This refers to the domain the contract is hosted on. + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + // Not an EIP712Domain definition. + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + // Refer to primaryType. + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + // Not an EIP712Domain definition. + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, +}) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { ethers } from 'ethers' +import { BrowserProvider, parseUnits } from 'ethers' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const ethersProvider = new ethers.BrowserProvider(provider) +const signer = await ethersProvider.getSigner() + +const fromAddress = await signer.getAddress() + +const message = { + domain: { + // This defines the network, in this case, Mainnet. + chainId: 1, + // Give a user-friendly name to the specific contract you're signing for. + name: 'Ether Mail', + // Add a verifying contract to make sure you're establishing contracts with the proper entity. + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + // This identifies the latest version. + version: '1', + }, + + // This defines the message you're proposing the user to sign, is dapp-specific, and contains + // anything you want. There are no required fields. Be as explicit as possible when building out + // the message schema. + message: { + contents: 'Hello, Bob!', + attachedMoneyInEth: 4.2, + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + }, + // This refers to the keys of the following types object. + primaryType: 'Mail', + types: { + // This refers to the domain the contract is hosted on. + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + // Not an EIP712Domain definition. + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + // Refer to primaryType. + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + // Not an EIP712Domain definition. + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, +} + +const params = [fromAddress, JSON.stringify(message)] +const method = 'eth_signTypedData_v4' + +const signature = await signer.provider.send(method, params) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { Web3 } from 'web3' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const web3 = new Web3(provider) + +// Get user's Ethereum public address +const fromAddress = (await web3.eth.getAccounts())[0] + +const message = { + domain: { + // This defines the network, in this case, Mainnet. + chainId: 1, + // Give a user-friendly name to the specific contract you're signing for. + name: 'Ether Mail', + // Add a verifying contract to make sure you're establishing contracts with the proper entity. + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + // This identifies the latest version. + version: '1', + }, + + // This defines the message you're proposing the user to sign, is dapp-specific, and contains + // anything you want. There are no required fields. Be as explicit as possible when building out + // the message schema. + message: { + contents: 'Hello, Bob!', + attachedMoneyInEth: 4.2, + from: { + name: 'Cow', + wallets: [ + '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF', + ], + }, + to: [ + { + name: 'Bob', + wallets: [ + '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + '0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57', + '0xB0B0b0b0b0b0B000000000000000000000000000', + ], + }, + ], + }, + // This refers to the keys of the following types object. + primaryType: 'Mail', + types: { + // This refers to the domain the contract is hosted on. + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + // Not an EIP712Domain definition. + Group: [ + { name: 'name', type: 'string' }, + { name: 'members', type: 'Person[]' }, + ], + // Refer to primaryType. + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person[]' }, + { name: 'contents', type: 'string' }, + ], + // Not an EIP712Domain definition. + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallets', type: 'address[]' }, + ], + }, +} + +const params = [fromAddress, JSON.stringify(message)] +const method = 'eth_signTypedData_v4' + +const signature = await web3.eth.sendAsync(method, params) +``` + + + + +## Use `personal_sign` + +[`personal_sign`](../../../reference/json-rpc-api/index.md) is the +easiest way to request human-readable signatures that don't need to be efficiently processed onchain. +It's often used for signature challenges that are authenticated on a web server, such as +[Sign-In with Ethereum](siwe.md). + +

+ +![Personal sign](../../_assets/personal_sign.png) + +

+ +:::caution important + +- Don't use this method to display binary data, because the user wouldn't be able to understand what + they're agreeing to. +- If using this method for a signature challenge, think about what would prevent a phisher from + reusing the same challenge and impersonating your site. + Add text referring to your domain, or the current time, so the user can easily verify if this + challenge is legitimate. + ::: + +### Example + +The following is an example of using `personal_sign` with MetaMask: + + + + +```javascript +import { createEVMClient } from '@metamask/connect/evm' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function signTypedDataV4() { + // Get current account + const accounts = await provider.request({ + method: 'eth_requestAccounts', + }) + + const exampleMessage = 'Example `personal_sign` message.' + const message = `0x${Buffer.from(exampleMessage, 'utf8').toString('hex')}` + + const signature = await provider.request({ + method: 'personal_sign', + params: [message, accounts[0]], + }) + + return signature +} +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { createPublicClient, createWalletClient, custom } from 'viem' +import { mainnet } from 'viem/chains' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const walletClient = createWalletClient({ chain: mainnet, transport: custom(provider) }) + +const address = await walletClient.getAddresses() + +const originalMessage = 'Example `personal_sign` message.' + +const signature = await walletClient.signMessage({ + account: address[0], + message: originalMessage, +}) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { ethers } from 'ethers' +import { BrowserProvider, parseUnits } from 'ethers' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const ethersProvider = new ethers.BrowserProvider(provider) +const signer = await ethersProvider.getSigner() + +const exampleMessage = 'Example `personal_sign` message.' + +const signature = await signer.signMessage(exampleMessage) +``` + + + + +```tsx +import { MetaMaskSDK } from '@metamask/sdk' +import { Web3 } from 'web3' + +// Initialize SDK +const MMSDK = new MetaMaskSDK() +const provider = MMSDK.getProvider() + +const web3 = new Web3(provider) + +// Get user's Ethereum public address +const fromAddress = (await web3.eth.getAccounts())[0] + +const exampleMessage = 'Example `personal_sign` message.' + +const signature = await web3.eth.sign(fromAddress, exampleMessage) +``` + + + + +`personal_sign` prepends the message with `\x19Ethereum Signed Message:\n` before +hashing and signing it. diff --git a/sdk/evm/connect/guides/javascript/sign-data/siwe.md b/sdk/evm/connect/guides/sign-data/siwe.md similarity index 79% rename from sdk/evm/connect/guides/javascript/sign-data/siwe.md rename to sdk/evm/connect/guides/sign-data/siwe.md index 6edab221a46..b0ad197771d 100644 --- a/sdk/evm/connect/guides/javascript/sign-data/siwe.md +++ b/sdk/evm/connect/guides/sign-data/siwe.md @@ -13,7 +13,7 @@ MetaMask parses the message and gives the user a friendly interface prompting th your dapp:

- Sign-in with Ethereum request + Sign-in with Ethereum request

## Domain binding @@ -31,10 +31,10 @@ This is to not break existing dapps that may have use cases for mismatched domai
- Sign-in bad domain + Sign-in bad domain
- Sign-in bad domain pop-up + Sign-in bad domain pop-up
@@ -44,17 +44,17 @@ The following is an example of setting up SIWE with MetaMask using [`personal_sign`](../../../reference/json-rpc-api/index.md): ```javascript title="index.js" -import { createEVMClient } from "@metamask/connect/evm"; +import { createEVMClient } from '@metamask/connect/evm' -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); +const evmClient = createEVMClient() +const provider = evmClient.getProvider() -const siweSign = async (siweMessage) => { +const siweSign = async siweMessage => { try { const from = accounts[0] - const msg = `0x${Buffer.from(siweMessage, "utf8").toString("hex")}` + const msg = `0x${Buffer.from(siweMessage, 'utf8').toString('hex')}` const sign = await provider.request({ - method: "personal_sign", + method: 'personal_sign', params: [msg, from], }) siweResult.innerHTML = sign diff --git a/sdk/evm/connect/guides/javascript/use-deeplinks.md b/sdk/evm/connect/guides/use-deeplinks.md similarity index 100% rename from sdk/evm/connect/guides/javascript/use-deeplinks.md rename to sdk/evm/connect/guides/use-deeplinks.md diff --git a/sdk/evm/connect/guides/wagmi/interact-with-contracts.md b/sdk/evm/connect/guides/wagmi/interact-with-contracts.md index bb165bbc6f2..3b7f9a66fb8 100644 --- a/sdk/evm/connect/guides/wagmi/interact-with-contracts.md +++ b/sdk/evm/connect/guides/wagmi/interact-with-contracts.md @@ -4,7 +4,7 @@ keywords: [SDK, Wagmi, JavaScript, batch, read, write, smart, contract, contract sidebar_label: Interact with contracts --- -# Interact with smart contracts +# Interact with smart contracts Interact with smart contracts in your Wagmi dapp. With MM Connect, you can: @@ -22,31 +22,31 @@ Wagmi provides dedicated hooks for smart contract interactions. The following example reads contract data using the [`useReadContract`](https://wagmi.sh/react/api/hooks/useReadContract) hook: ```tsx -import { useReadContract } from "wagmi" +import { useReadContract } from 'wagmi' function TokenBalance() { - const { + const { data: balance, isError, - isLoading + isLoading, } = useReadContract({ - address: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', abi: [ { - name: "balanceOf", - type: "function", - stateMutability: "view", - inputs: [{ name: "owner", type: "address" }], - outputs: [{ name: "balance", type: "uint256" }], + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ name: 'owner', type: 'address' }], + outputs: [{ name: 'balance', type: 'uint256' }], }, ], - functionName: "balanceOf", - args: ["0x03A71968491d55603FFe1b11A9e23eF013f75bCF"], + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], }) if (isLoading) return
Loading balance...
if (isError) return
Error fetching balance
- + return
Balance: {balance?.toString()}
} ``` @@ -113,7 +113,7 @@ In this example, four contract read calls are batched together. The results are returned as an array in the same order as the calls, allowing you to process each result accordingly. :::info -"Batching" can also refer to [batching JSON-RPC requests](../javascript/batch-requests.md) using MM Connect's `metamask_batch` method, or [sending atomic batch transactions](../javascript/send-transactions/batch-transactions.md) in MetaMask. +"Batching" can also refer to [batching JSON-RPC requests](../batch-requests.md) using MM Connect's `metamask_batch` method, or [sending atomic batch transactions](../send-transactions/batch-transactions.md) in MetaMask. ::: ## Write to contracts @@ -121,47 +121,36 @@ The results are returned as an array in the same order as the calls, allowing yo The following example writes to contracts using the [`useWriteContract`](https://wagmi.sh/react/api/hooks/useWriteContract) hook: ```tsx -import { useWriteContract, useWaitForTransactionReceipt } from "wagmi" +import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi' function MintNFT() { - const { - writeContract, - data: hash, - error, - isPending - } = useWriteContract() + const { writeContract, data: hash, error, isPending } = useWriteContract() - const { - isLoading: isConfirming, - isSuccess: isConfirmed - } = useWaitForTransactionReceipt({ - hash + const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ + hash, }) - + function mint() { writeContract({ - address: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', abi: [ { - name: "mint", - type: "function", - stateMutability: "nonpayable", - inputs: [{ name: "tokenId", type: "uint256" }], + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ name: 'tokenId', type: 'uint256' }], outputs: [], }, ], - functionName: "mint", + functionName: 'mint', args: [123n], // Token ID }) } - + return (
- {hash && ( @@ -205,17 +194,17 @@ Follow these best practices when interacting with smart contracts. ## Common errors -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected transaction | Show a retry option and a clear error message. | -| `-32000` | Invalid input | Validate the input data before sending. | -| `-32603` | Contract execution reverted | Check the contract conditions and handle the error gracefully. | -| `-32002` | Request already pending | Prevent multiple concurrent transactions. | +| Error code | Description | Solution | +| ---------- | --------------------------- | -------------------------------------------------------------- | +| `4001` | User rejected transaction | Show a retry option and a clear error message. | +| `-32000` | Invalid input | Validate the input data before sending. | +| `-32603` | Contract execution reverted | Check the contract conditions and handle the error gracefully. | +| `-32002` | Request already pending | Prevent multiple concurrent transactions. | ## Next steps See the following guides to add more functionality to your dapp: -- [Manage user accounts](manage-user-accounts.md) -- [Manage networks](manage-networks.md) -- [Send transactions](send-transactions.md) +- [Manage user accounts](../../guides/manage-user-accounts.md) +- [Manage networks](../../guides/manage-networks.md) +- [Send transactions](../../guides/send-transactions/index.md) diff --git a/sdk/evm/connect/guides/wagmi/manage-networks.md b/sdk/evm/connect/guides/wagmi/manage-networks.md index bcb080b0641..3185528d0b5 100644 --- a/sdk/evm/connect/guides/wagmi/manage-networks.md +++ b/sdk/evm/connect/guides/wagmi/manage-networks.md @@ -4,6 +4,8 @@ keywords: [SDK, Wagmi, JavaScript, detect, switch, add, network, networks, dapp] toc_max_heading_level: 2 --- +import networkGif from '../../\_assets/network.gif'; + # Manage networks Manage networks in your Wagmi dapp. @@ -16,7 +18,7 @@ With MM Connect, you can:

- Switch Networks + Switch Networks

@@ -28,14 +30,14 @@ The following are examples of using these hooks. Detect the current network: ```tsx -import { useChainId, useChains } from "wagmi" +import { useChainId, useChains } from 'wagmi' function NetworkStatus() { const chainId = useChainId() const chains = useChains() - + const currentChain = chains.find(c => c.id === chainId) - + if (!currentChain) { return
Not connected to any network
} @@ -44,7 +46,7 @@ function NetworkStatus() {
Connected to {currentChain.name}
Chain ID: {chainId}
-
Supported chains: {chains.map(c => c.name).join(", ")}
+
Supported chains: {chains.map(c => c.name).join(', ')}
) } @@ -53,18 +55,15 @@ function NetworkStatus() { Switch networks: ```tsx -import { useSwitchChain } from "wagmi" +import { useSwitchChain } from 'wagmi' function NetworkSwitcher() { const { chains, switchChain } = useSwitchChain() - + return (
- {chains.map((chain) => ( - ))} @@ -76,16 +75,16 @@ function NetworkSwitcher() { Handle network changes: ```tsx -import { useChainId } from "wagmi" -import { useEffect } from "react" +import { useChainId } from 'wagmi' +import { useEffect } from 'react' function NetworkWatcher() { const chainId = useChainId() - + useEffect(() => { - console.log("Chain ID changed:", chainId) + console.log('Chain ID changed:', chainId) }, [chainId]) - + return null } ``` @@ -111,11 +110,11 @@ Follow these best practices when managing networks. The following table lists common network management errors and their codes: -| Error code | Description | Solution | -|------------|-------------|----------| -| `4902` | Network not added | Use [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) to add the network first. | -| `4001` | User rejected request | Show a message asking the user to approve the network switch. | -| `-32002` | Request already pending | Disable the switch network button while the request is pending. | +| Error code | Description | Solution | +| ---------- | ----------------------- | ------------------------------------------------------------------------------------------------ | +| `4902` | Network not added | Use [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) to add the network first. | +| `4001` | User rejected request | Show a message asking the user to approve the network switch. | +| `-32002` | Request already pending | Disable the switch network button while the request is pending. | ## Next steps diff --git a/sdk/evm/connect/guides/wagmi/manage-user-accounts.md b/sdk/evm/connect/guides/wagmi/manage-user-accounts.md index da7d64a6f24..563d65f81b0 100644 --- a/sdk/evm/connect/guides/wagmi/manage-user-accounts.md +++ b/sdk/evm/connect/guides/wagmi/manage-user-accounts.md @@ -4,6 +4,8 @@ keywords: [SDK, Wagmi, JavaScript, authenticate, connect, sign, accounts, wallet toc_max_heading_level: 3 --- +import connectGif from '../../\_assets/connect.gif'; + # Manage user accounts Connect and manage user wallet sessions in your Wagmi dapp. @@ -18,7 +20,7 @@ With MM Connect, you can:

- Connect to MetaMask + Connect to MetaMask

@@ -28,7 +30,7 @@ Wagmi provides a simple, hook-based approach for handling wallet connections. For example: ```tsx title="Handle wallet connections" -import { useAccount, useConnect, useDisconnect } from "wagmi" +import { useAccount, useConnect, useDisconnect } from 'wagmi' function ConnectWallet() { const { address, isConnected } = useAccount() @@ -46,13 +48,9 @@ function ConnectWallet() { return (
- {connectors.map((connector) => ( - ))}
@@ -63,22 +61,22 @@ function ConnectWallet() { Wagmi provides a dedicated hook for handling account lifecycle events: ```tsx -import { useAccountEffect } from "wagmi" +import { useAccountEffect } from 'wagmi' function WatchAccount() { useAccountEffect({ onConnect(data) { - console.log("Connected!", { + console.log('Connected!', { address: data.address, chainId: data.chainId, - isReconnected: data.isReconnected + isReconnected: data.isReconnected, }) }, onDisconnect() { - console.log("Disconnected!") - } + console.log('Disconnected!') + }, }) - + return
Watching for account changes...
} ``` @@ -117,11 +115,11 @@ Learn how to [manage networks](manage-networks.md). The following table lists common authentication errors and their codes: -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected request | Show a message asking the user to approve the connection. | -| `-32002` | Request already pending | Disable the connect button while the request is pending. | -| `-32603` | Internal JSON-RPC error | Check if MetaMask is properly installed. | +| Error code | Description | Solution | +| ---------- | ----------------------- | --------------------------------------------------------- | +| `4001` | User rejected request | Show a message asking the user to approve the connection. | +| `-32002` | Request already pending | Disable the connect button while the request is pending. | +| `-32603` | Internal JSON-RPC error | Check if MetaMask is properly installed. | ## Next steps diff --git a/sdk/evm/connect/guides/connectkit.md b/sdk/evm/connect/quickstart/connectkit.md similarity index 100% rename from sdk/evm/connect/guides/connectkit.md rename to sdk/evm/connect/quickstart/connectkit.md diff --git a/sdk/evm/connect/guides/dynamic.md b/sdk/evm/connect/quickstart/dynamic.md similarity index 100% rename from sdk/evm/connect/guides/dynamic.md rename to sdk/evm/connect/quickstart/dynamic.md diff --git a/sdk/evm/connect/guides/javascript/index.md b/sdk/evm/connect/quickstart/javascript.md similarity index 62% rename from sdk/evm/connect/guides/javascript/index.md rename to sdk/evm/connect/quickstart/javascript.md index 38d753f6cde..a65df60b0f9 100644 --- a/sdk/evm/connect/guides/javascript/index.md +++ b/sdk/evm/connect/quickstart/javascript.md @@ -14,7 +14,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall

- JavaScript SDK Quickstart + JavaScript SDK Quickstart

@@ -43,15 +43,15 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall Degit vs. Git clone
- `degit` is a tool that enables cloning only the directory structure from a GitHub repository, without retrieving the entire repository. - - Alternatively, you can use `git clone`, which will download the entire repository. - To do so, clone the MM Connect examples repository and navigate into the `quickstarts/javascript` directory: + `degit` is a tool that enables cloning only the directory structure from a GitHub repository, without retrieving the entire repository. - ```bash - git clone https://github.com/MetaMask/metamask-sdk-examples - cd metamask-sdk-examples/quickstarts/javascript - ``` + Alternatively, you can use `git clone`, which will download the entire repository. + To do so, clone the MM Connect examples repository and navigate into the `quickstarts/javascript` directory: + + ```bash + git clone https://github.com/MetaMask/metamask-sdk-examples + cd metamask-sdk-examples/quickstarts/javascript + ```
@@ -100,13 +100,13 @@ The following are examples of using MM Connect in various JavaScript environment ```javascript -import { createEVMClient } from "@metamask/connect/evm" +import { createEVMClient } from '@metamask/connect/evm' const evmClient = createEVMClient({ dappMetadata: { - name: "Example JavaScript dapp", + name: 'Example JavaScript dapp', url: window.location.href, - iconUrl: "https://mydapp.com/icon.png" // Optional + iconUrl: 'https://mydapp.com/icon.png', // Optional }, infuraAPIKey: process.env.INFURA_API_KEY, }) @@ -121,9 +121,9 @@ const evmClient = createEVMClient({