diff --git a/docusaurus.config.js b/docusaurus.config.js index d9780a87934..4253b908c43 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -125,7 +125,7 @@ const config = { markdown: { mermaid: true, hooks: { - onBrokenMarkdownLinks: 'throw', + onBrokenMarkdownLinks: 'warn', }, }, themes: ['@docusaurus/theme-mermaid'], diff --git a/gator_versioned_docs/version-0.1.0/guides/advanced-permissions/execute-on-metamask-users-behalf.md b/gator_versioned_docs/version-0.1.0/guides/advanced-permissions/execute-on-metamask-users-behalf.md index d7f62ac5639..3566315584b 100644 --- a/gator_versioned_docs/version-0.1.0/guides/advanced-permissions/execute-on-metamask-users-behalf.md +++ b/gator_versioned_docs/version-0.1.0/guides/advanced-permissions/execute-on-metamask-users-behalf.md @@ -99,7 +99,7 @@ const sessionAccount = privateKeyToAccount("0x..."); Currently, Advanced Permissions do not support automatically upgrading a MetaMask user's account to a [MetaMask smart account](../../concepts/smart-accounts.md). Therefore, you must ensure that the user is upgraded to a smart account before requesting Advanced Permissions. -If the user has not yet been upgraded, you can handle the upgrade [programmatically](/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions) or ask the +If the user has not yet been upgraded, you can handle the upgrade [programmatically](/sdk/evm/connect/guides/send-transactions/batch-transactions) or ask the user to [switch to a smart account manually](https://support.metamask.io/configure/accounts/switch-to-or-revert-from-a-smart-account/#how-to-switch-to-a-metamask-smart-account). :::info Why is a Smart Account upgrade is required? diff --git a/gator_versioned_docs/version-0.13.0/guides/erc7715/execute-on-metamask-users-behalf.md b/gator_versioned_docs/version-0.13.0/guides/erc7715/execute-on-metamask-users-behalf.md index 0968976d6cc..9da4f083f4d 100644 --- a/gator_versioned_docs/version-0.13.0/guides/erc7715/execute-on-metamask-users-behalf.md +++ b/gator_versioned_docs/version-0.13.0/guides/erc7715/execute-on-metamask-users-behalf.md @@ -100,7 +100,7 @@ const sessionAccount = privateKeyToAccount("0x..."); Currently, ERC-7715 does not support automatically upgrading a MetaMask user's account to a [MetaMask smart account](../../concepts/smart-accounts.md). Therefore, you must ensure that the user is upgraded to a smart account before requesting ERC-7715 permissions. -If the user has not yet been upgraded, you can handle the upgrade [programmatically](/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions) or ask the +If the user has not yet been upgraded, you can handle the upgrade [programmatically](/sdk/evm/connect/guides/send-transactions/batch-transactions) or ask the user to [switch to a smart account manually](https://support.metamask.io/configure/accounts/switch-to-or-revert-from-a-smart-account/#how-to-switch-to-a-metamask-smart-account). :::info Why is a Smart Account upgrade is required? diff --git a/sdk-sidebar.js b/sdk-sidebar.js index 20e3b20f1c7..31d4b1781a0 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -49,76 +49,72 @@ 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: 'Send transactions', + collapsible: true, + collapsed: true, + link: { type: "doc", id: "evm/connect/guides/send-transactions/index" }, + items: [ + 'evm/connect/guides/send-transactions/batch-transactions', + ], + }, { type: 'category', - label: 'JavaScript', + label: 'Sign data', collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/guides/javascript/index" }, + link: { type: "doc", id: "evm/connect/guides/sign-data/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/sign-data/siwe', ], }, + 'evm/connect/guides/interact-with-contracts', { type: 'category', - label: 'Wagmi', + label: 'MetaMask exclusive', 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', + 'evm/connect/guides/metamask-exclusive/batch-requests', + 'evm/connect/guides/metamask-exclusive/use-deeplinks', + 'evm/connect/guides/metamask-exclusive/display-tokens', + ], + }, + { + type: 'category', + label: 'Best practices', + collapsible: true, + collapsed: true, + items: [ + 'evm/connect/guides/best-practices/display', + 'evm/connect/guides/best-practices/run-devnet', + 'evm/connect/guides/best-practices/production-readiness', ], }, - 'evm/connect/guides/rainbowkit', - 'evm/connect/guides/connectkit', - 'evm/connect/guides/react-native', - 'evm/connect/guides/dynamic', - 'evm/connect/guides/web3auth', ], }, { @@ -132,7 +128,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 +148,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/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 90% rename from sdk/evm/connect/guides/javascript/best-practices/production-readiness.md rename to sdk/evm/connect/guides/best-practices/production-readiness.md index 7b414191acb..424b2ceafee 100644 --- a/sdk/evm/connect/guides/javascript/best-practices/production-readiness.md +++ b/sdk/evm/connect/guides/best-practices/production-readiness.md @@ -44,6 +44,6 @@ For example: - **Clear feedback** - Display user friendly messages when wallet connection or transaction errors occur (for example, network switch failures or user rejections). -- **Event management** - If you're using Vanilla JavaScript, handle MetaMask events such as [`chainChanged`](../../../reference/provider-api.md#chainchanged) - and [`accountsChanged`](../../../reference/provider-api.md#accountschanged) to promptly update the UI and internal state. +- **Event management** - If you're using Vanilla JavaScript, handle MetaMask events such as [`chainChanged`](../../reference/provider-api.md#chainchanged) + and [`accountsChanged`](../../reference/provider-api.md#accountschanged) to promptly update the UI and internal state. If you're using Wagmi, you generally don't need to handle MetaMask events, because the hooks will handle the events for you. 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 97% rename from sdk/evm/connect/guides/javascript/best-practices/run-devnet.md rename to sdk/evm/connect/guides/best-practices/run-devnet.md index 89a054ce0f2..fe73b2c4d0f 100644 --- a/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md +++ b/sdk/evm/connect/guides/best-practices/run-devnet.md @@ -83,7 +83,7 @@ Follow these steps to connect MetaMask to Hardhat Network. :::tip Alternatively, you can add Hardhat Network to MetaMask using - [`wallet_addEthereumChain`](../../../reference/json-rpc-api/index.md). + [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md). ::: ## Reset your local nonce calculation 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..d70a80225ec --- /dev/null +++ b/sdk/evm/connect/guides/interact-with-contracts.md @@ -0,0 +1,587 @@ +--- +description: Interact with contracts with MM Connect in your JavaScript dapp. +keywords: [SDK, JavaScript, wagmi, 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**. + +The following examples demonstrate how to use MM Connect with viem, web3.js, ethers.js, Ethereum APIs, or Wagmi to interact with Solidity smart contracts. + +This 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 contracts + + + + +```tsx +import { createEVMClient } from '@metamask/connect/evm' +import { createPublicClient, custom } from 'viem' +import { sepolia } from 'viem/chains' + +const evmClient = createEVMClient() +const provider = evmClient.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 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 { createEVMClient } from '@metamask/connect/evm' +import { Web3 } from 'web3' + +const evmClient = createEVMClient() +const provider = evmClient.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 { createEVMClient } from '@metamask/connect/evm' +import { ethers } from 'ethers' + +const evmClient = createEVMClient() +const provider = evmClient.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 { createEVMClient } from '@metamask/connect/evm' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +async function getMessage(contractAddress, userAddress) { + try { + // Create function signature for balanceOf(address) + const functionSignature = '0x06fdde03' + // 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 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}` + } +} +``` + + + + +```tsx +import { useReadContract } from 'wagmi' + +function TokenBalance() { + const { + data: balance, + isError, + isLoading, + } = useReadContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { + name: 'balanceOf', + type: 'function', + stateMutability: 'view', + inputs: [{ name: 'owner', type: 'address' }], + outputs: [{ name: 'balance', type: 'uint256' }], + }, + ], + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + if (isLoading) return
Loading balance...
+ if (isError) return
Error fetching balance
+ + return
Balance: {balance?.toString()}
+} +``` + +
+
+ +### Batch contract reads + +With Wagmi, you can perform multiple contract read operations using the [`useReadContracts`](https://wagmi.sh/react/api/hooks/useReadContracts) hook. +This hook batches contract calls internally, returning the results as an array. +For example: + +```js +import { useReadContracts } from "wagmi"; + +// Example contract definitions with their address and ABI +const contractA = { + address: "0xContractAddress1", + abi: contractABI1, +} as const; + +const contractB = { + address: "0xContractAddress2", + abi: contractABI2, +} as const; + +function MyBatchReadComponent() { + const { data, isError, isLoading } = useReadContracts({ + contracts: [ + { + ...contractA, + functionName: "getValueA", + }, + { + ...contractA, + functionName: "getValueB", + }, + { + ...contractB, + functionName: "getValueX", + args: [42], + }, + { + ...contractB, + functionName: "getValueY", + args: [42], + }, + ], + }); + + if (isLoading) return
Loading...
; + if (isError) return
Error fetching data.
; + + return ( +
+

getValueA: {data?.[0]?.toString()}

+

getValueB: {data?.[1]?.toString()}

+

getValueX: {data?.[2]?.toString()}

+

getValueY: {data?.[3]?.toString()}

+
+ ); +} +``` + +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](metamask-exclusive/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 + + + + +```tsx +import { createEVMClient } from '@metamask/connect/evm' +import { createPublicClient, custom } from 'viem' +import { sepolia } from 'viem/chains' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +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 walletClient = createWalletClient({ + chain: sepolia, + transport: custom(provider), +}) + +const contractAddress = '0x8AA6820B3F197384874fAdb355361758258cb981' // On Sepolia, replace with your contract address +const 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 { createEVMClient } from '@metamask/connect/evm' +import { Web3 } from 'web3' + +const evmClient = createEVMClient() +const provider = evmClient.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 { createEVMClient } from '@metamask/connect/evm' +import { ethers } from 'ethers' + +const evmClient = createEVMClient() +const provider = evmClient.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 { createEVMClient } from '@metamask/connect/evm' + +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}` + } +} +``` + + + + +```tsx +import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi' + +function MintNFT() { + const { writeContract, data: hash, error, isPending } = useWriteContract() + + const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ + hash, + }) + + function mint() { + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { + name: 'mint', + type: 'function', + stateMutability: 'nonpayable', + inputs: [{ name: 'tokenId', type: 'uint256' }], + outputs: [], + }, + ], + functionName: 'mint', + args: [123n], // Token ID + }) + } + + return ( +
+ + + {hash && ( +
+ Transaction Hash: {hash} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
NFT Minted Successfully!
} +
+ )} + + {error &&
Error: {error.message}
} +
+ ) +} +``` + +
+
+ +## 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/manage-networks.md b/sdk/evm/connect/guides/javascript/manage-networks.md deleted file mode 100644 index 6fe5cc64efd..00000000000 --- a/sdk/evm/connect/guides/javascript/manage-networks.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -description: Manage networks with MM Connect in your JavaScript dapp. -keywords: [SDK, JavaScript, detect, switch, add, network, networks, dapp] -toc_max_heading_level: 2 ---- - -# Manage networks - -Manage networks in your JavaScript dapp. -With MM Connect, you can: - -- **Detect the current network** and monitor network changes. -- **Switch between networks** programmatically. -- **Add new networks** to MetaMask. -- **Handle common network-related errors**. - -

- - Switch Networks - -

- -## Detect and switch networks - -You can implement network management directly in JavaScript. - -The following example detects the current network using the -[`eth_chainId`](../../reference/json-rpc-api/index.md) RPC method and -[`chainChanged`](../../reference/provider-api.md#chainchanged) provider event: - -```javascript -import { createEVMClient } from "@metamask/connect/evm"; - -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; - } catch (err) { - console.error("Error getting chain:", err); - } -} - -// Listen for network changes -provider.on("chainChanged", (chainId) => { - console.log("Network changed to:", chainId); - // We recommend reloading the page - window.location.reload(); -}); -``` - -The following example switches networks using the -[`wallet_switchEthereumChain`](../../reference/json-rpc-api/index.md) -and [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) -RPC methods: - -```javascript -// Network configurations -const networks = { - mainnet: { - chainId: "0x1", - name: "Ethereum Mainnet" - }, - optimism: { - chainId: "0xA", - name: "Optimism", - rpcUrls: ["https://mainnet.optimism.io"], - nativeCurrency: { - name: "Ethereum", - symbol: "ETH", - decimals: 18 - }, - blockExplorerUrls: ["https://optimistic.etherscan.io"] - } -}; - -async function switchNetwork(networkKey) { - const network = networks[networkKey]; - - try { - // Try to switch to the network - await provider.request({ - 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 - }] - }); - } catch (addError) { - console.error("Error adding network:", addError); - } - } else { - console.error("Error switching network:", err); - } - } -} -``` - -Display the current network and a switch network button in HTML: - -```html -
-
Current Network: Loading...
- - -
-``` - -## Best practices - -Follow these best practices when managing networks. - -#### Error handling - -- Implement error handling for network switching operations. -- Provide **clear feedback messages** to users when network operations fail. -- Handle cases where networks need to be **added before switching**. - -#### User experience - -- Display **loading states** during network switches. -- Show **clear network status information** at all times. -- Consider **warning users** before initiating network switches. -- Use an **RPC provider** that supports your target networks. - -## Common errors - -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. | - -## Next steps - -See the following guides to add more functionality to your dapp: - -- [Manage user accounts](manage-user-accounts.md) -- [Send transactions](send-transactions/index.md) -- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/javascript/manage-user-accounts.md b/sdk/evm/connect/guides/javascript/manage-user-accounts.md deleted file mode 100644 index c133af023cb..00000000000 --- a/sdk/evm/connect/guides/javascript/manage-user-accounts.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -description: Authenticate users with MM Connect in your JavaScript dapp. -keywords: [SDK, JavaScript, authenticate, connect, sign, accounts, wallet, dapp] -toc_max_heading_level: 3 ---- - -# Manage user accounts - -Connect and manage user wallet sessions in your JavaScript dapp. -With MM Connect, you can: - -- **Connect users' wallets** to your dapp. -- **Access user accounts** (addresses). -- [**Connect and sign**](#connect-and-sign) in a single user interaction. -- **Handle connection states** (connected/disconnected). -- **Listen for account changes** in real time. -- **Manage wallet sessions** (connect/disconnect). -- **Support multiple wallet types** (extension, mobile app). - -

- - Connect to MetaMask - -

- -## Connect wallet - -You can implement user authentication directly in JavaScript, using the -[`eth_requestAccounts`](../../reference/json-rpc-api/index.md) RPC method -and [`accountsChanged`](../../reference/provider-api.md#accountschanged) provider event. -For example: - -```javascript -import { createEVMClient } from "@metamask/connect/evm"; - -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); - - // Update UI - 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"); - } else { - console.error(err); - } - } finally { - document.getElementById("connectBtn").disabled = false; - } -} - -// Disconnect wallet -async function disconnectWallet() { - try { - await evmClient.terminate() - } catch (err) { - console.error("Error with disconnecting:", err) - } -} - -// Handle account changes -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"; - } else { - // Account changed - document.getElementById("status").textContent = `Connected: ${accounts[0]}`; - } -}); -``` - -Display connect and disconnect buttons in HTML: - -```html -
-
Not connected
- - -
-``` - -## Connect and sign - -You can use MM Connect's [`connectAndSign`](../../reference/methods.md#connectandsign) method to request wallet access and sign a message in a single user interaction. -For example: - -```js -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); - -async function handleConnectAndSign() { - try { - const signature = await evmClient.connectAndSign({ msg: "Hello in one go!" }) - console.log("Signature:", signature) - } catch (err) { - console.error("Error with connectAndSign:", err) - } -} - -document - .getElementById("connectSignBtn") - .addEventListener("click", handleConnectAndSign) -``` - -The following HTML displays a **Connect & Sign** button: - -```html - -``` - -:::tip -This one-step flow is unique to MM Connect's `connectAndSign` method. -It's not part of Wagmi or other wallet libraries. -::: - -## Best practices - -Follow these best practices when authenticating users. - -#### User interaction - -- Only trigger connection requests in response to user actions (like selecting a button). -- Never auto-connect on page load. -- Provide clear feedback during connection states. - -#### Error handling - -- Handle [common errors](#common-errors) like user rejection (code `4001`). -- Provide clear error messages to users. -- Fall back gracefully when MetaMask is not installed. - -#### Account changes - -- Always listen for account changes. -- Update your UI when accounts change. -- Handle disconnection events. - -#### Chain support - -- Listen for network/chain changes. -- Verify the current chain meets your requirements. -- Provide clear messaging when users need to switch networks. - -Learn how to [manage networks](manage-networks.md). - -## Common errors - -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. | - -## Next steps - -See the following guides to add more functionality to your dapp: - -- [Manage networks](manage-networks.md) -- [Send transactions](send-transactions/index.md) -- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/javascript/send-transactions/index.md b/sdk/evm/connect/guides/javascript/send-transactions/index.md deleted file mode 100644 index cd732835db4..00000000000 --- a/sdk/evm/connect/guides/javascript/send-transactions/index.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -description: Handle transactions with MM Connect in your JavaScript dapp. -keywords: [SDK, JavaScript, send, transaction, transactions, status, estimate, gas, dapp] -toc_max_heading_level: 2 ---- - -# Send transactions - -Handle EVM transactions in your JavaScript dapp. -With MM Connect, you can: - -- **Send transactions**. -- **Track transaction status** in real time. -- **Estimate gas costs** accurately. -- **Handle transaction errors** gracefully. -- **Manage complex transaction patterns**. - -You can implement transaction handling directly in JavaScript. -The following are examples of sending a [basic transaction](#send-a-basic-transaction) and an -[advanced transaction with gas estimation](#send-an-advanced-transaction-with-gas-estimation). - -## Send a basic transaction - -The basic transaction uses 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 -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); - -async function sendTransaction(recipientAddress, amount) { - try { - // Get current account - const accounts = await provider.request({ - method: "eth_requestAccounts" - }); - const from = accounts[0]; - - // Convert ETH amount to wei (hex) - const value = `0x${(amount * 1e18).toString(16)}`; - - // Prepare transaction - const transaction = { - from, - to: recipientAddress, - value, - // Gas fields are optional - MetaMask will estimate - }; - - // Send transaction - const txHash = await provider.request({ - method: "eth_sendTransaction", - params: [transaction], - }); - - return txHash; - } catch (error) { - if (error.code === 4001) { - throw new Error("Transaction rejected by user"); - } - throw error; - } -} - -// Track transaction status -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); // Check every 2 seconds - } - } catch (error) { - reject(error); - } - }; - - checkTransaction(); - }); -} -``` - -The following is an example implementation of the basic transaction: - -```html -
- - - -
-
- - -``` - -## Send an advanced transaction with gas estimation - -To add gas estimation, use the [`eth_estimateGas`](../../../reference/json-rpc-api/index.md) -RPC method. - -```javascript -import { createEVMClient } from "@metamask/connect/evm"; - -const evmClient = createEVMClient(); -const provider = evmClient.getProvider(); - -async function estimateGas(transaction) { - try { - const gasEstimate = await provider.request({ - method: "eth_estimateGas", - params: [transaction] - }); - - // Add 20% buffer for safety - return BigInt(gasEstimate) * 120n / 100n; - } catch (error) { - console.error("Gas estimation failed:", error); - throw error; - } -} -``` - -## Best practices - -Follow these best practices when handling transactions. - -#### Transaction security - -- Always **validate inputs** before sending transactions. -- Check wallet balances to **ensure sufficient** funds. -- **Verify addresses** are valid. - -#### Error handling - -- Handle [common errors](#common-errors) like **user rejection** and **insufficient funds**. -- Provide **clear error messages** to users. -- Implement proper **error recovery** flows. -- Consider **network congestion** in gas estimates. - -#### User experience - -- Display **clear loading states** during transactions. -- Show **transaction progress** in real time. -- Provide **detailed transaction information**. -## Common errors - -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected transaction | Show a retry option and a clear error message. | -| `-32603` | Insufficient funds | Check the balance before sending a transaction. | -| `-32000` | Gas too low | Increase the gas limit or add a buffer to the estimation. | -| `-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) -- [Interact with smart contracts](../interact-with-contracts.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/manage-networks.md b/sdk/evm/connect/guides/manage-networks.md new file mode 100644 index 00000000000..e4ecf654732 --- /dev/null +++ b/sdk/evm/connect/guides/manage-networks.md @@ -0,0 +1,235 @@ +--- +description: Manage networks with MM Connect in your Vanilla JS or Wagmi dapp. +keywords: [SDK, JavaScript, wagmi, detect, switch, add, network, networks, dapp] +toc_max_heading_level: 2 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Manage networks + +Manage networks in your Vanilla JavaScript or Wagmi dapp. +With MM Connect, you can: + +- **Detect the current network** and monitor network changes. +- **Switch between networks** programmatically. +- **Add new networks** to MetaMask. +- **Handle common network-related errors**. + +

+ + Switch Networks + +

+ +## Detect and switch networks + +Wtih Vanilla JavaScript, you can implement network management directly using the +[`eth_chainId`](../reference/json-rpc-api/index.md) RPC method and +[`chainChanged`](../reference/provider-api.md#chainchanged) provider event. + +With Wagmi, you can use provided hooks for several network-related operations. + +Start by detecting the current network: + + + + +```javascript +import { createEVMClient } from '@metamask/connect/evm' + +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 + } catch (err) { + console.error('Error getting chain:', err) + } +} + +// Listen for network changes +provider.on('chainChanged', chainId => { + console.log('Network changed to:', chainId) + // We recommend reloading the page + window.location.reload() +}) +``` + +Switch networks using the +[`wallet_switchEthereumChain`](../reference/json-rpc-api/index.md) +and [`wallet_addEthereumChain`](../reference/json-rpc-api/index.md) +RPC methods: + +```javascript +// Network configurations +const networks = { + mainnet: { + chainId: '0x1', + name: 'Ethereum Mainnet', + }, + optimism: { + chainId: '0xA', + name: 'Optimism', + rpcUrls: ['https://mainnet.optimism.io'], + nativeCurrency: { + name: 'Ethereum', + symbol: 'ETH', + decimals: 18, + }, + blockExplorerUrls: ['https://optimistic.etherscan.io'], + }, +} + +async function switchNetwork(networkKey) { + const network = networks[networkKey] + + try { + // Try to switch to the network + await provider.request({ + 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, + }, + ], + }) + } catch (addError) { + console.error('Error adding network:', addError) + } + } else { + console.error('Error switching network:', err) + } + } +} +``` + +Display the current network and a switch network button in HTML: + +```html +
+
Current Network: Loading...
+ + +
+``` + +
+ + +```tsx +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
+ } + + return ( +
+
Connected to {currentChain.name}
+
Chain ID: {chainId}
+
Supported chains: {chains.map(c => c.name).join(', ')}
+
+ ) +} +``` + +Switch networks: + +```tsx +import { useSwitchChain } from 'wagmi' + +function NetworkSwitcher() { + const { chains, switchChain } = useSwitchChain() + + return ( +
+ {chains.map(chain => ( + + ))} +
+ ) +} +``` + +Handle network changes: + +```tsx +import { useChainId } from 'wagmi' +import { useEffect } from 'react' + +function NetworkWatcher() { + const chainId = useChainId() + + useEffect(() => { + console.log('Chain ID changed:', chainId) + }, [chainId]) + + return null +} +``` + +
+
+ +## Best practices + +Follow these best practices when managing networks. + +#### Error handling + +- Implement error handling for network switching operations. +- Provide **clear feedback messages** to users when network operations fail. +- Handle cases where networks need to be **added before switching**. + +#### User experience + +- Display **loading states** during network switches. +- Show **clear network status information** at all times. +- Consider **warning users** before initiating network switches. +- Use an **RPC provider** that supports your target networks. + +## Common errors + +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. | + +## Next steps + +See the following guides to add more functionality to your dapp: + +- [Manage user accounts](manage-user-accounts.md) +- [Send transactions](send-transactions/index.md) +- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/manage-user-accounts.md b/sdk/evm/connect/guides/manage-user-accounts.md new file mode 100644 index 00000000000..a70cf6c3870 --- /dev/null +++ b/sdk/evm/connect/guides/manage-user-accounts.md @@ -0,0 +1,244 @@ +--- +description: Authenticate users with MM Connect in your Vanilla JS or Wagmi dapp. +keywords: [SDK, JavaScript, wagmi, authenticate, connect, sign, accounts, wallet, dapp] +toc_max_heading_level: 3 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Manage user accounts + +Connect and manage user wallet sessions in your Vanilla JavaScript or Wagmi dapp. +With MM Connect, you can: + +- **Connect users' wallets** to your dapp. +- **Access user accounts** (addresses). +- [**Connect and sign**](#connect-and-sign) in a single user interaction. +- **Handle connection states** (connected/disconnected). +- **Listen for account changes** in real time. +- **Manage wallet sessions** (connect/disconnect). +- **Support multiple wallet types** (extension, mobile app). + +

+ + Connect to MetaMask + +

+ +## Connect wallet + +With Vanilla JavaScript, you can implement user authentication directly using the +[`eth_requestAccounts`](../reference/json-rpc-api/index.md) RPC method +and [`accountsChanged`](../reference/provider-api.md#accountschanged) provider event. + +With Wagmi, you can use provided hooks for handling wallet connections. + + + + +```javascript +import { createEVMClient } from '@metamask/connect/evm' + +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) + + // Update UI + 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') + } else { + console.error(err) + } + } finally { + document.getElementById('connectBtn').disabled = false + } +} + +// Disconnect wallet +async function disconnectWallet() { + try { + await evmClient.terminate() + } catch (err) { + console.error('Error with disconnecting:', err) + } +} + +// Handle account changes +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' + } else { + // Account changed + document.getElementById('status').textContent = `Connected: ${accounts[0]}` + } +}) +``` + +Display connect and disconnect buttons in HTML: + +```html +
+
Not connected
+ + +
+``` + +
+ + +```tsx +import { useAccount, useConnect, useDisconnect } from 'wagmi' + +function ConnectWallet() { + const { address, isConnected } = useAccount() + const { connectors, connect, isPending } = useConnect() + const { disconnect } = useDisconnect() + + if (isConnected) { + return ( +
+
Connected to {address}
+ +
+ ) + } + + return ( +
+ {connectors.map(connector => ( + + ))} +
+ ) +} +``` + +Wagmi provides a dedicated hook for handling account lifecycle events: + +```tsx +import { useAccountEffect } from 'wagmi' + +function WatchAccount() { + useAccountEffect({ + onConnect(data) { + console.log('Connected!', { + address: data.address, + chainId: data.chainId, + isReconnected: data.isReconnected, + }) + }, + onDisconnect() { + console.log('Disconnected!') + }, + }) + + return
Watching for account changes...
+} +``` + +
+
+ +## Connect and sign + +You can use MM Connect's [`connectAndSign`](../reference/methods.md#connectandsign) method to request wallet access and sign a message in a single user interaction. +For example: + +```js +import { createEVMClient } from '@metamask/connect/evm' + +const evmClient = createEVMClient() + +async function handleConnectAndSign() { + try { + const signature = await evmClient.connectAndSign({ msg: 'Hello in one go!' }) + console.log('Signature:', signature) + } catch (err) { + console.error('Error with connectAndSign:', err) + } +} + +document.getElementById('connectSignBtn').addEventListener('click', handleConnectAndSign) +``` + +The following HTML displays a **Connect & Sign** button: + +```html + +``` + +:::tip +This one-step flow is unique to MM Connect's `connectAndSign` method. +It's not part of Wagmi or other wallet libraries. +::: + +## Best practices + +Follow these best practices when authenticating users. + +#### User interaction + +- Only trigger connection requests in response to user actions (like selecting a button). +- Never auto-connect on page load. +- Provide clear feedback during connection states. + +#### Error handling + +- Handle [common errors](#common-errors) like user rejection (code `4001`). +- Provide clear error messages to users. +- Fall back gracefully when MetaMask is not installed. + +#### Account changes + +- Always listen for account changes. +- Update your UI when accounts change. +- Handle disconnection events. + +#### Chain support + +- Listen for network/chain changes. +- Verify the current chain meets your requirements. +- Provide clear messaging when users need to switch networks. + +Learn how to [manage networks](manage-networks.md). + +## Common errors + +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. | + +## Next steps + +See the following guides to add more functionality to your dapp: + +- [Manage networks](manage-networks.md) +- [Send transactions](send-transactions/index.md) +- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/javascript/batch-requests.md b/sdk/evm/connect/guides/metamask-exclusive/batch-requests.md similarity index 74% rename from sdk/evm/connect/guides/javascript/batch-requests.md rename to sdk/evm/connect/guides/metamask-exclusive/batch-requests.md index ed1b777ea8d..a5eb57e940a 100644 --- a/sdk/evm/connect/guides/javascript/batch-requests.md +++ b/sdk/evm/connect/guides/metamask-exclusive/batch-requests.md @@ -10,8 +10,8 @@ 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 -[sending atomic batch transactions](send-transactions/batch-transactions.md) in MetaMask. +"Batching" can also refer to [Wagmi contract read batching](../interact-with-contracts.md#batch-contract-reads) or +[sending atomic batch transactions](../send-transactions/batch-transactions.md) in MetaMask. ::: ## Batch JSON-RPC requests @@ -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/display-tokens.md b/sdk/evm/connect/guides/metamask-exclusive/display-tokens.md similarity index 83% rename from sdk/evm/connect/guides/javascript/display-tokens.md rename to sdk/evm/connect/guides/metamask-exclusive/display-tokens.md index 63471b28168..23761bbae3c 100644 --- a/sdk/evm/connect/guides/javascript/display-tokens.md +++ b/sdk/evm/connect/guides/metamask-exclusive/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) @@ -79,11 +79,11 @@ try { :::note If the chain ID of your token doesn't match the user's network, they can get unexpected results. -We recommend [detecting the user's network chain ID](manage-networks.md) and +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 @@ -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/javascript/use-deeplinks.md b/sdk/evm/connect/guides/metamask-exclusive/use-deeplinks.md similarity index 100% rename from sdk/evm/connect/guides/javascript/use-deeplinks.md rename to sdk/evm/connect/guides/metamask-exclusive/use-deeplinks.md 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 88% rename from sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md rename to sdk/evm/connect/guides/send-transactions/batch-transactions.md index 1963b3733ef..59c70409b05 100644 --- a/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md +++ b/sdk/evm/connect/guides/send-transactions/batch-transactions.md @@ -7,9 +7,9 @@ description: Send atomic batch transactions using `wallet_sendCalls`. You can send and manage batch transactions in MetaMask, using the methods specified by [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792): -- [`wallet_getCapabilities`](../../../reference/json-rpc-api/index.md) - Query whether support for atomic batch transactions is available. -- [`wallet_sendCalls`](../../../reference/json-rpc-api/index.md) - Submit multiple transactions to be processed atomically by MetaMask. -- [`wallet_getCallsStatus`](../../../reference/json-rpc-api/index.md) - Track the status of your transaction batch. +- [`wallet_getCapabilities`](../../reference/json-rpc-api/index.md) - Query whether support for atomic batch transactions is available. +- [`wallet_sendCalls`](../../reference/json-rpc-api/index.md) - Submit multiple transactions to be processed atomically by MetaMask. +- [`wallet_getCallsStatus`](../../reference/json-rpc-api/index.md) - Track the status of your transaction batch. ## About atomic batch transactions @@ -45,7 +45,7 @@ You can send batch transactions using the following third-party libraries that s ### 1. Query whether atomic batch is supported -Use [`wallet_getCapabilities`](../../../reference/json-rpc-api/index.md) to query +Use [`wallet_getCapabilities`](../../reference/json-rpc-api/index.md) to query whether MetaMask supports atomic batch transactions for a specific address and specific chain IDs. For example: @@ -113,14 +113,14 @@ MetaMask will support this feature on more networks as they adopt EIP-7702. :::note Atomic batch unsupported - If the user has already upgraded their account to a third-party smart contract account, MetaMask does not currently support atomic batch transactions for that account. - If atomic batch is not supported, fall back to [`eth_sendTransaction`](index.md) instead of - [`wallet_sendCalls`](../../../reference/json-rpc-api/index.md), - and [`eth_getTransactionReceipt`](../../../reference/json-rpc-api/index.md) - instead of [`wallet_getCallsStatus`](../../../reference/json-rpc-api/index.md). + [`wallet_sendCalls`](../../reference/json-rpc-api/index.md), + and [`eth_getTransactionReceipt`](../../reference/json-rpc-api/index.md) + instead of [`wallet_getCallsStatus`](../../reference/json-rpc-api/index.md). ::: ### 2. Submit a batch of transactions -Use [`wallet_sendCalls`](../../../reference/json-rpc-api/index.md) to submit a batch of transactions. +Use [`wallet_sendCalls`](../../reference/json-rpc-api/index.md) to submit a batch of transactions. For example: ```js title="index.js" @@ -164,7 +164,7 @@ For example: ### 3. Track the status of the batch of transactions -Use [`wallet_getCallsStatus`](../../../reference/json-rpc-api/index.md) to track +Use [`wallet_getCallsStatus`](../../reference/json-rpc-api/index.md) to track the status of the submitted batch of transactions, using the batch ID returned by `wallet_sendCalls`. For example: @@ -225,6 +225,7 @@ In these cases, `atomic` is `true` but multiple receipts are returned. ## Resources - See the [MetaMask 7702/5792 Readiness dapp](https://7702playground.metamask.io/) to quickly test sending batch transactions. +- Follow the [tutorial to send atomic batch transactions using Wagmi](/tutorials/upgrade-eoa-to-smart-account), upgrading an externally owned account to a smart account. - See the [MetaMask Smart Accounts Kit documentation](/smart-accounts-kit) for more information about MetaMask Smart Accounts and their capabilities. - See the following topics in the MetaMask end user documentation: - [What is a smart account?](https://support.metamask.io/configure/accounts/what-is-a-smart-account/) diff --git a/sdk/evm/connect/guides/send-transactions/index.md b/sdk/evm/connect/guides/send-transactions/index.md new file mode 100644 index 00000000000..95e312a10b4 --- /dev/null +++ b/sdk/evm/connect/guides/send-transactions/index.md @@ -0,0 +1,336 @@ +--- +description: Handle transactions with MM Connect in your JavaScript dapp. +keywords: [SDK, JavaScript, wagmi, send, transaction, transactions, status, estimate, gas, dapp] +toc_max_heading_level: 2 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Send transactions + +Handle EVM transactions in your JavaScript dapp. +With MM Connect, you can: + +- **Send transactions**. +- **Track transaction status** in real time. +- **Estimate gas costs** accurately. +- **Handle transaction errors** gracefully. +- **Manage complex transaction patterns**. + +The following examples demonstrate how to use MM Connect with viem, web3.js, ethers.js, Ethereum APIs, or Wagmi to send a [basic transaction](#send-a-basic-transaction) and an +[advanced transaction with gas estimation](#send-an-advanced-transaction-with-gas-estimation). + +## Send a basic transaction + + + + +```tsx +import { createEVMClient } from '@metamask/connect/evm' +import { createPublicClient, createWalletClient, custom } from 'viem' +import { mainnet } from 'viem/chains' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +const publicClient = createPublicClient({ chain: mainnet, transport: custom(provider) }) +const walletClient = createWalletClient({ chain: mainnet, transport: custom(provider) }) + +// data for the transaction +const destination = '0xRECIPIENT_ADDRESS' +const amount = parseEther('0.0001') +const address = await walletClient.getAddresses() + +// Submit transaction to the blockchain +const hash = await walletClient.sendTransaction({ + account: address[0], + to: destination, + value: amount, +}) + +const receipt = await publicClient.waitForTransactionReceipt({ hash }) +``` + + + + +```tsx +import { createEVMClient } from '@metamask/connect/evm' +import { Web3 } from 'web3' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +const web3 = new Web3(provider) + +// Get user's Ethereum public address +const fromAddress = (await web3.eth.getAccounts())[0] + +const destination = '0xRECIPIENT_ADDRESS' +const amount = web3.utils.toWei('0.0001') // Convert 0.0001 ether to wei + +// Submit transaction to the blockchain and wait for it to be mined +const receipt = await web3.eth.sendTransaction({ + from: fromAddress, + to: destination, + value: amount, +}) +``` + + + + +```tsx +import { createEVMClient } from '@metamask/connect/evm' +import { ethers } from 'ethers' +import { BrowserProvider, parseUnits } from 'ethers' + +const evmClient = createEVMClient() +const provider = evmClient.getProvider() + +const ethersProvider = new ethers.BrowserProvider(provider) +const signer = await ethersProvider.getSigner() + +const destination = '0xRECIPIENT_ADDRESS' +const amount = parseUnits('0.0001', 'ether') + +// Submit transaction to the blockchain +const tx = await signer.sendTransaction({ + to: destination, + value: amount, +}) + +// Wait for the transaction to be mined +const receipt = await tx.wait() +``` + + + + +```javascript +import { createEVMClient } from "@metamask/connect/evm"; + +const evmClient = createEVMClient(); +const provider = evmClient.getProvider(); + +async function sendTransaction(recipientAddress, amount) { + try { + // Get current account + const accounts = await provider.request({ + method: "eth_requestAccounts" + }); + const from = accounts[0]; + + // Convert ETH amount to wei (hex) + const value = `0x${(amount * 1e18).toString(16)}`; + + // Prepare transaction + const transaction = { + from, + to: recipientAddress, + value, + // Gas fields are optional - MetaMask will estimate + }; + + // Send transaction + const txHash = await provider.request({ + method: "eth_sendTransaction", + params: [transaction], + }); + + return txHash; + } catch (error) { + if (error.code === 4001) { + throw new Error("Transaction rejected by user"); + } + throw error; + } +} + +// Track transaction status +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); // Check every 2 seconds + } + } catch (error) { + reject(error); + } + }; + + checkTransaction(); + }); +} +``` + + + + +```tsx +import { parseEther } from "viem" +import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi" + +function SendTransaction() { + const { + data: hash, + error, + isPending, + sendTransaction + } = useSendTransaction() + + const { + isLoading: isConfirming, + isSuccess: isConfirmed + } = useWaitForTransactionReceipt({ + hash + }) + + async function handleSend() { + sendTransaction({ + to: "0x...", + value: parseEther("0.1") // 0.1 ETH + }) + } + + return ( +
+ + + {hash && ( +
+ Transaction Hash: {hash} + {isConfirming &&
Waiting for confirmation...
} + {isConfirmed &&
Transaction confirmed!
} +
+ )} + + {error &&
Error: {error.message}
} +
+ ) +} +``` + +
+
+ +## Send an advanced transaction with gas estimation + + + + +```javascript +import { createEVMClient } from "@metamask/connect/evm"; + +const evmClient = createEVMClient(); +const provider = evmClient.getProvider(); + +async function estimateGas(transaction) { + try { + const gasEstimate = await provider.request({ + method: "eth_estimateGas", + params: [transaction] + }); + + // Add 20% buffer for safety + return (BigInt(gasEstimate) * 120n) / 100n + } catch (error) { + console.error('Gas estimation failed:', error) + throw error + } +} +``` + + + + +```tsx +import { parseEther } from "viem" +import { + useSendTransaction, + useWaitForTransactionReceipt, + useEstimateGas +} from "wagmi" + +function AdvancedTransaction() { + const transaction = { + to: "0x...", + value: parseEther("0.1"), + data: "0x..." // Optional contract interaction data + } + + // Estimate gas + const { data: gasEstimate } = useEstimateGas(transaction) + + const { sendTransaction } = useSendTransaction({ + ...transaction, + gas: gasEstimate, + onSuccess: (hash) => { + console.log("Transaction sent:", hash) + } + }) + + return +} +``` + + + + +## Best practices + +Follow these best practices when handling transactions. + +#### Transaction security + +- Always **validate inputs** before sending transactions. +- Check wallet balances to **ensure sufficient** funds. +- **Verify addresses** are valid. + +#### Error handling + +- Handle [common errors](#common-errors) like **user rejection** and **insufficient funds**. +- Provide **clear error messages** to users. +- Implement proper **error recovery** flows. +- Consider **network congestion** in gas estimates. + +#### User experience + +- Display **clear loading states** during transactions. +- Show **transaction progress** in real time. +- Provide **detailed transaction information**. + +## Common errors + +| Error code | Description | Solution | +| ---------- | ------------------------- | --------------------------------------------------------- | +| `4001` | User rejected transaction | Show a retry option and a clear error message. | +| `-32603` | Insufficient funds | Check the balance before sending a transaction. | +| `-32000` | Gas too low | Increase the gas limit or add a buffer to the estimation. | +| `-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) +- [Interact with smart contracts](../interact-with-contracts.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..55904dc05b8 --- /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 77% rename from sdk/evm/connect/guides/javascript/sign-data/siwe.md rename to sdk/evm/connect/guides/sign-data/siwe.md index 6edab221a46..7735f597860 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,30 +31,30 @@ 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
## Example The following is an example of setting up SIWE with MetaMask using -[`personal_sign`](../../../reference/json-rpc-api/index.md): +[`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/wagmi/interact-with-contracts.md b/sdk/evm/connect/guides/wagmi/interact-with-contracts.md deleted file mode 100644 index bb165bbc6f2..00000000000 --- a/sdk/evm/connect/guides/wagmi/interact-with-contracts.md +++ /dev/null @@ -1,221 +0,0 @@ ---- -description: Interact with contracts with MM Connect in your Wagmi dapp. -keywords: [SDK, Wagmi, JavaScript, batch, read, write, smart, contract, contracts, dapp] -sidebar_label: Interact with contracts ---- - -# Interact with smart contracts - -Interact with smart contracts in your Wagmi dapp. -With MM Connect, you can: - -- **Read data** from smart contracts. -- **Batch contract reads**. -- **Write data** to smart contracts. -- **Handle contract events**. -- **Manage transaction states**. -- **Handle contract errors**. - -## Read contracts - -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" - -function TokenBalance() { - const { - data: balance, - isError, - isLoading - } = useReadContract({ - address: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", - abi: [ - { - name: "balanceOf", - type: "function", - stateMutability: "view", - inputs: [{ name: "owner", type: "address" }], - outputs: [{ name: "balance", type: "uint256" }], - }, - ], - functionName: "balanceOf", - args: ["0x03A71968491d55603FFe1b11A9e23eF013f75bCF"], - }) - - if (isLoading) return
Loading balance...
- if (isError) return
Error fetching balance
- - return
Balance: {balance?.toString()}
-} -``` - -### Batch contract reads - -You can perform multiple contract read operations using the [`useReadContracts`](https://wagmi.sh/react/api/hooks/useReadContracts) hook. -This hook batches contract calls internally, returning the results as an array. -For example: - -```js -import { useReadContracts } from "wagmi"; - -// Example contract definitions with their address and ABI -const contractA = { - address: "0xContractAddress1", - abi: contractABI1, -} as const; - -const contractB = { - address: "0xContractAddress2", - abi: contractABI2, -} as const; - -function MyBatchReadComponent() { - const { data, isError, isLoading } = useReadContracts({ - contracts: [ - { - ...contractA, - functionName: "getValueA", - }, - { - ...contractA, - functionName: "getValueB", - }, - { - ...contractB, - functionName: "getValueX", - args: [42], - }, - { - ...contractB, - functionName: "getValueY", - args: [42], - }, - ], - }); - - if (isLoading) return
Loading...
; - if (isError) return
Error fetching data.
; - - return ( -
-

getValueA: {data?.[0]?.toString()}

-

getValueB: {data?.[1]?.toString()}

-

getValueX: {data?.[2]?.toString()}

-

getValueY: {data?.[3]?.toString()}

-
- ); -} -``` - -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. -::: - -## Write to contracts - -The following example writes to contracts using the [`useWriteContract`](https://wagmi.sh/react/api/hooks/useWriteContract) hook: - -```tsx -import { useWriteContract, useWaitForTransactionReceipt } from "wagmi" - -function MintNFT() { - const { - writeContract, - data: hash, - error, - isPending - } = useWriteContract() - - const { - isLoading: isConfirming, - isSuccess: isConfirmed - } = useWaitForTransactionReceipt({ - hash - }) - - function mint() { - writeContract({ - address: "0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2", - abi: [ - { - name: "mint", - type: "function", - stateMutability: "nonpayable", - inputs: [{ name: "tokenId", type: "uint256" }], - outputs: [], - }, - ], - functionName: "mint", - args: [123n], // Token ID - }) - } - - return ( -
- - - {hash && ( -
- Transaction Hash: {hash} - {isConfirming &&
Waiting for confirmation...
} - {isConfirmed &&
NFT Minted Successfully!
} -
- )} - - {error &&
Error: {error.message}
} -
- ) -} -``` - -## 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.md) diff --git a/sdk/evm/connect/guides/wagmi/manage-networks.md b/sdk/evm/connect/guides/wagmi/manage-networks.md deleted file mode 100644 index bcb080b0641..00000000000 --- a/sdk/evm/connect/guides/wagmi/manage-networks.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -description: Manage networks with MM Connect in your Wagmi dapp. -keywords: [SDK, Wagmi, JavaScript, detect, switch, add, network, networks, dapp] -toc_max_heading_level: 2 ---- - -# Manage networks - -Manage networks in your Wagmi dapp. -With MM Connect, you can: - -- **Detect the current network** and monitor network changes. -- **Switch between networks** programmatically. -- **Add new networks** to MetaMask. -- **Handle common network-related errors**. - -

- - Switch Networks - -

- -## Detect and switch networks - -Wagmi provides intuitive hooks for several network-related operations. -The following are examples of using these hooks. - -Detect the current network: - -```tsx -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
- } - - return ( -
-
Connected to {currentChain.name}
-
Chain ID: {chainId}
-
Supported chains: {chains.map(c => c.name).join(", ")}
-
- ) -} -``` - -Switch networks: - -```tsx -import { useSwitchChain } from "wagmi" - -function NetworkSwitcher() { - const { chains, switchChain } = useSwitchChain() - - return ( -
- {chains.map((chain) => ( - - ))} -
- ) -} -``` - -Handle network changes: - -```tsx -import { useChainId } from "wagmi" -import { useEffect } from "react" - -function NetworkWatcher() { - const chainId = useChainId() - - useEffect(() => { - console.log("Chain ID changed:", chainId) - }, [chainId]) - - return null -} -``` - -## Best practices - -Follow these best practices when managing networks. - -#### Error handling - -- Implement error handling for network switching operations. -- Provide **clear feedback messages** to users when network operations fail. -- Handle cases where networks need to be **added before switching**. - -#### User experience - -- Display **loading states** during network switches. -- Show **clear network status information** at all times. -- Consider **warning users** before initiating network switches. -- Use an **RPC provider** that supports your target networks. - -## Common errors - -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. | - -## Next steps - -See the following guides to add more functionality to your dapp: - -- [Manage user accounts](manage-user-accounts.md) -- [Send transactions](send-transactions.md) -- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/wagmi/manage-user-accounts.md b/sdk/evm/connect/guides/wagmi/manage-user-accounts.md deleted file mode 100644 index da7d64a6f24..00000000000 --- a/sdk/evm/connect/guides/wagmi/manage-user-accounts.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -description: Authenticate users with MM Connect in your Wagmi dapp. -keywords: [SDK, Wagmi, JavaScript, authenticate, connect, sign, accounts, wallet, dapp] -toc_max_heading_level: 3 ---- - -# Manage user accounts - -Connect and manage user wallet sessions in your Wagmi dapp. -With MM Connect, you can: - -- **Connect users' wallets** to your dapp. -- **Access user accounts** (addresses). -- **Handle connection states** (connected/disconnected). -- **Listen for account changes** in real time. -- **Manage wallet sessions** (connect/disconnect). -- **Support multiple wallet types** (extension, mobile app). - -

- - Connect to MetaMask - -

- -## Connect wallet - -Wagmi provides a simple, hook-based approach for handling wallet connections. -For example: - -```tsx title="Handle wallet connections" -import { useAccount, useConnect, useDisconnect } from "wagmi" - -function ConnectWallet() { - const { address, isConnected } = useAccount() - const { connectors, connect, isPending } = useConnect() - const { disconnect } = useDisconnect() - - if (isConnected) { - return ( -
-
Connected to {address}
- -
- ) - } - - return ( -
- {connectors.map((connector) => ( - - ))} -
- ) -} -``` - -Wagmi provides a dedicated hook for handling account lifecycle events: - -```tsx -import { useAccountEffect } from "wagmi" - -function WatchAccount() { - useAccountEffect({ - onConnect(data) { - console.log("Connected!", { - address: data.address, - chainId: data.chainId, - isReconnected: data.isReconnected - }) - }, - onDisconnect() { - console.log("Disconnected!") - } - }) - - return
Watching for account changes...
-} -``` - -## Best practices - -Follow these best practices when authenticating users. - -#### User interaction - -- Only trigger connection requests in response to user actions (like selecting a button). -- Never auto-connect on page load. -- Provide clear feedback during connection states. - -#### Error handling - -- Handle [common errors](#common-errors) like user rejection (code `4001`). -- Provide clear error messages to users. -- Fall back gracefully when MetaMask is not installed. - -#### Account changes - -- Always listen for account changes. -- Update your UI when accounts change. -- Handle disconnection events. - -#### Chain support - -- Listen for network/chain changes. -- Verify the current chain meets your requirements. -- Provide clear messaging when users need to switch networks. - -Learn how to [manage networks](manage-networks.md). - -## Common errors - -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. | - -## Next steps - -See the following guides to add more functionality to your dapp: - -- [Manage networks](manage-networks.md) -- [Send transactions](send-transactions.md) -- [Interact with smart contracts](interact-with-contracts.md) diff --git a/sdk/evm/connect/guides/wagmi/send-transactions.md b/sdk/evm/connect/guides/wagmi/send-transactions.md deleted file mode 100644 index 598d840bba4..00000000000 --- a/sdk/evm/connect/guides/wagmi/send-transactions.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -description: Handle transactions with MM Connect in your Wagmi dapp. -keywords: [SDK, Wagmi, JavaScript, send, transaction, transactions, status, estimate, gas, dapp] -toc_max_heading_level: 2 ---- - -# Send transactions - -Handle EVM transactions in your Wagmi dapp. -With MM Connect, you can: - -- **Send transactions**. -- **Track transaction status** in real time. -- **Estimate gas costs** accurately. -- **Handle transaction errors** gracefully. -- **Manage complex transaction patterns**. - -Wagmi provides hooks for sending transactions and tracking their status. -The following are examples of sending a [basic transaction](#send-a-basic-transaction) and an -[advanced transaction with gas estimation](#send-an-advanced-transaction-with-gas-estimation). - -:::info Send batch transactions -This page describes how to send one transaction at a time using Wagmi. -You can also follow [this tutorial to send atomic batch transactions](/tutorials/upgrade-eoa-to-smart-account) with Wagmi and MM Connect, upgrading an externally owned account to a smart account (specified by [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792) and [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)). -::: - -## Send a basic transaction - -```tsx -import { parseEther } from "viem" -import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi" - -function SendTransaction() { - const { - data: hash, - error, - isPending, - sendTransaction - } = useSendTransaction() - - const { - isLoading: isConfirming, - isSuccess: isConfirmed - } = useWaitForTransactionReceipt({ - hash - }) - - async function handleSend() { - sendTransaction({ - to: "0x...", - value: parseEther("0.1") // 0.1 ETH - }) - } - - return ( -
- - - {hash && ( -
- Transaction Hash: {hash} - {isConfirming &&
Waiting for confirmation...
} - {isConfirmed &&
Transaction confirmed!
} -
- )} - - {error &&
Error: {error.message}
} -
- ) -} -``` - -## Send an advanced transaction with gas estimation - -```tsx -import { parseEther } from "viem" -import { - useSendTransaction, - useWaitForTransactionReceipt, - useEstimateGas -} from "wagmi" - -function AdvancedTransaction() { - const transaction = { - to: "0x...", - value: parseEther("0.1"), - data: "0x..." // Optional contract interaction data - } - - // Estimate gas - const { data: gasEstimate } = useEstimateGas(transaction) - - const { sendTransaction } = useSendTransaction({ - ...transaction, - gas: gasEstimate, - onSuccess: (hash) => { - console.log("Transaction sent:", hash) - } - }) - - return -} -``` - -## Best practices - -Follow these best practices when handling transactions. - -#### Transaction security - -- Always **validate inputs** before sending transactions. -- Check wallet balances to **ensure sufficient** funds. -- **Verify addresses** are valid. - -#### Error handling - -- Handle [common errors](#common-errors) like **user rejection** and **insufficient funds**. -- Provide **clear error messages** to users. -- Implement proper **error recovery** flows. -- Consider **network congestion** in gas estimates. - -#### User experience - -- Display **clear loading states** during transactions. -- Show **transaction progress** in real time. -- Provide **detailed transaction information**. -## Common errors - -| Error code | Description | Solution | -|------------|-------------|----------| -| `4001` | User rejected transaction | Show a retry option and a clear error message. | -| `-32603` | Insufficient funds | Check the balance before sending a transaction. | -| `-32000` | Gas too low | Increase the gas limit or add a buffer to the estimation. | -| `-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) -- [Interact with smart contracts](interact-with-contracts.md) 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..979a8ac10f1 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({