From 0b7903717640770419ad18a11042dd058d7a06fc Mon Sep 17 00:00:00 2001 From: Alexandra Tran Date: Tue, 23 Sep 2025 15:57:21 -0700 Subject: [PATCH 01/17] SDK new structure PoC --- docusaurus.config.js | 25 -- sdk-sidebar.js | 166 ++++++++-- sdk/{ => evm/connect}/_assets/connect.gif | Bin sdk/{ => evm/connect}/_assets/network.gif | Bin .../connect}/_assets/quickstart-dynamic.png | Bin .../_assets/quickstart-javascript.png | Bin .../connect}/_assets/quickstart-web3auth.png | Bin sdk/{ => evm/connect}/_assets/quickstart.jpg | Bin .../evm/connect}/assets/add-network.png | Bin .../assets/contract-abi-converter-dialog.png | Bin .../evm/connect}/assets/custom-modal.gif | Bin .../evm/connect}/assets/personal_sign.png | Bin .../assets/react-tutorial-01-final.png | Bin .../assets/react-tutorial-01-start.png | Bin .../connect}/assets/request-permissions-2.png | Bin .../connect}/assets/request-permissions.png | Bin .../assets/sdk-android-architecture.png | Bin .../assets/sdk-android-communication.png | Bin .../connect}/assets/sdk-clear-connections.png | Bin .../evm/connect}/assets/signTypedData.png | Bin .../evm/connect}/assets/siwe-bad-domain-2.png | Bin .../evm/connect}/assets/siwe-bad-domain.png | Bin {wallet => sdk/evm/connect}/assets/siwe.png | Bin .../evm/connect}/assets/switch-network.png | Bin .../tutorials/beginner-tutorial/account.png | Bin .../tutorials/beginner-tutorial/connect.png | Bin .../beginner-tutorial/enable-button.png | Bin .../assets/tutorials/react-dapp/pt1-01.png | Bin .../assets/tutorials/react-dapp/pt1-02.png | Bin .../assets/tutorials/react-dapp/pt1-03.png | Bin .../assets/tutorials/react-dapp/pt1-04.png | Bin .../assets/tutorials/react-dapp/pt1-05.png | Bin .../assets/tutorials/react-dapp/pt1-06.png | Bin .../assets/tutorials/react-dapp/pt1-07.png | Bin .../assets/tutorials/react-dapp/pt1-08.png | Bin .../assets/tutorials/react-dapp/pt1-09.png | Bin .../assets/tutorials/react-dapp/pt1-10.png | Bin .../assets/tutorials/react-dapp/pt2-01.png | Bin .../assets/tutorials/react-dapp/pt2-02.png | Bin .../assets/tutorials/react-dapp/pt2-03.png | Bin .../assets/tutorials/react-dapp/pt2-04.png | Bin .../assets/tutorials/react-dapp/pt2-05.png | Bin .../react-tutorial-02-final-preview.png | Bin .../react-tutorial-02-selected-wallet.png | Bin .../react-tutorial-02-wallet-error.png | Bin .../react-tutorial-02-wallet-list.png | Bin .../connect}/assets/unity-empty-template.png | Bin .../assets/unity-example-template.png | Bin .../evm/connect}/assets/unity-infura.png | Bin .../evm/connect}/assets/wagmi-errors.png | Bin .../evm/connect}/assets/watchasset-nft-2.png | Bin .../evm/connect}/assets/watchasset-nft.png | Bin .../evm/connect}/assets/web3-architecture.png | Bin .../connect/get-started}/javascript-wagmi.md | 0 .../connect/get-started}/javascript.md | 0 .../connect/get-started}/react-native.md | 0 .../connect}/guides/batch-requests.md | 0 .../connect/guides/best-practices/display.md | 30 ++ .../best-practices}/production-readiness.md | 0 .../guides/best-practices}/run-devnet.md | 0 .../evm/connect/guides}/connect-extension.md | 0 .../evm/connect/guides/display-tokens.md | 4 +- .../guides/interact-with-contracts.md | 0 .../connect}/guides/manage-networks.md | 0 .../connect/guides/manage-user-accounts.md} | 2 +- .../guides/send-transactions/index.md} | 2 +- .../send-batch-transactions.md | 0 .../evm/connect/guides}/sign-data/index.md | 0 .../evm/connect/guides}/sign-data/siwe.md | 0 sdk/{ => evm/connect}/guides/use-deeplinks.md | 0 sdk/evm/connect/index.md | 1 + .../connect/partners/dynamic.md} | 0 .../connect/partners/web3auth.md} | 0 .../reference/json-rpc-methods/index.md | 0 .../evm/connect}/reference/provider-api.md | 0 .../connect}/reference/sdk-methods.md | 0 .../connect}/reference/sdk-options.md | 0 sdk/evm/index.md | 9 + sdk/index.md | 3 +- .../connect/guides/connect-to-multichain.md | 1 + .../connect/guides/send-transactions.md | 1 + sdk/multichain/connect/index.md | 1 + .../connect}/reference/multichain-api.md | 0 .../tutorials/create-multichain-dapp.md | 1 + sdk/reference/llm-prompt.md | 146 --------- sdk/reference/supported-platforms.md | 28 -- sdk/solana/connect/index.md | 1 + .../solana/connect/wallet-standard.md | 2 +- sdk/solana/index.md | 5 + .../assets/starknet-dapp-connected.png | Bin .../assets/starknet-metamask-connection.png | Bin .../assets/starknet-token-update.png | Bin .../assets/starknet-tutorial-connected.png | Bin .../assets/starknet-tutorial-modal.png | Bin .../assets/starknet-tutorial-start-dapp.png | Bin .../starknet-tutorial-transfer-token.png | Bin .../assets/starknet-wallet-modal.png | Bin .../starknet/concepts}/about-get-starknet.md | 0 .../starknet/guides}/connect-to-starknet.md | 6 +- .../guides}/manage-starknet-accounts.md | 0 .../guides}/manage-starknet-networks.md | 0 .../guides}/send-starknet-transactions.md | 0 .../starknet/guides}/sign-starknet-data.md | 0 .../starknet/guides}/troubleshoot.md | 0 .../starknet/index.md | 1 + .../starknet/reference}/starknet-snap-api.md | 0 .../create-a-simple-starknet-dapp.md | 8 +- src/components/CustomReferencePage/index.tsx | 10 +- src/components/NavDropdown/Products.html | 22 -- .../SidebarSectionDropdown/configs.ts | 23 -- src/components/SubNavBar/configs.ts | 32 +- src/plugins/plugin-json-rpc.ts | 2 +- src/theme/DocSidebar/Desktop/index.tsx | 16 - src/theme/DocSidebar/Mobile/index.tsx | 7 - wallet-sidebar.js | 287 ------------------ 115 files changed, 244 insertions(+), 598 deletions(-) rename sdk/{ => evm/connect}/_assets/connect.gif (100%) rename sdk/{ => evm/connect}/_assets/network.gif (100%) rename sdk/{ => evm/connect}/_assets/quickstart-dynamic.png (100%) rename sdk/{ => evm/connect}/_assets/quickstart-javascript.png (100%) rename sdk/{ => evm/connect}/_assets/quickstart-web3auth.png (100%) rename sdk/{ => evm/connect}/_assets/quickstart.jpg (100%) rename {wallet => sdk/evm/connect}/assets/add-network.png (100%) rename {wallet => sdk/evm/connect}/assets/contract-abi-converter-dialog.png (100%) rename {wallet => sdk/evm/connect}/assets/custom-modal.gif (100%) rename {wallet => sdk/evm/connect}/assets/personal_sign.png (100%) rename {wallet => sdk/evm/connect}/assets/react-tutorial-01-final.png (100%) rename {wallet => sdk/evm/connect}/assets/react-tutorial-01-start.png (100%) rename {wallet => sdk/evm/connect}/assets/request-permissions-2.png (100%) rename {wallet => sdk/evm/connect}/assets/request-permissions.png (100%) rename {wallet => sdk/evm/connect}/assets/sdk-android-architecture.png (100%) rename {wallet => sdk/evm/connect}/assets/sdk-android-communication.png (100%) rename {wallet => sdk/evm/connect}/assets/sdk-clear-connections.png (100%) rename {wallet => sdk/evm/connect}/assets/signTypedData.png (100%) rename {wallet => sdk/evm/connect}/assets/siwe-bad-domain-2.png (100%) rename {wallet => sdk/evm/connect}/assets/siwe-bad-domain.png (100%) rename {wallet => sdk/evm/connect}/assets/siwe.png (100%) rename {wallet => sdk/evm/connect}/assets/switch-network.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/beginner-tutorial/account.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/beginner-tutorial/connect.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/beginner-tutorial/enable-button.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-01.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-02.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-03.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-04.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-05.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-06.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-07.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-08.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-09.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt1-10.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt2-01.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt2-02.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt2-03.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt2-04.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/pt2-05.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/react-tutorial-02-final-preview.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png (100%) rename {wallet => sdk/evm/connect}/assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png (100%) rename {wallet => sdk/evm/connect}/assets/unity-empty-template.png (100%) rename {wallet => sdk/evm/connect}/assets/unity-example-template.png (100%) rename {wallet => sdk/evm/connect}/assets/unity-infura.png (100%) rename {wallet => sdk/evm/connect}/assets/wagmi-errors.png (100%) rename {wallet => sdk/evm/connect}/assets/watchasset-nft-2.png (100%) rename {wallet => sdk/evm/connect}/assets/watchasset-nft.png (100%) rename {wallet => sdk/evm/connect}/assets/web3-architecture.png (100%) rename sdk/{connect => evm/connect/get-started}/javascript-wagmi.md (100%) rename sdk/{connect => evm/connect/get-started}/javascript.md (100%) rename sdk/{connect => evm/connect/get-started}/react-native.md (100%) rename sdk/{ => evm/connect}/guides/batch-requests.md (100%) create mode 100644 sdk/evm/connect/guides/best-practices/display.md rename sdk/{guides => evm/connect/guides/best-practices}/production-readiness.md (100%) rename {wallet/how-to => sdk/evm/connect/guides/best-practices}/run-devnet.md (100%) rename {wallet/how-to => sdk/evm/connect/guides}/connect-extension.md (100%) rename wallet/how-to/display/tokens.md => sdk/evm/connect/guides/display-tokens.md (95%) rename sdk/{ => evm/connect}/guides/interact-with-contracts.md (100%) rename sdk/{ => evm/connect}/guides/manage-networks.md (100%) rename sdk/{guides/authenticate-users.md => evm/connect/guides/manage-user-accounts.md} (99%) rename sdk/{guides/handle-transactions.md => evm/connect/guides/send-transactions/index.md} (99%) rename {wallet/how-to => sdk/evm/connect/guides}/send-transactions/send-batch-transactions.md (100%) rename {wallet/how-to => sdk/evm/connect/guides}/sign-data/index.md (100%) rename {wallet/how-to => sdk/evm/connect/guides}/sign-data/siwe.md (100%) rename sdk/{ => evm/connect}/guides/use-deeplinks.md (100%) create mode 100644 sdk/evm/connect/index.md rename sdk/{connect/javascript-dynamic.md => evm/connect/partners/dynamic.md} (100%) rename sdk/{connect/javascript-web3auth.md => evm/connect/partners/web3auth.md} (100%) rename {wallet => sdk/evm/connect}/reference/json-rpc-methods/index.md (100%) rename {wallet => sdk/evm/connect}/reference/provider-api.md (100%) rename sdk/{ => evm/connect}/reference/sdk-methods.md (100%) rename sdk/{ => evm/connect}/reference/sdk-options.md (100%) create mode 100644 sdk/evm/index.md create mode 100644 sdk/multichain/connect/guides/connect-to-multichain.md create mode 100644 sdk/multichain/connect/guides/send-transactions.md create mode 100644 sdk/multichain/connect/index.md rename {wallet => sdk/multichain/connect}/reference/multichain-api.md (100%) create mode 100644 sdk/multichain/connect/tutorials/create-multichain-dapp.md delete mode 100644 sdk/reference/llm-prompt.md delete mode 100644 sdk/reference/supported-platforms.md create mode 100644 sdk/solana/connect/index.md rename wallet/how-to/use-non-evm-networks/solana.md => sdk/solana/connect/wallet-standard.md (98%) create mode 100644 sdk/solana/index.md rename {wallet => sdk/starknet}/assets/starknet-dapp-connected.png (100%) rename {wallet => sdk/starknet}/assets/starknet-metamask-connection.png (100%) rename {wallet => sdk/starknet}/assets/starknet-token-update.png (100%) rename {wallet => sdk/starknet}/assets/starknet-tutorial-connected.png (100%) rename {wallet => sdk/starknet}/assets/starknet-tutorial-modal.png (100%) rename {wallet => sdk/starknet}/assets/starknet-tutorial-start-dapp.png (100%) rename {wallet => sdk/starknet}/assets/starknet-tutorial-transfer-token.png (100%) rename {wallet => sdk/starknet}/assets/starknet-wallet-modal.png (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/concepts}/about-get-starknet.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/connect-to-starknet.md (96%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/manage-starknet-accounts.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/manage-starknet-networks.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/send-starknet-transactions.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/sign-starknet-data.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/guides}/troubleshoot.md (100%) rename {wallet/how-to/use-non-evm-networks => sdk}/starknet/index.md (99%) rename {wallet/reference/non-evm-apis => sdk/starknet/reference}/starknet-snap-api.md (100%) rename {wallet/how-to/use-non-evm-networks/starknet => sdk/starknet/tutorials}/create-a-simple-starknet-dapp.md (98%) delete mode 100644 wallet-sidebar.js diff --git a/docusaurus.config.js b/docusaurus.config.js index dc57a2be935..81287e8ea97 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -251,31 +251,6 @@ const config = { rehypePlugins, }, ], - [ - '@docusaurus/plugin-content-docs', - { - id: 'wallet', - path: 'wallet', - routeBasePath: 'wallet', - editUrl: 'https://github.com/MetaMask/metamask-docs/edit/main/', - sidebarPath: require.resolve('./wallet-sidebar.js'), - breadcrumbs: false, - remarkPlugins, - rehypePlugins, - sidebarItemsGenerator: async function ({ defaultSidebarItemsGenerator, ...args }) { - const sidebarItems = await defaultSidebarItemsGenerator(args) - const dynamicItems = await fetchAndGenerateDynamicSidebarItems( - MM_RPC_URL, - MM_REF_PATH, - NETWORK_NAMES.metamask - ) - if (args.item.dirName === 'reference/json-rpc-methods') { - return [...sidebarItems, ...dynamicItems] - } - return sidebarItems - }, - }, - ], [ '@docusaurus/plugin-content-docs', { diff --git a/sdk-sidebar.js b/sdk-sidebar.js index 6bba0097956..a7d9b8d9fd3 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -1,24 +1,49 @@ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebar = { - sdkSidebar: [ +const sdkSidebar = { + multichain: [ + 'index', { - type: 'doc', - label: 'Introduction', - id: "index", + type: 'category', + label: 'Guides', + collapsible: false, + collapsed: false, + items: [ + 'multichain/connect/guides/connect-to-multichain', + 'multichain/connect/guides/send-transactions', + ], + }, + { + type: 'category', + label: 'Tutorials', + collapsible: false, + collapsed: false, + items: [ + 'multichain/connect/tutorials/create-multichain-dapp', + ], + }, + { + type: 'category', + label: 'Reference', + collapsible: false, + collapsed: false, + items: [ + 'multichain/connect/reference/multichain-api', + ], }, + ], + evm: [ + 'evm/index', { type: 'category', - label: 'Connect to MetaMask', + label: 'Get started', collapsible: false, collapsed: false, items: [ - 'connect/javascript-wagmi', - 'connect/javascript', - 'connect/javascript-dynamic', - 'connect/javascript-web3auth', - 'connect/react-native', + 'evm/connect/get-started/javascript-wagmi', + 'evm/connect/get-started/javascript', + 'evm/connect/get-started/react-native', ], }, { @@ -27,13 +52,53 @@ const sidebar = { collapsible: false, collapsed: false, items: [ - 'guides/authenticate-users', - 'guides/manage-networks', - 'guides/handle-transactions', - 'guides/interact-with-contracts', - 'guides/use-deeplinks', - 'guides/batch-requests', - 'guides/production-readiness', + '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/send-batch-transactions', + ], + }, + { + type: 'category', + label: 'Sign data', + collapsible: true, + collapsed: true, + link: { type: "doc", id: "evm/connect/guides/sign-data/index" }, + items: [ + 'evm/connect/guides/sign-data/siwe', + ], + }, + 'evm/connect/guides/batch-requests', + 'evm/connect/guides/interact-with-contracts', + 'evm/connect/guides/use-deeplinks', + 'evm/connect/guides/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', + ], + }, + ], + }, + { + type: 'category', + label: 'Partner guides', + collapsible: false, + collapsed: false, + items: [ + 'evm/connect/partners/dynamic', + 'evm/connect/partners/web3auth', ], }, { @@ -60,13 +125,68 @@ const sidebar = { collapsible: false, collapsed: false, items: [ - 'reference/llm-prompt', - 'reference/supported-platforms', - 'reference/sdk-options', - 'reference/sdk-methods', + 'evm/connect/reference/sdk-options', + 'evm/connect/reference/sdk-methods', + 'evm/connect/reference/provider-api', + { + type: "category", + label: "JSON-RPC API", + collapsible: true, + collapsed: true, + link: { type: "doc", id: "evm/connect/reference/json-rpc-methods/index" }, + items: [{ type: "autogenerated", dirName: "evm/connect/reference/json-rpc-methods" }], + }, + ], + }, + ], + solana: [ + 'solana/index', + 'solana/connect/wallet-standard', + ], + starknet: [ + 'starknet/index', + { + type: "category", + label: "Guides", + collapsible: false, + collapsed: false, + items: [ + 'starknet/guides/connect-to-starknet', + 'starknet/guides/manage-starknet-accounts', + 'starknet/guides/manage-starknet-networks', + 'starknet/guides/send-starknet-transactions', + 'starknet/guides/sign-starknet-data', + 'starknet/guides/troubleshoot', + ], + }, + { + type: "category", + label: "Concepts", + collapsible: false, + collapsed: false, + items: [ + 'starknet/concepts/about-get-starknet', + ], + }, + { + type: "category", + label: "Tutorials", + collapsible: false, + collapsed: false, + items: [ + 'starknet/tutorials/create-a-simple-starknet-dapp', + ], + }, + { + type: "category", + label: "Reference", + collapsible: false, + collapsed: false, + items: [ + 'starknet/reference/starknet-snap-api', ], }, ], } -module.exports = sidebar +module.exports = sdkSidebar diff --git a/sdk/_assets/connect.gif b/sdk/evm/connect/_assets/connect.gif similarity index 100% rename from sdk/_assets/connect.gif rename to sdk/evm/connect/_assets/connect.gif diff --git a/sdk/_assets/network.gif b/sdk/evm/connect/_assets/network.gif similarity index 100% rename from sdk/_assets/network.gif rename to sdk/evm/connect/_assets/network.gif diff --git a/sdk/_assets/quickstart-dynamic.png b/sdk/evm/connect/_assets/quickstart-dynamic.png similarity index 100% rename from sdk/_assets/quickstart-dynamic.png rename to sdk/evm/connect/_assets/quickstart-dynamic.png diff --git a/sdk/_assets/quickstart-javascript.png b/sdk/evm/connect/_assets/quickstart-javascript.png similarity index 100% rename from sdk/_assets/quickstart-javascript.png rename to sdk/evm/connect/_assets/quickstart-javascript.png diff --git a/sdk/_assets/quickstart-web3auth.png b/sdk/evm/connect/_assets/quickstart-web3auth.png similarity index 100% rename from sdk/_assets/quickstart-web3auth.png rename to sdk/evm/connect/_assets/quickstart-web3auth.png diff --git a/sdk/_assets/quickstart.jpg b/sdk/evm/connect/_assets/quickstart.jpg similarity index 100% rename from sdk/_assets/quickstart.jpg rename to sdk/evm/connect/_assets/quickstart.jpg diff --git a/wallet/assets/add-network.png b/sdk/evm/connect/assets/add-network.png similarity index 100% rename from wallet/assets/add-network.png rename to sdk/evm/connect/assets/add-network.png diff --git a/wallet/assets/contract-abi-converter-dialog.png b/sdk/evm/connect/assets/contract-abi-converter-dialog.png similarity index 100% rename from wallet/assets/contract-abi-converter-dialog.png rename to sdk/evm/connect/assets/contract-abi-converter-dialog.png diff --git a/wallet/assets/custom-modal.gif b/sdk/evm/connect/assets/custom-modal.gif similarity index 100% rename from wallet/assets/custom-modal.gif rename to sdk/evm/connect/assets/custom-modal.gif diff --git a/wallet/assets/personal_sign.png b/sdk/evm/connect/assets/personal_sign.png similarity index 100% rename from wallet/assets/personal_sign.png rename to sdk/evm/connect/assets/personal_sign.png diff --git a/wallet/assets/react-tutorial-01-final.png b/sdk/evm/connect/assets/react-tutorial-01-final.png similarity index 100% rename from wallet/assets/react-tutorial-01-final.png rename to sdk/evm/connect/assets/react-tutorial-01-final.png diff --git a/wallet/assets/react-tutorial-01-start.png b/sdk/evm/connect/assets/react-tutorial-01-start.png similarity index 100% rename from wallet/assets/react-tutorial-01-start.png rename to sdk/evm/connect/assets/react-tutorial-01-start.png diff --git a/wallet/assets/request-permissions-2.png b/sdk/evm/connect/assets/request-permissions-2.png similarity index 100% rename from wallet/assets/request-permissions-2.png rename to sdk/evm/connect/assets/request-permissions-2.png diff --git a/wallet/assets/request-permissions.png b/sdk/evm/connect/assets/request-permissions.png similarity index 100% rename from wallet/assets/request-permissions.png rename to sdk/evm/connect/assets/request-permissions.png diff --git a/wallet/assets/sdk-android-architecture.png b/sdk/evm/connect/assets/sdk-android-architecture.png similarity index 100% rename from wallet/assets/sdk-android-architecture.png rename to sdk/evm/connect/assets/sdk-android-architecture.png diff --git a/wallet/assets/sdk-android-communication.png b/sdk/evm/connect/assets/sdk-android-communication.png similarity index 100% rename from wallet/assets/sdk-android-communication.png rename to sdk/evm/connect/assets/sdk-android-communication.png diff --git a/wallet/assets/sdk-clear-connections.png b/sdk/evm/connect/assets/sdk-clear-connections.png similarity index 100% rename from wallet/assets/sdk-clear-connections.png rename to sdk/evm/connect/assets/sdk-clear-connections.png diff --git a/wallet/assets/signTypedData.png b/sdk/evm/connect/assets/signTypedData.png similarity index 100% rename from wallet/assets/signTypedData.png rename to sdk/evm/connect/assets/signTypedData.png diff --git a/wallet/assets/siwe-bad-domain-2.png b/sdk/evm/connect/assets/siwe-bad-domain-2.png similarity index 100% rename from wallet/assets/siwe-bad-domain-2.png rename to sdk/evm/connect/assets/siwe-bad-domain-2.png diff --git a/wallet/assets/siwe-bad-domain.png b/sdk/evm/connect/assets/siwe-bad-domain.png similarity index 100% rename from wallet/assets/siwe-bad-domain.png rename to sdk/evm/connect/assets/siwe-bad-domain.png diff --git a/wallet/assets/siwe.png b/sdk/evm/connect/assets/siwe.png similarity index 100% rename from wallet/assets/siwe.png rename to sdk/evm/connect/assets/siwe.png diff --git a/wallet/assets/switch-network.png b/sdk/evm/connect/assets/switch-network.png similarity index 100% rename from wallet/assets/switch-network.png rename to sdk/evm/connect/assets/switch-network.png diff --git a/wallet/assets/tutorials/beginner-tutorial/account.png b/sdk/evm/connect/assets/tutorials/beginner-tutorial/account.png similarity index 100% rename from wallet/assets/tutorials/beginner-tutorial/account.png rename to sdk/evm/connect/assets/tutorials/beginner-tutorial/account.png diff --git a/wallet/assets/tutorials/beginner-tutorial/connect.png b/sdk/evm/connect/assets/tutorials/beginner-tutorial/connect.png similarity index 100% rename from wallet/assets/tutorials/beginner-tutorial/connect.png rename to sdk/evm/connect/assets/tutorials/beginner-tutorial/connect.png diff --git a/wallet/assets/tutorials/beginner-tutorial/enable-button.png b/sdk/evm/connect/assets/tutorials/beginner-tutorial/enable-button.png similarity index 100% rename from wallet/assets/tutorials/beginner-tutorial/enable-button.png rename to sdk/evm/connect/assets/tutorials/beginner-tutorial/enable-button.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-01.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-01.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-01.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-01.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-02.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-02.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-02.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-02.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-03.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-03.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-03.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-03.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-04.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-04.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-04.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-04.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-05.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-05.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-05.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-05.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-06.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-06.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-06.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-06.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-07.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-07.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-07.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-07.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-08.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-08.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-08.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-08.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-09.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-09.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-09.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-09.png diff --git a/wallet/assets/tutorials/react-dapp/pt1-10.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt1-10.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt1-10.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt1-10.png diff --git a/wallet/assets/tutorials/react-dapp/pt2-01.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt2-01.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt2-01.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt2-01.png diff --git a/wallet/assets/tutorials/react-dapp/pt2-02.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt2-02.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt2-02.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt2-02.png diff --git a/wallet/assets/tutorials/react-dapp/pt2-03.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt2-03.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt2-03.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt2-03.png diff --git a/wallet/assets/tutorials/react-dapp/pt2-04.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt2-04.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt2-04.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt2-04.png diff --git a/wallet/assets/tutorials/react-dapp/pt2-05.png b/sdk/evm/connect/assets/tutorials/react-dapp/pt2-05.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/pt2-05.png rename to sdk/evm/connect/assets/tutorials/react-dapp/pt2-05.png diff --git a/wallet/assets/tutorials/react-dapp/react-tutorial-02-final-preview.png b/sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-final-preview.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/react-tutorial-02-final-preview.png rename to sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-final-preview.png diff --git a/wallet/assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png b/sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png rename to sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png diff --git a/wallet/assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png b/sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png rename to sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png diff --git a/wallet/assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png b/sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png similarity index 100% rename from wallet/assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png rename to sdk/evm/connect/assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png diff --git a/wallet/assets/unity-empty-template.png b/sdk/evm/connect/assets/unity-empty-template.png similarity index 100% rename from wallet/assets/unity-empty-template.png rename to sdk/evm/connect/assets/unity-empty-template.png diff --git a/wallet/assets/unity-example-template.png b/sdk/evm/connect/assets/unity-example-template.png similarity index 100% rename from wallet/assets/unity-example-template.png rename to sdk/evm/connect/assets/unity-example-template.png diff --git a/wallet/assets/unity-infura.png b/sdk/evm/connect/assets/unity-infura.png similarity index 100% rename from wallet/assets/unity-infura.png rename to sdk/evm/connect/assets/unity-infura.png diff --git a/wallet/assets/wagmi-errors.png b/sdk/evm/connect/assets/wagmi-errors.png similarity index 100% rename from wallet/assets/wagmi-errors.png rename to sdk/evm/connect/assets/wagmi-errors.png diff --git a/wallet/assets/watchasset-nft-2.png b/sdk/evm/connect/assets/watchasset-nft-2.png similarity index 100% rename from wallet/assets/watchasset-nft-2.png rename to sdk/evm/connect/assets/watchasset-nft-2.png diff --git a/wallet/assets/watchasset-nft.png b/sdk/evm/connect/assets/watchasset-nft.png similarity index 100% rename from wallet/assets/watchasset-nft.png rename to sdk/evm/connect/assets/watchasset-nft.png diff --git a/wallet/assets/web3-architecture.png b/sdk/evm/connect/assets/web3-architecture.png similarity index 100% rename from wallet/assets/web3-architecture.png rename to sdk/evm/connect/assets/web3-architecture.png diff --git a/sdk/connect/javascript-wagmi.md b/sdk/evm/connect/get-started/javascript-wagmi.md similarity index 100% rename from sdk/connect/javascript-wagmi.md rename to sdk/evm/connect/get-started/javascript-wagmi.md diff --git a/sdk/connect/javascript.md b/sdk/evm/connect/get-started/javascript.md similarity index 100% rename from sdk/connect/javascript.md rename to sdk/evm/connect/get-started/javascript.md diff --git a/sdk/connect/react-native.md b/sdk/evm/connect/get-started/react-native.md similarity index 100% rename from sdk/connect/react-native.md rename to sdk/evm/connect/get-started/react-native.md diff --git a/sdk/guides/batch-requests.md b/sdk/evm/connect/guides/batch-requests.md similarity index 100% rename from sdk/guides/batch-requests.md rename to sdk/evm/connect/guides/batch-requests.md diff --git a/sdk/evm/connect/guides/best-practices/display.md b/sdk/evm/connect/guides/best-practices/display.md new file mode 100644 index 00000000000..63c98c68f80 --- /dev/null +++ b/sdk/evm/connect/guides/best-practices/display.md @@ -0,0 +1,30 @@ +# Display in MetaMask + +## Display icons + +When your dapp makes a login request to a MetaMask user, MetaMask may render a modal that displays +your dapp icon. + +MetaMask retrieves this icon using the HTML selector ` link[rel="shortcut icon"]`, so you can +follow the [favicon standard](https://en.wikipedia.org/wiki/Favicon) to customize your dapp icon. +Make sure to have a `link` tag within your dapp's `head` with `rel = "shortcut icon"`, as in the +following example. +The tag's `href` attribute is used for assigning the dapp icon. + +```html + + + +``` + +## Display method names + +MetaMask uses the [Ethereum Signature Database](https://www.4byte.directory/) to display +method names on the confirmation screen. +For many common method names, such as token methods, this allows MetaMask to look up the method +names by their [method signature](https://solidity.readthedocs.io/en/v0.4.21/abi-spec.html). +However, sometimes you're using a method that isn't in that database, and MetaMask simply +displays **Contract Interaction** to the user. + +To register your contract's method names so they show in the MetaMask interface, +[submit each method's signature to the Ethereum Signature Database](https://www.4byte.directory/submit/). \ No newline at end of file diff --git a/sdk/guides/production-readiness.md b/sdk/evm/connect/guides/best-practices/production-readiness.md similarity index 100% rename from sdk/guides/production-readiness.md rename to sdk/evm/connect/guides/best-practices/production-readiness.md diff --git a/wallet/how-to/run-devnet.md b/sdk/evm/connect/guides/best-practices/run-devnet.md similarity index 100% rename from wallet/how-to/run-devnet.md rename to sdk/evm/connect/guides/best-practices/run-devnet.md diff --git a/wallet/how-to/connect-extension.md b/sdk/evm/connect/guides/connect-extension.md similarity index 100% rename from wallet/how-to/connect-extension.md rename to sdk/evm/connect/guides/connect-extension.md diff --git a/wallet/how-to/display/tokens.md b/sdk/evm/connect/guides/display-tokens.md similarity index 95% rename from wallet/how-to/display/tokens.md rename to sdk/evm/connect/guides/display-tokens.md index e7c18948ea2..4c78c1ab412 100644 --- a/wallet/how-to/display/tokens.md +++ b/sdk/evm/connect/guides/display-tokens.md @@ -97,10 +97,10 @@ The add NFT interfaces look like the following:
- NFT confirmation + NFT confirmation
- Multiple NFTs confirmation + Multiple NFTs confirmation
diff --git a/sdk/guides/interact-with-contracts.md b/sdk/evm/connect/guides/interact-with-contracts.md similarity index 100% rename from sdk/guides/interact-with-contracts.md rename to sdk/evm/connect/guides/interact-with-contracts.md diff --git a/sdk/guides/manage-networks.md b/sdk/evm/connect/guides/manage-networks.md similarity index 100% rename from sdk/guides/manage-networks.md rename to sdk/evm/connect/guides/manage-networks.md diff --git a/sdk/guides/authenticate-users.md b/sdk/evm/connect/guides/manage-user-accounts.md similarity index 99% rename from sdk/guides/authenticate-users.md rename to sdk/evm/connect/guides/manage-user-accounts.md index 9a51b732043..43fce07694d 100644 --- a/sdk/guides/authenticate-users.md +++ b/sdk/evm/connect/guides/manage-user-accounts.md @@ -4,7 +4,7 @@ keywords: [SDK, Wagmi, JavaScript, authenticate, connect, sign, accounts, wallet toc_max_heading_level: 3 --- -# Authenticate users +# Manage user accounts Connect and manage user wallet sessions in your [Wagmi](#use-wagmi) or [Vanilla JavaScript](#use-vanilla-javascript) dapp. diff --git a/sdk/guides/handle-transactions.md b/sdk/evm/connect/guides/send-transactions/index.md similarity index 99% rename from sdk/guides/handle-transactions.md rename to sdk/evm/connect/guides/send-transactions/index.md index 6d59c39cd2a..5bb63cc7c3a 100644 --- a/sdk/guides/handle-transactions.md +++ b/sdk/evm/connect/guides/send-transactions/index.md @@ -4,7 +4,7 @@ keywords: [SDK, Wagmi, JavaScript, send, transaction, transactions, status, esti toc_max_heading_level: 2 --- -# Handle transactions +# Send transactions Handle EVM transactions in your [Wagmi](#use-wagmi) or [Vanilla JavaScript](#use-vanilla-javascript) dapp. With the SDK, you can: diff --git a/wallet/how-to/send-transactions/send-batch-transactions.md b/sdk/evm/connect/guides/send-transactions/send-batch-transactions.md similarity index 100% rename from wallet/how-to/send-transactions/send-batch-transactions.md rename to sdk/evm/connect/guides/send-transactions/send-batch-transactions.md diff --git a/wallet/how-to/sign-data/index.md b/sdk/evm/connect/guides/sign-data/index.md similarity index 100% rename from wallet/how-to/sign-data/index.md rename to sdk/evm/connect/guides/sign-data/index.md diff --git a/wallet/how-to/sign-data/siwe.md b/sdk/evm/connect/guides/sign-data/siwe.md similarity index 100% rename from wallet/how-to/sign-data/siwe.md rename to sdk/evm/connect/guides/sign-data/siwe.md diff --git a/sdk/guides/use-deeplinks.md b/sdk/evm/connect/guides/use-deeplinks.md similarity index 100% rename from sdk/guides/use-deeplinks.md rename to sdk/evm/connect/guides/use-deeplinks.md diff --git a/sdk/evm/connect/index.md b/sdk/evm/connect/index.md new file mode 100644 index 00000000000..69d47435ddc --- /dev/null +++ b/sdk/evm/connect/index.md @@ -0,0 +1 @@ +# MM Connect for EVM networks \ No newline at end of file diff --git a/sdk/connect/javascript-dynamic.md b/sdk/evm/connect/partners/dynamic.md similarity index 100% rename from sdk/connect/javascript-dynamic.md rename to sdk/evm/connect/partners/dynamic.md diff --git a/sdk/connect/javascript-web3auth.md b/sdk/evm/connect/partners/web3auth.md similarity index 100% rename from sdk/connect/javascript-web3auth.md rename to sdk/evm/connect/partners/web3auth.md diff --git a/wallet/reference/json-rpc-methods/index.md b/sdk/evm/connect/reference/json-rpc-methods/index.md similarity index 100% rename from wallet/reference/json-rpc-methods/index.md rename to sdk/evm/connect/reference/json-rpc-methods/index.md diff --git a/wallet/reference/provider-api.md b/sdk/evm/connect/reference/provider-api.md similarity index 100% rename from wallet/reference/provider-api.md rename to sdk/evm/connect/reference/provider-api.md diff --git a/sdk/reference/sdk-methods.md b/sdk/evm/connect/reference/sdk-methods.md similarity index 100% rename from sdk/reference/sdk-methods.md rename to sdk/evm/connect/reference/sdk-methods.md diff --git a/sdk/reference/sdk-options.md b/sdk/evm/connect/reference/sdk-options.md similarity index 100% rename from sdk/reference/sdk-options.md rename to sdk/evm/connect/reference/sdk-options.md diff --git a/sdk/evm/index.md b/sdk/evm/index.md new file mode 100644 index 00000000000..9cf5c111052 --- /dev/null +++ b/sdk/evm/index.md @@ -0,0 +1,9 @@ +--- +sidebar_label: Introduction +--- + +# MetaMask SDK for EVM networks + +## MM Connect + +## MM Pay \ No newline at end of file diff --git a/sdk/index.md b/sdk/index.md index 26995f44f00..991f01faf9d 100644 --- a/sdk/index.md +++ b/sdk/index.md @@ -1,6 +1,5 @@ --- -slug: / -title: SDK introduction +sidebar_label: Introduction description: Introduction page for MetaMask SDK documentation. keywords: [connect, sdk, integrate, dapp] --- diff --git a/sdk/multichain/connect/guides/connect-to-multichain.md b/sdk/multichain/connect/guides/connect-to-multichain.md new file mode 100644 index 00000000000..5e3d6e8d72b --- /dev/null +++ b/sdk/multichain/connect/guides/connect-to-multichain.md @@ -0,0 +1 @@ +# Connect to multiple chains \ No newline at end of file diff --git a/sdk/multichain/connect/guides/send-transactions.md b/sdk/multichain/connect/guides/send-transactions.md new file mode 100644 index 00000000000..4800ecae379 --- /dev/null +++ b/sdk/multichain/connect/guides/send-transactions.md @@ -0,0 +1 @@ +# Send multichain transactions \ No newline at end of file diff --git a/sdk/multichain/connect/index.md b/sdk/multichain/connect/index.md new file mode 100644 index 00000000000..6b6c55dfb4b --- /dev/null +++ b/sdk/multichain/connect/index.md @@ -0,0 +1 @@ +# MM Connect for multichain \ No newline at end of file diff --git a/wallet/reference/multichain-api.md b/sdk/multichain/connect/reference/multichain-api.md similarity index 100% rename from wallet/reference/multichain-api.md rename to sdk/multichain/connect/reference/multichain-api.md diff --git a/sdk/multichain/connect/tutorials/create-multichain-dapp.md b/sdk/multichain/connect/tutorials/create-multichain-dapp.md new file mode 100644 index 00000000000..42c5585f507 --- /dev/null +++ b/sdk/multichain/connect/tutorials/create-multichain-dapp.md @@ -0,0 +1 @@ +# Create a multichain dapp \ No newline at end of file diff --git a/sdk/reference/llm-prompt.md b/sdk/reference/llm-prompt.md deleted file mode 100644 index 3404f00e18a..00000000000 --- a/sdk/reference/llm-prompt.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -description: Context about MetaMask SDK that can be provided to an LLM. -keywords: [SDK, LLM, intro, context, integration, dapp] ---- - -# LLM prompt - -The following text is a condensed introduction to the MetaMask SDK, for use in an LLM's limited context. -You can copy and paste it into an LLM-based chatbot such as [ChatGPT](https://chatgpt.com/) to provide context about the toolkit. - -Copy the following text by selecting the copy icon in the upper right corner of the text block: - -````text -You are a helpful assistant with expertise in MetaMask SDK integration. -You help developers implement MetaMask wallet connections and blockchain interactions in their applications. - -Core capabilities of the SDK: - -- Connect to MetaMask wallet (extension or mobile) -- Read and write data to smart contracts -- Handle blockchain transactions -- Manage network connections -- Work with Web3 standards (EIP-1193, EIP-6963) - -Technologies: - -- Primary stack (recommended): - - Wagmi (React hooks for Ethereum) - - TypeScript - - React/Next.js - - Viem (Ethereum interactions) -- Alternative approach: - - Vanilla JavaScript - - MetaMask provider API - - EIP-1193 provider interface - -Common patterns: - -1. Wallet connection - - Using Wagmi (Recommended): - - ```js - import { useConnect } from "wagmi" - - function Connect() { - const { connect, connectors } = useConnect() - return ( - - ) - } - ``` - - Using Vanilla JS: - - ```js - const provider = window.ethereum; - const accounts = await provider.request({ - method: "eth_requestAccounts" - }); - ``` - -2. Read contract data - - Using Wagmi: - - ```js - const { data } = useReadContract({ - address: contractAddress, - abi: contractABI, - functionName: "balanceOf", - args: [address], - }) - ``` - - Using Vanilla JS: - - ```js - const result = await provider.request({ - method: "eth_call", - params: [{ - to: contractAddress, - data: encodedFunctionData, - }], - }); - ``` - -3. Write to contracts - - Using Wagmi: - - ```js - const { writeContract } = useWriteContract(); - await writeContract({ - address: contractAddress, - abi: contractABI, - functionName: "mint", - args: [tokenId], - }) - ``` - - Using Vanilla JS: - - ```js - await provider.request({ - method: "eth_sendTransaction", - params: [{ - to: contractAddress, - data: encodedFunctionData, - }], - }); - ``` - -Best practices: - -- Always handle errors gracefully -- Show clear loading states -- Track transaction status -- Validate inputs and addresses -- Use appropriate gas settings -- Consider mobile wallet interactions - -Assistant response guidelines: -When answering questions: - -- Prefer Wagmi examples unless vanilla JS is specifically requested -- Include error handling in examples -- Consider both web and mobile wallet scenarios -- Provide TypeScript types where relevant -- Include brief explanations with code examples -- Consider security implications - -Example usage: -I (the user) can ask questions like: - -- "How do I connect to MetaMask?" -- "How do I read a token balance?" -- "How do I send a transaction?" -- "How do I handle network changes?" -- "How do I implement wallet disconnection?" -- "How do I add error handling for contract calls?" - -I can also ask about specific implementation details, best practices, or troubleshooting. -```` diff --git a/sdk/reference/supported-platforms.md b/sdk/reference/supported-platforms.md deleted file mode 100644 index f8637c8cb91..00000000000 --- a/sdk/reference/supported-platforms.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -description: Supported dapp platforms for MetaMask SDK. -keywords: [SDK, supported, platforms, desktop, mobile, dapp] ---- - -# Supported platforms - -With MetaMask SDK, you can connect your dapp to MetaMask in the following ways: - -- **Desktop web dapps** - Automatically connect to the MetaMask extension, or connect to the MetaMask mobile app using a QR code. - -- **Mobile dapps** - The SDK generates a deeplink that takes users directly to the MetaMask mobile app. - -The following table expands on the SDK's connection methods: - -| Dapp location | User wallet location | Connection method | MetaMask SDK | Other SDKs | -|---------------|-------------|------------------|--------------------------|--------------------------| -| Desktop web | Wallet browser extension | Automatic connection via browser extension | Supported | Supported | -| Desktop web | Wallet mobile app | QR code scan with wallet mobile app | Supported | Limited | -| Mobile browser | Wallet mobile app | Deeplink directly to wallet mobile app | Supported | Limited | -| Mobile dapp | Wallet mobile app | Deeplink directly to wallet mobile app | Supported | Limited | - -
- -:::tip -For a better user experience on mobile, 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. -::: diff --git a/sdk/solana/connect/index.md b/sdk/solana/connect/index.md new file mode 100644 index 00000000000..9fe6c06468e --- /dev/null +++ b/sdk/solana/connect/index.md @@ -0,0 +1 @@ +# MM Connect for Solana \ No newline at end of file diff --git a/wallet/how-to/use-non-evm-networks/solana.md b/sdk/solana/connect/wallet-standard.md similarity index 98% rename from wallet/how-to/use-non-evm-networks/solana.md rename to sdk/solana/connect/wallet-standard.md index 1dc2842e10c..46c14c1c3c5 100644 --- a/wallet/how-to/use-non-evm-networks/solana.md +++ b/sdk/solana/connect/wallet-standard.md @@ -2,7 +2,7 @@ description: Interact with users' Solana accounts in MetaMask. --- -# Solana +# Connect to Solana [Solana](https://solana.com/) is a high-performance, non-EVM network that provides fast transaction speeds and low fees. You can interact with users' Solana accounts in MetaMask using the [Wallet Standard](#wallet-standard) or [third-party libraries](#third-party-libraries) for Solana dapps. diff --git a/sdk/solana/index.md b/sdk/solana/index.md new file mode 100644 index 00000000000..90f5d23dfd8 --- /dev/null +++ b/sdk/solana/index.md @@ -0,0 +1,5 @@ +--- +sidebar_label: Introduction +--- + +# MetaMask SDK for Solana \ No newline at end of file diff --git a/wallet/assets/starknet-dapp-connected.png b/sdk/starknet/assets/starknet-dapp-connected.png similarity index 100% rename from wallet/assets/starknet-dapp-connected.png rename to sdk/starknet/assets/starknet-dapp-connected.png diff --git a/wallet/assets/starknet-metamask-connection.png b/sdk/starknet/assets/starknet-metamask-connection.png similarity index 100% rename from wallet/assets/starknet-metamask-connection.png rename to sdk/starknet/assets/starknet-metamask-connection.png diff --git a/wallet/assets/starknet-token-update.png b/sdk/starknet/assets/starknet-token-update.png similarity index 100% rename from wallet/assets/starknet-token-update.png rename to sdk/starknet/assets/starknet-token-update.png diff --git a/wallet/assets/starknet-tutorial-connected.png b/sdk/starknet/assets/starknet-tutorial-connected.png similarity index 100% rename from wallet/assets/starknet-tutorial-connected.png rename to sdk/starknet/assets/starknet-tutorial-connected.png diff --git a/wallet/assets/starknet-tutorial-modal.png b/sdk/starknet/assets/starknet-tutorial-modal.png similarity index 100% rename from wallet/assets/starknet-tutorial-modal.png rename to sdk/starknet/assets/starknet-tutorial-modal.png diff --git a/wallet/assets/starknet-tutorial-start-dapp.png b/sdk/starknet/assets/starknet-tutorial-start-dapp.png similarity index 100% rename from wallet/assets/starknet-tutorial-start-dapp.png rename to sdk/starknet/assets/starknet-tutorial-start-dapp.png diff --git a/wallet/assets/starknet-tutorial-transfer-token.png b/sdk/starknet/assets/starknet-tutorial-transfer-token.png similarity index 100% rename from wallet/assets/starknet-tutorial-transfer-token.png rename to sdk/starknet/assets/starknet-tutorial-transfer-token.png diff --git a/wallet/assets/starknet-wallet-modal.png b/sdk/starknet/assets/starknet-wallet-modal.png similarity index 100% rename from wallet/assets/starknet-wallet-modal.png rename to sdk/starknet/assets/starknet-wallet-modal.png diff --git a/wallet/how-to/use-non-evm-networks/starknet/about-get-starknet.md b/sdk/starknet/concepts/about-get-starknet.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/about-get-starknet.md rename to sdk/starknet/concepts/about-get-starknet.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/connect-to-starknet.md b/sdk/starknet/guides/connect-to-starknet.md similarity index 96% rename from wallet/how-to/use-non-evm-networks/starknet/connect-to-starknet.md rename to sdk/starknet/guides/connect-to-starknet.md index 1e209780349..dfc670718c2 100644 --- a/wallet/how-to/use-non-evm-networks/starknet/connect-to-starknet.md +++ b/sdk/starknet/guides/connect-to-starknet.md @@ -208,17 +208,17 @@ When a user connects to MetaMask, `get-starknet` requests the user to connect to
- Starknet wallet modal + Starknet wallet modal
- Starknet MetaMask connection request + Starknet MetaMask connection request
After the user connects to Starknet, the dapp displays the user's connected wallet and wallet address:

- Connected Starknet dapp + Connected Starknet dapp

:::note diff --git a/wallet/how-to/use-non-evm-networks/starknet/manage-starknet-accounts.md b/sdk/starknet/guides/manage-starknet-accounts.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/manage-starknet-accounts.md rename to sdk/starknet/guides/manage-starknet-accounts.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/manage-starknet-networks.md b/sdk/starknet/guides/manage-starknet-networks.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/manage-starknet-networks.md rename to sdk/starknet/guides/manage-starknet-networks.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/send-starknet-transactions.md b/sdk/starknet/guides/send-starknet-transactions.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/send-starknet-transactions.md rename to sdk/starknet/guides/send-starknet-transactions.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/sign-starknet-data.md b/sdk/starknet/guides/sign-starknet-data.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/sign-starknet-data.md rename to sdk/starknet/guides/sign-starknet-data.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md b/sdk/starknet/guides/troubleshoot.md similarity index 100% rename from wallet/how-to/use-non-evm-networks/starknet/troubleshoot.md rename to sdk/starknet/guides/troubleshoot.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/index.md b/sdk/starknet/index.md similarity index 99% rename from wallet/how-to/use-non-evm-networks/starknet/index.md rename to sdk/starknet/index.md index a222d4b985b..cf3fc1ce634 100644 --- a/wallet/how-to/use-non-evm-networks/starknet/index.md +++ b/sdk/starknet/index.md @@ -1,4 +1,5 @@ --- +sidebar_label: Introduction description: Interact with users' Starknet accounts in MetaMask. --- diff --git a/wallet/reference/non-evm-apis/starknet-snap-api.md b/sdk/starknet/reference/starknet-snap-api.md similarity index 100% rename from wallet/reference/non-evm-apis/starknet-snap-api.md rename to sdk/starknet/reference/starknet-snap-api.md diff --git a/wallet/how-to/use-non-evm-networks/starknet/create-a-simple-starknet-dapp.md b/sdk/starknet/tutorials/create-a-simple-starknet-dapp.md similarity index 98% rename from wallet/how-to/use-non-evm-networks/starknet/create-a-simple-starknet-dapp.md rename to sdk/starknet/tutorials/create-a-simple-starknet-dapp.md index e4336f8e0d8..78854879579 100644 --- a/wallet/how-to/use-non-evm-networks/starknet/create-a-simple-starknet-dapp.md +++ b/sdk/starknet/tutorials/create-a-simple-starknet-dapp.md @@ -257,7 +257,7 @@ yarn start You are directed to the default dapp display.

- Starknet dapp start + Starknet dapp start

When you select **Connect**, `get-starknet` displays a modal that detects MetaMask and allows you to @@ -265,13 +265,13 @@ choose which Starknet wallet to connect to. Follow the on-screen prompts to connect your MetaMask wallet to Starknet.

- Starknet dapp select wallet + Starknet dapp select wallet

After you accept the terms in the prompts, your wallet is connected and its information is displayed.

- Starknet dapp connected + Starknet dapp connected

## 4. Display the balance of and transfer an ERC-20 token @@ -1613,7 +1613,7 @@ yarn start After connecting to MetaMask, the dapp should display your STRK token balance:

- Starknet transfer token + Starknet transfer token

You can select **Transfer 1 STRK** to make a transfer to the recipient address specified in [Step 4.5](#45-transfer-tokens). diff --git a/src/components/CustomReferencePage/index.tsx b/src/components/CustomReferencePage/index.tsx index 794591847f1..5fd95223336 100644 --- a/src/components/CustomReferencePage/index.tsx +++ b/src/components/CustomReferencePage/index.tsx @@ -5,7 +5,7 @@ import DocSidebar from '@theme/DocSidebar' import { useLocation } from '@docusaurus/router' import { prepareLinkItems, MM_REF_PATH } from '@site/src/plugins/plugin-json-rpc' import styles from './styles.module.css' -const sidebar = require('../../../wallet-sidebar.js') +const sidebar = require('../../../sdk-sidebar.js') function transformItems(items, dynamicItems) { return items.map(item => { @@ -36,9 +36,9 @@ function transformItems(items, dynamicItems) { newItem.href = newItem.href.slice(0, -5) } if (newItem.href === '/') { - newItem.href = '/wallet/' + newItem.href = '/sdk/' } else { - newItem.href = `/wallet${newItem.href}` + newItem.href = `/sdk${newItem.href}` } } return newItem @@ -50,9 +50,9 @@ const CustomReferencePage = props => { const { pathname } = useLocation() const refItems = prepareLinkItems(props.methodsData, MM_REF_PATH).map(item => ({ ...item, - href: item.href.replace('/wallet', ''), + href: item.href.replace('/sdk', ''), })) - const updatedSidebar = transformItems(sidebar.walletSidebar, refItems) + const updatedSidebar = transformItems(sidebar.sdkSidebar, refItems) return (
diff --git a/src/components/NavDropdown/Products.html b/src/components/NavDropdown/Products.html index 43982840b03..5756af966aa 100644 --- a/src/components/NavDropdown/Products.html +++ b/src/components/NavDropdown/Products.html @@ -113,28 +113,6 @@

MetaMask SDK

- -
  • - - - - - - - - - - -
    -

    Wallet API

    -

    Make direct calls to the MetaMask extension.

    -
    -
    -
  • diff --git a/src/components/SidebarSectionDropdown/configs.ts b/src/components/SidebarSectionDropdown/configs.ts index 36fe3c7f137..c6908e8d433 100644 --- a/src/components/SidebarSectionDropdown/configs.ts +++ b/src/components/SidebarSectionDropdown/configs.ts @@ -23,29 +23,6 @@ export const SERVICES_DASHBOARD_CONFIG: SidebarSectionDropdownProps = { defaultSection: 'services', } -export const SDK_WALLET_CONFIG: SidebarSectionDropdownProps = { - sections: [ - { - key: 'sdk', - label: 'SDK', - title: 'MetaMask SDK', - description: 'Connect to MetaMask extension and mobile', - path: '/sdk/', - pathPattern: '/sdk', - }, - { - key: 'wallet', - label: 'Wallet API', - title: 'Wallet API', - description: 'Connect to MetaMask extension only', - path: '/wallet/', - pathPattern: '/wallet', - }, - ], - dropdownLabel: 'Product:', - defaultSection: 'sdk', -} - export const SNAPS_CONFIG: SidebarStaticTitleProps = { title: 'Snaps', pathPattern: '/snaps', diff --git a/src/components/SubNavBar/configs.ts b/src/components/SubNavBar/configs.ts index 8d04f90f891..6ecff5d080d 100644 --- a/src/components/SubNavBar/configs.ts +++ b/src/components/SubNavBar/configs.ts @@ -37,8 +37,38 @@ export const EMBEDDED_WALLETS_SUBNAV_CONFIG: SubNavBarConfig = { ], } +export const SDK_SUBNAV_CONFIG: SubNavBarConfig = { + pathPattern: '/sdk', + sectionName: 'MetaMask SDK', + links: [ + { + key: 'multichain', + label: 'Multichain', + path: '/sdk', + }, + { + key: 'evm', + label: 'EVM', + path: '/sdk/evm/', + }, + { + key: 'solana', + label: 'Solana', + path: '/sdk/solana/', + }, + { + key: 'starknet', + label: 'Starknet', + path: '/sdk/starknet/', + }, + ], +} + // Array of all sub nav configs for easy iteration -export const ALL_SUBNAV_CONFIGS = [EMBEDDED_WALLETS_SUBNAV_CONFIG] +export const ALL_SUBNAV_CONFIGS = [ + EMBEDDED_WALLETS_SUBNAV_CONFIG, + SDK_SUBNAV_CONFIG, +] // Helper function to get config for current path export function getSubNavConfigForPath(pathname: string): SubNavBarConfig | null { diff --git a/src/plugins/plugin-json-rpc.ts b/src/plugins/plugin-json-rpc.ts index 10b2d8de420..385005e24e3 100644 --- a/src/plugins/plugin-json-rpc.ts +++ b/src/plugins/plugin-json-rpc.ts @@ -70,7 +70,7 @@ async function fetchMultipleData( export const RPC_NETWORK_URL = 'https://sot-network-methods.vercel.app/specs' export const MM_RPC_URL = 'https://metamask.github.io/api-specs/latest/openrpc.json' -export const MM_REF_PATH = 'wallet/reference/json-rpc-methods' +export const MM_REF_PATH = 'sdk/evm/connect/reference/json-rpc-methods' export enum NETWORK_NAMES { linea = 'linea', diff --git a/src/theme/DocSidebar/Desktop/index.tsx b/src/theme/DocSidebar/Desktop/index.tsx index fb6873dd787..557e56d22d1 100644 --- a/src/theme/DocSidebar/Desktop/index.tsx +++ b/src/theme/DocSidebar/Desktop/index.tsx @@ -11,7 +11,6 @@ import SidebarSectionDropdown, { } from '@site/src/components/SidebarSectionDropdown/SidebarSectionDropdown' import { SERVICES_DASHBOARD_CONFIG, - SDK_WALLET_CONFIG, SNAPS_CONFIG, DELEGATION_TOOLKIT_CONFIG, isPathInSections, @@ -40,7 +39,6 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { location.pathname, SERVICES_DASHBOARD_CONFIG.sections ) - const isSDKOrWallet = isPathInSections(location.pathname, SDK_WALLET_CONFIG.sections) const isSnaps = location.pathname.startsWith('/snaps') let delegationToolkitTitle = null @@ -75,19 +73,6 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { console.error('Failed to render services dropdown:', e) } - let sdkDropdown = null - try { - if (isSDKOrWallet) { - sdkDropdown = ( -
    - -
    - ) - } - } catch (e) { - console.error('Failed to render SDK dropdown:', e) - } - let snapsTitle = null try { if (isSnaps) { @@ -112,7 +97,6 @@ function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { {delegationToolkitTitle} {versionDropdown} {servicesDropdown} - {sdkDropdown} {snapsTitle} {hideable && } diff --git a/src/theme/DocSidebar/Mobile/index.tsx b/src/theme/DocSidebar/Mobile/index.tsx index 9903e5e73db..9dd7b17276d 100644 --- a/src/theme/DocSidebar/Mobile/index.tsx +++ b/src/theme/DocSidebar/Mobile/index.tsx @@ -14,7 +14,6 @@ import SidebarSectionDropdown, { } from '@site/src/components/SidebarSectionDropdown/SidebarSectionDropdown' import { SERVICES_DASHBOARD_CONFIG, - SDK_WALLET_CONFIG, SNAPS_CONFIG, DELEGATION_TOOLKIT_CONFIG, isPathInSections, @@ -31,7 +30,6 @@ const DocSidebarMobileSecondaryMenu: NavbarSecondaryMenuComponent = ({ si location.pathname, SERVICES_DASHBOARD_CONFIG.sections ) - const isSDKOrWallet = isPathInSections(location.pathname, SDK_WALLET_CONFIG.sections) const isSnaps = location.pathname.startsWith('/snaps') return ( @@ -51,11 +49,6 @@ const DocSidebarMobileSecondaryMenu: NavbarSecondaryMenuComponent = ({ si )} - {isSDKOrWallet && ( -
  • - -
  • - )} {isSnaps && (
  • diff --git a/wallet-sidebar.js b/wallet-sidebar.js deleted file mode 100644 index 2a1ea33cfaf..00000000000 --- a/wallet-sidebar.js +++ /dev/null @@ -1,287 +0,0 @@ -// @ts-check - -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebar = { - walletSidebar: [ - { - type: "doc", - label: "Introduction", - id: "index", - }, - { - type: "category", - label: "How to", - collapsible: true, - collapsed: false, - items: [ - { - type: "doc", - label: "Connect to the extension", - id: "how-to/connect-extension" - }, - { - type: "doc", - label: "Access a user's accounts", - id: "how-to/access-accounts" - }, - { - type: "category", - label: "Manage networks", - collapsible: true, - collapsed: true, - items: [ - { - type: "doc", - label: "Detect a user's network", - id: "how-to/manage-networks/detect-network" - }, - { - type: "doc", - label: "Add a network", - id: "how-to/manage-networks/add-network" - }, - { - type: "doc", - label: "Interact with multiple networks simultaneously", - id: "how-to/manage-networks/use-multichain" - } - ] - }, - { - type: "category", - label: "Sign data", - collapsible: true, - collapsed: true, - link: { type: "doc", id: "how-to/sign-data/index" }, - items: [ - { - type: "doc", - label: "Sign in with Ethereum", - id: "how-to/sign-data/siwe" - } - ] - }, - { - type: "category", - label: "Send transactions", - collapsible: true, - collapsed: true, - link: {type: "doc", id: "how-to/send-transactions/index" }, - items: [ - { - type: "doc", - label: "Send batch transactions", - id: "how-to/send-transactions/send-batch-transactions" - } - ] - }, - { - type: "category", - label: "Display in MetaMask", - collapsible: true, - collapsed: true, - items: [ - { - type: "doc", - label: "Display tokens", - id: "how-to/display/tokens" - }, - { - type: "doc", - label: "Display a contract's method names", - id: "how-to/display/method-names" - }, - { - type: "doc", - label: "Display a dapp icon", - id: "how-to/display/icon" - } - ] - }, - { - type: "doc", - label: "Manage permissions", - id: "how-to/manage-permissions" - }, - { - type: "category", - label: "Use non-EVM networks", - collapsible: true, - collapsed: true, - link: { type: "doc", id: "how-to/use-non-evm-networks/index" }, - items: [ - { - type: "doc", - label: "Solana", - id: "how-to/use-non-evm-networks/solana" - }, - { - type: "category", - label: "Starknet", - link: { type: "doc", id: "how-to/use-non-evm-networks/starknet/index" }, - items: [ - { - type: "doc", - label: "Connect to Starknet", - id: "how-to/use-non-evm-networks/starknet/connect-to-starknet" - }, - { - type: "doc", - label: "Manage Starknet accounts", - id: "how-to/use-non-evm-networks/starknet/manage-starknet-accounts" - }, - { - type: "doc", - label: "Manage Starknet networks", - id: "how-to/use-non-evm-networks/starknet/manage-starknet-networks" - }, - { - type: "doc", - label: "Send Starknet transactions", - id: "how-to/use-non-evm-networks/starknet/send-starknet-transactions" - }, - { - type: "doc", - label: "Sign Starknet transactions", - id: "how-to/use-non-evm-networks/starknet/sign-starknet-data" - }, - { - type: "doc", - label: "Create a simple Starknet dapp", - id: "how-to/use-non-evm-networks/starknet/create-a-simple-starknet-dapp" - }, - { - type: "doc", - label: "Troubleshoot", - id: "how-to/use-non-evm-networks/starknet/troubleshoot" - }, - { - type: "doc", - label: "About get-starknet", - id: "how-to/use-non-evm-networks/starknet/about-get-starknet" - } - ] - } - ] - }, - { - type: "doc", - label: "Onboard users", - id: "how-to/onboard-users" - }, - { - type: "doc", - label: "Run a development network", - id: "how-to/run-devnet" - }, - { - type: "doc", - label: "Secure your dapp", - id: "how-to/secure-dapp" - } - ], - }, - { - type: "category", - label: "Concepts", - collapsible: true, - collapsed: true, - items: [ - { - type: "doc", - label: "About the Wallet API", - id: "concepts/wallet-api" - }, - { - type: "doc", - label: "About the Multichain API", - id: "concepts/multichain-api" - }, - { - type: "doc", - label: "Convenience libraries", - id: "concepts/convenience-libraries" - }, - { - type: "doc", - label: "Signing methods", - id: "concepts/signing-methods" - }, - { - type: "doc", - label: "Wallet interoperability", - id: "concepts/wallet-interoperability" - }, - { - type: "doc", - label: "Smart contracts", - id: "concepts/smart-contracts" - }, - ], - }, - { - type: "category", - label: "Tutorials", - collapsible: true, - collapsed: true, - items: [ - { - type: "doc", - label: "Create a React dapp with local state", - id: "tutorials/react-dapp-local-state", - }, - { - type: "doc", - label: "Create a React dapp with global state", - id: "tutorials/react-dapp-global-state", - }, - { - type: "doc", - label: "Create a simple dapp", - id: "tutorials/javascript-dapp-simple", - }, - ], - }, - { - type: "category", - label: "Reference", - collapsible: true, - collapsed: false, - items: [ - { - type: "category", - label: "Non-EVM APIs", - collapsible: true, - collapsed: true, - items: [ - { - type: "doc", - label: "Starknet Snap API", - id: "reference/non-evm-apis/starknet-snap-api", - }, - ], - }, - { - type: "doc", - label: "Ethereum provider API", - id: "reference/provider-api", - }, - { - type: "doc", - label: "Multichain API", - id: "reference/multichain-api", - }, - { - type: "category", - label: "JSON-RPC API", - collapsible: true, - collapsed: true, - link: { type: "doc", id: "reference/json-rpc-methods/index" }, - items: [{ type: "autogenerated", dirName: "reference/json-rpc-methods" }], - }, - ], - }, - ], -}; - -module.exports = sidebar; From 44a62485b0c0ba1b9614ba9c3a6df5994985645a Mon Sep 17 00:00:00 2001 From: Alexandra Tran Date: Wed, 24 Sep 2025 14:50:16 -0700 Subject: [PATCH 02/17] edits --- sdk-sidebar.js | 18 +++- sdk/about.md | 26 ++++++ sdk/evm/connect/index.md | 1 - sdk/evm/index.md | 45 +++++++++- sdk/index.md | 84 ++++++------------- .../connect/guides/connect-to-multichain.md | 2 +- .../connect/guides/connector-libraries.md | 1 + .../connect/guides/send-transactions.md | 6 +- sdk/multichain/connect/index.md | 1 - sdk/multichain/index.md | 6 ++ .../connect => }/reference/sdk-options.md | 0 sdk/solana/connect/index.md | 1 - sdk/solana/connect/wallet-standard.md | 33 -------- sdk/solana/index.md | 30 ++++++- sdk/starknet/index.md | 4 +- src/components/CustomReferencePage/index.tsx | 10 ++- src/components/NavDropdown/Products.html | 49 +++++++++-- src/components/SubNavBar/configs.ts | 7 +- 18 files changed, 205 insertions(+), 119 deletions(-) create mode 100644 sdk/about.md delete mode 100644 sdk/evm/connect/index.md create mode 100644 sdk/multichain/connect/guides/connector-libraries.md delete mode 100644 sdk/multichain/connect/index.md create mode 100644 sdk/multichain/index.md rename sdk/{evm/connect => }/reference/sdk-options.md (100%) delete mode 100644 sdk/solana/connect/index.md delete mode 100644 sdk/solana/connect/wallet-standard.md diff --git a/sdk-sidebar.js b/sdk-sidebar.js index a7d9b8d9fd3..9de8a3cfce2 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -2,8 +2,21 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sdkSidebar = { - multichain: [ + overview: [ 'index', + 'about', + { + type: 'category', + label: 'Reference', + collapsible: false, + collapsed: false, + items: [ + 'reference/sdk-options', + ], + }, + ], + multichain: [ + 'multichain/index', { type: 'category', label: 'Guides', @@ -12,6 +25,7 @@ const sdkSidebar = { items: [ 'multichain/connect/guides/connect-to-multichain', 'multichain/connect/guides/send-transactions', + 'multichain/connect/guides/connector-libraries', ], }, { @@ -125,7 +139,6 @@ const sdkSidebar = { collapsible: false, collapsed: false, items: [ - 'evm/connect/reference/sdk-options', 'evm/connect/reference/sdk-methods', 'evm/connect/reference/provider-api', { @@ -141,7 +154,6 @@ const sdkSidebar = { ], solana: [ 'solana/index', - 'solana/connect/wallet-standard', ], starknet: [ 'starknet/index', diff --git a/sdk/about.md b/sdk/about.md new file mode 100644 index 00000000000..2eaa20f7db7 --- /dev/null +++ b/sdk/about.md @@ -0,0 +1,26 @@ +# About the SDK + +## Architecture + +## Supported platforms + +With MetaMask SDK, you can connect your dapp to MetaMask in the following ways: + +- **Desktop web dapps** - Automatically connect to the MetaMask extension, or connect to the MetaMask mobile app using a QR code. +- **Mobile dapps** - The SDK generates a deeplink that takes users directly to the MetaMask mobile app. + +The following table expands on the SDK's connection methods: + +| Dapp location | User wallet location | Connection method | MetaMask SDK | Other SDKs | +|---------------|-------------|------------------|--------------------------|--------------------------| +| Desktop web | Wallet browser extension | Automatic connection via browser extension | Supported | Supported | +| Desktop web | Wallet mobile app | QR code scan with wallet mobile app | Supported | Limited | +| Mobile browser | Wallet mobile app | Deeplink directly to wallet mobile app | Supported | Limited | +| Mobile dapp | Wallet mobile app | Deeplink directly to wallet mobile app | Supported | Limited | + +
    + +:::tip +For a better user experience on mobile, 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. +::: \ No newline at end of file diff --git a/sdk/evm/connect/index.md b/sdk/evm/connect/index.md deleted file mode 100644 index 69d47435ddc..00000000000 --- a/sdk/evm/connect/index.md +++ /dev/null @@ -1 +0,0 @@ -# MM Connect for EVM networks \ No newline at end of file diff --git a/sdk/evm/index.md b/sdk/evm/index.md index 9cf5c111052..34eca37d910 100644 --- a/sdk/evm/index.md +++ b/sdk/evm/index.md @@ -2,8 +2,47 @@ sidebar_label: Introduction --- -# MetaMask SDK for EVM networks +import CardList from '@site/src/components/CardList' -## MM Connect +# Connect to EVM networks -## MM Pay \ No newline at end of file +## Supported platforms and libraries + +MetaMask SDK is available in a variety of ways to make integration as easy as possible. +You can access it directly via npm, through popular developer libraries like Wagmi, or as part of popular convenience libraries. + + diff --git a/sdk/index.md b/sdk/index.md index 991f01faf9d..e110dbe8409 100644 --- a/sdk/index.md +++ b/sdk/index.md @@ -4,81 +4,47 @@ description: Introduction page for MetaMask SDK documentation. keywords: [connect, sdk, integrate, dapp] --- -import Button from '@site/src/components/elements/buttons/button' import CardList from '@site/src/components/CardList' # Seamlessly connect to MetaMask using the SDK MetaMask SDK enables a fast, reliable, and seamless connection from your dapp to the MetaMask extension and MetaMask mobile app. -With the SDK, you can easily onboard users and interact with their accounts on desktop or mobile, across all EVM networks. - -

    - -

    Account:

    -``` - -## Handle accounts - -Use the [`eth_accounts`](/wallet/reference/json-rpc-methods/eth_accounts) -RPC method to handle user accounts. -Listen to the [`accountsChanged`](../reference/provider-api.md#accountschanged) provider event to -be notified when the user changes accounts. - -The following code handles user accounts and detects when the user changes accounts: - -```javascript title="index.js" -let currentAccount = null -provider // Or window.ethereum if you don't support EIP-6963. - .request({ method: "eth_accounts" }) - .then(handleAccountsChanged) - .catch((err) => { - // Some unexpected error. - // For backwards compatibility reasons, if no accounts are available, eth_accounts returns an - // empty array. - console.error(err) - }) - -// Note that this event is emitted on page load. If the array of accounts is non-empty, you're -// already connected. -provider // Or window.ethereum if you don't support EIP-6963. - .on("accountsChanged", handleAccountsChanged) - -// eth_accounts always returns an array. -function handleAccountsChanged(accounts) { - if (accounts.length === 0) { - // MetaMask is locked or the user has not connected any accounts. - console.log("Please connect to MetaMask.") - } else if (accounts[0] !== currentAccount) { - // Reload your interface with accounts[0]. - currentAccount = accounts[0] - // Update the account displayed (see the HTML for the connect button) - showAccount.innerHTML = currentAccount - } -} -``` - -:::note -`eth_accounts` now returns the full list of accounts for which the user has permitted access to. -Previously, `eth_accounts` returned at most one account in the `accounts` array. -The first account in the array will always be considered the user's "selected" account. -::: - -## Disconnect a user's accounts - -Since `eth_requestAccounts` internally calls `wallet_requestPermissions` for permission to call -`eth_accounts`, you can use [`wallet_revokePermissions`](/wallet/reference/json-rpc-methods/wallet_revokepermissions) -to revoke this permission, revoking your dapp's access to the user's accounts. - -This is useful as a method for users to log out (or disconnect) from your dapp. -You can then use [`wallet_getPermissions`](/wallet/reference/json-rpc-methods/wallet_getpermissions) to determine -whether the user is connected or disconnected to your dapp. - -See [how to revoke permissions](manage-permissions.md#revoke-permissions-example) for an example. diff --git a/wallet/how-to/display/icon.md b/wallet/how-to/display/icon.md deleted file mode 100644 index 9f840f324dd..00000000000 --- a/wallet/how-to/display/icon.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -description: Set an icon on MetaMask for your dapp. ---- - -# Display a dapp icon - -When your dapp makes a login request to a MetaMask user, MetaMask may render a modal that displays -your dapp icon. - -MetaMask retrieves this icon using the HTML selector ` link[rel="shortcut icon"]`, so you can -follow the [favicon standard](https://en.wikipedia.org/wiki/Favicon) to customize your dapp icon. -Make sure to have a `link` tag within your dapp's `head` with `rel = "shortcut icon"`, as in the -following example. -The tag's `href` attribute is used for assigning the dapp icon. - -```html - - - -``` diff --git a/wallet/how-to/display/method-names.md b/wallet/how-to/display/method-names.md deleted file mode 100644 index 406f0e642eb..00000000000 --- a/wallet/how-to/display/method-names.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -description: Register a contract's method names with users. ---- - -# Display a contract's method names - -MetaMask uses the [Ethereum Signature Database](https://www.4byte.directory/) to display -method names on the confirmation screen. -For many common method names, such as token methods, this allows MetaMask to look up the method -names by their [method signature](https://solidity.readthedocs.io/en/v0.4.21/abi-spec.html). -However, sometimes you're using a method that isn't in that database, and MetaMask simply -displays **Contract Interaction** to the user. - -To register your contract's method names so they show in the MetaMask interface, -[submit each method's signature to the Ethereum Signature Database](https://www.4byte.directory/submit/). diff --git a/wallet/how-to/manage-networks/add-network.md b/wallet/how-to/manage-networks/add-network.md deleted file mode 100644 index 84ffaff5776..00000000000 --- a/wallet/how-to/manage-networks/add-network.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -description: Prompt a user to add or switch to an Ethereum network. ---- - -# Add a network - -In some cases, such as when [interacting with smart contracts](../../concepts/smart-contracts.md), -your dapp must connect a user to a new network in MetaMask. -Instead of the user [adding a new network manually](https://support.metamask.io/hc/en-us/articles/360043227612-How-to-add-a-custom-network-RPC#h_01G63GGJ83DGDRCS2ZWXM37CV5), -which requires them to configure RPC URLs and chain IDs, your dapp can use the -[`wallet_addEthereumChain`](/wallet/reference/json-rpc-methods/wallet_addethereumchain) and -[`wallet_switchEthereumChain`](/wallet/reference/json-rpc-methods/wallet_switchethereumchain) RPC methods to prompt -the user to add a specific, pre-configured network to their MetaMask wallet. - -These methods are specified by [EIP-3085](https://eips.ethereum.org/EIPS/eip-3085) and -[EIP-3326](https://eips.ethereum.org/EIPS/eip-3326), and we recommend using them together. - -1. `wallet_addEthereumChain` creates a confirmation asking the user to add the specified network to MetaMask. -2. `wallet_switchEthereumChain` creates a confirmation asking the user to switch to the specified network. - -The confirmations look like the following: - -
    -
    - Add network confirmation -
    -
    - Switch network confirmation -
    -
    - -:::info Development and non-EVM networks -- To add a local development network such as [Hardhat](https://hardhat.org) to MetaMask, see [Run a development network](../run-devnet.md). -- To add a non-EVM network such as [Starknet](../use-non-evm-networks/starknet/index.md) to MetaMask, - see [Use non-EVM networks](/wallet/how-to/use-non-evm-networks). -::: - -## Example - -The following is an example of using `wallet_addEthereumChain` and `wallet_switchEthereumChain` to -prompt a user to add and switch to a new network: - -```javascript -try { - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_switchEthereumChain", - params: [{ chainId: "0xf00" }], - }) -} catch (switchError) { - // This error code indicates that the chain has not been added to MetaMask. - if (switchError.code === 4902) { - try { - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_addEthereumChain", - params: [ - { - chainId: "0xf00", - chainName: "...", - rpcUrls: ["https://..."] /* ... */, - }, - ], - }) - } catch (addError) { - // Handle "add" error. - } - } - // Handle other "switch" errors. -} -``` diff --git a/wallet/how-to/manage-networks/detect-network.md b/wallet/how-to/manage-networks/detect-network.md deleted file mode 100644 index 46c7d85c07e..00000000000 --- a/wallet/how-to/manage-networks/detect-network.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -description: Detect a user's network and network changes. ---- - -# Detect a user's network - -It's important to keep track of the user's network chain ID because all RPC requests are submitted -to the currently connected network. - -Use the [`eth_chainId`](/wallet/reference/json-rpc-methods/eth_chainid) -RPC method to detect the chain ID of the user's current network. -Listen to the [`chainChanged`](../../reference/provider-api.md#chainchanged) provider event to -detect when the user changes networks. - -For example, the following code detects a user's network and when the user changes networks: - -```javascript title="index.js" -const chainId = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ method: "eth_chainId" }) - -provider // Or window.ethereum if you don't support EIP-6963. - .on("chainChanged", handleChainChanged) - -function handleChainChanged(chainId) { - // We recommend reloading the page, unless you must do otherwise. - window.location.reload() -} -``` diff --git a/wallet/how-to/manage-networks/use-multichain.md b/wallet/how-to/manage-networks/use-multichain.md deleted file mode 100644 index 6e6c03197dd..00000000000 --- a/wallet/how-to/manage-networks/use-multichain.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -description: Interact with multiple networks simultaneously using the Multichain API. -toc_max_heading_level: 4 ---- - -# Interact with multiple networks simultaneously - -:::tip Experimental -The Multichain API is an experimental feature. -::: - -You can use the Multichain API to interact with multiple blockchain networks in MetaMask simultaneously. -The API allows you to target specific chains as part of each method call, eliminating the need to -detect and switch networks before executing signatures and transactions. - -:::note See also -- [About the Multichain API](../../concepts/multichain-api.md) -- [Multichain API reference](../../reference/multichain-api.md) -::: - -## Prerequisites - -[Install MetaMask Flask.](/snaps/get-started/install-flask) - -## Steps - -### 1. Set up your project - -Establish a connection to MetaMask Flask and set up basic message handling using the -[`wallet_notify`](../../reference/multichain-api.md#wallet_notify) event: - -```javascript -// Initialize the connection to Flask. -const EXTENSION_ID = "ljfoeinjpaedjfecbmggjgodbgkmjkjk"; // Flask extension ID (Chrome) -const extensionPort = chrome.runtime.connect(EXTENSION_ID) - -// Set up a message listener for events. -extensionPort.onMessage.addListener((msg) => { - if (msg.data.method === "wallet_notify") { - console.log("wallet_notify:", msg.data.params) - return; - } - console.log(msg.data) -}) -``` - -### 2. Manage multichain connections - -To interact with multiple networks simultaneously, you'll create and manage -[CAIP-25](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-25.md) multichain connections -with MetaMask. - -#### 2.1. Check existing connections - -Before creating a new connection, check if one already exists using the -[`wallet_getSession`](../../reference/multichain-api.md#wallet_getsession) method. -For example: - -```javascript -extensionPort.postMessage({ - type: "caip-348", - data: { - jsonrpc: "2.0", - method: "wallet_getSession", - params: {} - } -}); -``` - -If the result returns an empty `sessionScopes` parameter, then a multichain connection is not active -and you must create a new connection. - -#### 2.2. Create a new connection - -Create a new connection using the [`wallet_createSession`](../../reference/multichain-api.md#wallet_createsession) method. -Specify which chains and methods your dapp needs access to, using the `optionalScopes` parameter. -For example: - -```javascript -extensionPort.postMessage({ - type: "caip-348", - data: { - jsonrpc: "2.0", - method: "wallet_createSession", - params: { - optionalScopes: { - "eip155:1": { // Ethereum Mainnet - methods: [ - "personal_sign", - "eth_blockNumber", - "eth_gasPrice", - "eth_getBalance", - "eth_getTransactionCount", - "eth_sendTransaction", - "eth_subscribe" - ], - notifications: ["eth_subscription"], - accounts: [] - }, - "eip155:59141": { // Linea Sepolia - methods: [ - "personal_sign", - "eth_blockNumber", - "eth_gasPrice", - "eth_getBalance", - "eth_getTransactionCount", - "eth_sendTransaction", - "eth_subscribe" - ], - notifications: ["eth_subscription"], - accounts: [] - } - } - } - } -}); -``` - -In `optionalScopes`: - -- Request access to networks that your dapp intends to interact with. - If a requested network is not configured by the MetaMask user, you might need to - [add the network](add-network.md). -- For each network, request access to [Wallet API methods](../../reference/json-rpc-methods/index.md) - that your dapp expects to call at any time. - The methods listed in the `sessionScope` of each Multichain API response indicate which wallet - capabilities your dapp can use during the connection. - -#### 2.3. Check for connection changes - -To ensure your dapp responds appropriately to changes in the multichain connection, such as network or -account updates, check for connection changes using the -[`wallet_sessionChanged`](../../reference/multichain-api.md#wallet_sessionchanged) event. -Based on the event data, you can determine whether your dapp needs to request additional permissions -using [`wallet_createSession`](../../reference/multichain-api.md#wallet_createsession). - -```javascript -extensionPort.onMessage.addListener((msg) => { - // Check for wallet_sessionChanged events. - if (msg.data.method === "wallet_sessionChanged") { - // Update permissions if required. - } -}); -``` - -### 3. Invoke Wallet API methods - -You can invoke a subset of the [Wallet JSON-RPC API methods](../../reference/json-rpc-methods/index.md) -on a specified chain using the [`wallet_invokeMethod`](../../reference/multichain-api.md#wallet_invokemethod) -Multichain API method. -The following are example Wallet API functionalities that are compatible with the Multichain API. - -#### 3.1. Sign in with Ethereum - -You can implement Sign-In with Ethereum (SIWE) by invoking -[`personal_sign`](/wallet/reference/json-rpc-methods/personal_sign). -For example: - -```javascript -// Specify an account that the signature will be requested for. -const address = "0xAddress"; -const message = `Sign-in request for ${address} at ${new Date().toLocaleString()}`; - -// Invoke the personal_sign Wallet API method. -const sign = await extensionPort.postMessage({ - type: "caip-348", - data: { - "jsonrpc": "2.0", - method: "wallet_invokeMethod", - params: { - scope: "eip155:1", - request: { - method: "personal_sign", - params: [message, address], - } - } - } -}) -``` - -#### 3.2. Check balances - -You can read gas token balances by invoking -[`eth_getBalance`](/wallet/reference/json-rpc-methods/personal_sign). -For example: - -```javascript -extensionPort.postMessage({ - type: "caip-348", - data: { - jsonrpc: "2.0", - method: "wallet_invokeMethod", - params: { - scope: "eip155:1", - request: { - method: "eth_getBalance", - params: ["0xAddress", "latest"], - } - } - } -}); -``` - -#### 3.3. Send transactions - -You can send transactions on a specific network, by invoking -[`eth_sendTransaction`](/wallet/reference/json-rpc-methods/eth_sendtransaction). -For example: - -```javascript -return extensionPort.postMessage({ - type: "caip-348", - data: { - jsonrpc: "2.0", - method: "wallet_invokeMethod", - params: { - // Specify a chain ID where the user has sufficient gas. - scope: "eip155:1", // Ethereum Mainnet - request: { - method: "eth_sendTransaction", - params: [{ - from: "0xFromAccount", - to: "0xToAccount", - value: "0x0", - gasLimit: "0x5028", - maxPriorityFeePerGas: "0x3b9aca00", - maxFeePerGas: "0x2540be400", - }] - } - } - } -}); -``` diff --git a/wallet/how-to/manage-permissions.md b/wallet/how-to/manage-permissions.md deleted file mode 100644 index b51ecb1c236..00000000000 --- a/wallet/how-to/manage-permissions.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -description: Request and revoke permissions to call restricted methods. ---- - -# Manage permissions - -To call a restricted RPC method, your dapp must request permission from the user using -the [`wallet_requestPermissions`](/wallet/reference/json-rpc-methods/wallet_requestpermissions) RPC method. -You can get the user's current permissions using [`wallet_getPermissions`](/wallet/reference/json-rpc-methods/wallet_getpermissions), -and revoke permissions previously granted to your dapp using -[`wallet_revokePermissions`](/wallet/reference/json-rpc-methods/wallet_revokepermissions). -These methods are specified by [EIP-2255](https://eips.ethereum.org/EIPS/eip-2255) and -[MIP-2](https://github.com/MetaMask/metamask-improvement-proposals/blob/main/MIPs/mip-2.md). - -`wallet_requestPermissions` creates a confirmation asking the user to connect to an account and -allow the dapp to call the requested method. -The confirmation screen describes the functions and data the requested method can access. -For example, something like the following confirmation displays when you request permission to call -the restricted method [`eth_accounts`](/wallet/reference/json-rpc-methods/eth_accounts): - -
    -
    - Request permissions confirmation 1 -
    -
    - Request permissions confirmation 2 -
    -
    - -:::info note -To access accounts, we recommend using [`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts), -which automatically asks for permission to use `eth_accounts` by calling `wallet_requestPermissions` -internally. -See [how to access a user's accounts](access-accounts.md) for more information. -Granting permission for `eth_accounts` also grants access to [`eth_sendTransaction`](/wallet/reference/json-rpc-methods/eth_sendtransaction), [`personal_sign`](/wallet/reference/json-rpc-methods/personal_sign), and [`eth_signTypedData_v4`](/wallet/reference/json-rpc-methods/eth_signtypeddata_v4). -::: - -## Request permissions example - -The following example uses `wallet_requestPermissions` to request permission from the user to call `eth_accounts`: - -```javascript -document.getElementById("requestPermissionsButton", requestPermissions) - -function requestPermissions() { - provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_requestPermissions", - params: [{ eth_accounts: {} }], - }) - .then((permissions) => { - const accountsPermission = permissions.find( - (permission) => permission.parentCapability === "eth_accounts" - ) - if (accountsPermission) { - console.log("eth_accounts permission successfully requested!") - } - }) - .catch((error) => { - if (error.code === 4001) { - // EIP-1193 userRejectedRequest error - console.log("Permissions needed to continue.") - } else { - console.error(error) - } - }) -} -``` - -## Revoke permissions example - -The following example uses `wallet_revokePermissions` to revoke the dapp's permission to call `eth_accounts`: - -```javascript -await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_revokePermissions", - params: [ - { - eth_accounts: {}, - }, - ], - }) -``` diff --git a/wallet/how-to/onboard-users.md b/wallet/how-to/onboard-users.md deleted file mode 100644 index 196e7d06bcf..00000000000 --- a/wallet/how-to/onboard-users.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -description: Simplify the MetaMask onboarding experience for your users. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Use the MetaMask onboarding library - -Sending users away from your dapp to install MetaMask presents challenges. -You must inform the user to return to your dapp and refresh their browser after the installation. -Your dapp detects the user's newly installed MetaMask extension only after that refresh. - -You can use MetaMask's [onboarding library](https://github.com/MetaMask/metamask-onboarding) to -improve and simplify the onboarding experience. -The library exposes an API to initiate the onboarding process. - -During the onboarding process, the library registers your dapp as the origin of the onboarding request. -MetaMask checks for this origin after the user completes the onboarding flow. -If it finds an origin, the final confirmation button of the MetaMask onboarding flow indicates that -the user will be redirected back to your dapp. - -:::tip -[MetaMask SDK](/sdk) incorporates the functionality of the MetaMask onboarding library. -You don't need to set up the onboarding library if you use the SDK. -::: - -## Steps - -1. Install [`@metamask/onboarding`](https://github.com/MetaMask/metamask-onboarding). -1. Import the library or include it in your page: - - ```javascript - // As an ES6 module - import MetaMaskOnboarding from "@metamask/onboarding" - // Or as an ES5 module - const MetaMaskOnboarding = require("@metamask/onboarding") - ``` - - Alternatively, you can include the prebuilt ES5 bundle that ships with the library: - - ```html - - ``` - -1. Create a new instance of the onboarding library: - - ```javascript - const onboarding = new MetaMaskOnboarding() - ``` - -1. Start the onboarding process in response to a user event (for example, a button click): - - ```javascript - onboarding.startOnboarding() - ``` - -## Example - -The following are example ways to use the onboarding library in various frameworks: - - - - -```jsx -import MetaMaskOnboarding from "@metamask/onboarding" -import React from "react" - -const ONBOARD_TEXT = "Click here to install MetaMask!" -const CONNECT_TEXT = "Connect" -const CONNECTED_TEXT = "Connected" - -export function OnboardingButton() { - const [buttonText, setButtonText] = React.useState(ONBOARD_TEXT) - const [isDisabled, setDisabled] = React.useState(false) - const [accounts, setAccounts] = React.useState([]) - const onboarding = React.useRef() - - React.useEffect(() => { - if (!onboarding.current) { - onboarding.current = new MetaMaskOnboarding() - } - }, []) - - React.useEffect(() => { - if (MetaMaskOnboarding.isMetaMaskInstalled()) { - if (accounts.length > 0) { - setButtonText(CONNECTED_TEXT) - setDisabled(true) - onboarding.current.stopOnboarding() - } else { - setButtonText(CONNECT_TEXT) - setDisabled(false) - } - } - }, [accounts]) - - React.useEffect(() => { - function handleNewAccounts(newAccounts) { - setAccounts(newAccounts) - } - if (MetaMaskOnboarding.isMetaMaskInstalled()) { - provider // Or window.ethereum if you don't support EIP-6963. - .request({ method: "eth_requestAccounts" }) - .then(handleNewAccounts) - provider // Or window.ethereum if you don't support EIP-6963. - .on("accountsChanged", handleNewAccounts) - return () => { - provider // Or window.ethereum if you don't support EIP-6963. - .removeListener("accountsChanged", handleNewAccounts) - } - } - }, []) - - const onClick = () => { - if (MetaMaskOnboarding.isMetaMaskInstalled()) { - provider // Or window.ethereum if you don't support EIP-6963. - .request({ method: "eth_requestAccounts" }) - .then((newAccounts) => setAccounts(newAccounts)) - } else { - onboarding.current.startOnboarding() - } - } - return ( - - ) -} -``` - - - - -The onboarding library ships with MetaMask's TypeScript types. -Modify the React example as follows to get type safety: - -```jsx --const onboarding = React.useRef(); -+const onboarding = React.useRef(); -``` - -This gives you editor autocomplete for the methods exposed by the library, and -helpful documentation: - -![Editor Highlighting](https://user-images.githubusercontent.com/4448075/85584481-ccc7ec00-b604-11ea-9b74-49c76ee0bf22.png) - - - - -```html - - - - MetaMask Onboarding Example - - - -

    Sample Dapp

    - - - - - -``` - -
    -
    diff --git a/wallet/how-to/secure-dapp.md b/wallet/how-to/secure-dapp.md deleted file mode 100644 index b3185fe181c..00000000000 --- a/wallet/how-to/secure-dapp.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -description: Secure your dapp using HTTPS and CSP. ---- - -# Secure your dapp - -We recommend implementing security controls, such as [HTTPS](#use-https) and -[Content Security Policy (CSP)](#use-content-security-policy), to improve the security of your dapp -and protect your users. - -:::caution -The following security advice isn't exhaustive. -::: - -## Use HTTPS - -HTTPS can protect your dapp against attackers who might try to eavesdrop or tamper with the communication -channel between your dapp and your users. -HTTPS encrypts data transmitted between the web server and the user's browser, making it -difficult for attackers to intercept or modify the data. - -To secure your dapp using HTTPS, obtain an SSL/TLS certificate from a trusted certificate authority (CA). -For example, [Let's Encrypt](https://letsencrypt.org/) offers free SSL/TLS certificates. - -Install the certificate on your web server. -If you're using a static website hosting service, it might have a default way to enable HTTPS on -your dapp. - -## Use Content Security Policy - -Content Security Policy (CSP) is a security feature that can protect your dapp against various -types of attacks, such as [cross-site scripting (XSS)](https://owasp.org/www-community/attacks/xss/) -and [clickjacking](https://owasp.org/www-community/attacks/Clickjacking). - -CSP defines a set of policies that the browser must follow when displaying the dapp. -See the full list of CSP directives that you can enable for your dapp in the -[MDN CSP reference documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). - -### Use CSP with a server setup - -If your dapp uses a server setup, enable CSP by setting the `Content-Security-Policy` header in all -responses from your server. -For example, in Express.js, add the following middleware at the top of your server: - -```js -app.use((req, res, next) => { - res.setHeader( - "Content-Security-Policy", - "default-src 'self'; frame-ancestors 'none'" - ) - next() -}) -``` - -In a header, this looks like the following: - -``` -Content-Security-Policy: default-src 'self'; frame-ancestors 'none' -``` - -See [more examples](https://content-security-policy.com/examples/) of CSP in popular web frameworks -and languages. - -### Use CSP with a static site - -If your dapp uses a third-party hosting provider, and you can't set a custom -`Content-Security-Policy` header in the server responses, you can enable CSP by using the -[`` HTML tag](https://content-security-policy.com/examples/meta/). - -Add this tag to the `head` section of an HTML file to instruct the browser on which CSP directives -should be followed. -For example: - -```html - - - -``` - -### Configure your CSP - -CSP configuration is specific to each dapp. -We recommend starting with the following secure and restrictive CSP: - -```text -default-src 'self'; frame-ancestors 'none' -``` - -- `default-src 'self'` - By default, your dapp's code can't load or connect to content from outside - your domain. -- `frame-ancestors 'none'` - Your dapp can't be embedded within the webpage of another domain (to - prevent [clickjacking attacks](https://owasp.org/www-community/attacks/Clickjacking)). - -From here, you can make adjustments as needed by your dapp to support the content you want to load. -For example, if your dapp loads images hosted on [OpenSea](https://opensea.io/), you can enable this -by adding `img-src 'opensea.io'` to your CSP: - -```text -default-src: 'self'; frame-ancestors 'none'; img-src: 'opensea.io' -``` - -For more information and common use cases for CSP, see the -[MDN CSP documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP). diff --git a/wallet/how-to/send-transactions/index.md b/wallet/how-to/send-transactions/index.md deleted file mode 100644 index a6db6d52aea..00000000000 --- a/wallet/how-to/send-transactions/index.md +++ /dev/null @@ -1,215 +0,0 @@ ---- -description: Send transactions using `eth_sendTransaction`. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Send transactions - -You can send a transaction in MetaMask using the -[`eth_sendTransaction`](/wallet/reference/json-rpc-methods/eth_sendtransaction) -RPC method. - -:::note -To [send batch transactions](send-batch-transactions.md), use `wallet_sendCalls`. -::: - -For example, the following JavaScript gets the user's accounts and sends a transaction when they -select each button: - -```javascript title="index.js" -const ethereumButton = document.querySelector(".enableEthereumButton"); -const sendEthButton = document.querySelector(".sendEthButton"); - -let accounts = []; - -sendEthButton.addEventListener("click", () => { - provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "eth_sendTransaction", - params: [ - { - from: accounts[0], // The user's active address. - to: "0x0000000000000000000000000000000000000000", // Address of the recipient. Not used in contract creation transactions. - value: "0x0", // Value transferred, in wei. Only required to send ether to the recipient from the initiating external account. - gasLimit: "0x5028", // Customizable by the user during MetaMask confirmation. - maxPriorityFeePerGas: "0x3b9aca00", // Customizable by the user during MetaMask confirmation. - maxFeePerGas: "0x2540be400", // Customizable by the user during MetaMask confirmation. - }, - ], - }) - .then((txHash) => console.log(txHash)) - .catch((error) => console.error(error)); -}); - -ethereumButton.addEventListener("click", () => { - getAccount(); -}); - -async function getAccount() { - accounts = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ method: "eth_requestAccounts" }); -} -``` - -The following HTML displays the buttons: - -```html title="index.html" - - -``` - -## Transaction parameters - -The transaction parameters depend on the [transaction type](/services/concepts/transaction-types). -The following are examples of transaction objects for each type: - - - - -```js -{ - nonce: "0x0", // Number of transactions made by the sender before this one. - gasPrice: "0x09184e72a000", // Gas price, in wei, provided by the sender. - gasLimit: "0x2710", // Maximum gas provided by the sender. - to: "0x0000000000000000000000000000000000000000", // Address of the recipient. Not used in contract creation transactions. - value: "0x0", // Value transferred, in wei. - data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057", // Used for defining contract creation and interaction. - v: "0x1", // ECDSA recovery ID. - r: "0xa07fd6c16e169f0e54b394235b3a8201101bb9d0eba9c8ae52dbdf556a363388", // ECDSA signature r. - s: "0x36f5da9310b87fefbe9260c3c05ec6cbefc426f1ff3b3a41ea21b5533a787dfc", // ECDSA signature s. -} -``` - - - - -```js -{ - nonce: "0x0", // Number of transactions made by the sender before this one. - gasPrice: "0x09184e72a000", // Gas price, in wei, provided by the sender. - gasLimit: "0x2710", // Maximum gas provided by the sender. - to: "0x0000000000000000000000000000000000000000", // Address of the recipient. Not used in contract creation transactions. - value: "0x0", // Value transferred, in wei. - data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057", // Used for defining contract creation and interaction. - v: "0x1", // ECDSA recovery ID. - r: "0xa07fd6c16e169f0e54b394235b3a8201101bb9d0eba9c8ae52dbdf556a363388", // ECDSA signature r. - s: "0x36f5da9310b87fefbe9260c3c05ec6cbefc426f1ff3b3a41ea21b5533a787dfc", // ECDSA signature s. - chainId: "0x1", // Chain ID of the transaction. - accessList: [ // List of addresses and storage keys the transaction plans to access. - { - "address": "0xa02457e5dfd32bda5fc7e1f1b008aa5979568150", - "storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000081"] - } - ], - yParity: "0x1" // Parity of the y-value of a secp256k1 signature. -} -``` - - - - -```js -{ - nonce: "0x0", // Number of transactions made by the sender before this one. - gasLimit: "0x2710", // Maximum gas provided by the sender. - maxPriorityFeePerGas: "0x0", // Maximum fee, in wei, the sender is willing to pay per gas above the base fee. - maxFeePerGas: "0x6f4d3132b", // Maximum total fee (base fee + priority fee), in wei, the sender is willing to pay per gas. - to: "0x0000000000000000000000000000000000000000", // Address of the recipient. Not used in contract creation transactions. - value: "0x0", // Value transferred, in wei. - data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057", // Used for defining contract creation and interaction. - v: "0x1", // ECDSA recovery ID. - r: "0xa07fd6c16e169f0e54b394235b3a8201101bb9d0eba9c8ae52dbdf556a363388", // ECDSA signature r. - s: "0x36f5da9310b87fefbe9260c3c05ec6cbefc426f1ff3b3a41ea21b5533a787dfc", // ECDSA signature s. - chainId: "0x1", // Chain ID of the transaction. - accessList: [], // List of addresses and storage keys the transaction plans to access. - yParity: "0x1" // Parity of the y-value of a secp256k1 signature. -} -``` - - - - -### Nonce - -:::note -MetaMask ignores this field. -::: - -In Ethereum, every transaction has a nonce, so each transaction can only be processed by the -blockchain once. -To be a valid transaction, either: - -- The nonce must be `0`. -- A transaction with a nonce of the previous number, from the same account, must have been processed. - -This means that transactions are always processed in order for a given account. - -Nonces are easy to mess up, especially when a user is interacting with multiple applications with -pending transactions using the same account, potentially across multiple devices. -Because of this, MetaMask doesn't allow dapp developers to customize nonces. -Instead, MetaMask -[assists the user in managing their transaction queue themselves](https://support.metamask.io/manage-crypto/transactions/how-to-speed-up-or-cancel-a-pending-transaction/). - -### Gas price - -`gasPrice` is an optional parameter. -It is used in [legacy transactions](/services/concepts/transaction-types/#legacy-transactions) and specifies the gas price the sender is willing to pay for the transaction. -MetaMask automatically configures gas settings, but [users can also customize these settings](https://support.metamask.io/configure/transactions/how-to-customize-gas-settings/). - -### Gas limit - -`gasLimit` is an optional parameter. -It specifies the maximum amount of gas units the sender is willing to pay for the transaction. -MetaMask automatically sets this parameter, but [users can also customize their gas settings](https://support.metamask.io/configure/transactions/how-to-customize-gas-settings/). - -### Max priority fee per gas - -`maxPriorityFeePerGas` is an optional parameter. -It is used in [EIP-1559 transactions](/services/concepts/transaction-types/#eip-1559-transactions) and specifies the maximum fee the sender is willing to pay per gas above the base fee, in order to get their transaction prioritized. -MetaMask automatically sets this parameter, but [users can also customize their gas settings](https://support.metamask.io/configure/transactions/how-to-customize-gas-settings/). - -### Max fee per gas - -`maxFeePerGas` is an optional parameter. -It is used in [EIP-1559 transactions](/services/concepts/transaction-types/#eip-1559-transactions) and specifies the maximum total fee (base fee + priority fee) the sender is willing to pay per gas. -MetaMask automatically sets this parameter, but [users can also customize their gas settings](https://support.metamask.io/configure/transactions/how-to-customize-gas-settings/). - -### To - -The `to` parameter is a hex-encoded Ethereum address. -It's required for transactions with a recipient (all transactions except for contract creation). - -Contract creation occurs when there is no `to` value but there is a `data` value. - -### Value - -The `value` parameter is a hex-encoded value of the network's native currency to send. -On Mainnet, this is [ether](https://www.ethereum.org/eth), which is denominated in wei. - -These numbers are often far higher precision than native JavaScript numbers, and can cause -unpredictable behavior if not anticipated. -We recommend using [BN.js](https://github.com/indutny/bn.js/) when manipulating -values intended for Ethereum. - -### Data - -The `data` parameter is required for smart contract creation. - -This field is also used for specifying contract methods and their parameters. -See the [Solidity ABI spec](https://solidity.readthedocs.io/en/develop/abi-spec.html) for more -information on how the data is encoded. - -### Chain ID - -:::note -MetaMask ignores this field. -::: - -The chain ID is derived from the user's current selected network. -Use [`eth_chainId`](/wallet/reference/json-rpc-methods/eth_chainid) to get the user's chain ID. -If you need the network version, use [`net_version`](https://ethereum.org/en/developers/docs/apis/json-rpc/#net_version). - -In the future, MetaMask might allow connecting to multiple networks at the same time, at which point -this parameter will become important, so it might be useful to be in the habit of including it now. diff --git a/wallet/how-to/use-non-evm-networks/index.md b/wallet/how-to/use-non-evm-networks/index.md deleted file mode 100644 index 0c4ee034656..00000000000 --- a/wallet/how-to/use-non-evm-networks/index.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: Interact with users' accounts on non-EVM networks. ---- - -import CardList from "@site/src/components/CardList" - -# Use non-EVM networks - -Non-EVM networks are blockchain networks that are not compatible with the Ethereum Virtual Machine (EVM). -MetaMask provides different types of support for some non-EVM networks: - -- **Native support** - MetaMask implements a standard interface for [Solana](solana.md) dapps to natively connect to MetaMask. - Several third party libraries for Solana also detect and handle MetaMask by default. -- **Non-EVM Snaps** - MetaMask provides dedicated non-EVM [Snaps](https://metamask.io/snaps/) that dapps can use to interact with users' non-EVM accounts in MetaMask. - For example, you can connect to [Starknet](starknet/index.md) in this way. - -MetaMask supports the following non-EVM networks: - - - -:::info -See the [full list of available non-EVM Snaps](https://snaps.metamask.io/interoperability). -::: diff --git a/wallet/index.md b/wallet/index.md deleted file mode 100644 index e5d1a9a9126..00000000000 --- a/wallet/index.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -slug: / -title: Wallet API introduction -description: Introduction page for the Wallet API documentation. -keywords: [extension, api] ---- - -# Integrate your dapp with MetaMask using the Wallet API - -:::tip Building a cross-platform or mobile dapp? -For cross-platform development, mobile integration, or advanced features like QR codes and -deeplinking, see the [**MetaMask SDK** documentation](/sdk). -::: - -The Wallet API is MetaMask's core interface for web dapps to interact with the MetaMask browser extension. -With the Wallet API, you can: - -- Interact with users' EVM and non-EVM accounts. -- Send transactions and sign messages. -- Listen to account and network changes. - -The Wallet API uses standardized [JSON-RPC calls](reference/json-rpc-methods/index.md) to -interact with users' EVM accounts, and implements standard interfaces like -[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) for Ethereum provider methods and -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) for wallet detection. - -MetaMask also supports non-EVM networks, including native support for [Solana](how-to/use-non-evm-networks/solana.md). - -[Get started with the Wallet API.](how-to/connect-extension.md) - -## Wallet API vs. SDK - -The **Wallet API** is designed for web dapps that connect to the MetaMask browser extension. -If you're building a desktop web-only dapp, the Wallet API provides everything you need. - -[**MetaMask SDK**](/sdk) builds on top of the Wallet API, adding cross-platform support and features like mobile deeplinking and QR code connections. -We recommend the SDK if you want to build a cross-platform onchain dapp. - -:::note -Directly using the Wallet API also enables your dapp to work in the in-app browser of the MetaMask mobile app. -However, the [SDK](/sdk) offers a more consistent mobile connection. -::: diff --git a/wallet/tutorials/javascript-dapp-simple.md b/wallet/tutorials/javascript-dapp-simple.md deleted file mode 100644 index b8fff49a6e2..00000000000 --- a/wallet/tutorials/javascript-dapp-simple.md +++ /dev/null @@ -1,333 +0,0 @@ ---- -description: Create a simple dapp to integrate with MetaMask. ---- - -# Create a simple dapp - -This tutorial walks you through creating a simple JavaScript dapp and integrating it with MetaMask. -It demonstrates the basics of connecting to MetaMask: detecting the MetaMask provider, detecting the user's network, and accessing the user's accounts. - -:::caution Learning tutorial -This tutorial is for educational purposes and connects to MetaMask using the legacy provider object, `window.ethereum`, for the sake of simplicity. -For deployment in a production environment, we recommend [connecting to MetaMask using EIP-6963](../how-to/connect-extension.md) instead. - -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) introduces an alternative wallet detection mechanism to the `window.ethereum` provider, and enables dapps to support [wallet interoperability](../concepts/wallet-interoperability.md). - -For a full end-to-end tutorial that can be used in production, see the -[Create a simple React dapp](../tutorials/react-dapp-local-state.md) tutorial. -::: - -## Prerequisites - -- [Node.js](https://nodejs.org/en/) version 20+ -- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) version 9+ -- A text editor of your choice, such as [VS Code](https://code.visualstudio.com/). -- [MetaMask](https://metamask.io/) installed in the browser of your choice on your development machine. - -## Steps - -### 1. Set up the project - -Create a new project using [Vite](https://vitejs.dev/guide/): - -```bash -npm create vite@latest simple-dapp -- --template vanilla -``` - -Change into your project directory: - -```bash -cd simple-dapp -``` - -Install the dependencies listed in the project's `package.json`: - -```bash -npm install -``` - -### 2. Create the dapp structure - -In your project directory, create a `main.js` file: - -```bash -touch main.js -``` - -In `main.js`, add the following: - -```js title="main.js" -import "./style.css" - -document.querySelector("#app").innerHTML = ` - -

    Account:

    ` -``` - -Update `index.html` to include the script: - -```html title="index.html" - - - - - - - Simple dapp - - -
    - - - -``` - -### 3. Detect MetaMask - -:::caution -The `@metamask/detect-provider` module is deprecated, and is only used here for educational purposes. -In production environments, we recommend [connecting to MetaMask using EIP-6963](../how-to/connect-extension.md). -::: - -Install the `@metamask/detect-provider` module in your project directory: - -```bash -npm i @metamask/detect-provider -``` - -Create a `src` directory and create a new file `detect.js`: - -```bash -mkdir src && touch src/detect.js -``` - -In a text editor, add the following code to `src/detect.js` to detect the MetaMask provider using `@metamask/detect-provider`: - -```js title="detect.js" -import detectEthereumProvider from "@metamask/detect-provider" - -async function setup() { - const provider = await detectEthereumProvider() - - if (provider && provider === window.ethereum) { - console.log("MetaMask is available!") - startApp(provider) // Initialize your dapp with MetaMask. - } else { - console.log("Please install MetaMask!") - } -} - -function startApp(provider) { - if (provider !== window.ethereum) { - console.error("Do you have multiple wallets installed?") - } -} - -window.addEventListener("load", setup) -``` - -### 4. Detect a user's network - -[Detect the user's network](../how-to/manage-networks/detect-network.md) to ensure all RPC requests -are submitted to the currently connected network. -Add the following code to `src/detect.js`, which uses the [`eth_chainId`](/wallet/reference/json-rpc-methods/eth_chainid) -RPC method to detect the chain ID of the user's current network, and listens to the -[`chainChanged`](/wallet/reference/provider-api/#chainchanged) provider event to detect when the -user changes networks: - -```js title="detect.js" -const chainId = await window.ethereum.request({ method: "eth_chainId" }) - -window.ethereum.on("chainChanged", handleChainChanged) - -function handleChainChanged(chainId) { - // We recommend reloading the page, unless you must do otherwise. - window.location.reload() -} -``` - -### 5. Access a user's accounts - -To interact with Ethereum on the user's behalf, such as sending transactions or requesting balances, -your dapp needs to [access the user's accounts](../how-to/access-accounts.md) by calling -[`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts). - -Add the following code to `src/detect.js`, which creates a button to allow users to connect to -MetaMask from your dapp. -Selecting the button activates the call to `eth_requestAccounts`, allowing you to access the user's accounts. - -```jsx title="detect.js" -// You should only attempt to request the user's account in response to user interaction, such as -// selecting a button. Otherwise, you risk spamming the user. If you fail to retrieve -// the user's account, you should encourage the user to initiate the attempt. -const ethereumButton = document.querySelector(".enableEthereumButton") -const showAccount = document.querySelector(".showAccount") - -ethereumButton.addEventListener("click", () => { - getAccount() -}) - -// While awaiting the call to eth_requestAccounts, you should disable any buttons the user can -// select to initiate the request. MetaMask rejects any additional requests while the first is still -// pending. -async function getAccount() { - const accounts = await window.ethereum - .request({ method: "eth_requestAccounts" }) - .catch((err) => { - if (err.code === 4001) { - // EIP-1193 userRejectedRequest error. - // If this happens, the user rejected the connection request. - console.log("Please connect to MetaMask.") - } else { - console.error(err) - } - }) - const account = accounts[0] - showAccount.innerHTML = account -} -``` - -Update `index.html` with the following HTML code, which displays the button and the current account: - -```html title="index.html" - - - - - - - Simple dapp - - - - - - -

    Account:

    - - -``` - -Save your changes and run the following command in your project directory to start a local -development server: - -```bash -npm run dev -``` - -Navigate to the local server URL to view the live dapp. -Something like the following displays: - -

    -Enable button -

    - -When you select the **Enable Ethereum** button, you are prompted to connect to MetaMask. - -![Connect and access dapp](../assets/tutorials/beginner-tutorial/connect.png) - -After connecting, your connected account displays: - -

    -View account -

    - -## Example - -The following code samples contain the full simple dapp JavaScript and HTML code that this tutorial walks through. -You can copy the following full examples to get started quickly. - -### JavaScript - -```jsx title="detect.js" -/*****************************************/ -/* Detect the MetaMask Ethereum provider */ -/*****************************************/ - -import detectEthereumProvider from "@metamask/detect-provider" - -async function setup() { - const provider = await detectEthereumProvider() - - if (provider && provider === window.ethereum) { - console.log("MetaMask is available!") - startApp(provider) - } else { - console.log("Please install MetaMask!") - } -} - -function startApp(provider) { - if (provider !== window.ethereum) { - console.error("Do you have multiple wallets installed?") - } -} - -window.addEventListener("load", setup) - -/**********************************************************/ -/* Handle chain (network) and chainChanged (per EIP-1193) */ -/**********************************************************/ - -const chainId = await window.ethereum.request({ method: "eth_chainId" }) - -window.ethereum.on("chainChanged", handleChainChanged) - -function handleChainChanged(chainId) { - window.location.reload() -} - -/*********************************************/ -/* Access the user's accounts (per EIP-1102) */ -/*********************************************/ - -const ethereumButton = document.querySelector(".enableEthereumButton") -const showAccount = document.querySelector(".showAccount") - -ethereumButton.addEventListener("click", () => { - getAccount() -}) - -async function getAccount() { - const accounts = await window.ethereum - .request({ method: "eth_requestAccounts" }) - .catch((err) => { - if (err.code === 4001) { - console.log("Please connect to MetaMask.") - } else { - console.error(err) - } - }) - const account = accounts[0] - showAccount.innerHTML = account -} -``` - -### HTML - -```html title="index.html" - - - - - - - Simple dapp - - - - - -

    Account:

    - - -``` - -## Next steps - -You've successfully created a simple dapp and connected it to MetaMask using JavaScript, Vite, and the `window.ethereum` provider. -With this setup, your dapp can interact with MetaMask and allow users to securely access accounts and send transactions on the Ethereum blockchain. - -As a next step, you can create a [React dapp with local state](react-dapp-local-state.md). -This follow-up tutorial walks you through integrating a simple React dapp with MetaMask using a -single JSX component for managing local state, and the Vite build tool with React and TypeScript to create the dapp. diff --git a/wallet/tutorials/react-dapp-global-state.md b/wallet/tutorials/react-dapp-global-state.md deleted file mode 100644 index 0f892ce61c5..00000000000 --- a/wallet/tutorials/react-dapp-global-state.md +++ /dev/null @@ -1,820 +0,0 @@ ---- -description: Create a multi-component React dapp with global state using EIP-6963. -toc_max_heading_level: 4 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Create a React dapp with global state - -This tutorial walks you through integrating a React dapp with MetaMask. -The dapp has multiple components and requires managing the state globally, which can be helpful for -real-world use cases. -You'll use the [Vite](https://v3.vitejs.dev/guide) build tool with React and TypeScript to create -the dapp. - -The final state of the dapp will look like the following: - -![React dapp with global state](../assets/tutorials/react-dapp/react-tutorial-02-final-preview.png) - -In this tutorial, you'll put the state into a [React -Context](https://react.dev/reference/react/useContext) component, creating a [global -state](https://react.dev/learn/reusing-logic-with-custom-hooks#custom-hooks-sharing-logic-between-components) -that allows other components and UI elements to benefit from its data and functions. -You'll use `localStorage` to persist the selected wallet, ensuring the last connected wallet state -remains intact even after a page refresh. - -This tutorial addresses the edge case where a browser wallet might be disabled or uninstalled -between refreshes or visits to the dapp. -You'll add a disconnect function to reset the state, and use -[`wallet_revokePermissions`](/wallet/reference/json-rpc-methods/wallet_revokepermissions) to properly disconnect from MetaMask. - -:::info Project source code -You can view the [dapp source code on GitHub](https://github.com/MetaMask/vite-react-global-tutorial). -::: - -## Prerequisites - -- [Node.js](https://nodejs.org/) version 18+ -- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) version 9+ -- A text editor (for example, [VS Code](https://code.visualstudio.com/)) -- The [MetaMask extension](https://metamask.io/download) installed -- Basic knowledge of TypeScript, React, React Context, and React Hooks - -:::tip -We recommend following the [Create a React dapp with local state](react-dapp-local-state.md) -tutorial first, which introduces [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). -The tutorial demonstrates how to iterate over all discovered providers, connect to the selected -wallet, and remember the selection within a single component. - -If you skip the tutorial, consider reviewing [wallet -interoperability](../concepts/wallet-interoperability.md) to understand how multiple injected wallet -providers work. -::: - -## Steps - -### 1. Set up the project - -This project introduces a new structure, independent of previous tutorials. -Instead of reusing code or states, this tutorial guides you through breaking down the -single-component structure into multiple components. - -Set up a new project using Vite, React, and TypeScript by running the following command: - -```bash -npm create vite@latest vite-react-global-state -- --template react-ts -``` - -Install the node module dependencies: - -```bash -cd vite-react-global-state && npm install -``` - -Launch the development server: - -```bash -npm run dev -``` - -This displays a `localhost` URL in your terminal, where you can view the dapp in your browser. - -:::note -If you use VS Code, you can run the command `code .` to open the project. -If the development server has stopped, you can run the command `npx vite` or `npm run dev` to -restart your project. -::: - -Open the project in your editor. -Create three directories, `src/components`, `src/hooks`, and `src/utils`, in the root of the project -using the following commands: - -```bash -mkdir src/components && mkdir src/hooks && mkdir src/utils -``` - -Create the following files in `src/components`, which will be used to create components for listing -installed wallets, displaying connected wallet information, and handling errors: - -- `WalletList.tsx` -- `WalletList.module.css` -- `SelectedWallet.tsx` -- `SelectedWallet.module.css` -- `WalletError.tsx` -- `WalletError.module.css` - -Create the following files in `src/hooks`: - -- `Eip6963Provider.tsx` -- `useEip6963Provider.tsx` - -Create the following file in `src/utils`: - -- `index.ts` - -#### 1.1. Style the components - -Add the following CSS code to `SelectedWallet.module.css`: - -```css title="SelectedWallet.module.css" -.selectedWallet { - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - - padding: 0.6em 1.2em; - margin-bottom: 0.5em; - - font-family: inherit; - font-size: 1em; - font-weight: 500; -} -.selectedWallet > img { - width: 2em; - height: 1.5em; - margin-right: 1em; -} - -.providers { - display: flex; - flex-flow: column wrap; - justify-content: center; - align-items: center; - align-content: center; - - padding: 0.6em 1.2em; -} -``` - -Add the following CSS code to `WalletError.module.css`: - -```css title="WalletError.module.css" -.walletError { - margin-top: 1em; - border-radius: 0.5em; - height: 36px; - padding: 16px; - color: #efefef; - background-color: transparent; - user-select: none; -} -``` - -Add the following CSS code to `WalletList.module.css`: - -```css title="WalletList.module.css" -.walletList { - display: flex; - flex-direction: column; - align-items: center; -} -``` - -Append the following code to the end of `src/index.css`: - -```css title="index.css" -/* Added CSS */ -:root { - text-align: left; -} - -hr { - margin-top: 2em; - height: 1px; -} - -button { - min-width: 12em; - display: flex; - flex-flow: row nowrap; - justify-content: flex-start; - - align-items: center; - border-radius: 0.5em; - margin-bottom: 0.5em; - border: 1px solid transparent; -} - -button > img { - width: 1.5em; - height: 1.5em; - margin-right: 1em; -} - -button:hover { - border-color: #75079d; -} - -button:first-child { - margin-top: 0.5em; -} -button:last-child { - margin-bottom: 0; -} -``` - -#### 1.2. Project structure - -You now have some basic global and component-level styling for your dapp. -The directory structure in the dapp's `/src` directory should look like the following: - -```text -├── src -│ ├── assets -│ ├── components -│ │ ├── SelectedWallet.module.css -│ │ ├── SelectedWallet.tsx -│ │ ├── WalletError.module.css -│ │ ├── WalletError.tsx -│ │ ├── WalletList.module.css -│ │ └── WalletList.tsx -│ ├── hooks -│ │ ├── WalletProvider.tsx -│ │ └── useWalletProvider.tsx -│ ├── utils -│ │ └── index.tsx -├── App.css -├── App.tsx -├── index.css -├── main.tsx -├── vite-env.d.ts -``` - -### 2. Import EIP-6963 interfaces - -The dapp will connect to MetaMask using the mechanism introduced by -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). - -:::info Why EIP-6963? -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) introduces an alternative wallet detection -mechanism to the `window.ethereum` injected provider. -This alternative mechanism enables dapps to support -[wallet interoperability](../concepts/wallet-interoperability.md) by discovering multiple injected -wallet providers in a user's browser. -::: - -Update the Vite environment variable file, `src/vite-env.d.ts`, with the types and interfaces -needed for [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) and -[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193): - -```tsx title="vite-env.d.ts" -/// - -// Describes metadata related to a provider based on EIP-6963. -interface EIP6963ProviderInfo { - rdns: string - uuid: string - name: string - icon: string -} - -// Represents the structure of a provider based on EIP-1193. -interface EIP1193Provider { - isStatus?: boolean - host?: string - path?: string - sendAsync?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - send?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - request: (request: { - method: string - params?: Array - }) => Promise -} - -// Combines the provider's metadata with an actual provider object, creating a complete picture of a -// wallet provider at a glance. -interface EIP6963ProviderDetail { - info: EIP6963ProviderInfo - provider: EIP1193Provider -} - -// Represents the structure of an event dispatched by a wallet to announce its presence based on EIP-6963. -type EIP6963AnnounceProviderEvent = { - detail: { - info: EIP6963ProviderInfo - provider: Readonly - } -} - -// An error object with optional properties, commonly encountered when handling eth_requestAccounts errors. -interface WalletError { - code?: string - message?: string -} -``` - -### 3. Build the context provider - -In this step, you'll create the React Context component, which wraps the dapp and provides all -components access to the state and functions required to modify the state and manage connections to -discovered wallets. - -Add the following code to `src/hooks/WalletProvider.tsx` to import the context, define the -type alias, and define the context interface for the EIP-6963 provider: - -```tsx title="WalletProvider.tsx" -import { - PropsWithChildren, - createContext, - useCallback, - useEffect, - useState, -} from "react" - -// Type alias for a record where the keys are wallet identifiers and the values are account -// addresses or null. -type SelectedAccountByWallet = Record - -// Context interface for the EIP-6963 provider. -interface WalletProviderContext { - wallets: Record // A list of wallets. - selectedWallet: EIP6963ProviderDetail | null // The selected wallet. - selectedAccount: string | null // The selected account address. - errorMessage: string | null // An error message. - connectWallet: (walletUuid: string) => Promise // Function to connect wallets. - disconnectWallet: () => void // Function to disconnect wallets. - clearError: () => void -} -``` - -Add the following code to `src/hooks/WalletProvider.tsx` to extend the global `WindowEventMap` -interface with the custom `eip6963:announceProvider` event: - -```tsx title="WalletProvider.tsx" -declare global { - interface WindowEventMap { - "eip6963:announceProvider": CustomEvent - } -} -``` - -Explicitly declaring the custom `eip6963:announceProvider` event prevents type errors, enables -proper type checking, and supports autocompletion in TypeScript. - -Add the following code to `src/hooks/WalletProvider.tsx` to create the React Context for the -EIP-6963 provider with the defined interface `WalletProviderContext`, and define the -`WalletProvider` component: - -```tsx title="WalletProvider.tsx" showLineNumbers {6-12,14} -export const WalletProviderContext = createContext(null) - -// The WalletProvider component wraps all other components in the dapp, providing them with the -// necessary data and functions related to wallets. -export const WalletProvider: React.FC = ({ children }) => { - const [wallets, setWallets] = useState>({}) - const [selectedWalletRdns, setSelectedWalletRdns] = useState(null) - const [selectedAccountByWalletRdns, setSelectedAccountByWalletRdns] = useState({}) - - const [errorMessage, setErrorMessage] = useState("") - const clearError = () => setErrorMessage("") - const setError = (error: string) => setErrorMessage(error) - - useEffect(() => { - const savedSelectedWalletRdns = localStorage.getItem("selectedWalletRdns") - const savedSelectedAccountByWalletRdns = localStorage.getItem("selectedAccountByWalletRdns") - - if (savedSelectedAccountByWalletRdns) { - setSelectedAccountByWalletRdns(JSON.parse(savedSelectedAccountByWalletRdns)) - } - - function onAnnouncement(event: EIP6963AnnounceProviderEvent){ - setWallets(currentWallets => ({ - ...currentWallets, - [event.detail.info.rdns]: event.detail - })) - - if (savedSelectedWalletRdns && event.detail.info.rdns === savedSelectedWalletRdns) { - setSelectedWalletRdns(savedSelectedWalletRdns) - } - } - - window.addEventListener("eip6963:announceProvider", onAnnouncement) - window.dispatchEvent(new Event("eip6963:requestProvider")) - - return () => window.removeEventListener("eip6963:announceProvider", onAnnouncement) - }, []) -``` - -In this code sample, lines 6–12 are state definitions: - -- `wallets` - State to hold detected wallets. -- `selectedWalletRdns` - State to hold the Reverse Domain Name System (RDNS) of the selected wallet. -- `selectedAccountByWalletRdns` - State to hold accounts associated with each wallet. -- `errorMessage` - State to hold the error message when a wallet throws an error on connection. -- `clearError` - Function to clear the state in `errorMessage`. -- `setError` - Function to set the state in `errorMessage`. - -Line 14 is the `useEffect` hook and it handles the following: - -- Local storage retrieval - On mount, it retrieves the saved selected wallet and accounts from local storage. -- Event listener - It adds an event listener for the custom `eip6963:announceProvider` event. -- State update - When the provider announces itself, it updates the state. -- Provider request - It dispatches an event to request existing providers. -- Cleanup - It removes the event listener on unmount. - -Add the following code to `src/hooks/WalletProvider.tsx` to connect a wallet and update the component's state: - -```tsx title="WalletProvider.tsx" -const connectWallet = useCallback( - async (walletRdns: string) => { - try { - const wallet = wallets[walletRdns] - const accounts = (await wallet.provider.request({ - method: "eth_requestAccounts", - })) as string[] - - if (accounts?.[0]) { - setSelectedWalletRdns(wallet.info.rdns) - setSelectedAccountByWalletRdns((currentAccounts) => ({ - ...currentAccounts, - [wallet.info.rdns]: accounts[0], - })) - - localStorage.setItem("selectedWalletRdns", wallet.info.rdns) - localStorage.setItem( - "selectedAccountByWalletRdns", - JSON.stringify({ - ...selectedAccountByWalletRdns, - [wallet.info.rdns]: accounts[0], - }) - ) - } - } catch (error) { - console.error("Failed to connect to provider:", error) - const walletError: WalletError = error as WalletError - setError( - `Code: ${walletError.code} \nError Message: ${walletError.message}` - ) - } - }, - [wallets, selectedAccountByWalletRdns] -) -``` - -This code uses the `walletRdns` parameter to identify the wallet's RDNS for connecting. -It performs an asynchronous operation to request accounts from the wallet provider using the -[`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts) RPC method. - -Add the following code to `src/hooks/WalletProvider.tsx` to disconnect from a wallet: - -```tsx title="WalletProvider.tsx" -const disconnectWallet = useCallback(async () => { - if (selectedWalletRdns) { - setSelectedAccountByWalletRdns((currentAccounts) => ({ - ...currentAccounts, - [selectedWalletRdns]: null, - })) - - const wallet = wallets[selectedWalletRdns] - setSelectedWalletRdns(null) - localStorage.removeItem("selectedWalletRdns") - - try { - await wallet.provider.request({ - method: "wallet_revokePermissions", - params: [{ eth_accounts: {} }], - }) - } catch (error) { - console.error("Failed to revoke permissions:", error) - } - } -}, [selectedWalletRdns, wallets]) -``` - -:::caution important -[`wallet_revokePermission`](/wallet/reference/json-rpc-methods/wallet_revokepermissions) is an experimental RPC -method that might only work with MetaMask. -Configuring the revocation in a try/catch block and separating it from the rest of the cleanup -ensures that if a wallet does not support this feature, the rest of the disconnect functionality -will still execute. -::: - -
    -Use of `useCallback` -
    -Both of the previous functions use `useCallback`. -It is used to memoize the `connectWallet` function, optimize performance, and prevent unnecessary re-renders. -It ensures the function instance remains consistent between renders if its dependencies are changed. - -For example, when using `disconnectWallet`, each time the `WalletProvider` component re-renders -without `useCallback`, a new instance of `disconnectWallet` is created. -This can cause unnecessary re-renders of child components that depend on this function. -By memoizing it with `useCallback`, React keeps the function instance consistent between renders, as -long as its dependencies (wallets and `selectedWalletRdns`) haven't changed, preventing unnecessary -re-renders of child components. - -Although `useCallback` is not necessary, it demonstrates best practices. -Predicting how a context provider will be used or how the dapp might change or scale is difficult. -Using `useCallback` can improve performance in some cases by reducing unnecessary re-renders. -
    -
    - -Add the following code to `src/hooks/WalletProvider.tsx` to bundle the state and functions using `contextValue`: - -```tsx title="WalletProvider.tsx" -const contextValue: WalletProviderContext = { - wallets, - selectedWallet: - selectedWalletRdns === null ? null : wallets[selectedWalletRdns], - selectedAccount: - selectedWalletRdns === null - ? null - : selectedAccountByWalletRdns[selectedWalletRdns], - errorMessage, - connectWallet, - disconnectWallet, - clearError, -} - -return ( - - {children} - -) -``` - -In the return statement, the `contextValue` object is constructed with all necessary state and -functions related to wallet management. -It is passed to the `WalletProviderContext.Provider`, making wallet-related data and functions -available to all descendant components. -The context provider wraps the children components, allowing them to access the context values. - -Add the following code to `src/hooks/useWalletProvider.tsx` to provide a custom hook that simplifies -the process of consuming the `WalletProviderContext`: - -```tsx title="useWalletProvider.tsx" -import { useContext } from "react" -import { WalletProviderContext } from "./WalletProvider" - -export const useWalletProvider = () => useContext(WalletProviderContext) -``` - -The benefit of this separate file exporting the hook is that components can directly call -`useWalletProvider()` instead of `useContext(WalletProviderContext)`, making the code cleaner and -more readable. - -### 4. Update the utility file - -Add the following code to `src/utils/index.ts`: - -```ts title="index.ts" -export const formatBalance = (rawBalance: string) => { - const balance = (parseInt(rawBalance) / 1000000000000000000).toFixed(2) - return balance -} - -export const formatChainAsNum = (chainIdHex: string) => { - const chainIdNum = parseInt(chainIdHex) - return chainIdNum -} - -export const formatAddress = (addr: string) => { - const upperAfterLastTwo = addr.slice(0, 2) + addr.slice(2) - return `${upperAfterLastTwo.substring(0, 5)}...${upperAfterLastTwo.substring(39)}` -} -``` - -Although `formatAddress` is the only function used, `formatBalance` and `formatChainAsNum` are -added as useful utility functions. -Explore [Viem formatters](https://viem.sh/docs/chains/formatters) or other libraries for additional -formatting options. - -### 5. Wrap components with the context provider - -With `WalletProvider.tsx` and `useWalletProvider.tsx`, the dapp can manage and access wallet-related -state and functionality across various components. -You can now wrap the entire dapp (the part that requires wallet connection and data) with a -`WalletProvider` component. - -Replace the code in `src/App.tsx` with the following: - -```tsx title="App.tsx" -import "./App.css" -import { WalletProvider } from "~/hooks/WalletProvider" -// import { WalletList } from "./components/WalletList" -// import { SelectedWallet } from "./components/SelectedWallet" -// import { WalletError } from "./components/WalletError" - -function App() { - return ( - - {/* - -
    - - - */} -
    - ) -} - -export default App -``` - -The child components are currently commented out, but as you create each of these components, you'll -uncomment the specific lines. - -### 6. Display detected wallets - -Add the following code to `src/components/WalletList.tsx` to display detected wallets: - -```tsx title="WalletList.tsx" -import { useWalletProvider } from "~/hooks/useWalletProvider" -import styles from "./WalletList.module.css" - -export const WalletList = () => { - const { wallets, connectWallet } = useWalletProvider() - return ( - <> -

    Wallets Detected:

    -
    - {Object.keys(wallets).length > 0 ? ( - Object.values(wallets).map((provider: EIP6963ProviderDetail) => ( - - )) - ) : ( -
    there are no Announced Providers
    - )} -
    - - ) -} -``` - -This component checks if there are any detected wallets. -If wallets are detected, it iterates over them and renders a button for each one. - -- `Object.keys(wallets)` returns an array of the wallet keys (`rdns` values). - It is used to check the length. -- `Object.values(wallets)` returns an array of the wallet objects. - It is used to map and render. -- `wallet.info.rdns` is used as the key to ensure that each wallet button is uniquely identified. - -Uncomment the `WalletList` component in `src/App.tsx` and run the dapp. -Something like the following displays: - -![View of WalletList component](../assets/tutorials/react-dapp/react-tutorial-02-wallet-list.png) - -### 7. Display wallet data - -Add the following code to `src/components/SelectedWallet.tsx` to display data for the selected wallet: - -```tsx title="SelectedWallet.tsx" showLineNumbers {11-22} -import { useWalletProvider } from "~/hooks/useWalletProvider" -import { formatAddress } from "~/utils" -import styles from "./SelectedWallet.module.css" - -export const SelectedWallet = () => { - const { selectedWallet, selectedAccount, disconnectWallet } = - useWalletProvider() - - return ( - <> -

    - {selectedAccount ? "" : "No "}Wallet Selected -

    - {selectedAccount && ( - <> -
    - {selectedWallet.info.name} -
    {selectedWallet.info.name}
    -
    ({formatAddress(selectedAccount)})
    -
    - uuid: {selectedWallet.info.uuid} -
    -
    - rdns: {selectedWallet.info.rdns} -
    -
    - - - )} - - ) -} -``` - -The code in lines 11-22 have conditional rendering, ensuring that the content inside is only -displayed if `selectedAccount` is true. This ensures that detailed information about the selected wallet is only displayed when an active -wallet is connected. - -You can display information about the wallet, and conditionally render anything related to the following: - -- Wallet address -- Wallet balance -- Chain ID or name -- Other components that first need a connected wallet to work - -Uncomment the `SelectedWallet` component in `src/App.tsx` and run the dapp. -When you connect to MetaMask, something like the following displays: - -![View of SelectedWallet component](../assets/tutorials/react-dapp/react-tutorial-02-selected-wallet.png) - -### 8. Display wallet connection errors - -Add the following code to `src/components/WalletError.tsx` to handle wallet connection errors: - -```tsx title="WalletError.tsx" -import { useWalletProvider } from "~/hooks/useWalletProvider" -import styles from "./WalletError.module.css" - -export const WalletError = () => { - const { errorMessage, clearError } = useWalletProvider() - const isError = !!errorMessage - - return ( -
    - {isError && ( -
    - Error: {errorMessage} -
    - )} -
    - ) -} -``` - -An error message renders only if `errorMessage` contains data. -After the error is selected, `errorMessage` resets to an empty string, which hides the content. - -This method demonstrates how to display specific content, such as a modal or notification, in -response to connection errors when connecting to a wallet. - -Uncomment the `WalletError` component in `src/App.tsx` and run the dapp. -Disconnect from MetaMask, reconnect, and reject or cancel the connection. -Something like the following displays: - -![View of WalletError component](../assets/tutorials/react-dapp/react-tutorial-02-wallet-error.png) - -### 9. Run the final state of the dapp - -Make sure all code in `App.tsx` is uncommented: - -```tsx title="App.tsx" -import "./App.css" -import { WalletProvider } from "~/hooks/WalletProvider" -import { WalletList } from "./components/WalletList" -import { SelectedWallet } from "./components/SelectedWallet" -import { WalletError } from "./components/WalletError" - -function App() { - return ( - - -
    - - -
    - ) -} - -export default App -``` - -Run the dapp to view the wallet list and select a wallet to connect to. -The final state of the dapp when connected to MetaMask looks like the following: - -![Final view of dapp](../assets/tutorials/react-dapp/react-tutorial-02-final-preview.png) - -### 10. Test the dapp features - -You can conduct user tests to evaluate the functionality and features demonstrated in this tutorial: - -1. Test the ability to connect and disconnect from multiple wallets installed in your browser. -2. After selecting a wallet, refresh the page and ensure that the selected wallet persists without - reverting to **No Wallet Selected**. -3. Select a wallet, disable it, refresh the page, then re-enable the wallet and refresh the page again. - Observe the behavior of the dapp. -4. When connecting to a wallet, cancel the connection or close the wallet prompt. - This action should trigger the `WalletError` component, which you can dismiss by selecting it. - -## Conclusion - -This tutorial guided you through applying EIP-6963 to connect to MetaMask. -This method also works with any wallet that [complies with -EIP-6963](https://github.com/WalletConnect/EIP6963/blob/master/src/utils/constants.ts) and supports -multi-injected provider discovery. - -In this tutorial, you addressed edge cases and created a context provider that facilitates data -sharing, manages functions for connecting and disconnecting from wallets, and handles errors. -You can view the [project source code on GitHub](https://github.com/MetaMask/vite-react-global-tutorial). diff --git a/wallet/tutorials/react-dapp-local-state.md b/wallet/tutorials/react-dapp-local-state.md deleted file mode 100644 index a104c8fca5f..00000000000 --- a/wallet/tutorials/react-dapp-local-state.md +++ /dev/null @@ -1,444 +0,0 @@ ---- -description: Create a single component React dapp with local state using EIP-6963. ---- - -# Create a React dapp with local state - -This tutorial walks you through integrating a simple React dapp with MetaMask. -The dapp has a single JSX component, which is used for managing local state. -You'll use the [Vite](https://v3.vitejs.dev/guide) build tool with React and TypeScript to create -the dapp. - -:::tip Why React? -React is familiar to most web developers and is standard in web3. -It makes it easy to work with state management, build components that use a one-way data flow, and -re-render those components upon state changes. -::: - -:::info Project source code -You can view the [dapp source code on GitHub](https://github.com/MetaMask/vite-react-local-tutorial). -::: - -## Prerequisites - -- [Node.js](https://nodejs.org/) version 18+ -- [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) version 9+ -- A text editor (for example, [VS Code](https://code.visualstudio.com/)) -- The [MetaMask extension](https://metamask.io/download) installed -- Basic knowledge of TypeScript and React - -## Steps - -### 1. Set up the project - -Set up a new project using Vite, React, and TypeScript by running the following command: - -```bash -npm create vite@latest vite-react-local-state -- --template react-ts -``` - -Install the node module dependencies: - -```bash -cd vite-react-local-state && npm install -``` - -Launch the development server: - -```bash -npm run dev -``` - -This displays a `localhost` URL in your terminal, where you can view the dapp in your browser. - -:::note -If you use VS Code, you can run the command `code .` to open the project. -If the development server has stopped, you can run the command `npx vite` or `npm run dev` to -restart your project. -::: - -Open the project in your editor. -To start with a blank slate, replace the code in `src/App.tsx` with the following: - -```tsx title="App.tsx" -import "./App.css" - -const App = () => { - return ( -
    -

    Wallets Detected:

    -
    - ) -} - -export default App -``` - -### 2. Import EIP-6963 interfaces - -The dapp will connect to MetaMask using the mechanism introduced by -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). - -:::info Why EIP-6963? -[EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) introduces an alternative wallet detection -mechanism to the `window.ethereum` injected provider. -This alternative mechanism enables dapps to support -[wallet interoperability](../concepts/wallet-interoperability.md) by discovering multiple injected -wallet providers in a user's browser. -::: - -Update the Vite environment variable file, `src/vite-env.d.ts`, with the types and interfaces -needed for [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) and -[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193): - -```tsx title="vite-env.d.ts" -/// - -// Describes metadata related to a provider based on EIP-6963. -interface EIP6963ProviderInfo { - walletId: string - uuid: string - name: string - icon: string -} - -// Represents the structure of a provider based on EIP-1193. -interface EIP1193Provider { - isStatus?: boolean - host?: string - path?: string - sendAsync?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - send?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - request: (request: { - method: string - params?: Array - }) => Promise -} - -// Combines the provider's metadata with an actual provider object, creating a complete picture of a -// wallet provider at a glance. -interface EIP6963ProviderDetail { - info: EIP6963ProviderInfo - provider: EIP1193Provider -} - -// Represents the structure of an event dispatched by a wallet to announce its presence based on EIP-6963. -type EIP6963AnnounceProviderEvent = { - detail: { - info: EIP6963ProviderInfo - provider: EIP1193Provider - } -} - -// An error object with optional properties, commonly encountered when handling eth_requestAccounts errors. -interface MMError { - code?: string - message?: string -} -``` - -### 3. Create store file - -Create a store file to manage the state of the detected wallet providers. -This file provides a centralized place to store and synchronize the detected wallet providers, -ensuring that your dapp always has access to the latest provider information. - -Create a `src/hooks` directory, and create a file `store.ts` in that directory with the following code: - -```ts title="store.ts" -// Extends WindowEventMap interface, including a custom event eip6963:announceProvider. -declare global { - interface WindowEventMap { - "eip6963:announceProvider": CustomEvent - } -} - -// Array that stores detected wallet providers and their details. -let providers: EIP6963ProviderDetail[] = [] - -// Object containing two methods. The store holds the state of detected Ethereum wallet providers. -// It's implemented as an external store, making it available for subscription and synchronization -// across the dapp. -export const store = { - // Returns the current state of providers. - value: () => providers, - // Subscribes to provider announcements and updates the store accordingly. - // Takes a callback function to be invoked on each store update, returning a function to - // unsubscribe from the event. - subscribe: (callback: () => void) => { - function onAnnouncement(event: EIP6963AnnounceProviderEvent) { - if (providers.map((p) => p.info.uuid).includes(event.detail.info.uuid)) - return - providers = [...providers, event.detail] - callback() - } - window.addEventListener("eip6963:announceProvider", onAnnouncement) - window.dispatchEvent(new Event("eip6963:requestProvider")) - - return () => - window.removeEventListener("eip6963:announceProvider", onAnnouncement) - }, -} -``` - -### 4. Sync provider state with React component - -With the store in place, create a custom hook that synchronizes the provider state with the React component. -Use the [`useSyncExternalStore`](https://react.dev/reference/react/useSyncExternalStore) React hook -to subscribe to changes in the provider store, and to ensure the component re-renders whenever -the store updates. - -Create a file `useSyncProviders.ts` in the `hooks` directory with the following code: - -```tsx title="useSyncProviders.ts" -import { useSyncExternalStore } from "react" -import { store } from "./store" - -export const useSyncProviders = () => - useSyncExternalStore(store.subscribe, store.value, store.value) -``` - -`useSyncExternalStore` takes three arguments: - -- A subscription function to listen for changes in the external store (`store.subscribe`). -- A function to get the current value of the store (`store.value`). -- An initial value for the store (`store.value`). - -:::note -As an alternative to `useSyncExternalStore`, you can use the `useState` React hook to manage the -provider state, and the `useEffect` React hook to subscribe to changes in the store. -When the React component mounts, you can subscribe to changes in the store, set the initial state -using the current value from the store, and return a cleanup function to unsubscribe from the store -when the component unmounts. -::: - -### 5. Create connect buttons - -Create an array of buttons that the user can select to connect to the EIP-6963 wallet providers that -you detect. - -Update `src/App.tsx` to the following: - -```tsx title="App.tsx" -import { useSyncProviders } from "./hooks/useSyncProviders" -import "./App.css" - -const App = () => { - const providers = useSyncProviders() - - const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => { - try { - const accounts = (await providerWithInfo.provider.request({ - method: "eth_requestAccounts", - })) as string[] - } catch (error) { - console.error(error) - } - } - - return ( -
    -

    Wallets Detected:

    -
    - {providers.length > 0 ? ( - providers?.map((provider: EIP6963ProviderDetail) => ( - - )) - ) : ( -
    No Announced Wallet Providers
    - )} -
    -
    - ) -} - -export default App -``` - -To style the buttons, update `src/App.css` to the following: - -```css title="App.css" -.App { - min-width: 100vw; - min-height: 100vh; - text-align: center; -} - -.providers { - display: flex; - flex-flow: column wrap; - justify-content: space-between; - align-items: center; - align-content: center; - gap: 1em; - - padding: 0.6em 1.2em; -} - -.providers button { - width: 12em; -} - -.providers button img { - width: 2em; -} -``` - -Run `npm run dev` to test the dapp. -Make sure you're signed in to MetaMask and that it's not currently connected to your dapp. -If you have multiple EIP-6963 wallets installed in your browser, something like the following should display: - -

    - -![View of Dapp - Wallets Detected](../assets/react-tutorial-01-start.png) - -

    - -### 6. Show connected wallet address - -Indicate when a wallet has been connected to by displaying the user's address on the page. - -Update everything above the `return` statement in `src/App.tsx` to the following, which -adds code to format and display user addresses, and handle errors: - -```tsx title="App.tsx" -import { useState } from "react" -import { useSyncProviders } from "./hooks/useSyncProviders" -import "./App.css" - -const App = () => { - const [selectedWallet, setSelectedWallet] = useState() - const [userAccount, setUserAccount] = useState("") - const providers = useSyncProviders() - - const [errorMessage, setErrorMessage] = useState("") - const clearError = () => setErrorMessage("") - const setError = (error: string) => setErrorMessage(error) - const isError = !!errorMessage - - // Display a readable user address. - const formatAddress = (addr: string) => { - const upperAfterLastTwo = addr.slice(0, 2) + addr.slice(2) - return `${upperAfterLastTwo.substring(0, 5)}...${upperAfterLastTwo.substring(39)}` - } - - const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => { - try { - const accounts = await providerWithInfo.provider.request({ - method: "eth_requestAccounts" - }) as string[] - - setSelectedWallet(providerWithInfo) - setUserAccount(accounts?.[0]) - } catch (error) { - console.error(error) - const mmError: MMError = error as MMError - setError(`Code: ${mmError.code} \nError Message: ${mmError.message}`) - } - } - ... -``` - -Below the `return` statement in `src/App.tsx`, update the `div` with the class of `.App` to the -following: - -```tsx title="App.tsx" - ... - return ( -
    -

    Wallets Detected:

    -
    - { - providers.length > 0 ? providers?.map((provider: EIP6963ProviderDetail) => ( - - )) : -
    - No Announced Wallet Providers -
    - } -
    -
    -

    {userAccount ? "" : "No"} Wallet Selected

    - {userAccount && -
    - {selectedWallet?.info.name} -
    {selectedWallet?.info.name}
    -
    ({formatAddress(userAccount)})
    -
    - } -
    - {isError && -
    - Error: {errorMessage} -
    - } -
    -
    - ) -``` - -Add the following CSS to `src/App.css` to style the error message: - -```css title="App.css" -.mmError { - height: 36px; - padding: 16px; - color: #efefef; - background-color: transparent; -} -``` - -Your dapp should look similar to the following: - -

    - -![Final View of Dapp](../assets/react-tutorial-01-final.png) - -

    - -## Troubleshoot - -#### Doesn't look right? - -This tutorial creates `className`s for each section's parent `div` in the JSX (HTML). -If your dapp does not look the same but functions properly, check the naming of your classes and -their corresponding CSS. - -#### Doesn't function properly? - -Try the following: - -- Check the code examples against your own. -- Place `console` statements in key areas such as `handleConnect` and `store`. -- Clone the - [GitHub repository containing the project source code](https://github.com/MetaMask/vite-react-local-tutorial) - and run it. - -If you find inconsistencies or erroneous code, feel free to create an issue on the repository. - -## Next steps - -This tutorial walked you through creating a single component dapp using Vite, detecting wallet -providers using EIP-6963, and managing the state in React locally. -You can view the [project source code on GitHub](https://github.com/MetaMask/vite-react-local-tutorial). - -As a next step, you can [create a React dapp with global state](react-dapp-global-state.md). -This follow-up tutorial walks you through adding multiple components that use a global state. -You'll use [React's Context API](https://react.dev/reference/react/useContext) to manage the state -globally and move away from using the `useSyncExternalStore`. -This is a more realistic (but also more complex) approach for building a real-world dapp. From 905d9a8ffe4162cc197f2c29b005416cfedddfcc Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Fri, 17 Oct 2025 17:17:52 -0700 Subject: [PATCH 06/17] add simplified api reference with two examples --- sdk-sidebar.js | 5 +- .../DetailsBox/index.tsx | 128 ++++++++++++++++++ .../DetailsBox/styles.module.css | 47 +++++++ .../Expandable/index.tsx | 33 +++++ .../Expandable/styles.module.css | 55 ++++++++ .../ParamField/index.tsx | 69 ++++++++++ .../ParamField/styles.module.css | 46 +++++++ .../RequestBox/index.tsx | 34 +++++ .../RequestBox/styles.module.css | 4 + .../SimplifiedApiReference/index.tsx | 72 ++++++++++ .../SimplifiedApiReference/styles.module.css | 54 ++++++++ 11 files changed, 546 insertions(+), 1 deletion(-) create mode 100644 src/components/SimplifiedApiReference/DetailsBox/index.tsx create mode 100644 src/components/SimplifiedApiReference/DetailsBox/styles.module.css create mode 100644 src/components/SimplifiedApiReference/Expandable/index.tsx create mode 100644 src/components/SimplifiedApiReference/Expandable/styles.module.css create mode 100644 src/components/SimplifiedApiReference/ParamField/index.tsx create mode 100644 src/components/SimplifiedApiReference/ParamField/styles.module.css create mode 100644 src/components/SimplifiedApiReference/RequestBox/index.tsx create mode 100644 src/components/SimplifiedApiReference/RequestBox/styles.module.css create mode 100644 src/components/SimplifiedApiReference/index.tsx create mode 100644 src/components/SimplifiedApiReference/styles.module.css diff --git a/sdk-sidebar.js b/sdk-sidebar.js index a049a45ea6e..1031aa6f988 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -149,7 +149,10 @@ const sdkSidebar = { collapsible: true, collapsed: true, link: { type: "doc", id: "evm/connect/reference/json-rpc-api/index" }, - items: [], + items: [ + "evm/connect/reference/json-rpc-api/wallet_sendCalls", + "evm/connect/reference/json-rpc-api/eth_signTypedData_v4", + ], }, ], }, diff --git a/src/components/SimplifiedApiReference/DetailsBox/index.tsx b/src/components/SimplifiedApiReference/DetailsBox/index.tsx new file mode 100644 index 00000000000..87f3faf816a --- /dev/null +++ b/src/components/SimplifiedApiReference/DetailsBox/index.tsx @@ -0,0 +1,128 @@ +import React from 'react' +import Heading from '@theme/Heading' +import ParamField from '../ParamField' +import styles from './styles.module.css' + +interface NestedParam { + name: string + type: string + required: boolean + description: string + children?: NestedParam[] +} + +interface Parameter { + name: string + type: string + required: boolean + description: string + children?: NestedParam[] +} + +interface Error { + code: number | string + message: string + description: string +} + +interface Returns { + type: string + description: string +} + +interface DetailsBoxProps { + method: string + description?: string + parameters: Parameter[] + returns?: Returns + errors: Error[] +} + +const DetailsBox: React.FC = ({ + method, + description, + parameters, + returns, + errors +}) => { + return ( + <> + {method} + + {description && ( +
    +

    {description}

    +
    + )} + + + Parameters + + +
    + {parameters.length === 0 ? ( +
    + This method doesn't accept any parameters. +
    + ) : ( +
    + {parameters.map((param, index) => ( + + ))} +
    + )} +
    + + {returns && ( + <> + + Returns + +
    +
    +

    {returns.description}

    +
    +
    + + )} + + {errors.length > 0 && ( + <> + + Errors + +
    + + + + + + + + + + {errors.map((error, index) => ( + + + + + + ))} + +
    CodeMessageDescription
    {error.code}{error.message}{error.description}
    +
    + + )} + + ) +} + +export default DetailsBox diff --git a/src/components/SimplifiedApiReference/DetailsBox/styles.module.css b/src/components/SimplifiedApiReference/DetailsBox/styles.module.css new file mode 100644 index 00000000000..5808746399b --- /dev/null +++ b/src/components/SimplifiedApiReference/DetailsBox/styles.module.css @@ -0,0 +1,47 @@ +.sectionHeading { + padding-top: 1.5rem; + padding-bottom: 1rem; + margin-bottom: 0; +} + +.paramContainer { + margin-bottom: 1rem; +} + +.paramTable { + margin-top: 1rem !important; +} + +.noParams { + padding: 1rem 0; + color: var(--ifm-color-emphasis-700); + font-style: italic; +} + +.paramFields { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.returnsInfo { + padding-bottom: 1rem; +} + +.returnsInfo p { + margin: 0.5rem 0; +} + +.returnsInfo p:first-child { + margin-top: 0; +} + +.returnsInfo p:last-child { + margin-bottom: 0; +} + +html[data-theme='dark'] { + .paramTable th { + background: var(--ifm-color-emphasis-200); + } +} diff --git a/src/components/SimplifiedApiReference/Expandable/index.tsx b/src/components/SimplifiedApiReference/Expandable/index.tsx new file mode 100644 index 00000000000..66322682bc4 --- /dev/null +++ b/src/components/SimplifiedApiReference/Expandable/index.tsx @@ -0,0 +1,33 @@ +import React, { useState } from 'react' +import styles from './styles.module.css' + +interface ExpandableProps { + title: string + children: React.ReactNode +} + +const Expandable: React.FC = ({ title, children }) => { + const [isExpanded, setIsExpanded] = useState(false) + + return ( +
    + + {isExpanded && ( +
    + {children} +
    + )} +
    + ) +} + +export default Expandable diff --git a/src/components/SimplifiedApiReference/Expandable/styles.module.css b/src/components/SimplifiedApiReference/Expandable/styles.module.css new file mode 100644 index 00000000000..ea21925a93e --- /dev/null +++ b/src/components/SimplifiedApiReference/Expandable/styles.module.css @@ -0,0 +1,55 @@ +.expandable { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0.5rem; + margin: 0.5rem 0; + overflow: hidden; +} + +.expandableHeader { + width: 100%; + background: var(--ifm-color-emphasis-100); + border: none; + padding: 1rem; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + text-align: left; + font-size: 1.4rem; + font-weight: 600; + transition: background-color 0.2s ease; +} + +.expandableHeader:hover { + background: var(--ifm-color-emphasis-200); +} + +.expandableTitle { + color: var(--ifm-color-primary); +} + +.expandableIcon { + font-size: 0.8rem; + transition: transform 0.2s ease; + color: var(--ifm-color-emphasis-700); +} + +.expandableIcon.expanded { + transform: rotate(180deg); +} + +.expandableContent { + padding: 1rem; + background: var(--ifm-color-emphasis-50); + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +html[data-theme='dark'] { + .expandableHeader { + background: var(--ifm-color-emphasis-200); + } + + .expandableHeader:hover { + background: var(--ifm-color-emphasis-300); + } +} diff --git a/src/components/SimplifiedApiReference/ParamField/index.tsx b/src/components/SimplifiedApiReference/ParamField/index.tsx new file mode 100644 index 00000000000..c760cca99cb --- /dev/null +++ b/src/components/SimplifiedApiReference/ParamField/index.tsx @@ -0,0 +1,69 @@ +import React from 'react' +import Expandable from '../Expandable' +import styles from './styles.module.css' + +interface NestedParam { + name: string + type: string + required: boolean + description: string + children?: NestedParam[] +} + +interface ParamFieldProps { + name: string + type: string + required: boolean + description: string + children?: NestedParam[] + expandableTitle?: string +} + +const ParamField: React.FC = ({ + name, + type, + required, + description, + children = [], + expandableTitle +}) => { + const hasChildren = children.length > 0 + + const renderNestedParams = (params: NestedParam[], title?: string) => { + if (params.length === 0) return null + + return ( + + {params.map((param, index) => ( + + ))} + + ) + } + + return ( +
    +
    +
    + {name} + ({type}) + {required && required} +
    +
    +
    + {description} +
    + {hasChildren && renderNestedParams(children, expandableTitle)} +
    + ) +} + +export default ParamField diff --git a/src/components/SimplifiedApiReference/ParamField/styles.module.css b/src/components/SimplifiedApiReference/ParamField/styles.module.css new file mode 100644 index 00000000000..98826bcfddb --- /dev/null +++ b/src/components/SimplifiedApiReference/ParamField/styles.module.css @@ -0,0 +1,46 @@ +.paramField { + margin-bottom: 1rem; + padding-top: 1rem; +} + +.paramHeader { + margin-bottom: 0.5rem; +} + +.paramName { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.paramName code { + font-family: var(--font-mm-sans-mono); + font-size: 1.3rem; +} + +.paramType { + color: var(--ifm-color-emphasis-700); + font-size: 1.4rem; + font-style: italic; +} + +.required { + background: var(--ifm-color-primary); + color: white; + padding: 0rem 0.3rem; + border-radius: 0.25rem; + font-size: 1.2rem; + font-weight: 500; +} + +.paramDescription { + font-size: 1.6rem; +} + +html[data-theme='dark'] { + .required { + color: black; + font-weight: 400; + } +} \ No newline at end of file diff --git a/src/components/SimplifiedApiReference/RequestBox/index.tsx b/src/components/SimplifiedApiReference/RequestBox/index.tsx new file mode 100644 index 00000000000..d059e57bdab --- /dev/null +++ b/src/components/SimplifiedApiReference/RequestBox/index.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import Heading from '@theme/Heading' +import CodeBlock from '@theme/CodeBlock' +import styles from './styles.module.css' + +interface RequestBoxProps { + method: string + exampleRequest: string + exampleResponse: string +} + +const RequestBox: React.FC = ({ + method, + exampleRequest, + exampleResponse +}) => { + return ( + <> +
    + + {exampleRequest} + +
    + +
    + + {exampleResponse} + +
    + + ) +} + +export default RequestBox diff --git a/src/components/SimplifiedApiReference/RequestBox/styles.module.css b/src/components/SimplifiedApiReference/RequestBox/styles.module.css new file mode 100644 index 00000000000..c676f9a40b1 --- /dev/null +++ b/src/components/SimplifiedApiReference/RequestBox/styles.module.css @@ -0,0 +1,4 @@ +.cardWrapper { + margin-bottom: 1.5rem; + overflow: hidden; +} diff --git a/src/components/SimplifiedApiReference/index.tsx b/src/components/SimplifiedApiReference/index.tsx new file mode 100644 index 00000000000..f3744618229 --- /dev/null +++ b/src/components/SimplifiedApiReference/index.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import styles from './styles.module.css' +import DetailsBox from './DetailsBox' +import RequestBox from './RequestBox' + +interface NestedParam { + name: string + type: string + required: boolean + description: string + children?: NestedParam[] +} + +interface SimplifiedApiReferenceProps { + method: string + description?: string + parameters: Array<{ + name: string + type: string + required: boolean + description: string + children?: NestedParam[] + }> + returns?: { + type: string + description: string + } + errors?: Array<{ + code: number | string + message: string + description: string + }> + exampleRequest: string + exampleResponse: string +} + +const SimplifiedApiReference: React.FC = ({ + method, + description, + parameters, + returns, + errors = [], + exampleRequest, + exampleResponse +}) => { + return ( +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + ) +} + +export default SimplifiedApiReference diff --git a/src/components/SimplifiedApiReference/styles.module.css b/src/components/SimplifiedApiReference/styles.module.css new file mode 100644 index 00000000000..6cfd35a71c2 --- /dev/null +++ b/src/components/SimplifiedApiReference/styles.module.css @@ -0,0 +1,54 @@ +.rowWrap { + display: flex; + flex-wrap: wrap; +} + +.stickyCol { + position: sticky; +} + +.colLeft { + position: relative; + width: calc(100% - 45rem); + border-right: 1px solid var(--ifm-toc-border-color); + overflow: hidden; +} + +.colContentWrap { + height: calc(100vh - 15rem); + padding-right: 2.5rem; + overflow-y: auto; +} + +.colRight { + width: 45rem; + padding-left: 1.5rem; +} + +@media (width <= 1300px) { + .colLeft { + width: 100%; + padding: 0; + border-right: 0; + } + + .colRight { + width: 100%; + padding: 0; + } + + .colContentWrap { + height: auto; + padding: 0; + } + + .rowWrap { + padding: 0; + } +} + +html[data-theme='dark'] { + .colLeft { + border-color: var(--ifm-toc-border-color); + } +} From 549887c4c341666c30b696f700971f1613395221 Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Fri, 17 Oct 2025 17:19:17 -0700 Subject: [PATCH 07/17] missing files --- .../json-rpc-api/eth_signTypedData_v4.mdx | 142 ++++++++++++++++ .../json-rpc-api/wallet_sendCalls.mdx | 155 ++++++++++++++++++ 2 files changed, 297 insertions(+) create mode 100644 sdk/evm/connect/reference/json-rpc-api/eth_signTypedData_v4.mdx create mode 100644 sdk/evm/connect/reference/json-rpc-api/wallet_sendCalls.mdx diff --git a/sdk/evm/connect/reference/json-rpc-api/eth_signTypedData_v4.mdx b/sdk/evm/connect/reference/json-rpc-api/eth_signTypedData_v4.mdx new file mode 100644 index 00000000000..963d9ff04f7 --- /dev/null +++ b/sdk/evm/connect/reference/json-rpc-api/eth_signTypedData_v4.mdx @@ -0,0 +1,142 @@ +--- +title: eth_signTypedData_v4 +description: Sign structured data according to EIP-712 +hide_title: true +hide_table_of_contents: true +--- + +import SimplifiedApiReference from '@site/src/components/SimplifiedApiReference' + + diff --git a/sdk/evm/connect/reference/json-rpc-api/wallet_sendCalls.mdx b/sdk/evm/connect/reference/json-rpc-api/wallet_sendCalls.mdx new file mode 100644 index 00000000000..0bb46c21bd2 --- /dev/null +++ b/sdk/evm/connect/reference/json-rpc-api/wallet_sendCalls.mdx @@ -0,0 +1,155 @@ +--- +title: wallet_sendCalls +description: Submit a batch of calls to the wallet for execution +hide_title: true +hide_table_of_contents: true +--- + +import SimplifiedApiReference from '@site/src/components/SimplifiedApiReference' + + \ No newline at end of file From bb103fb07bd3ab8d67f8b9c9ebcd20cabbdc1991 Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Mon, 20 Oct 2025 17:45:47 -0700 Subject: [PATCH 08/17] remove starknet docs --- sdk-sidebar.js | 44 - .../_assets/starknet-dapp-connected.png | Bin 19209 -> 0 bytes .../_assets/starknet-metamask-connection.png | Bin 34058 -> 0 bytes .../_assets/starknet-tutorial-connected.png | Bin 49384 -> 0 bytes .../_assets/starknet-tutorial-modal.png | Bin 43688 -> 0 bytes .../_assets/starknet-tutorial-start-dapp.png | Bin 12165 -> 0 bytes .../starknet-tutorial-transfer-token.png | Bin 56207 -> 0 bytes .../_assets/starknet-wallet-modal.png | Bin 36077 -> 0 bytes sdk/starknet/concepts/about-get-starknet.md | 99 - sdk/starknet/guides/connect-to-starknet.md | 469 ----- sdk/starknet/guides/manage-networks.md | 139 -- sdk/starknet/guides/manage-user-accounts.md | 431 ----- sdk/starknet/guides/send-transactions.md | 204 --- sdk/starknet/guides/sign-data.md | 103 -- sdk/starknet/guides/troubleshoot.md | 122 -- sdk/starknet/index.md | 175 -- sdk/starknet/reference/snap-api.md | 1117 ----------- .../tutorials/create-simple-starknet-dapp.md | 1629 ----------------- src/components/SubNavBar/configs.ts | 5 - vercel.json | 40 +- 20 files changed, 6 insertions(+), 4571 deletions(-) delete mode 100644 sdk/starknet/_assets/starknet-dapp-connected.png delete mode 100644 sdk/starknet/_assets/starknet-metamask-connection.png delete mode 100644 sdk/starknet/_assets/starknet-tutorial-connected.png delete mode 100644 sdk/starknet/_assets/starknet-tutorial-modal.png delete mode 100644 sdk/starknet/_assets/starknet-tutorial-start-dapp.png delete mode 100644 sdk/starknet/_assets/starknet-tutorial-transfer-token.png delete mode 100644 sdk/starknet/_assets/starknet-wallet-modal.png delete mode 100644 sdk/starknet/concepts/about-get-starknet.md delete mode 100644 sdk/starknet/guides/connect-to-starknet.md delete mode 100644 sdk/starknet/guides/manage-networks.md delete mode 100644 sdk/starknet/guides/manage-user-accounts.md delete mode 100644 sdk/starknet/guides/send-transactions.md delete mode 100644 sdk/starknet/guides/sign-data.md delete mode 100644 sdk/starknet/guides/troubleshoot.md delete mode 100644 sdk/starknet/index.md delete mode 100644 sdk/starknet/reference/snap-api.md delete mode 100644 sdk/starknet/tutorials/create-simple-starknet-dapp.md diff --git a/sdk-sidebar.js b/sdk-sidebar.js index 1031aa6f988..d0ef9144e98 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -160,50 +160,6 @@ const sdkSidebar = { solana: [ 'solana/index', ], - starknet: [ - 'starknet/index', - { - type: "category", - label: "Guides", - collapsible: false, - collapsed: false, - items: [ - 'starknet/guides/connect-to-starknet', - 'starknet/guides/manage-user-accounts', - 'starknet/guides/manage-networks', - 'starknet/guides/send-transactions', - 'starknet/guides/sign-data', - 'starknet/guides/troubleshoot', - ], - }, - { - type: "category", - label: "Concepts", - collapsible: false, - collapsed: false, - items: [ - 'starknet/concepts/about-get-starknet', - ], - }, - { - type: "category", - label: "Tutorials", - collapsible: false, - collapsed: false, - items: [ - 'starknet/tutorials/create-simple-starknet-dapp', - ], - }, - { - type: "category", - label: "Reference", - collapsible: false, - collapsed: false, - items: [ - 'starknet/reference/snap-api', - ], - }, - ], } module.exports = sdkSidebar diff --git a/sdk/starknet/_assets/starknet-dapp-connected.png b/sdk/starknet/_assets/starknet-dapp-connected.png deleted file mode 100644 index df5e1dfb60d38a5e64155ab771d98fe5bfd45cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19209 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 L{an^LB{Ts5C^>&Y diff --git a/sdk/starknet/_assets/starknet-metamask-connection.png b/sdk/starknet/_assets/starknet-metamask-connection.png deleted file mode 100644 index f56d929fa3a06fc0c14b30b7f4ceac7e74197a50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34058 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 diff --git a/sdk/starknet/_assets/starknet-tutorial-connected.png b/sdk/starknet/_assets/starknet-tutorial-connected.png deleted file mode 100644 index c4ec0363aef6780e845c8f04168db3c56db98dbe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49384 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 CefJXp diff --git a/sdk/starknet/_assets/starknet-tutorial-modal.png b/sdk/starknet/_assets/starknet-tutorial-modal.png deleted file mode 100644 index 1137384195ac9cd459afd547d842dd64b6462b7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43688 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 diff --git a/sdk/starknet/_assets/starknet-tutorial-transfer-token.png b/sdk/starknet/_assets/starknet-tutorial-transfer-token.png deleted file mode 100644 index c6fe6eedcb3684ca160b4f7f5601193e8a897267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56207 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 LP%OP`8utGHK|v{A diff --git a/sdk/starknet/_assets/starknet-wallet-modal.png b/sdk/starknet/_assets/starknet-wallet-modal.png deleted file mode 100644 index 81db588e97c2c2bc9b770d0e432b57766110ed33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36077 zcmeFZbyQs4*Ct2^!JPmBf;+)AxCfU2!6gt1C^WcBf&~fg4nacD;8r-np>QbN-QBx@ zyx;Fz(=)Srt?s{OURYG!d(S?)kL>5!`x2zAD20YhhztV*gC-*_p#lQ~ivt7mJP{Ed z`Xu{4ix~z6IUgu4t}G)iPNr;UV*<1Uz`#fc#l|D3scvHX+`Es6i=mK_QJ>IA)B41K zpG)s^i(!++U_Q^BitWJFeJ>%QjO9R|wV(~7MfALSL+B#5iNX_EF)L^w{Xt$ zuKTGx>wT&FeW~7em+Ldarm!$tk=^UGEyFNW$HQ=1w#GZ`gkh4h5MCL~qZ}-)lq~+N)^6h&)5=ELGL&MogDHK?`A#eh%}&_o9-0 z_=005s5AwJ9@B6LwX^@Qo**m{!0E+JL)C?AhzoN-MWc#|03$V&7Y~!Bl|yf%S}<7dFTCy=CWp z+zQp=4T;;gsHGSBqTgaA+|Hzog2NMB*}U$K`v8M~2JITaZyeIJ@{8XerzE}XOEBdg z>6c2O?H+Va`&3J^^var$(m0Jbf3M}3Q5p$*pXkl!oIwuNQe|IOHnpAbb=LOF@@4VX zb>38>HQHh(wn&xv4mMVdV|qP}nk;T)dJ(Tv)tReBP!-uh5_6n&*o*kpY!Vo7s zmlJvZg$&h)Q_%#{N3N@Sp#^KL>p>VBHp7V?+wv)qF*7MSNe7nI-ez4Wq#DCypEBFW z;NYVpSH z_#^E(-c0iLx$^?rjVR(bxPoUW?Y_ojFa#e3Ct>YGg(lI?zNjdphkYgdjGY3numEfJ zVxq=*PtBk3Zg`w%UvUvST$ge=4#P$6efDI?RN#jphj*hyag$Ws1I_spD<0pmmH zFNv`q+RDT~-RZE*U!P)qmsAC-Rj@%wcKy*M%fS{yL__>TIU9ryly$_O7=qu@M7%p9 z*ce_>h7t#S32JUPWM{2SFp|ezd%Z@Oi_a128s1@qTk57sREW=wo$R9*I2$DBXVt9V z;`Ax7H2UppMI=){`{v8#gc?p=<~^7_xIOk_1fI}qpZMm2<^DsG3pxReCR~G1;jkvZ z{g&{BJfvYb;;+Ol>CH68SjdQ-h)jO`euk}_?*o2HIgkZl;(XKjM&0hV#I*!-SmfvD zm+My~kpZV+R4x(JP9H>s?Tbw+Q${5o^#({SBMp)KMWaV9NCl#f?yfU7wR_3?y&*(b zu2I%Y_Kr*@k~7j+x{R7fCQgQ1_Ds4lib8r*dMHO-4j_jsml|0|$;GS?F&^s_Ya(kC z!yGNdGRq=JFCNJjeHbqjH$`u+yO!JclTlG!V_YweDVfq;?d;bM<_pQ8tQr-~7^|31 zF%~g~yoD)guf>@~nGN=`b>nCJ8VVb7JQ6%|JrZvuT_a&-MK!);Mr12zeAdoXS{$Jm zlRS}3k>tl4W7*Q;%&E~F(u(|%kT;nzS#Ti}^lPO!=4XXezRYG2yY`eauQIRrW$6*u z>S2dn$S!_4-a3;I=pqui(C8<2cA4VpPuaYl>|aU0@)!(GeYQS~*>b($GOdKGq^OiL z|FreeQOdE{E%FL*im&F%keVdHlv$-p%c;iM!0AVbOQ=UUlH`_@nWV!-#F=h}RcWUE zTU%1wN*f2V1?jDvu$TY}50h_z9hMx=wjDO)H@7wewyh_uhKa{iM?Y-in) z5LLa>!Jk&B!nYKLXieu#rB+?{&P@sp-PPP~u!8_8fX+BHUNb&cVusfeVQQUbos?k| zv}m-ssyQPZrc9<)_OZu^vp#7e!lK@*Mq3o0dDq!j&DZN%PJ@Yh`bU@si+c6O1xG=n zxA}p&QYliYoql<->wJT6^k1=%mI&(KqJDr6dEc9GP<1%BFuNyygmgxK>bhpJ@#erv z3!UX$ztQ>9Cq0aGu%w&xg5&1=N*D=@qDgdMLB`EWR?2wb8leTg$_V=?dyurS+_EpO zD~2pa61WwJEI!?u-Krii7NFo??~fl4LJ{}Q@>>mY{_F1VmSL@u{*oUg6^cI_{I=;h zkS&Ol|6<(Azfrx%!~6q2t8bgFi2+ z(~$#{!G12CZ#vl3H&tT}7K`J~)2-E&D?isP2MDfOjsxp-yLCma@13r9SB0~@SMDY& z+l)HD5E~p%x>g@49EThW?;GznoJLP6Q44!cKR{6}`SXX(e}k4>p`QWXC2Lyp~b2*c{Hn_nzr4d(9^ zloi~I6s&e_W0zkzeoig5-f8c_aaO8JwJhc-E-_O!>k@vUh@=F%puUQ491+T!9sm!F zrjPNuAG}`G)u$Xy7<)VPa-PaO%yLg?-?Oo(qj7bngr-T|09>Q&^#0DMg?cF`MQ)zi z)F7>)wTXR>aDs2NDtGsA>|4S4^z-TZ;!E9n+u7x-F?6@iRU&jA=9!9m*E_sj^%1M> z>F=rPsq0?lSG&6LjvC9v$^8A!R{K>BEZdEv%cCQ1gS&I5*P++d?ze9B*J9TsCnY=h zf**xnc_y43oc=hG8VTpY_y~WLCM`_w_3NnO$k}(ENMbxI#Mjz4b?|GtAc+wg=!Wt% ze6WpouzN5>YE;Txs$YsF)HQV0^Y>E6f!tm1=HZMm;7;H|_I_-tDdm1E3?g%$8+?pw_Fk%;a#t^W_R|TT%#s7f zO9STZ*HZLWW54H^=(g~s{{EWlrVCT9p6^OcBN72|@M_OngaOuZ_r-hO+N{xI*f%40 zD8o4LX}HA3R#4m32%srrqM!i70KG z`99fLF&i4&7y+1FtUfM|#BJ;VWZcZG%&Zhb$Yf+>0(QnGd@2&}{$_{X2~wCjIDF<~VR3eLW_IRawy`s1 zVdLfHWnpD!VP|K8N-){GT00oJFj?DEJ{9s$IT8T-PjdsrjEO zxj5PWdzJq!<-g^8=mMXT9S~ZZ;lmgTLG%1i&;H5_usjU#e;e>8Z9YDQs#6GAfaSlY zh7dCH*;zFVj0lX3gs7U!vz=Llk1xj{frmUV#P&h5(OC{JMBU;kB%8%l3#F@_cZ}-Q z)!GAMO+>LU{a`3y8OVYiBIo!sX{OxgaGd&Sl1sbcoOoQ{IX4csYph zh>3Tx;o&evV4gug$Wmb!Hw&e>{<&{P#-w?R`8TmS62`CJNN!Ew8h0O;B7Lg44 z5eA2d6L#@4^zW@7QrJUj4@G{a5=r>nE(Q2|OD6vr^&tZ^mk;H$RzFH12FkyM7=Hcq z*aqmwYa~pX*N5*3{uY9W^ZNN?HHc&j(mt$<-@<$T-ijnFyn1R4%rhxiKO`v+wRe9D zX@%?j+Zx2T*ktl=mwvE6wG|QCCHQ~41Opwef3@^5kN%x_k1O!swe+;9{JT9o?UVm* z4^QXEzuUvV+rz)s!@oA(zh>(*sFnY(CH?>%{=WwrUpGzl1og zh5S=DS%$&csYj7F1g6yKblz@jl%9Y6q)Iuv{<4{vigOW@iv8n0JIWKPWG?lAM-~j+ zuATAJR~lr<=9k}Sb!c$|`r-whfK?G5QRJ6qlnq|p!Bsa!rTvIK)6NUXei!g?+!EOc!IqDXDb#ZpiwWq{i@=_K)#m?J#SUFWcQY=oE&+0WMYqt)Kko_A6G}Jrq!tiqe;Q-SPqFi@)#8SMR56=K-{<&B>FwZ|w)l@asgt zenCY#)za0)dMks%_c3S0d|ehLP2>jN$3>xTH%LLw9n&8VPgIrk&C-1sZI0KxbM#ZS zk5+J$;EXR@8o(uuDMnVgO7}tu8qtmqv-<|S#vV+}?mZ`fgRR|@He0V+SnfW-3>+)- zziIfKIpZ~5L?4GT>X(6tBNa{(yQf!z*FkuGhroV14Ya!^_FefEM!eb*ND?>ir9`B#qWUP0~O4+)#M`b4c=D@ka{6UdS@*k4NCS^{$ggR$G4oMMabEV+h z2tAJuT`spvnpOr{#8)5|C301=k|DU!@%QJyWB~oUm1k@=pk4Z9yIRZU1Ys8KD(hK> z?{pz)TQ z-jYF3=gRD8&N4Wv?D-IevS#N#n(%g!~p?mAfA8C-Uf zp56ZX;5{V=CmH-Qa@%Gj+EPDAYC{xH(7Bj*a$Gi%9gyvH+nX)f8y6Jf0-At19||Ou z-i0`3YaOqp0S^xk$A4MY+6`A)@C>?m{Qzb!`s2E z&iml}gVMqksk2~Wh2`w{yg7Yb{b^kCZjkxd7MDuC%IeGBB7G1frH>+8Ha5hHK=AN< zYf3!V`yS-y?=Q1AQ@bm}q*G;i)IYkP?U>A6HIuKJN98>?GYoRa=#Hce^f>HThweZm zI_fB9-!A9hl;qYHs!KPZV_{)M3A#J++AjIUFzG1b-8D!D$I}_E7u=iGjXsyec*T_G zdlp`EvGnU`jA#B^05QlZmsVcSG&SA2mpWdTLioO{J?{K;FxjSo8AI^GCspI_=CWfp z{o_J^br+FM*zaQ*ZKZR=OFMvs5~SQ@@KAm{W28dn6TKyD-?$aO<-%|fA)f3@IYh4I zIrn+EYNoT{k^QE7>xuW`0oK*d8UV>VQ_doIHz(yW%zE#NcV|`A3pE$H_;2xwXgj=l zY+`5K&!>Co>-wta38VMQ-15E#BubGIQBOPXRUM~tuF6f7n^vD}%zaQy697(-)6l%D z)1RFpdAT3vuH>`>p`3QwiNX*jv}x<$nysI<6Yjfu@1;{U)3`6Z@Q&AEMe~=am~Xe| z`5a}uzFU5wR`ndSfw)$lV(O~z`TTKR2nAaJ37r+BA~r`ZZW5=)9l|(LmqiQQ-6*%5 zVm~*#1PoCGeg7Wov^za*3rQFEZP&4B`ZVjjH}|G)R@-JWf?x3T#%ymWos0XGcK^$lCO>7>V;Lk!&%X6#!iVlP@@_ z`jVTPy=q4fz`rC>lp=nr7hSnxU@LmUlF*zO7cil7nU5ZLpq!chINO{0l)PD4zE(c? zy$Yx>qUIZb23T65&!XMWt|ndUOH>uQy+N5qEuob!n$EK^7_!-vi)TF3E))h-ChElx z)t^@$9Z-hLet~^q8PYbU;T)Tn?mC`kc}i4`&S4^t57Jr93?#)uL%Q1KH)!-Yi)x8h z5@f2iTb0-F#stN%?_LvdztVGf|DvAJYSy*+glovKH3zZL~Fdj zKv+vS0D5*#v-3Oe$=hq4^Nn(+igV3S-4*DA0#cz{EYkMnwGGm?ox)(6e(w+lq%3Q` z>(uU@HUg+P*AwOB=qoy5)=Av3i#?Kb;B5X0`uN8Tr@dy|4KVq1`j1a}io=bb2t(?~ z=NW*lWI6m7D7a-miYc5+E^HREHKD<1WUrq^%7@knU>T;NVO$$@epn(yA_hR_5a5pho({VGZ7&WHVuVw0q+S5Q0SOCLkVEe)g!$h19P zbm|~9VgF-}o6q7bTy&NX>wq;AK{B=Obm#QaK9#j%IThW_?v1&kAK&Y@F3pWJw^GUd zr|3u^)Qr^EgPqD7JJk*#&q~*0JpB;x=Pd6%Jl~~`;AuraslK+jnfW~sL-w(+5Ou}i z>BQ6kFV8F|YvyT&NNXzNE`+9{E}MJXDfh)vrG~-p?ObQ+4Lv%yOUY~1jjL@v+80Oo zr6bc-3N_o3oz>jV;~N~JC&K|wUm5l4LP34UqpC`inDG#H9Ho(igf|+@L!a4?1E+#$ zr_Lcgna`xw2Olh0WA!iD#H>HoX?3&4(G&Kj-wiq=$kSGdV$qyTA0q6B2;9%u9~mN_ z4{kYLyhCG&*9W;xTWp^86@~L{SOw|T>FxqDXfwuW`t{~AzT|yqEf9hC{9NKYCmIQ{ zN~n7al3=x6g1c;+IZLc4)Tg*G9ihor`)!ou`-gV9bGqo#3BsAL`8&$A_e5fUFyq-` zt!X-2yq{M!`NBYpuN4=lvT6}mW73wo&r#sEu=@8_#!Lx*w>-`0t%VNBOwuUYDdG=gxf{V*pVxDBWR>olR+jQSzU-%&B zIWPVx-*+Yh3FJ&VoW}z24Z!lXGl@&E@45Tl0oZZGtJPUelOx(&jZC_l$mhBI%9 z#apkXn%lMFY7-igKaI!pOqBK5$9EM8<(T2;VoTo9wY0c-6 ziMg#OKN?&<=CWH4ki=Nr9h zBl{pvjmkSzQj`7R4Kr)2Lgmb_pti*kCE?%IujicapPvyn73b=2C0K54lp{eZ%&9>o zAa&-t4T|dp8c=Mh!gQ70Am}`Gf@%0P?JY1^cQz;5lTWK;4%A;x!7y#JyWhk!AY>s% zDxKpymy=P=wq=z<__}y=syD!AzwvrpNW-WLY?u}(2#jv5g4@_laQ^<v zhYXK2e};KF(k(HL6+IZIVGeeC(0BaCi^9@q{9J13Xnh%^Uv{w*f^@bS3ON_y_t-+xX6Y2* zcdNJ^-`C$OU8LMW`kcjUD}2?}b~arFj{y44X?rk)lP9F^F&ROAHa_y=cwI);(C8o{ z^DIC4>XSC1!02|8g&Sm^J6}7e%zY@oaceil!2329^nu&9MZ3~`O0AIo?V+le&ZAOt1&vQvKUWzKmB5GFBy=)!7JUH?fqWUzJB8IayYx|5tWW!^aTQ`m$A(U z$3Z31g1gC|ovW+y`W3_?=gD#sen{2mCD7x|9M-0ZL;a-XyMfnbr65Lb#T_BtN!5`4 zTB5+!Li0zWuS-Y?GVf0=(TI4gE1X}BvRAv-k#eP0=q?Sq1$~@v1U=jRnYW!0k9@Kj z53!4aWQ5O#aS_YT5_76D6bxE`Kie`t2f<~ZQ{&UCoTLC$wbkAJY+n)cIe@Lp>rcr9 zk7abF(CpV(tAp{FQ(P)0+-LQ7Euz*fX?041JRVW9bh%G7?MHY(f^<0y?i08bX^!-X zW{vzz4jEd?|Hm1vrRBpK8G9}?9rnmAh`>JpVwgy+Cq6GC_5~u2mg|1`?i12ChWZ03 zo?|Aycp^Iff09|m7`~5E+;q{dmfgXt5A`qhq=WK5c$~?&oQ7iJ61oj?OZ}v_6lP%< z>TnMNWQZ>~-}waw%J^NP1Sn&@G^Ah_(qCFa#1eIBD3g60O;NfGgSuIRW=aZ9HMpt? zv5)cAtV>APqAm%41v1G#iu+%(7q1VCpg%*yL5k;Isx8FoqVxegMg{m)h={%M=Z%Li z9W-oi^V_6o`G>JVhda?tqHgoEc4!SiP_j`&xQ_hwq(C@jvc5Fc=eTsRbL?0mu6W;{ zw4RHFs1~8z>=O_qUyd+^`9zhJl|q$`vhTr7{M6%6Ofq?6KZ@hOEDQzF-#Dn=X z!PbwL#0a7Y$M%@sMIE%pQk7}*2Ngopsn`RX%1c*u^TK3zAX?bfCUNbg5C-o*dW@^n zs4>_d%yWQ3Eoup@Sn}?^jt@u*6H)ey@AQ2nX{P{Po>5p=anD$`?FC-Y{iPKzXvGtU z4kB}wg-hKavZ*#9xYuj~h5A3xu-5`PS0ps8wQ4{6r|=mPrZa zWe~nAd#B#{p>!Lk+WmX`T1m=MU1|`+8WQ#!C4&`Qs+m{^g1SQG*N8ZoH4~bq4@&xi zLXi>AbC<$J-T^4mtWv?-dBgnskeL@ZlBdHiug$J2Uo&1}pP8cI?t-Jku8vX9>t*BnJI|m$M|h82`~i1&WqZVHTIo3gpIFDBSXHb~jvXtkp_m~YXq@m#Pn*BUa)A-Yq=b4uOlwL zpPl~330+vR;)<_@nGF=vR&aQjnH7a@FDTPT2DpymzC1U+IsvXTtl8Hwa|j8|tyj=T z)AV#l(^5+fqzL74U_W-h25xEDC`bCz+^a+_y4ON>R<3~RjX|}MA<#u@*LYc#FtW*I zDu(98?nMMDPUKXX7Wn)RXdCCObE@2Ue-0#+mpT{;MLRJYwe}v&Ml+MnI~QHj))|LL z?a~kXNf(^%p=}hC>7IS@hrO?o*$TJ8DrXzWLE(bYg$=zYc|Tn&6FCbsBwHy5Jkh)K zO3lh#;jX_ys|tpcpI4S0h2ugwu_WU^5V4GrXmlvBj>=1WMg5&fpJt4un63>TBg#sr z8e-JMc%{W$q9Yg9+1^#a(quf(as&L5>a+nCC1Brhs?F90~F8&kTOvesNVv5&o`!e3&N^08ctfxfSOHVG0fKJf8zRA>htGaVlO{+*liB~2E{OG z$#0D{wikO_S{7;~wv*B*Ug#3?{Is#c4qq31I9Ws>aABkn7B&P`Z$3a!2!mX2*`W$2 z&0>|Z#?GcOif!Wk__p%Z1tCxiRJy{fOsAFDPKmxZNhNi@CoRCRF&}7|@Ka^g20>`5`L8i;DC7wJ17`3V-ZVoY{zw|me(s&*>ZXkY_PDM;iq4tDn z$5-m!o*YMWxpI$b>ix}o?N&WgMVdGJ!Pg~~S(-cOK>ed^wb~PvAv`n#asMkO$#`Pf zw%Tc(uPzo>Xf^NO2_8j+x*Y*+4TKUdI`Gf3Jpj?U`WD4Z80lW@`fGjHGR*Z2UsA_^ ztD26Gm&E}UUa?fqvPoC0e(YM{&owL&L>^(>D6i?3N_PM`wLp9FrYr0 z;p%x9BC3-;tRi;^94fKXji1UKTp#z38NTnsJE>S*iRiHeh8w$GxS*z{X3*O_j~@oF5Im zYs5n$Pi~qBHdtI{3;J0%$5>G-WZCl`glzpZZ>xy6wsh}|l-v-I^s=_qvE7CT# zE%ybD*d2A*(v$o)AY7Se6l4~n@`d0<4hYs`(L^Dotz+@puaJ9HV-7-Kq zg4pfr6S*p5!S~x4)v+{pBhw<4isrs^Tv@k-Ju!6V+fG~H0X*l;{x+EaqN}s`CIwQY z-R%tDv&;?0UB{@APQBL!cUL3&f9O=RzUgX%f_FJ=*ZK&KyUzSBw{n8dg^DtReRNS+dH%R5*1c$TzGKF=VAi7|uv-s`Xt>Pk^X^?N@-{NySnzhoRXclip`%AvofjDXg570p03Jf+Jr@F14-xpBrKxc*+jxms_XZU zdqGr-%_!=q{+Zyb1cuLO@8 %rZ3V9D?#f6&ud#h*k_o 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 - -```mermaid -flowchart TD - A(Dapp) -->|get-starknet| B(Starknet Window Object) - A -->|wallet_invokeSnap| C(MetaMask / Starknet Snap) - B --> C - B --> D(Other Starknet wallets) - C --> E(Starknet network) - D --> E -```` - -

    - -## How `get-starknet` and MetaMask interact - -A dapp with `get-starknet` installed interacts with MetaMask as follows: - -1. The dapp uses `get-starknet` to request the user connect to MetaMask. - MetaMask automatically requests the user to add the Starknet Snap, if it's not already present. - -1. After the dapp is connected to MetaMask and the Starknet Snap, `get-starknet` receives a Starknet - Window Object (SWO), which represents the MetaMask wallet with Starknet functionality. - -1. The dapp uses `swo.account` to retrieve an [Account object](https://starknetjs.com/docs/API/#account) from the SWO. - This Account object enables the dapp to manage Starknet interactions within MetaMask. - -```mermaid -sequenceDiagram - participant user as End user - participant dapp as Dapp - participant get as get-starknet - participant mm as MetaMask - participant Snap as Starknet Snap - participant network as Starknet network - - dapp->>get: Initialize connection - get->>mm: Request connection - mm->>Snap: Activate - Snap-->>mm: Activated - get->>Snap: Request Starknet account address - Snap-->>mm: Recover account and return Starknet account address - mm-->>get: Return Starknet account address - get-->>dapp: Connection established with SWO return - - dapp->>get: Read blockchain data - get->>network: Query data - network-->>get: Return data - get-->>dapp: Processed data - - dapp->>get: Write transaction - get->>mm: Request write transaction - mm->>Snap: Write transaction - Snap-->>mm: Request confirmation to write transaction - mm-->>user: Request confirmation - user-->>mm: Confirm transaction - mm-->>Snap: Confirm transaction - - alt If the account has deployed - Snap-->>network: Deploying account transaction - end - Snap-->>network: Submit transaction - network-->>Snap: Transaction result - Snap-->>mm: Return transaction result - mm-->>get: Return transaction result - get-->>dapp: Return transaction result -``` - -The `get-starknet` library offers several features that improve how dapps interact with the Starknet -network through MetaMask: - -- The `Account` object uses a specified provider to access data from the Starknet network. -- For transactions, `get-starknet` prepares the data and sends it to MetaMask for signing through - the Starknet Snap. -- `get-starknet` enables the dapp to create contract instances connected to the `AccountInterface`, - allowing smart contract functions to be invoked, with MetaMask handling the signatures. -- `get-starknet` sets up listeners for account and network changes in MetaMask, so the dapp can - subscribe and update its state accordingly. -- `get-starknet` can request network changes through MetaMask, allowing users to switch between - Starknet networks, such as Mainnet and Sepolia testnet. -- `get-starknet` can also request MetaMask to display specific tokens, improving the user experience. diff --git a/sdk/starknet/guides/connect-to-starknet.md b/sdk/starknet/guides/connect-to-starknet.md deleted file mode 100644 index 9e1194134fc..00000000000 --- a/sdk/starknet/guides/connect-to-starknet.md +++ /dev/null @@ -1,469 +0,0 @@ ---- -description: Connect your dapp to Starknet in MetaMask. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Connect to Starknet - -Connect your dapp to Starknet in MetaMask by using the -[`get-starknet`](#connect-using-get-starknet) library or the -[`wallet_invokeSnap`](#connect-using-wallet_invokesnap) JSON-RPC method. - -:::warning Important - -We recommend using the `get-starknet` library for most use cases due to its ease of configuration -and multi-wallet support. -See [a comparison of the connection options](../index.md). - -::: - -:::tip - -If you're new to Starknet, you can also follow the -[Create a simple Starknet dapp tutorial](../tutorials/create-simple-starknet-dapp.md). - -::: - -## Prerequisites - -- [MetaMask installed](https://metamask.io/download/) -- A text editor (for example, [VS Code](https://code.visualstudio.com/)) -- [Node](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) version 20.11 or later -- [Yarn](https://yarnpkg.com/) -- (Optional) A JavaScript or TypeScript React project set up - -:::note - -This connection guide uses [`get-starknet`](https://github.com/starknet-io/get-starknet) version `3.3.0` and `starknet.js` version `6.11.0`. - -::: - -## Connect using `get-starknet` - -### 1. Set up the project - -If you don't have an existing React project set up, you can use -[Create React App](https://create-react-app.dev/) to set up a new React project with TypeScript. -Create a new project named `get-starknet-dapp`: - - - - -```bash -yarn create react-app get-starknet-dapp -``` - - - - -```bash -npm create react-app get-starknet-dapp -``` - - - - -Change into the project directory: - -```bash -cd get-starknet-dapp -``` - -### 2. Add `get-starknet` and `starknet.js` - -Add [`get-starknet`](https://github.com/starknet-io/get-starknet) version `3.3.0` and `starknet.js` -version `6.11.0` to your project's dependencies: - - - - - ```bash - yarn add get-starknet@3.3.0 starknet@6.11.0 - ``` - - - - - - ```bash - npm install get-starknet@3.3.0 starknet@6.11.0 - ``` - - - - -### 3. Connect to the Snap - -Create a `src/components` directory, and add a new file named `WalletConnectButton.js` to the directory. -Add the following code to the file, which handles the connection to the Starknet Snap and displays a button -for users to initiate the wallet connection: - -```javascript title="WalletConnectButton.js" -import React, { useState } from "react"; -import { connect, disconnect } from "get-starknet"; -import { encode } from "starknet"; -function WalletConnectButton() { - const [walletAddress, setWalletAddress] = useState(""); - const [walletName, setWalletName] = useState(""); - const [wallet, setWallet] = useState(""); - const handleDisconnect = async () => { - await disconnect({clearLastWallet: true}); - setWallet(""); - setWalletAddress(""); - setWalletName("") - } - const handleConnect = async () => { - try{ - const getWallet = await connect({ modalMode: "alwaysAsk", modalTheme: "light" }); - await getWallet?.enable({ starknetVersion: "v5" }); - setWallet(getWallet); - const addr = encode.addHexPrefix(encode.removeHexPrefix(getWallet?.selectedAddress ?? "0x").padStart(64, "0")); - setWalletAddress(addr); - setWalletName(getWallet?.name || "") - } - catch(e){ - // Handle user rejection to install MetaMask / the Starknet Snap. - console.log(e) - } - }; - return ( -
    - {!walletAddress && ( - - )} - {walletAddress && ( -
    - -
    -

    Wallet Name: {walletName}

    -

    Wallet Address: {walletAddress}

    -
    -
    - )} -
    - ); -} -export default WalletConnectButton; -``` - -:::note - -This code automatically requests the user to add the Starknet Snap to MetaMask, if it's not already present. -Handle the error if the user rejects the connection request in the `try` / `catch` block of the -`handleConnect` function. - -::: - -Add a new file named `App.js` to the `src` directory, and add the following code to the file to use -the `WalletConnectButton` component: - -```js title="App.js" -import WalletConnectButton from "./components/WalletConnectButton.js" - -function App() { - return ( -
    -
    - -
    -
    - ); -} - -export default App; -``` - -### 4. Start the dapp - -Start the dapp and navigate to it in your browser. - - - - - ```bash - yarn start - ``` - - - - - - ```bash - npm start - ``` - - - - -A **Connect Wallet** button displays. -When a user selects it, `get-starknet` displays a modal that detects MetaMask and allows users to -choose which Starknet wallet to connect to. -When a user connects to MetaMask, `get-starknet` requests the user to connect to the Starknet Snap in MetaMask. - -
    -
    - Starknet wallet modal -
    -
    - Starknet MetaMask connection request -
    -
    - -After the user connects to Starknet, the dapp displays the user's connected wallet and wallet address: - -

    - Connected Starknet dapp -

    - -:::note - -An account can submit transactions only after it's deployed. -It does not deploy immediately upon creation. -Deployment happens during the first [transaction](send-transactions.md). - -::: - -## Connect using `wallet_invokeSnap` - -Alternatively, you can manage the Snap invocation manually. -Use the [`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC -method to directly interact with the Starknet Snap. - -:::warning Important - -We recommend using [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) for detecting MetaMask when using the `wallet_invokeSnap` approach. -This ensures you can connect to MetaMask and other installed wallets without conflict. - -::: - -:::note - -The [Starknet Snap companion dapp](https://snaps.consensys.io/starknet) serves as a reference implementation and example dapp. It demonstrates how to manually invoke the snap using `wallet_invokeSnap`, and presents potential use cases and UI integration. For more details, see the [source code of the companion dapp's UI](https://github.com/Consensys/starknet-snap/tree/main/packages/wallet-ui). - -::: - -### 1. Connect to the Snap - -Create a `src/utils` directory, and add a new file named `snapHelper.js` to the directory. -In `snapHelper.js`, add a `connect` function and a `callSnap` helper function as follows. -This file handles the interactions with the Starknet Snap: - -```javascript title="snapHelper.js" -const snapId = "npm:starknet-snap"; - -export async function connect() { - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_requestSnaps", - params: { - [snapId]: {}, - }, - }); -} - -export async function callSnap(method, params) { - try { - const response = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId, - request: { - method, - params, - }, - }, - }); - console.log(`${method} response:`, response); - return response; - } catch (err) { - console.error(`${method} error:`, err); - alert(`${method} error: ${err.message || err}`); - throw err; - } -} -``` - -:::note - -To connect to Starknet, the dapp user must add the Starknet Snap to MetaMask. - -::: - -### 2. Call a specific Snap method - -Use the `callSnap` function to call a specific Snap method. -The following example calls [`starkNet_createAccount`](../reference/snap-api.md#starknet_createaccount): - -```javascript -const deploy = false; // Set to true to deploy the actual account. -const addressIndex = 0; // Specify which address to derive. -const chainId = "0x534e5f5345504f4c4941"; // Chain ID of the network to use. - -const accountInfo = await callSnap("starkNet_createAccount", { addressIndex, deploy, chainId }); -``` - -:::note - -An account can submit transactions only after it's deployed. -It does not deploy immediately upon creation. -Deployment happens during the first [transaction](send-transactions.md). - -::: - -### Examples - -#### HTML and Vanilla JS - -The following is a full example of a simple HTML and Vanilla JavaScript dapp that connects to the -Starknet Snap using `wallet_invokeSnap`. - -It displays a button that, when selected: - -- Connects to Starknet in MetaMask. -- Creates a Starknet account. -- Displays the account address. - -```html - - - - - Connect Starknet Snap - - - -

    - - - -``` - -#### React - -The following is a full example of a simple React component that connects to the Starknet Snap using -`wallet_invokeSnap`. - -```javascript -import React, { useState } from "react"; -const ConnectWallet = () => { - const [accountInfo, setAccountInfo] = useState(''); - const connect = async (snapId) => { - try { - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_requestSnaps", - params: { - [snapId]: {}, - }, - }); - } catch (err) { - console.error("Snap connection error:", err); - alert(`Error connecting to Snap: ${err.message || err}`); - } - }; - const callSnap = async (snapId, method, params) => { - try { - const response = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId, - request: { - method, - params, - }, - }, - }); - return response; - } catch (err) { - console.error(`${method} problem happened:`, err); - alert(`${method} problem happened: ${err.message || err}`); - } - }; - const handleConnectClick = async () => { - try { - const snapId = "npm:@consensys/starknet-snap"; // Snap ID. - await connect(snapId); - const deploy = false; // Whether to deploy the actual account. - const addressIndex = 0; // The address to derive. - const chainId = "0x534e5f5345504f4c4941"; // Chain ID of the network to use. - const account = await callSnap(snapId, "starkNet_createAccount", { addressIndex, deploy, chainId }); - setAccountInfo(`Connected Starknet Account: ${account.address}`); - } catch (error) { - console.error("Error connecting to Starknet Snap:", error); - } - }; - return ( -
    - -

    {accountInfo}

    -
    - ); -}; -export default ConnectWallet; -``` - -:::note - -See how to [troubleshoot](troubleshoot.md) connection issues when configuring your dapp using -`wallet_invokeSnap`. - -::: - -## Next steps - -After connecting your dapp to Starknet in MetaMask, you can follow these next steps: - -- [Manage users' Starknet accounts](manage-user-accounts.md). -- [Manage Starknet networks](manage-networks.md). -- Explore the [Starknet Snap API reference](../reference/snap-api.md). diff --git a/sdk/starknet/guides/manage-networks.md b/sdk/starknet/guides/manage-networks.md deleted file mode 100644 index fd44ee2e0c0..00000000000 --- a/sdk/starknet/guides/manage-networks.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -description: Manage Starknet networks in MetaMask. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Manage networks - -You can detect a user's Starknet network and prompt them to switch Starknet networks in MetaMask, -using the -[`get-starknet`](https://github.com/starknet-io/get-starknet) library or the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method. - -## Prerequisites - -[Connect to Starknet](connect-to-starknet.md) from your dapp. - -## Detect a user's network - -Detect the Starknet network a user is currently connected to using the following: - - - - - ```javascript - const checkCurrentNetwork = (wallet) => { - try { - if(wallet?.isConnected !== true){ - throw("Wallet not connected"); - } - const currentNetwork = wallet?.chainId - console.log("Currently connected to:", currentNetwork); - return currentNetwork; - } catch (error) { - console.error("Error of detect current connected network:", error); - } - }; - ``` - - - - - ```javascript - const checkCurrentNetwork = async () => { - const provider = await getEip6963Provider(); // Or window.ethereum if you don't support EIP-6963. - if (provider) { - try { - const response = await provider.request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_getCurrentNetwork" - } - } - }); - - console.log("Currently connected to:", response.name); - return response.chainId; // Returns the chain ID. - } catch (error) { - console.error("Error getting current Starknet network:", error); - throw error; - } - } else { - console.error("MetaMask not detected or Snaps not supported"); - throw new Error("MetaMask not detected or Snaps not supported"); - } - }; - - checkCurrentNetwork().then(chainId => { - console.log("Chain ID:", chainId); - }).catch(error => { - console.error("Error:", error); - }); - ``` - - - - -## Switch networks - -Starknet currently supports two public networks, Mainnet and Sepolia testnet. -Prompt users to switch between networks by setting the -[chain ID](../reference/snap-api.md#supported-networks) of the target network: - - - - - ```javascript - const switchChain = async (wallet, chainId) => { - try { - if(wallet?.isConnected !== true){ - throw("Wallet not connected"); - } - - await wallet?.request({ - type: "wallet_switchStarknetChain", - params: { chainId: chainId }, - }); - console.log(`Switched to chainId: ${chainId}`); - } catch (e) { - console.error("Failed to switch chain:", e); - } - }; - ``` - - - - - ```javascript - const switchStarknetNetwork = async (chainId) => { - const provider = await getEip6963Provider(); // Or window.ethereum if you don't support EIP-6963. - if (provider) { - try { - await provider.request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_switchNetwork", - params: { chainId: chainId } - } - } - }); - console.log(`Switched to Starknet network with chainId: ${chainId}`); - } catch (error) { - console.error("Error switching Starknet network:", error); - throw error; - } - } else { - console.error("MetaMask not detected or Snaps not supported"); - throw new Error("MetaMask not detected or Snaps not supported"); - } - }; - ``` - - - \ No newline at end of file diff --git a/sdk/starknet/guides/manage-user-accounts.md b/sdk/starknet/guides/manage-user-accounts.md deleted file mode 100644 index 6da4c06075c..00000000000 --- a/sdk/starknet/guides/manage-user-accounts.md +++ /dev/null @@ -1,431 +0,0 @@ ---- -description: Manage users' Starknet accounts in MetaMask. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Manage user accounts - -You can manage users' Starknet accounts in MetaMask using the -[`get-starknet`](https://github.com/starknet-io/get-starknet) library or the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method. - -:::note Notes - -- Account creation in Starknet is handled by the wallet provider. - As a dapp developer, you do not create accounts directly. - Instead, you can guide users to [create an account](connect-to-starknet.md) with MetaMask. -- Currently, the Starknet Snap doesn't support multiple Starknet accounts. - -::: - -## Prerequisites - -[Connect to Starknet](connect-to-starknet.md) from your dapp. - -## Display account information - -After a user connects to their Starknet account in MetaMask, you can display the account details. -The following example displays the account address: - - - - - ```javascript - const connectStarknetAccount = async () => { - const starknet = await connect(); - await starknet.enable(); // Prompts the user to connect their Starknet account using MetaMask. - return starknet; - }; - - const showAccountInfo = async () => { - const starknet = await connectStarknetAccount(); - - if (account) { - document.getElementById("accountAddress").innerText = `Account Address: ${starknet.selectedAddress}`; - } - }; - ``` - - - - - ```javascript - const showAccountInfo = async () => { - if (typeof provider !== "undefined" && provider.isMetaMask) { - try { - // Invoke the Starknet Snap to get account information. - const response = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@starknet-snap/snap", - request: { - method: "starknet_recoverAccounts" - } - } - }); - - if (response && response.length > 0) { - const account = response[0]; // Get the first account. - document.getElementById("accountAddress").innerText = `Account Address: ${account.address}`; - } else { - document.getElementById("accountAddress").innerText = "No Starknet account found"; - } - } catch (error) { - console.error("Error fetching Starknet account:", error); - document.getElementById("accountAddress").innerText = "Error fetching account information"; - } - } else { - document.getElementById("accountAddress").innerText = "MetaMask not detected or Snaps not supported"; - } - }; - - // Call the function when needed. - showAccountInfo(); - ``` - - - - -## Retrieve the connected account - -You can retrieve and display a user's connected Starknet account. -The following example displays the connected account address if available, and displays buttons to -connect or disconnect the account. - - - - - ```javascript - import { useStarknet, useConnectors } from "@starknet-react/core"; - import { useState, useEffect } from "react"; - - function AccountDisplay() { - const { account } = useStarknet(); - const { available, connect, disconnect } = useConnectors(); - const [accountAddress, setAccountAddress] = useState(); - - useEffect(() => { - setAccountAddress(account?.address); - }, [account]); - - return ( -
    - {accountAddress ? ( -

    Connected Account: {accountAddress}

    - ) : ( -

    No account connected

    - )} - {available.map((connector) => ( - - ))} - {account && ( - - )} -
    - ); - } - ``` - -
    - - - ```javascript - import React, { useState, useEffect } from "react"; - - const STARKNET_SNAP_ID = "npm:@starknet-snap/snap"; - - function AccountDisplay() { - const [accountAddress, setAccountAddress] = useState(); - const [isConnected, setIsConnected] = useState(false); - const [error, setError] = useState(null); - - const connectToSnap = async () => { - if (typeof provider !== "undefined" && provider.isMetaMask) { - try { - // Request permission to access the Snap. - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_requestSnaps", - params: { [STARKNET_SNAP_ID]: {} } - }); - setIsConnected(true); - fetchAccount(); - } catch (err) { - console.error("Error connecting to Starknet Snap:", err); - setError("Failed to connect to Starknet Snap"); - } - } else { - setError("MetaMask not detected or Snaps not supported"); - } - }; - - const disconnectFromSnap = async () => { - setAccountAddress(undefined); - setIsConnected(false); - }; - - const fetchAccount = async () => { - if (typeof provider !== "undefined" && provider.isMetaMask) { - try { - const response = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId: STARKNET_SNAP_ID, - request: { - method: "starknet_recoverAccounts" - } - } - }); - - if (response && response.length > 0) { - setAccountAddress(response[0].address); - } else { - setError("No Starknet account found"); - } - } catch (err) { - console.error("Error fetching Starknet account:", err); - setError("Failed to fetch account information"); - } - } - }; - - useEffect(() => { - if (isConnected) { - fetchAccount(); - } - }, [isConnected]); - - return ( -
    - {accountAddress ? ( -

    Connected Account: {accountAddress}

    - ) : ( -

    No account connected

    - )} - {!isConnected ? ( - - ) : ( - - )} - {error &&

    {error}

    } -
    - ); - } - - export default AccountDisplay; - ``` - -
    -
    - - -## Manage account transactions - -You can manage a user's Starknet account transactions. -The following example invokes a specific function on a Starknet smart contract, handles wallet -connection and transaction submission, and logs the result or any errors: - - - - -```javascript -const invokeStarknetContract = async () => { - try { - const starknet = getStarknet(); - await starknet.enable(); // Make sure the wallet is enabled. - - const contractAddress = "0xYourContractAddress"; // Replace with your contract address. - const entrypoint = "function_name"; // The function you want to call. - const calldata = [/* your function arguments */]; // Replace with calldata. - - const result = await starknet.account.execute({ - contractAddress: contractAddress, - entrypoint: entrypoint, - calldata: calldata - }); - - console.log("Transaction result: ", result); - } catch (error) { - console.error("Error invoking contract:", error); - } -}; -``` - - - - -```javascript -const invokeStarknetContract = async () => { - if (typeof provider !== "undefined" && provider.isMetaMask) { - try { - const calls = [ - { - entrypoint: "transfer", // The function name to call on the contract. - calldata: [ - "0x1234567890abcdef1234567890abcdef12345678", // Recipient's address. - "1000000000000000000" // Amount to transfer in wei (1 token, assuming 18 decimals). - ] - } - ]; - - const result = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_executeTxn", - params: { - address: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", // The sender's address. - calls: calls, // The array of calls with entrypoint and calldata. - details: { - nonce: 1, // Optional nonce. - maxFee: "1000000000000000", // Maximum gas fee allowed. - }, - chainId: "0x534e5f5345504f4c4941" // Starknet Sepolia testnet chain ID. - } - } - } - }); - - console.log("Transaction result: ", result); - } catch (error) { - console.error("Error invoking contract:", error); - } - } else { - console.error("MetaMask not detected or Snaps not supported"); - } -}; -``` - - - - -## Handle account changes and disconnections - -You can handle account changes and disconnections. -Use the following component at the top level of your dapp to handle account changes globally: - - - - - ```javascript - import { getStarknet } from "get-starknet"; - import { useEffect, useState } from "react"; - - function AccountChangeHandler() { - const [account, setAccount] = useState(null); - - useEffect(() => { - const starknet = getStarknet(); - - const handleAccountsChanged = (accounts: string[]) => { - console.log("Accounts changed:", accounts); - setAccount(accounts[0] || null); - }; - - const handleDisconnect = () => { - console.log("Disconnected from wallet"); - setAccount(null); - }; - - if (starknet) { - starknet.on("accountsChanged", handleAccountsChanged); - starknet.on("networkChanged", handleDisconnect); - - // Initial account setup. - starknet.enable().then((accounts: string[]) => { - setAccount(accounts[0] || null); - }); - - return () => { - starknet.off("accountsChanged", handleAccountsChanged); - starknet.off("networkChanged", handleDisconnect); - }; - } - }, []); - - return ( -
    - {account ? ( -

    Connected Account: {account}

    - ) : ( -

    No account connected

    - )} -
    - ); - } - - export default AccountChangeHandler; - ``` -
    - - - ```javascript - import React, { useEffect, useState } from "react"; - - const STARKNET_SNAP_ID = "npm:@starknet-snap/snap"; - - function AccountChangeHandler() { - const [account, setAccount] = useState(null); - - const fetchAccount = async () => { - if (typeof provider !== "undefined" && provider.isMetaMask) { - try { - const response = await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_invokeSnap", - params: { - snapId: STARKNET_SNAP_ID, - request: { - method: "starknet_recoverAccounts" - } - } - }); - - if (response && response.length > 0) { - setAccount(response[0].address); - } else { - setAccount(null); - } - } catch (error) { - console.error("Error fetching Starknet account:", error); - setAccount(null); - } - } else { - console.error("MetaMask not detected or Snaps not supported"); - setAccount(null); - } - }; - - useEffect(() => { - fetchAccount(); - - // Retrieve account changes every 5 seconds. - const intervalId = setInterval(fetchAccount, 5000); - - return () => clearInterval(intervalId); - }, []); - - return ( -
    - {account ? ( -

    Connected Account: {account}

    - ) : ( -

    No account connected

    - )} -
    - ); - } - - export default AccountChangeHandler; - ``` - -
    -
    diff --git a/sdk/starknet/guides/send-transactions.md b/sdk/starknet/guides/send-transactions.md deleted file mode 100644 index e55ae6f6b74..00000000000 --- a/sdk/starknet/guides/send-transactions.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -description: Send Starknet transactions in MetaMask. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Send transactions - -You can send Starknet transactions using the -[`get-starknet`](https://github.com/starknet-io/get-starknet) library or the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method. - -## Prerequisites - -[Connect to Starknet](connect-to-starknet.md) from your dapp. - -## Send a transaction - -Send a transaction using the -[`starknet.account.execute()`](https://starknetjs.com/docs/api/classes/accountinterface/#execute) -function (with `get-starknet`) or the -[`starknet_executeTxn`](../reference/snap-api.md#starknet_executetxn) -method (with `wallet_invokeSnap`): - - - - - ```javascript - const sendStarknetTransaction = async (wallet, contractAddress, entrypoint, calldata) => { - try { - if(wallet?.isConnected !== true){ - throw("Wallet not connected"); - } - - // Send the transaction. - const result = await wallet?.account?.execute({ - contractAddress: contractAddress, // The address of the contract. - entrypoint: entrypoint, // The function to call in the contract. - calldata: calldata // The parameters to pass to the function. - }); - console.log("Transaction successful:", result); - return result; - } catch (error) { - console.error("Error sending transaction:", error); - } - }; - ``` - - - - - ```javascript - const sendStarknetTransaction = async (address, chainId, contractAddress, entrypoint, calldata, maxFee = null) => { - const provider = await getEip6963Provider() // Or window.ethereum if you don't support EIP-6963. - if (!provider) { - throw new Error("MetaMask not detected or Snaps not supported"); - } - try { - const response = await provider.request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_executeTxn", - params: { - address, // The address of the account. - chainId, // The chain ID of the request. - calls: { - contractAddress, // The address of the contract. - entrypoint, // The function to call in the contract. - calldata // Arguments to the contract. - }, - details: { - maxFee, // This is optional. It will re-estimate in the snap if not provided. - } - } - } - } - }); - console.log("Transaction sent:", response); - return response; - } catch (error) { - console.error("Error sending transaction:", error); - throw error; - } - }; - ``` - - - - -## Simplified example - -The following is a full, simplified example of connecting to a Starknet account and sending a transaction: - - - - -```javascript - import { connect } from "get-starknet"; - - const connectStarknetAccount = async () => { - const starknet = await connect(); - await starknet.enable(); // Prompts the user to connect their Starknet account using MetaMask. - return starknet; - }; - - const sendStarknetTransaction = async (contractAddress, entrypoint, calldata) => { - try { - const starknet = await connectStarknetAccount(); // Ensure the account is connected. - - // Send the transaction. - const result = await starknet.account.execute({ - contractAddress: contractAddress, - entrypoint: entrypoint, - calldata: calldata - }); - - console.log("Transaction successful:", result); - return result; - } catch (error) { - console.error("Error sending transaction:", error); - } - }; - - const contractAddress = "0xYourContractAddress"; - const entrypoint = "your_function_name"; - const calldata = [/* your function arguments */]; - - sendStarknetTransaction(contractAddress, entrypoint, calldata); - ``` - - - - - ```javascript - const connectStarknetAccount = async (provider) => { - try { - await provider // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_requestSnaps", - params: { - "npm:@consensys/starknet-snap": {} - } - }); - console.log("Starknet Snap connected"); - } catch (error) { - console.error("Error connecting to Starknet Snap:", error); - throw error; - } - }; - - const sendStarknetTransaction = async (provider, address, calls, maxFee = null) => { - try { - await connectStarknetAccount(provider); - const requestParams = { - address, - calls, - details, - }; - if (maxFee) { - requestParams.details = { - maxFee - }; - } - const response = await provider.request({ // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_executeTxn", - params: requestParams - } - } - }); - console.log("Transaction sent:", response); - return response; - } - catch (error) { - console.error("Error sending transaction:", error); - throw error; - } - }; - - // Example usage. - const calls = [ - { - "entrypoint": "transfer", - "calldata": ["0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "1000"], - "contractAddress": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" - } - ]; - const address = "0xb60e8dd61c5d32be8058bb8eb970870f07233155"; - const maxFee = "1000000000000000"; // Optional. - - sendStarknetTransaction(address, calls, maxFee) - .then(result => console.log("Transaction result:", result)) - .catch(error => console.error("Transaction error:", error)); -} -``` - - - diff --git a/sdk/starknet/guides/sign-data.md b/sdk/starknet/guides/sign-data.md deleted file mode 100644 index 0ab7152ae68..00000000000 --- a/sdk/starknet/guides/sign-data.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -description: Sign Starknet transactions in MetaMask. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Sign data - -You can sign Starknet transactions using the -[`get-starknet`](https://github.com/starknet-io/get-starknet) library or the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method. - -## Prerequisites - -[Connect to Starknet](connect-to-starknet.md) from your dapp. - -## Sign a transaction - -Sign a Starknet transaction using the following: - - - - - ```typescript - const signStarknetTransaction = async (wallet, contractAddress, entrypoint, calldata) => { - try { - if(wallet?.isConnected !== true){ - throw("Wallet not connected"); - } - - // Sign the transaction. - const result = await wallet?.account?.signer.signTransaction({ - contractAddress: contractAddress, // The address of the contract. - entrypoint: entrypoint, // The function to call in the contract. - calldata: calldata // The parameters to pass to the function. - }); - console.log("Transaction signed successfully:", result); - return result; - } catch (error) { - console.error("Error signing transaction:", error); - } - }; - ``` - - - - ```typescript - const signStarknetTransactionWithSnap = async (contractAddress, entrypoint, calldata, chainId, address) => { - try { - const provider = await getEip6963Provider(); // Or window.ethereum if you don't support EIP-6963. - if (!provider) { - throw new Error("MetaMask not detected or Snaps not supported"); - } - // Connect to the Starknet Snap if it's not already connected. - await provider.request({ - method: "wallet_requestSnaps", - params: { - "npm:@consensys/starknet-snap": {} - } - }); - console.log("Starknet Snap connected"); - - // Use the wallet_invokeSnap method to sign the transaction. - const response = await provider.request({ - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_signTransaction", - params: { - address, // The address of the account. - chainId, // The chain ID of the request. - transactions: { - contractAddress, // The address of the contract. - entrypoint, // The function to call in the contract. - calldata // The parameters to pass to the function (as an array). - } - } - } - } - }); - - console.log("Transaction signed successfully:", response); - return response; - } catch (error) { - console.error("Error signing transaction:", error); - throw error; - } - }; - - // Example usage. - const contractAddress = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"; - const entrypoint = "transfer"; - const calldata = ["0xRecipientAddress", "1000"]; - - signStarknetTransactionWithSnap(contractAddress, entrypoint, calldata) - .then(result => console.log("Signed transaction result:", result)) - .catch(error => console.error("Transaction error:", error)); - ``` - - - \ No newline at end of file diff --git a/sdk/starknet/guides/troubleshoot.md b/sdk/starknet/guides/troubleshoot.md deleted file mode 100644 index ee2e5db8006..00000000000 --- a/sdk/starknet/guides/troubleshoot.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -description: Troubleshoot common Starknet issues. -toc_max_heading_level: 4 ---- - -# Troubleshoot - -This guide addresses issues that might occur when connecting your dapp to the Starknet Snap in MetaMask. - -When using `get-starknet`, the library automatically handles detecting and connecting to MetaMask, -and adding the Starknet Snap. -If you're using `wallet_invokeSnap` directly, you might need to manage these processes manually. - -## MetaMask is not connected - -When using `wallet_invokeSnap`, use the following function to detect if MetaMask is installed: - -```typescript -async function detectMetamaskSupport(windowObject: Window & typeof globalThis): Promise { - const provider = await waitForMetaMaskProvider(windowObject, { retries: 3 }); - return provider; -} -``` - -This function uses the `waitForMetaMaskProvider` helper function, which -attempts to detect the MetaMask provider three times. - -In the event MetaMask is not installed, for example `isMetaMaskInstallRequired=true`, you can prompt -the user to install MetaMask using the following: - -```typescript -function checkAndPromptForMetaMask() { - const isMetaMaskInstalled = typeof provider !== "undefined" && provider.isMetaMask; - - if (!isMetaMaskInstalled) { - console.log("MetaMask is not installed. Prompting user to install."); - - // Update UI to inform the user. - const messageElement = document.getElementById("metamask-message") || document.createElement("div"); - messageElement.id = "metamask-message"; - messageElement.innerHTML = ` -

    MetaMask is required to use this dapp. Please install MetaMask to continue.

    - - `; - document.body.appendChild(messageElement); - - // Add click event to the install button. - document.getElementById("install-metamask").addEventListener("click", () => { - window.open("https://metamask.io/download.html", "_blank"); - }); - } else { - console.log("MetaMask is installed. Proceeding with this dapp."); - } -} - -// Call this function when your dapp initializes. -checkAndPromptForMetaMask(); -``` - -## Snap is not connected - -After connecting to MetaMask, verify that it supports Snaps: - -```typescript -const isSupportSnap = async (provider: any): Promise => { - try { - await provider.request({ - method: "wallet_getSnaps", - }); - return true; - } catch { - return false; - } -}; -``` - -If MetaMask is installed but the Snap is not added, use the following code to prompt the user to install the Snap: - -```typescript -async function installSnap(provider: MetaMaskProvider, snapId: string, snapVersion: string) { - try { - await provider.request({ - method: "wallet_requestSnaps", - params: { - [snapId]: { version: snapVersion }, - }, - }); - console.log("Snap installed successfully"); - } catch (error) { - console.error("Failed to install Snap:", error); - // Handle the error (for example, user rejected installation). - } -} -``` - - - \ No newline at end of file diff --git a/sdk/starknet/index.md b/sdk/starknet/index.md deleted file mode 100644 index f50624bc034..00000000000 --- a/sdk/starknet/index.md +++ /dev/null @@ -1,175 +0,0 @@ ---- -sidebar_label: Introduction -description: Interact with users' Starknet accounts in MetaMask. ---- - -# Connect to Starknet - -[Starknet](https://www.starknet.io/) is a Layer 2 network. -You can interact with users' Starknet accounts in MetaMask by connecting to the -[Starknet Snap](https://snaps.metamask.io/snap/npm/consensys/starknet-snap/). -You can use the [`get-starknet`](https://github.com/starknet-io/get-starknet) library or the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method from -your dapp to connect to the Starknet Snap. -Both options support similar functionalities, but offer different ways of interacting with users' -Starknet accounts. - -See [**Connect to Starknet**](guides/connect-to-starknet.md) to get started. - -The following sections compare the two connection options. - -## `get-starknet` - -:::warning Important - -We recommend using the `get-starknet` library for most use cases due to its ease of configuration -and multi-wallet support. -Learn more [about how `get-starknet` interacts with MetaMask](concepts/about-get-starknet.md). - -::: - -The `get-starknet` library: - -- Provides a high-level API that abstracts complex operations. -- Standardizes error handling. -- Supports connecting to multiple Starknet wallets, not limited to MetaMask. -- Manages wallet connections and Starknet interactions. -- Provides results in more readable code. - -`get-starknet` provides the same functionalities as `wallet_invokeSnap` and integrates a Starknet -Window Object (SWO). -The SWO simplifies account management and signing, and enhances the experience of handling account -states and transactions. -A dapp uses the [Account object](https://starknetjs.com/docs/API/classes/Account) in the SWO to manage operations. - -## `wallet_invokeSnap` - -The `wallet_invokeSnap` method: - -- Requires precise method names and parameter structures. -- Handles both MetaMask-specific and Starknet-specific errors. -- Is designed for operating within the MetaMask framework. -- Manages lower-level Starknet interactions directly. -- Provides results in more detailed, lower-level code. - -`wallet_invokeSnap` manages direct interactions between the dapp and the Starknet Snap. -It facilitates network communication for account creation, transaction signing, fee estimation, and -other Starknet-related actions. - -## Supported functionalities - -The following section lists the core functionalities and API methods that each connection option supports: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Functionality`get-starknet``wallet_invokeSnap`
    Account managementDeploy an account`deployAccount` ↗`starkNet_createAccount`
    Recover an account address`getAddress` ↗`starkNet_recoverAccounts`
    Display a private keyn/a`starkNet_displayPrivateKey`
    Gas and feesEstimate the gas fee`estimateFeeBulk` ↗`starkNet_estimateFee`
    Estimate the account deploy fee`estimateAccountDeployFee` ↗`starkNet_estimateAccountDeployFee`
    Token managementAdd an ERC-20 token`watchAsset` ↗`starkNet_addErc20Token`
    Get the ERC-20 token balance`callContract` ↗`starkNet_getErc20TokenBalance`
    Signing and transactionsSign a message`signMessage` ↗`starkNet_signMessage`
    Sign a transaction`signTransaction` ↗`starkNet_signTransaction`
    Sign a declare transaction`signDeclareTransaction` ↗`starkNet_signDeclareTransaction`
    Verify a signed messagen/a`starkNet_verifySignedMessage`
    Execute a transaction`execute` ↗`starkNet_executeTxn`
    Declare a contract`declareContract` ↗`starkNet_declareContract`
    Get transactions`getTransaction` ↗`starkNet_getTransaction`
    Get the transaction status`getTransactionStatus` ↗`starkNet_getTransactionStatus`
    Network managementSwitch networks`switchNetwork` ↗`starkNet_switchNetwork`
    Get the current network`getChainId` ↗`starkNet_getCurrentNetwork`
    - -## Resources - -To get started, [connect your dapp to Starknet in MetaMask](guides/connect-to-starknet.md). - -The following external resources provide additional information for learning about and interacting with Starknet: - -- [Official Starknet documentation](https://www.starknet.io/developers/) -- [Starknet companion dapp](https://snaps.consensys.io/starknet) -- [Voyager block explorer](https://voyager.online/) -- [Cairo language for Starknet](https://book.cairo-lang.org/) diff --git a/sdk/starknet/reference/snap-api.md b/sdk/starknet/reference/snap-api.md deleted file mode 100644 index a0769044bb8..00000000000 --- a/sdk/starknet/reference/snap-api.md +++ /dev/null @@ -1,1117 +0,0 @@ ---- -description: See the Starknet Snap API reference. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Starknet Snap API - -When connected to the Starknet Snap, dapps -can use the Starknet Snap API to interact with users' Starknet accounts (for example, to send transactions). - -The examples on this page use the -[`wallet_invokeSnap`](/snaps/reference/wallet-api-for-snaps/#wallet_invokesnap) JSON-RPC method, -which supports all Starknet Snap API methods. - -:::note - -We recommend using [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) for detecting the MetaMask wallet when using the `wallet_invokeSnap` approach. This ensures better interoperability and improved wallet integration. - -::: - -:::note - -You can also communicate with the Starknet network using the -[Starknet API](/services/reference/starknet). - -::: - -## Supported networks - -Starknet currently supports two public networks. -Use these networks' chain IDs with the Starknet Snap API methods. - -| Network | Chain ID (Hexadecimal) | -|-------------------|--------------------------| -| Mainnet | `0x534e5f4d41494e` | -| Testnet (Sepolia) | `0x534e5f5345504f4c4941` | - -## API methods - -### `starkNet_createAccount` - -Deploys an account contract. - -#### Parameters - -- `addressIndex`: `integer` - (Optional) Specific address index of the derived key in - [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). -- `deploy`: `boolean` - (Optional) Indicate whether to include send the deploy transaction for the - account contract. - The default is `false`. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The address of the account and the transaction hash if the account has been created. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_createAccount", - params: { - addressIndex: 1, - deploy: true, - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -{ - "transaction_hash": "0x05a56e2d52c817161883f50c441c3228cfe54d9f84b5b5b8b1c8b8e0e6f7e6d8", - "address": "0xb60e8dd61c5d32be8058bb8eb970870f07233155" -} -``` - - - - -### `starkNet_declareContract` - -Registers a contract's class on the Starknet blockchain without deploying it. - -#### Parameters - -- `senderAddress`: `string` - Address of the sender. -- `chainId`: `string` - (Optional) ID of the target Starknet network. The default is the Starknet Sepolia testnet. -- `contractPayload`: `object` - Transaction payload to be deployed. - - `contract`: `CompiledContract` | `string` - The compiled contract code ([in Cairo](https://book.cairo-lang.org/ch13-00-introduction-to-starknet-smart-contracts.html)). - - `classHash?`: `string` - (Optional) The computed class hash of compiled contract. - - `casm?`: `CompiledContract` | `string` - (Optional) - The compiled [casm](https://docs.starknet.io/architecture-and-concepts/smart-contracts/cairo-and-sierra/). - - `compiledClassHash?`: `string` - (Optional) The compiled class hash from casm. -- `invocationsDetails`: `object` - (Optional) Transaction details object containing: - - `nonce`: (Optional) Nonce for the transaction. - - `blockIdentifier`: (Optional) Block identifier for the transaction. - - `maxFee`: (Optional) Maximum gas fee allowed for the transaction. If not specified, the fee is automatically calculated. - - `tip`: (Optional) Additional fee to incentivize miners. - - `paymasterData`: (Optional) Paymaster-related data for the transaction. - - `accountDeploymentData`: (Optional) Data for account deployment. - - `nonceDataAvailabilityMode`: (Optional) Mode for nonce data availability. - - `feeDataAvailabilityMode`: (Optional) Mode for fee data availability. - - `version`: (Optional) The transaction version. - - `resourceBounds`: (Optional) The boundaries on resource consumption during the transaction. - - `skipValidate`: `boolean` - (Optional) Skip validation of the transaction. - -#### Returns - -The confirmation of sending a transaction on the Starknet contract, which contains: - -- `transaction_hash`: `string` - The transaction hash. -- `class_hash`: `string` - The computed class hash of compiled contract. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_declareContract", - params: { - senderAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - contractPayload: { - contract: { - // The compiled contract code - } - }, - chainId: "0x534e5f5345504f4c4941", - }, - }, - }, -}) - -``` - - - - -```json -{ - "transaction_hash": "0x05a56e2d52c817161883f50c441c3228cfe54d9f84b5b5b8b1c8b8e0e6f7e6d8", - "class_hash": "0xb60e8dd61c5d32be8058bb8eb970870f07233155" -} -``` - - - - -### `starkNet_displayPrivateKey` - -Extracts the private key from the deployed Starknet account and displays it in MetaMask. - -#### Parameters - -- `userAddress`: `string` - Address of the account contract. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -Always returns `null` for security reasons. -The private key is only shown in the MetaMask pop-up window. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_displayPrivateKey", - params: { - userAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -null -``` - - - - -### `starkNet_estimateAccountDeployFee` - -Gets the estimated gas fee for deploying an account contract. - -#### Parameters - -- `addressIndex`: `integer` - (Optional) Specific address index of the derived key in - [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -An object with the following properties (where values are originally `bigint` but converted to -base-10 `string` format using `.toString(10)`): - -- `suggestedMaxFee`: `string` - The maximum suggested fee for deploying the contract, in wei. -- `overallFee`: `string` - The overall fee for the deployment transaction, in wei. -- `gasConsumed`: `string` - The amount of gas consumed during the transaction. - The default is `0`. -- `gasPrice`: `string` - The gas price used for the transaction, in wei. - The default is `0`. -- `unit`: `string` - The unit of the fees and gas values, which is `wei`. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_estimateAccountDeployFee", - params: { - addressIndex: 0, - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -{ - "suggestedMaxFee": "1000000000000000", - "overallFee": "900000000000000", - "gasConsumed": "1000000", - "gasPrice": "1000000000", - "unit": "wei" -} -``` - - - - -### `starkNet_estimateFee` - -Gets the estimated gas fee for calling a method on any contract. - -#### Parameters - -- `address`: `string` - The account address from which the transaction is being made. -- `invocations`: `array` - The [invocations](https://starknetjs.com/docs/API/namespaces/types#invocations) - to estimate the fee for. - Each invocation represents a contract call. -- `chainId`: `string` - The chain ID of the target Starknet network. - If not provided, the default is the Starknet Sepolia testnet. -- `details`: `object` - (Optional) The [universal details](https://starknetjs.com/docs/API/interfaces/types.EstimateFeeDetails) - associated with the invocations, such as nonce and version. - -#### Returns - -A promise that resolves to an `EstimateFeeResponse` object, which contains the following properties: - -- `suggestedMaxFee`: `string` - The maximum suggested fee for the transaction, in wei. - This value is originally a `bigint` and is converted to a base-10 `string`. -- `overallFee`: `string` - The overall fee for the transaction, in wei. - This value is originally a `bigint` and is converted to a base-10 `string`. -- `gasConsumed`: `string`- The estimated amount of gas the transaction uses. -- `gasPrice`: `string` - The gas price per unit, represented as a string in wei. -- `unit`: `string` - The unit of the fees, typically `wei`. -- `includeDeploy`: `boolean` - Whether the transaction includes an account deployment step. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_estimateFee", - params: { - address: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - invocations: [ - { - entrypoint: "transfer", - calldata: [ - "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - "1000000000000000000" - ] - } - ], - chainId: "0x534e5f5345504f4c4941", - details: { - nonce: 1, - maxFee: "2000000000000000" - } - }, - }, - }, -}); -``` - - - - -```json -{ - "suggestedMaxFee": "1000000000000000", - "overallFee": "900000000000000", - "gasConsumed": "1000000", - "gasPrice": "1000000000", - "unit": "wei", - "includeDeploy": false -} -``` - - - - -### `starkNet_executeTxn` - -Signs and executes a transaction. - -#### Parameters - -- `address`: `string` - The address of the sender. -- `calls`: An array of call objects to be executed. - Each call contains the target contract address, function name, and call data. -- `details`: `object` - (Optional) Transaction details as received by `starknet.js`, including: - - `nonce`: (Optional) Nonce for the transaction. - - `blockIdentifier`: (Optional) Block identifier for the transaction. - - `maxFee`: (Optional) Maximum gas fee allowed for the transaction. - If not specified, the fee is automatically calculated. - - `tip`: (Optional) Additional fee to incentivize miners. - - `paymasterData`: (Optional) Paymaster-related data for the transaction. - - `accountDeploymentData`: (Optional) Data for account deployment. - - `nonceDataAvailabilityMode`: (Optional) Mode for nonce data availability. - - `feeDataAvailabilityMode`: (Optional) Mode for fee data availability. - - `version`: (Optional) The transaction version. - - `resourceBounds`: (Optional) The boundaries on resource consumption during the transaction. - - `skipValidate`: `boolean` - (Optional) Skip validation of the transaction. -- `abis`: `any` - (Optional) Contract ABI for decoding the call data. -- `chainId`: `string` - The Starknet chain ID. Default is Starknet Sepolia testnet. - -#### Returns - -The hash of the transaction. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_executeTxn", - params: { - address: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - calls: [ - { - entrypoint: "transfer", - calldata: [ - "0x1234567890abcdef1234567890abcdef12345678", - "1000000000000000000" - ] - } - ], - details: { - nonce: 1, - maxFee: "2000000000000000", - }, - chainId: "0x534e5f5345504f4c4941" - } - }, - }, -}) -``` - - - - -```json -{ - "transaction_hash": "0x05a56e2d52c817161883f50c441c3228cfe54d9f84b5b5b8b1c8b8e0e6f7e6d8" -} -``` - - - - -### `starkNet_extractPublicKey` - -Extracts the public key from a Starknet account address. - -#### Parameters - -- `userAddress`: `string` - Address of the account contract. -- `chainId`: `string` - (Optional) ID of the target Starknet network. The default network is the Starknet Sepolia testnet. - -#### Returns - -The public key associated with the specified account address (which may differ from the signer's public key). - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_extractPublicKey", - params: { - userAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -"0x04bfcab3b7ca7e8b3f3b62b2f7f77e9e4b68080bbf8f0f4a1c8f890864d2c7c1d3c45d8b2e3f5f1c27dfeea4c2f5733e90bfc7484e2a690aa9b8ac4559d2e6a8d7" -``` - - - - -### `starkNet_getCurrentNetwork` - -Retrieves the current Starknet Snap's network. - -#### Parameters - -None - -#### Returns - -A network object containing the name and chain ID of the network. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_getCurrentNetwork" - }, - }, -}) -``` - - - - -```json -{ - "name": "StarkNet Sepolia Testnet", - "chainId": "0x534e5f5345504f4c4941" -} -``` - - - - -### `starkNet_getErc20TokenBalance` - -Gets the user's current balance of an ERC-20 token. - -#### Parameters - -- `tokenAddress`: `string` - Address of the ERC-20 token contract. -- `userAddress`: `string` - Address of the user account. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The latest and pending token balance, represented in hexadecimal. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_getErc20TokenBalance", - params: { - tokenAddress: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - userAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -{ - "balancePending": "0x3e8", - "balanceLatest": "0x3e8", -} -``` - - - - -### `starkNet_addErc20Token` - -Add an ERC-20 token to the watch list. - -#### Parameters - -- `address`: `string` - Address of the sender. -- `chainId`: `string` - ID of the target Starknet network. -- `tokenAddress`: `string` - The contract address of the token. -- `tokenName`: `string` - The name of the token. -- `tokenSymbol`: `string` - The string symbol of the token. -- `tokenDecimals?`: `string` | `number` - The decimals of the token. - -#### Returns - -The token object that was added to the watch list. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_addErc20Token", - params: { - address: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941", - tokenAddress: "0x1234567890123456789012345678901234567890", - tokenName: "Ether", - tokenSymbol: "ETH", - tokenDecimals: 18 - }, - }, - }, -}) -``` - - - - -```json -{ - "address": "0x1c4c12F3dfDAEa0D9a2b981A5a5DDE1a3c11dD54", - "symbol": "SNK", - "decimals": 18, - "logo": "https://starknet-logo.com/snk-logo.png" -} -``` - - - - -### `starkNet_getTransaction` - -Gets the transaction records from a sender address. - -#### Parameters - -- `senderAddress`: `string` - Address of the sender. -- `contractAddress`: `string` - (Optional) Address of the called contract. -- `pageSize`: `integer` - (Optional) Number of records to fetch per page from the Voyager API `/txn` endpoints. - Options are `10`, `25`, and `50`. - The default is `10`. -- `txnsInLastNumOfDays`: `integer` - (Optional) Number of days of transaction records to retrieve from Voyager. - The default is `5`. -- `withDeployTxn`: `boolean` - (Optional) Indicates whether to include the deployment transaction of the - sender's account contract. - The default is `false`. -- `onlyFromState`: `boolean` - (Optional) Indicates whether to only retrieve transaction records - that are stored in Snap state, such as those in `RECEIVED`, `PENDING`, `ACCEPTED_ON_L2`, or - `REJECTED` statuses. - The default is `false`. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The list of the transaction records. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_getTransactions", - params: { - senderAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - contractAddress: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - pageSize: 25, - txnsInLastNumOfDays: 7, - withDeployTxn: true, - onlyFromState: false, - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -{ - "txnHash": "0x12f84d2b52a172c817161883f50c441c3e5f84d9f84b5b5b8b1c8b8e0e6f7e6f", - "txnType": "invoke", - "senderAddress": "0x1a2B3c4D5e6F7G8h9I0J1K2L3M4N5P6Q7R8S9T0U", - "contractAddress": "0x7AeD16c89125645FA675bD12C1f894Ae6458bcdF", - "contractFuncName": "transfer", - "contractCallData": [ - "0x3cAe7bD56f7b6A7De348F5dC856E1b123D45B3A5", - "1000000000000000000", - ], - "status": "ACCEPTED_ON_L2", - "failureReason": null, - "eventIds": [ - "0xabc123def4567890abc123def4567890" - ], - "timestamp": 1697070653, - "chainId": "0x534e5f5345504f4c4941" -} -``` - - - - -### `starkNet_getTransactionStatus` - -Gets the status of a transaction. - -#### Parameters - -- `transactionHash`: `string` - Hash of the target transaction. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The [status](https://docs.starknet.io/architecture-and-concepts/network-architecture/transaction-life-cycle/) -of the transaction. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_getTransactionStatus", - params: { - transactionHash: "0x05a56e2d52c817161883f50c441c3228cfe54d9f84b5b5b8b1c8b8e0e6f7e6d8", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -"ACCEPTED_ON_L2" -``` - - - - -### `starkNet_recoverAccounts` - -Recovers deployed user accounts from the seed phrase of MetaMask based on -[BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). - -#### Parameters - -- `startScanIndex`: `integer` - (Optional) Starting address index of the derived key in - [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). - The default is `0`. -- `maxScanned`: `integer` - (Optional) Maximum number of addresses to scan during the recovery process. - The default is `1`. -- `maxMissed`: `integer` - (Optional) Maximum number of uninitialized addresses hit during the - recovery process. - The default is `1`. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The list of the scanned user accounts during the recovery process. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_recoverAccounts", - params: { - startScanIndex: 0, - maxScanned: 5, - maxMissed: 2, - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -[ - { - "address": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "addressIndex": 0, - "publicKey": "0x04bfcab3b7ca7e8b3f3b62b2f7f77e9e4b68080bbf8f0f4a1c8f890864d2c7c1d3c45d8b2e3f5f1c27dfeea4c2f5733e90bfc7484e2a690aa9b8ac4559d2e6a8d7", - "addressSalt": "0x789abc123456789abc123456789abc123456789abc123456789abc1234567890", - "deployTxnHash": "0x05a56e2d52c817161883f50c441c3228cfe54d9f84b5b5b8b1c8b8e0e6f7e6d8", - "derivationPath": "m/44'/9004'/0'/0/0", - "chainId": "0x534e5f5345504f4c4941" - }, - ... -] -``` - - - - -### `starkNet_signDeclareTransaction` - -Signs a [`DECLARE` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction) using the private key, returns the signature, and uses it to declare the contract class on Starknet. - -#### Parameters - -- `address`: `string` - Address of the sender. -- `chainId`: `string` - ID of the target Starknet network. -- `details`: `object` - The declare transaction details to be signed, including: - - `nonce`: (Optional) Nonce for the transaction. - - `blockIdentifier`: (Optional) Block identifier for the transaction. - - `maxFee`: (Optional) Maximum gas fee allowed for the transaction. If not specified, the fee is automatically calculated. - - `tip`: (Optional) Additional fee to incentivize miners. - - `paymasterData`: (Optional) Paymaster-related data for the transaction. - - `accountDeploymentData`: (Optional) Data for account deployment. - - `nonceDataAvailabilityMode`: (Optional) Mode for nonce data availability. - - `feeDataAvailabilityMode`: (Optional) Mode for fee data availability. - - `version`: (Optional) The transaction version. - - `resourceBounds`: (Optional) The boundaries on resource consumption during the transaction. - - `skipValidate`: `boolean` - (Optional) Skip validation of the transaction. - - `classHash`: The computed class hash of compiled contract - - `compiledClassHash`: `string` - (Optional) The compiled class hash from casm. - - `senderAddress`: `string` - Address of the sender. - - `chainId`: `string` - ID of the target Starknet network. - -#### Returns - -The signature of the transaction that declares a contract class. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_signDeclareTransaction", - params: { - senderAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941", - details: { - version: "0x2", - chainId: "0x534e5f5345504f4c4941", - senderAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - classHash: "0x5f3614e8671257aff9ac38e929c74d65b02d460ae966cd826c9f04a7fa8e0d4" - } - }, - }, - }, -}) -``` - - - - -```js -{ - "signature": [ - "0x1b6efab56d39b2acdf5c631c5b1ec1e365e8a36576fa8e3e29ec53566b5782c6", - "0x43a9e7f9f9c9238b4d534c5a7621a9c5946f34e72604b539c7bb1ac0a7c2b95b" - ] -} -``` - - - - -### `starkNet_signMessage` - -Signs a typed data message. - -#### Parameters - -- `typedDataMessage`: `string` - JSON representation of the typed data to be signed. -- `signerAddress`: `string` - Address of the signer. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -The signed hash of typed data. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_signMessage", - params: { - typedDataMessage, - signerAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -"1234567890,9876543210" -``` - - - - -### `starkNet_signTransaction` - -Signs a transaction using the private key and returns the resulting signature. - -#### Parameters - -- `address`: `string` - Address of the sender. -- `chainId`: `string` - ID of the target Starknet network. -- `transactions`: `object` - An array of call objects to be executed. Each call contains the target contract address, function name, and call data. -- `transactionsDetail`: `object` - (Optional) The transaction details to be signed, including: - - `nonce`: (Optional) Nonce for the transaction. - - `blockIdentifier`: (Optional) Block identifier for the transaction. - - `maxFee`: (Optional) Maximum gas fee allowed for the transaction. If not specified, the fee is automatically calculated. - - `tip`: (Optional) Additional fee to incentivize miners. - - `paymasterData`: (Optional) Paymaster-related data for the transaction. - - `accountDeploymentData`: (Optional) Data for account deployment. - - `nonceDataAvailabilityMode`: (Optional) Mode for nonce data availability. - - `feeDataAvailabilityMode`: (Optional) Mode for fee data availability. - - `version`: (Optional) The transaction version. - - `resourceBounds`: (Optional) The boundaries on resource consumption during the transaction. - - `skipValidate`: `boolean` - (Optional) Skip validation of the transaction. - -#### Returns - -The signature of the transaction. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_signTransaction", - params: { - senderAddress: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - chainId: "0x534e5f5345504f4c4941", - transactions: [ - { - entrypoint: "transfer", - calldata: [ - "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - "1000000000000000000" - ] - } - ] - } - }, - }, -}) -``` - - - - -```json -[ - "0x1b6efab56d39b2acdf5c631c5b1ec1e365e8a36576fa8e3e29ec53566b5782c6", - "0x43a9e7f9f9c9238b4d534c5a7621a9c5946f34e72604b539c7bb1ac0a7c2b95b" -] -``` - - - - -### `starkNet_switchNetwork` - -Switch the current Starknet Snap's network to a different Starknet network. - -#### Parameters - -`chainId`: `string` - ID of the target Starknet network. - -#### Returns - -`true` if the network switch was successful, `false` otherwise. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_switchNetwork", - "params": { - "chainId": "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -true -``` - - - - - -### `starkNet_verifySignedMessage` - -Verifies a signed typed data message. - -#### Parameters - -- `typedDataMessage`: `string` - JSON representation of the original typed data message to be verified. -- `address`: `string` - Address of the signer. -- `signature`: `string` - Signature of the typed data message. -- `chainId`: `string` - (Optional) ID of the target Starknet network. - The default is the Starknet Sepolia testnet. - -#### Returns - -`true` if the signature verification was successful, `false` otherwise. - -#### Example - - - - -```js -await provider.request({           // Or window.ethereum if you don't support EIP-6963. - method: "wallet_invokeSnap", - params: { - snapId: "npm:@consensys/starknet-snap", - request: { - method: "starkNet_verifySignedMessage", - params: { - typedDataMessage: { - "types": { - "StarkNetDomain": [ - { "name": "name", "type": "felt" }, - { "name": "version", "type": "felt" }, - { "name": "chainId", "type": "felt" }, - { "name": "verifyingContract", "type": "felt" } - ], - "Transaction": [ - { "name": "from", "type": "felt" }, - { "name": "to", "type": "felt" }, - { "name": "amount", "type": "felt" } - ] - }, - "primaryType": "Transaction", - "domain": { - "name": "StarkNet", - "version": "1", - "chainId": "0x534e5f5345504f4c4941", - "verifyingContract": "0x6789abcd1234ef567890abcdef1234567890abcd" - }, - "message": { - "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - "to": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", - "amount": "1000000000000000000" - } - }, - address: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", - signature: "0x1b2d3f4e5a6c7b8d9e0f1234567890ab,0x9876543210fedcba0123456789abcdef", - chainId: "0x534e5f5345504f4c4941" - }, - }, - }, -}) -``` - - - - -```json -true -``` - - - diff --git a/sdk/starknet/tutorials/create-simple-starknet-dapp.md b/sdk/starknet/tutorials/create-simple-starknet-dapp.md deleted file mode 100644 index f13aa7abf1b..00000000000 --- a/sdk/starknet/tutorials/create-simple-starknet-dapp.md +++ /dev/null @@ -1,1629 +0,0 @@ ---- -description: Create a simple dapp using get-starknet and React TypeScript. ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Create a simple Starknet dapp - -In this tutorial, you'll learn how to set up a React TypeScript dapp that uses the [`get-starknet`](https://github.com/starknet-io/get-starknet) library to connect to MetaMask and display the user's wallet address. -You'll also display the balance of an ERC-20 token and perform a token transfer. - -## Prerequisites - -- [MetaMask installed](https://metamask.io/download/) -- A text editor (for example, [VS Code](https://code.visualstudio.com/)) -- [Node](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) version 20.11 or later -- [Yarn](https://yarnpkg.com/) - -:::note - -This tutorial uses [`get-starknet`](https://github.com/starknet-io/get-starknet) version `3.3.0` and `starknet.js` version `6.11.0`. - -::: - -## 1. Set up the project - -Use [Create React App](https://create-react-app.dev/) to set up a new React project with TypeScript. -Create a new project named `get-starknet-tutorial`: - -```bash -yarn create react-app get-starknet-tutorial --template typescript -``` - -Change into the project directory: - -```bash -cd get-starknet-tutorial -``` - -Configure Yarn to use the `node-module` linker instead of its default linking strategy: - -```bash -yarn config set nodeLinker node-modules -``` - -## 2. Add `get-starknet` and `starknet.js` - -Add [`get-starknet`](https://github.com/starknet-io/get-starknet) version `3.3.0` and `starknet.js` -version `6.11.0` to your project's dependencies: - -```bash -yarn add get-starknet@3.3.0 starknet@6.11.0 -``` - -Your file structure should look similar to the following: - -```text -get-starknet-tutorial/ -├── public/ -│ ├── index.html -│ └── ... -├── src/ -│ ├── App.tsx -│ ├── index.tsx -│ ├── App.css -│ └── ... -└── ... -``` - -## 3. Configure the wallet connection - -### 3.1. Connect to MetaMask - -The `connect` function from `get-starknet` is the primary way to connect your dapp to a user's MetaMask wallet. -It opens a connection to MetaMask and returns an object containing important details about the wallet, including: - -- `name`: The name of the wallet. -- `icon`: The wallet's icon, which displays the wallet's logo. -- `account`: The account object of type `AccountInterface` from `starknet.js`, which contains the wallet's address and provides access to account-specific operations. - -To import the necessary functions and connect to a wallet, add the following code to `src/App.tsx`: - -```typescript title="App.tsx" -import { - type ConnectOptions, - type DisconnectOptions, - connect, - disconnect, -} from "get-starknet" -import { AccountInterface } from "starknet" -import { useState } from "react" - -function App() { - const [walletName, setWalletName] = useState("") - const [walletAddress, setWalletAddress] = useState("") - const [walletIcon, setWalletIcon] = useState("") - const [walletAccount, setWalletAccount] = useState(null) - - async function handleConnect(options?: ConnectOptions) { - const res = await connect(options) - setWalletName(res?.name || "") - setWalletAddress(res?.account?.address || "") - setWalletIcon(res?.icon || "") - setWalletAccount(res?.account) - } - - async function handleDisconnect(options?: DisconnectOptions) { - await disconnect(options) - setWalletName("") - setWalletAddress("") - setWalletAccount(null) - } -} -``` - -### 3.2. Display connection options - -The `connect` function accepts an optional `ConnectOptions` object. -This object can control the connection process, including: - -- `modalMode`: Determines how the connection modal behaves. The options are: - - `alwaysAsk`: Prompts the user every time a connection is initiated. - - `neverAsk`: Attempts to connect without showing the modal. -- `modalTheme`: Sets the visual theme of the connection modal. The options are `"dark"` and `"light"`. - -The `disconnect` function allows users to disconnect their wallet. -You can enable `clearLastWallet` to clear the last connected wallet information. - -In `App.tsx`, you can display connect and disconnect buttons with various options as follows: - -```typescript title="App.tsx" -function App() { - // ... - return ( -
    -

    get-starknet

    -
    - // Default connection: - - // Always show modal: - - // Never show modal: - - // Dark theme modal: - - // Light theme modal: - - // Default disconnect: - - // Disconnect and clear last wallet: - -
    -
    - ) -}; -``` - -### 3.3. Display wallet information - -Update `App.tsx` with the following code to display the name and icon of the connected wallet, and -the connected address. -This provides visual feedback to the user, confirming which wallet and account they are using. - -```typescript title="App.tsx" -function App() { - // ... - return ( -
    - // ... - {walletName && ( -
    -

    - Selected Wallet:
    {walletName}
    - Wallet icon -

    -
      -
    • Wallet address:
      {walletAddress}
    • -
    -
    - )} -
    - ) -}; -``` - -### 3.4. Full example - -The following is a full example of configuring the wallet connection. -It displays basic connect and disconnect buttons, and the connected wallet's information. - -```typescript title="App.tsx" -import "./App.css" -import { - type ConnectOptions, - type DisconnectOptions, - connect, - disconnect, -} from "get-starknet" -import { AccountInterface } from "starknet"; -import { useState } from "react" -function App() { - const [walletName, setWalletName] = useState("") - const [walletAddress, setWalletAddress] = useState("") - const [walletIcon, setWalletIcon] = useState("") - const [walletAccount, setWalletAccount] = useState(null) - async function handleConnect(options?: ConnectOptions) { - const res = await connect(options) - setWalletName(res?.name || "") - setWalletAddress(res?.account?.address || "") - setWalletIcon(res?.icon || "") - setWalletAccount(res?.account) - } - async function handleDisconnect(options?: DisconnectOptions) { - await disconnect(options) - setWalletName("") - setWalletAddress("") - setWalletAccount(null) - } - return ( -
    -

    get-starknet

    -
    - - -
    - {walletName && ( -
    -

    - Selected Wallet:
    {walletName}
    - Wallet icon -

    -
      -
    • Wallet address:
      {walletAddress}
    • -
    -
    - )} -
    - ) -}; - -export default App -``` - -### 3.5. Start the dapp - -Start the dapp and navigate to it in your browser. - -```bash -yarn start -``` - -You are directed to the default dapp display. - -

    - Starknet dapp start -

    - -When you select **Connect**, `get-starknet` displays a modal that detects MetaMask and allows you to -choose which Starknet wallet to connect to. -Follow the on-screen prompts to connect your MetaMask wallet to Starknet. - -

    - Starknet dapp select wallet -

    - -After you accept the terms in the prompts, your wallet is connected and its information is displayed. - -

    - Starknet dapp connected -

    - -## 4. Display the balance of and transfer an ERC-20 token - -Now that you have configured the wallet connection, you can display the balance of a specific ERC-20 -token, such as STRK, and perform a transfer using the `AccountInterface` instance. - -### 4.1. Obtain tokens and switch to testnet - -Use the [Starknet Snap companion dapp](https://snaps.consensys.io/starknet) to generate a Starknet address and switch to the -Starknet Sepolia testnet. - -Obtain testnet ETH (for gas) and at least 1 STRK token from the [Starknet faucet](https://starknet-faucet.vercel.app/). - -### 4.2. Configure the TypeScript compiler - -In the `tsconfig.json` file in the root directory of your project, update the `compilerOptions` with -the `target` version set to `es2022`, and `jsx` set to `react-jsx`: - -```json title="tsconfig.json" -{ - "compilerOptions": { - "target": "es2022", - "jsx": "react-jsx", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true - } -} -``` - -### 4.3. Set up the contract - -Create a `src/components/` directory and add the following files to it: - -- `erc20Abi.json`: A JSON file containing the ERC-20 token contract's application binary interface (ABI). -- `TokenBalanceAndTransfer.tsx`: A React component file for handling token balance display and transfer operations. - -:::note ABI and contract address -The contract address for STRK (an ERC-20 token) on Sepolia testnet is `0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7`. -You can find the ABI of the ERC-20 contract on the **Code** tab in [Voyager](https://voyager.online/). -::: - -Add the following code to `TokenBalanceAndTransfer.tsx` to load the ABI from `erc20Abi.json`: - -```typescript title="TokenBalanceAndTransfer.tsx" -import { useEffect, useState } from "react"; -import { AccountInterface, Call, Contract } from "starknet"; -import erc20Abi from "./erc20Abi.json"; - -interface TokenBalanceAndTransferProps { - account: AccountInterface; - tokenAddress: string; -} - -export function TokenBalanceAndTransfer({ account, tokenAddress }: TokenBalanceAndTransferProps) { - const [balance, setBalance] = useState(null); -} -``` - -### 4.4. Fetch the token balance - -In the `TokenBalanceAndTransfer` function, add the following balance fetching logic. -Call the `balanceOf` method to fetch the balance of the connected account: - -```typescript title="TokenBalanceAndTransfer.tsx" -export function TokenBalanceAndTransfer({ account, tokenAddress }: TokenBalanceAndTransferProps) { - const [balance, setBalance] = useState(null); - - useEffect(() => { - async function fetchBalance() { - try { - if (account) { - const erc20 = new Contract(erc20Abi, tokenAddress, account); - const result = await erc20.balanceOf(account.address) as bigint; - const decimals = 18n; - const formattedBalance = result / 10n ** decimals; // Adjust the value for decimals using BigInt arithmetic. - setBalance(Number(formattedBalance)); // Convert the number for display. - } - } catch (error) { - console.error("Error fetching balance:", error); - } - } - - fetchBalance(); - }, [account, tokenAddress]); -} -``` - -### 4.5. Transfer tokens - -In the `TokenBalanceAndTransfer` function, add the following transfer logic. -Call the `transfer` method and execute the transaction using the `AccountInterface`. -Make sure to update `recipientAddress` with a Starknet address of your choice. - -```typescript title="TokenBalanceAndTransfer.tsx" -export function TokenBalanceAndTransfer({ account, tokenAddress }: TokenBalanceAndTransferProps) { - // ... - async function handleTransfer() { - try { - if (account) { - const erc20 = new Contract(erc20Abi, tokenAddress, account); - // Update this example recipient address. - const recipientAddress = "0x01aef74c082e1d6a0ec786696a12a0a5147e2dd8da11eae2d9e0f86e5fdb84b5"; - const amountToTransfer = 1n * 10n ** 18n; - - const transferCall: Call = erc20.populate("transfer", [recipientAddress, amountToTransfer]); - - // Execute the transfer. - const { transaction_hash: transferTxHash } = await account.execute([transferCall]); - - // Wait for the transaction to be accepted. - await account.waitForTransaction(transferTxHash); - - // Refresh the balance after the transfer. - const newBalance = await erc20.balanceOf(account.address) as bigint; - setBalance(Number(newBalance / 10n ** 18n)); - } - } catch (error) { - console.error("Error during transfer:", error); - } - } -} -``` - -### 4.6. Update `App.tsx` - -Call the `TokenBalanceAndTransfer` component in `App.tsx`. -Add the following to the header of `App.tsx` to import the component: - -```typescript title="App.tsx" -import { TokenBalanceAndTransfer } from "./components/TokenBalanceAndTransfer"; -``` - -After the displayed wallet information, add the `TokenBalanceAndTransfer` component with the contract address: - -```typescript title="App.tsx" -{walletAccount && - -} -``` - -:::note -The contract address for STRK (an ERC-20 token) on Sepolia testnet is `0x049D36570D4e46f48e99674bd3fcc84644DdD6b96F7C741B1562B82f9e004dC7`. -::: - -### 4.7. Full example - -The following is a full example of displaying the balance of an ERC-20 token and performing a transfer: - - - - -```typescript -import { useEffect, useState } from "react"; -import { AccountInterface, Call, Contract } from "starknet"; -import erc20Abi from "./erc20Abi.json"; - -interface TokenBalanceAndTransferProps { - account: AccountInterface; - tokenAddress: string; -} - -export function TokenBalanceAndTransfer({ account, tokenAddress }: TokenBalanceAndTransferProps) { - const [balance, setBalance] = useState(null); - - useEffect(() => { - async function fetchBalance() { - try { - if (account) { - const erc20 = new Contract(erc20Abi, tokenAddress, account); - const result = await erc20.balanceOf(account.address) as bigint; - - const decimals = 18n; - const formattedBalance = result / 10n ** decimals; // Adjust for decimals using BigInt arithmetic. - setBalance(Number(formattedBalance)); // Convert to a number for UI display. - } - } catch (error) { - console.error("Error fetching balance:", error); - } - } - - fetchBalance(); - }, [account, tokenAddress]); - - async function handleTransfer() { - try { - if (account) { - const erc20 = new Contract(erc20Abi, tokenAddress, account); - // Replace this example recipient address. - const recipientAddress = "0x01aef74c082e1d6a0ec786696a12a0a5147e2dd8da11eae2d9e0f86e5fdb84b5"; - const amountToTransfer = 1n * 10n ** 18n; // 1 token (in smallest units). - - // Populate transfer call. - const transferCall: Call = erc20.populate("transfer", [recipientAddress, amountToTransfer]); - - // Execute transfer. - const { transaction_hash: transferTxHash } = await account.execute([transferCall]); - - // Wait for the transaction to be accepted. - await account.waitForTransaction(transferTxHash); - - // Refresh balance after transfer. - const newBalance = await erc20.balanceOf(account.address) as bigint; - setBalance(Number(newBalance / 10n ** 18n)); // Adjust for decimals - } - } catch (error) { - console.error("Error during transfer:", error); - } - } - - return ( -
    -

    Token Balance: {balance !== null ? `${balance} STRK` : "Loading..."}

    - -
    - ); -} -``` - -
    - - -```json -[ - { - "type": "impl", - "name": "MintableToken", - "interface_name": "src::mintable_token_interface::IMintableToken" - }, - { - "type": "struct", - "name": "core::integer::u256", - "members": [ - { - "name": "low", - "type": "core::integer::u128" - }, - { - "name": "high", - "type": "core::integer::u128" - } - ] - }, - { - "type": "interface", - "name": "src::mintable_token_interface::IMintableToken", - "items": [ - { - "type": "function", - "name": "permissioned_mint", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "permissioned_burn", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - } - ] - }, - { - "type": "impl", - "name": "MintableTokenCamelImpl", - "interface_name": "src::mintable_token_interface::IMintableTokenCamel" - }, - { - "type": "interface", - "name": "src::mintable_token_interface::IMintableTokenCamel", - "items": [ - { - "type": "function", - "name": "permissionedMint", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "permissionedBurn", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [], - "state_mutability": "external" - } - ] - }, - { - "type": "impl", - "name": "Replaceable", - "interface_name": "src::replaceability_interface::IReplaceable" - }, - { - "type": "struct", - "name": "core::array::Span::", - "members": [ - { - "name": "snapshot", - "type": "@core::array::Array::" - } - ] - }, - { - "type": "struct", - "name": "src::replaceability_interface::EICData", - "members": [ - { - "name": "eic_hash", - "type": "core::starknet::class_hash::ClassHash" - }, - { - "name": "eic_init_data", - "type": "core::array::Span::" - } - ] - }, - { - "type": "enum", - "name": "core::option::Option::", - "variants": [ - { - "name": "Some", - "type": "src::replaceability_interface::EICData" - }, - { - "name": "None", - "type": "()" - } - ] - }, - { - "type": "enum", - "name": "core::bool", - "variants": [ - { - "name": "False", - "type": "()" - }, - { - "name": "True", - "type": "()" - } - ] - }, - { - "type": "struct", - "name": "src::replaceability_interface::ImplementationData", - "members": [ - { - "name": "impl_hash", - "type": "core::starknet::class_hash::ClassHash" - }, - { - "name": "eic_data", - "type": "core::option::Option::" - }, - { - "name": "final", - "type": "core::bool" - } - ] - }, - { - "type": "interface", - "name": "src::replaceability_interface::IReplaceable", - "items": [ - { - "type": "function", - "name": "get_upgrade_delay", - "inputs": [], - "outputs": [ - { - "type": "core::integer::u64" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_impl_activation_time", - "inputs": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData" - } - ], - "outputs": [ - { - "type": "core::integer::u64" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "add_new_implementation", - "inputs": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "remove_implementation", - "inputs": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "replace_to", - "inputs": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData" - } - ], - "outputs": [], - "state_mutability": "external" - } - ] - }, - { - "type": "impl", - "name": "AccessControlImplExternal", - "interface_name": "src::access_control_interface::IAccessControl" - }, - { - "type": "interface", - "name": "src::access_control_interface::IAccessControl", - "items": [ - { - "type": "function", - "name": "has_role", - "inputs": [ - { - "name": "role", - "type": "core::felt252" - }, - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "get_role_admin", - "inputs": [ - { - "name": "role", - "type": "core::felt252" - } - ], - "outputs": [ - { - "type": "core::felt252" - } - ], - "state_mutability": "view" - } - ] - }, - { - "type": "impl", - "name": "RolesImpl", - "interface_name": "src::roles_interface::IMinimalRoles" - }, - { - "type": "interface", - "name": "src::roles_interface::IMinimalRoles", - "items": [ - { - "type": "function", - "name": "is_governance_admin", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "is_upgrade_governor", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "register_governance_admin", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "remove_governance_admin", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "register_upgrade_governor", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "remove_upgrade_governor", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "renounce", - "inputs": [ - { - "name": "role", - "type": "core::felt252" - } - ], - "outputs": [], - "state_mutability": "external" - } - ] - }, - { - "type": "impl", - "name": "ERC20Impl", - "interface_name": "openzeppelin::token::erc20::interface::IERC20" - }, - { - "type": "interface", - "name": "openzeppelin::token::erc20::interface::IERC20", - "items": [ - { - "type": "function", - "name": "name", - "inputs": [], - "outputs": [ - { - "type": "core::felt252" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "symbol", - "inputs": [], - "outputs": [ - { - "type": "core::felt252" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "decimals", - "inputs": [], - "outputs": [ - { - "type": "core::integer::u8" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "total_supply", - "inputs": [], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "balance_of", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "allowance", - "inputs": [ - { - "name": "owner", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "transfer", - "inputs": [ - { - "name": "recipient", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "transfer_from", - "inputs": [ - { - "name": "sender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "recipient", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "approve", - "inputs": [ - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - } - ] - }, - { - "type": "impl", - "name": "ERC20CamelOnlyImpl", - "interface_name": "openzeppelin::token::erc20::interface::IERC20CamelOnly" - }, - { - "type": "interface", - "name": "openzeppelin::token::erc20::interface::IERC20CamelOnly", - "items": [ - { - "type": "function", - "name": "totalSupply", - "inputs": [], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "balanceOf", - "inputs": [ - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::integer::u256" - } - ], - "state_mutability": "view" - }, - { - "type": "function", - "name": "transferFrom", - "inputs": [ - { - "name": "sender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "recipient", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - } - ] - }, - { - "type": "constructor", - "name": "constructor", - "inputs": [ - { - "name": "name", - "type": "core::felt252" - }, - { - "name": "symbol", - "type": "core::felt252" - }, - { - "name": "decimals", - "type": "core::integer::u8" - }, - { - "name": "initial_supply", - "type": "core::integer::u256" - }, - { - "name": "recipient", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "permitted_minter", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "provisional_governance_admin", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "upgrade_delay", - "type": "core::integer::u64" - } - ] - }, - { - "type": "function", - "name": "increase_allowance", - "inputs": [ - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "added_value", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "decrease_allowance", - "inputs": [ - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "subtracted_value", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "increaseAllowance", - "inputs": [ - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "addedValue", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "decreaseAllowance", - "inputs": [ - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "subtractedValue", - "type": "core::integer::u256" - } - ], - "outputs": [ - { - "type": "core::bool" - } - ], - "state_mutability": "external" - }, - { - "type": "event", - "name": "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", - "kind": "struct", - "members": [ - { - "name": "from", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "to", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "value", - "type": "core::integer::u256", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "openzeppelin::token::erc20_v070::erc20::ERC20::Approval", - "kind": "struct", - "members": [ - { - "name": "owner", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "spender", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "value", - "type": "core::integer::u256", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::replaceability_interface::ImplementationAdded", - "kind": "struct", - "members": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::replaceability_interface::ImplementationRemoved", - "kind": "struct", - "members": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::replaceability_interface::ImplementationReplaced", - "kind": "struct", - "members": [ - { - "name": "implementation_data", - "type": "src::replaceability_interface::ImplementationData", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::replaceability_interface::ImplementationFinalized", - "kind": "struct", - "members": [ - { - "name": "impl_hash", - "type": "core::starknet::class_hash::ClassHash", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::access_control_interface::RoleGranted", - "kind": "struct", - "members": [ - { - "name": "role", - "type": "core::felt252", - "kind": "data" - }, - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "sender", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::access_control_interface::RoleRevoked", - "kind": "struct", - "members": [ - { - "name": "role", - "type": "core::felt252", - "kind": "data" - }, - { - "name": "account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "sender", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::access_control_interface::RoleAdminChanged", - "kind": "struct", - "members": [ - { - "name": "role", - "type": "core::felt252", - "kind": "data" - }, - { - "name": "previous_admin_role", - "type": "core::felt252", - "kind": "data" - }, - { - "name": "new_admin_role", - "type": "core::felt252", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::roles_interface::GovernanceAdminAdded", - "kind": "struct", - "members": [ - { - "name": "added_account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "added_by", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::roles_interface::GovernanceAdminRemoved", - "kind": "struct", - "members": [ - { - "name": "removed_account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "removed_by", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::roles_interface::UpgradeGovernorAdded", - "kind": "struct", - "members": [ - { - "name": "added_account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "added_by", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "src::roles_interface::UpgradeGovernorRemoved", - "kind": "struct", - "members": [ - { - "name": "removed_account", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - }, - { - "name": "removed_by", - "type": "core::starknet::contract_address::ContractAddress", - "kind": "data" - } - ] - }, - { - "type": "event", - "name": "openzeppelin::token::erc20_v070::erc20::ERC20::Event", - "kind": "enum", - "variants": [ - { - "name": "Transfer", - "type": "openzeppelin::token::erc20_v070::erc20::ERC20::Transfer", - "kind": "nested" - }, - { - "name": "Approval", - "type": "openzeppelin::token::erc20_v070::erc20::ERC20::Approval", - "kind": "nested" - }, - { - "name": "ImplementationAdded", - "type": "src::replaceability_interface::ImplementationAdded", - "kind": "nested" - }, - { - "name": "ImplementationRemoved", - "type": "src::replaceability_interface::ImplementationRemoved", - "kind": "nested" - }, - { - "name": "ImplementationReplaced", - "type": "src::replaceability_interface::ImplementationReplaced", - "kind": "nested" - }, - { - "name": "ImplementationFinalized", - "type": "src::replaceability_interface::ImplementationFinalized", - "kind": "nested" - }, - { - "name": "RoleGranted", - "type": "src::access_control_interface::RoleGranted", - "kind": "nested" - }, - { - "name": "RoleRevoked", - "type": "src::access_control_interface::RoleRevoked", - "kind": "nested" - }, - { - "name": "RoleAdminChanged", - "type": "src::access_control_interface::RoleAdminChanged", - "kind": "nested" - }, - { - "name": "GovernanceAdminAdded", - "type": "src::roles_interface::GovernanceAdminAdded", - "kind": "nested" - }, - { - "name": "GovernanceAdminRemoved", - "type": "src::roles_interface::GovernanceAdminRemoved", - "kind": "nested" - }, - { - "name": "UpgradeGovernorAdded", - "type": "src::roles_interface::UpgradeGovernorAdded", - "kind": "nested" - }, - { - "name": "UpgradeGovernorRemoved", - "type": "src::roles_interface::UpgradeGovernorRemoved", - "kind": "nested" - } - ] - } -] -``` - - - - -```typescript -import "./App.css" -import { - type ConnectOptions, - type DisconnectOptions, - connect, - disconnect, -} from "get-starknet" -import { AccountInterface, wallet } from "starknet"; -import { useState } from "react" -import { TokenBalanceAndTransfer } from "./components/TokenBalanceAndTransfer"; - -function App() { - const [walletName, setWalletName] = useState("") - const [walletAddress, setWalletAddress] = useState("") - const [walletIcon, setWalletIcon] = useState("") - const [walletAccount, setWalletAccount] = useState(null) - - async function handleConnect(options?: ConnectOptions) { - const res = await connect(options) - setWalletName(res?.name || "") - setWalletAddress(res?.account?.address || "") - setWalletIcon(res?.icon || "") - setWalletAccount(res?.account as unknown as AccountInterface) - } - - async function handleDisconnect(options?: DisconnectOptions) { - await disconnect(options) - setWalletName("") - setWalletAddress("") - setWalletAccount(null) - } - - return ( -
    -

    get-starknet

    -
    - - -
    - {walletName && ( -
    -

    - Selected Wallet:
    {walletName}
    - Wallet icon -

    -
      -
    • Wallet address:
      {walletAddress}
    • -
    -
    - )} - {walletAccount && - - } -
    - ) -}; - -export default App -``` - -
    -
    - -### 4.8. Start the dapp - -Start the dapp and navigate to it in your browser. - -```bash -yarn start -``` - -After connecting to MetaMask, the dapp should display your STRK token balance: - -

    - Starknet transfer token -

    - -You can select **Transfer 1 STRK** to make a transfer to the recipient address specified in [Step 4.5](#45-transfer-tokens). - -## Next steps - -You've set up a simple React dapp that connects to MetaMask, displays an ERC-20 token balance, and performs token transfers. Creating a contract instance using `AccountInterface` allows you to interact with smart contracts, retrieve token balances, and execute transactions, enabling more advanced functionality in your dapp. - -You can follow these next steps: - -- [Manage users' Starknet accounts](../guides/manage-user-accounts.md). -- [Manage Starknet networks](../guides/manage-networks.md). -- Explore the [Starknet Snap API reference](../reference/snap-api.md). diff --git a/src/components/SubNavBar/configs.ts b/src/components/SubNavBar/configs.ts index 3facd2ff91a..4ec4773a142 100644 --- a/src/components/SubNavBar/configs.ts +++ b/src/components/SubNavBar/configs.ts @@ -71,11 +71,6 @@ export const SDK_SUBNAV_CONFIG: SubNavBarConfig = { label: 'Solana', path: '/sdk/solana', }, - { - key: 'starknet', - label: 'Starknet', - path: '/sdk/starknet', - }, ], } diff --git a/vercel.json b/vercel.json index e204d9b3750..3ba0eda5659 100644 --- a/vercel.json +++ b/vercel.json @@ -547,12 +547,12 @@ "destination": "/sdk/" }, { - "source": "/wallet/how-to/use-non-evm-networks/solana", + "source": "/wallet/how-to/use-non-evm-networks/solana/", "destination": "/sdk/solana/" }, { - "source": "/wallet/how-to/use-non-evm-networks/starknet", - "destination": "/sdk/starknet/" + "source": "/wallet/how-to/use-non-evm-networks/starknet/", + "destination": "/sdk/" }, { "source": "/wallet/how-to/onboard-users/", @@ -624,7 +624,7 @@ }, { "source": "/wallet/reference/non-evm-apis/starknet-snap-api/", - "destination": "/sdk/starknet/reference/snap-api/" + "destination": "/sdk/" }, { "source": "/wallet/reference/provider-api/", @@ -639,36 +639,8 @@ "destination": "/sdk/evm/connect/reference/json-rpc-api/" }, { - "source": "/wallet/how-to/use-non-evm-networks/starknet/connect-to-starknet/", - "destination": "/sdk/starknet/guides/connect-to-starknet/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/manage-starknet-accounts/", - "destination": "/sdk/starknet/guides/manage-user-accounts/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/manage-starknet-networks/", - "destination": "/sdk/starknet/guides/manage-networks/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/send-starknet-transactions/", - "destination": "/sdk/starknet/guides/send-transactions/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/sign-starknet-data/", - "destination": "/sdk/starknet/guides/sign-data/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/create-a-simple-starknet-dapp/", - "destination": "/sdk/starknet/tutorials/create-simple-starknet-dapp/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/troubleshoot/", - "destination": "/sdk/starknet/guides/troubleshoot/" - }, - { - "source": "/wallet/how-to/use-non-evm-networks/starknet/about-get-starknet/", - "destination": "/sdk/starknet/concepts/about-get-starknet/" + "source": "/wallet/how-to/use-non-evm-networks/starknet/:path*/", + "destination": "/sdk/" }, { "source": "/guide/snaps/", From 8216d8bb794f069bc26146e5fbbc87ec831a88fe Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Tue, 21 Oct 2025 08:34:08 -0700 Subject: [PATCH 09/17] copy over solana docs --- sdk-sidebar.js | 11 + .../connect/guides/send-legacy-transaction.md | 130 ++++++++++++ .../guides/send-versioned-transaction.md | 194 ++++++++++++++++++ .../connect/guides/use-wallet-adapter.md | 56 +++++ 4 files changed, 391 insertions(+) create mode 100644 sdk/solana/connect/guides/send-legacy-transaction.md create mode 100644 sdk/solana/connect/guides/send-versioned-transaction.md create mode 100644 sdk/solana/connect/guides/use-wallet-adapter.md diff --git a/sdk-sidebar.js b/sdk-sidebar.js index d0ef9144e98..3754deb8a2d 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -159,6 +159,17 @@ const sdkSidebar = { ], solana: [ 'solana/index', + { + type: 'category', + label: 'Guides', + collapsible: false, + collapsed: false, + items: [ + 'solana/connect/guides/use-wallet-adapter', + 'solana/connect/guides/send-legacy-transaction', + 'solana/connect/guides/send-versioned-transaction', + ], + }, ], } diff --git a/sdk/solana/connect/guides/send-legacy-transaction.md b/sdk/solana/connect/guides/send-legacy-transaction.md new file mode 100644 index 00000000000..34228c2a7d4 --- /dev/null +++ b/sdk/solana/connect/guides/send-legacy-transaction.md @@ -0,0 +1,130 @@ +# Send a legacy transaction + +Once a web application is connected to Phantom, it can prompt the user for permission to send transactions on their behalf. + +In order to send a transaction, a web application must: + +1. Create an unsigned transaction. +2. Have the transaction be signed and submitted to the network by the user's Phantom wallet. +3. Optionally await network confirmation using a Solana JSON RPC connection. + +:::info +For more information about the nature of Solana transactions, refer to the [solana-web3.js](https://solana-foundation.github.io/solana-web3.js/) documentation and the [Solana Cookbook](https://solanacookbook.com/core-concepts/transactions.html#transactions). +::: + +For a sample Phantom transaction, check out our [sandbox](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L160). + +## Sign and send a transaction + +Once a transaction is created, the web application may ask the user's Phantom wallet to sign and send the transaction. If accepted, Phantom will sign the transaction with the user's private key and submit it via a Solana JSON RPC connection. By far the **easiest** and most **recommended** way of doing this is by using the `signAndSendTransaction` method on the provider, but it is also possible to do with `request`. In both cases, the call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the `signature`. + +### signAndSendTransaction() + +```javascript theme={null} +const provider = getProvider(); // see "Detecting the Provider" +const network = ""; +const connection = new Connection(network); +const transaction = new Transaction(); +const { signature } = await provider.signAndSendTransaction(transaction); +await connection.getSignatureStatus(signature); +``` + +### request() + +```javascript theme={null} +const provider = getProvider(); // see "Detecting the Provider" +const network = ""; +const connection = new Connection(network); +const transaction = new Transaction(); +const { signature } = await provider.request({ + method: "signAndSendTransaction", + params: { + message: bs58.encode(transaction.serializeMessage()), + }, +}); +await connection.getSignatureStatus(signature); +``` + +You can also specify a `SendOptions` [object](https://solana-foundation.github.io/solana-web3.js/modules.html#SendOptions) as a second argument into `signAndSendTransaction` or as an `options` parameter when using `request`. + +For a live demo of `signAndSendTransaction`, refer to [handleSignAndSendTransaction](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L160) in our sandbox. + +## Sign and send multiple transactions + +It is also possible to sign and send multiple transactions at once. This is exposed through the `signAndSendAllTransactions` method on the provider. This method accepts an array of Solana transactions, and will optionally accept a [SendOptions](https://solana-foundation.github.io/solana-web3.js/types/SendOptions.html) object as a second parameter. If successful, it will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the array of string `signatures` and the `publicKey` of the signer. + +### signAndSendAllTransactions() + +```typescript theme={null} +const provider = getProvider(); // see "Detecting the Provider" +const network = ""; +const connection = new Connection(network); +const transactions = [new Transaction(), new Transaction()]; +const { signatures, publicKey } = await provider.signAndSendAllTransactions(transactions); +await connection.getSignatureStatuses(signatures); +``` + +## Other signing methods + +The following methods are also supported, but are not recommended over `signAndSendTransaction`. It is safer for users, and a simpler API for developers, for Phantom to submit the transaction immediately after signing it instead of relying on the application to do so. + +:::warning +The following methods are not supported in the [wallet standard](#) implementation and may be removed in a future release. These methods are only available via the [window.solana object](/solana/detecting-the-provider). +::: + +## Sign a transaction (without sending) + +Once a transaction is created, a web application may ask the user's Phantom wallet to sign the transaction *without* also submitting it to the network. The easiest and most recommended way of doing this is via the `signTransaction` method on the provider, but it is also possible to do via `request`. In both cases, the call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for the signed transaction. After the transaction has been signed, an application may submit the transaction itself using [sendRawTransaction](https://solana-foundation.github.io/solana-web3.js/classes/Connection.html#sendRawTransaction) in web3.js. + +### signTransaction() + +```javascript theme={null} +const provider = getProvider(); +const network = ""; +const connection = new Connection(network); +const transaction = new Transaction(); +const signedTransaction = await provider.signTransaction(transaction); +const signature = await connection.sendRawTransaction(signedTransaction.serialize()); +``` + +### request() + +```javascript theme={null} +const provider = getProvider(); +const network = ""; +const connection = new Connection(network); +const transaction = new Transaction(); +const signedTransaction = await provider.request({ + method: "signTransaction", + params: { + message: bs58.encode(transaction.serializeMessage()), + }, +}); +const signature = await connection.sendRawTransaction(signedTransaction.serialize()); +``` + +For an example of `signTransaction`, refer to [handleSignTransaction](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L187) in our sandbox. + +## Sign multiple transactions + +For legacy integrations, Phantom supports signing multiple transactions at once without sending them. This is exposed through the `signAllTransactions` method on the provider. This method is **not recommended** for new integrations. Instead, developers should make use of `signAndSendAllTransactions`. + +### signAllTransactions() + +```javascript theme={null} +const signedTransactions = await provider.signAllTransactions(transactions); +``` + +### request() + +```javascript theme={null} +const message = transactions.map(transaction => { + return bs58.encode(transaction.serializeMessage()); +}); +const signedTransactions = await provider.request({ + method: "signAllTransactions", + params: { message }, +}); +``` + +For an example of `signAllTransactions`, refer to [handleSignAllTransactions](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L213) in our sandbox. diff --git a/sdk/solana/connect/guides/send-versioned-transaction.md b/sdk/solana/connect/guides/send-versioned-transaction.md new file mode 100644 index 00000000000..8c0d445d36f --- /dev/null +++ b/sdk/solana/connect/guides/send-versioned-transaction.md @@ -0,0 +1,194 @@ +# Send a versioned transaction + +The Solana runtime supports two types of transactions: `legacy` (see [Send a legacy transaction](/solana/sending-a-transaction)) and `v0` (transactions that can include Address Lookup Tables or LUTs). + +The goal of `v0` is to increase the maximum size of a transaction, and hence the number of accounts that can fit in a single atomic transaction. With LUTs, developers can now build transactions with a maximum of 256 accounts, as compared to the limit of 35 accounts in legacy transactions that do not utilize LUTs. + +:::info +For a dive deep on versioned transactions, LUTs, and how the above changes affect the anatomy of a transaction, see [Versioned Transactions - Anvit Mangal's Blog](https://anvit.hashnode.dev/versioned-transactions). +::: + +On this page, we'll go over the following: + +1. Building a versioned tansaction. +2. Signing and sending a versioned transaction. +3. Building an Address LUT. +4. Extending an Address LUT. +5. Signing and sending a versioned transaction using a LUT. + +## Build a versioned transaction + +Versioned transactions are built in a very similar fashion to [legacy transactions](sending-a-transaction). The only difference is that developers should use the `VersionedTransaction` class rather than the `Transaction` class. + +The following example shows how to build a simple transfer instruction. Once the transfer instruction is made, a `MessageV0` formatted transaction message is constructed with the transfer instruction. Finally, a new `VersionedTransaction` is created, parsing in the `v0` compatible message. + +### createTransactionV0() + +```typescript theme={null} +// create array of instructions +const instructions = [ + SystemProgram.transfer({ + fromPubkey: publicKey, + toPubkey: publicKey, + lamports: 10, + }), +]; + +// create v0 compatible message +const messageV0 = new TransactionMessage({ + payerKey: publicKey, + recentBlockhash: blockhash, + instructions, +}).compileToV0Message(); + +// make a versioned transaction +const transactionV0 = new VersionedTransaction(messageV0); +``` + +For a live example of creating a versioned transaction, refer to [createTransferTransactionV0](https://github.com/phantom-labs/sandbox/blob/main/src/utils/createTransferTransactionV0.ts) in our sandbox. + +## Sign and send a versioned transaction + +Once a Versioned transaction is created, it can be signed and sent via Phantom using the `signAndSendTransaction` method on the provider. The call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the `signature`. This is the same way a legacy transaction is sent via the Phantom provider. + +### signAndSendTransaction() + +```javascript theme={null} +const provider = getProvider(); // see "Detecting the Provider" +const network = ""; +const connection = new Connection(network); +const versionedTransaction = new VersionedTransaction(); +const { signature } = await provider.signAndSendTransaction(versionedTransaction); +await connection.getSignatureStatus(signature); +``` + +You can also specify a `SendOptions` [object](https://solana-foundation.github.io/solana-web3.js/modules.html#SendOptions) as a second argument into `signAndSendTransaction()` or as an `options` parameter when using `request`. + +For a live demo of signing and sending a versioned transaction, refer to [handleSignAndSendTransactionV0](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L191) in our sandbox. + +## Build an Address LUT + +Address LUTs can be used to load accounts into table-like data structures. These structures can then be referenced to significantly increase the number of accounts that can be loaded in a single transaction. + +This lookup method effectively "*compresses*" a 32-byte address into a 1-byte index value. This "*compression*" enables storing up to 256 address in a single LUT for use inside any given transaction. + +With the `@solana/web3.js` [createLookupTable](https://solana-foundation.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#createLookupTable) method, developers can construct the instruction needed to create a new LUT, as well as determine its address. Once we have the LUT instruction, we can construct a transaction, sign it, and send it to create a LUT on-chain. Address LUTs can be created with either a `v0` transaction or a `legacy` transaction. However, the Solana runtime can only retrieve and handle the additional addresses within a LUT while using `v0` transactions. + +The following is a code snippet that creates a LUT. + +### createAddressLookupTable() + +```typescript theme={null} +// create an Address Lookup Table +const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({ + authority: publicKey, + payer: publicKey, + recentSlot: slot, +}); + +// To create the Address Lookup Table on chain: +// send the `lookupTableInst` instruction in a transaction +const lookupMessage = new TransactionMessage({ + payerKey: publicKey, + recentBlockhash: blockhash, + instructions: [lookupTableInst], +}).compileToV0Message(); + +const lookupTransaction = new VersionedTransaction(lookupMessage); +const lookupSignature = await signAndSendTransaction(provider, lookupTransaction); +``` + +For a live demo of creating a LUT, refer to [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. + +## Extend an Address LUT + +Once an Address LUT is created, it can then be extended, which means that accounts can be appended to the table. Using the `@solana/web3.js` library, you can create a new `extend` instruction using the [extendLookupTable](https://solana-labs.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#extendLookupTable) method. Once the extend instruction is created, it can be sent in a transaction. + +### extendAddressLookupTable() + +```typescript theme={null} +// add addresses to the `lookupTableAddress` table via an `extend` instruction +const extendInstruction = AddressLookupTableProgram.extendLookupTable({ + payer: publicKey, + authority: publicKey, + lookupTable: lookupTableAddress, + addresses: [ + publicKey, + SystemProgram.programId, + // more `publicKey` addresses can be listed here + ], +}); + +// Send this `extendInstruction` in a transaction to the cluster +// to insert the listing of `addresses` into your lookup table with address `lookupTableAddress` +const extensionMessageV0 = new TransactionMessage({ + payerKey: publicKey, + recentBlockhash: blockhash, + instructions: [extendInstruction], +}).compileToV0Message(); + +const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0); +const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0); +``` + +For a live demo of extending a LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) function in our sandbox. + +## Sign and send a versioned transaction using a LUT + +Up until now, we have: + +1. Learned how to create a `VersionedTransaction`. +2. Created an Address LUT. +3. Extended the Address LUT. + +At this point, we are now ready to sign and send a `VersionedTransaction` using an Address LUT. + +First, we need to fetch the account of the created Address LUT. + +### getAddressLookupTable() + +```typescript theme={null} +// get the table from the cluster +const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value); +// `lookupTableAccount` will now be a `AddressLookupTableAccount` object +console.log('Table address from cluster:', lookupTableAccount.key.toBase58()); +``` + +We can also parse and read all the addresses currently stores in the fetched Address LUT. + +## Parse and read addresses + +```typescript theme={null} +// Loop through and parse all the address stored in the table +for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) { + const address = lookupTableAccount.state.addresses[i]; + console.log(i, address.toBase58()); +} +``` + +We can now create the instructions array with an arbitrary transfer instruction, just the way we did while creating the `VersionedTransaction` earlier. This `VersionedTransaction` can then be sent using the `signAndSendTransaction()` provider function. + +```typescript theme={null} +// create an array with your desired `instructions` +// in this case, just a transfer instruction +const instructions = [ + SystemProgram.transfer({ + fromPubkey: publicKey, + toPubkey: publicKey, + lamports: minRent, + }), +]; + +// create v0 compatible message +const messageV0 = new TransactionMessage({ + payerKey: publicKey, + recentBlockhash: blockhash, + instructions, +}).compileToV0Message([lookupTableAccount]); + +// make a versioned transaction +const transactionV0 = new VersionedTransaction(messageV0); +const signature = await signAndSendTransaction(provider, transactionV0); +``` + +For a live demo of of signing and sending a versioned transaction using an Address LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. diff --git a/sdk/solana/connect/guides/use-wallet-adapter.md b/sdk/solana/connect/guides/use-wallet-adapter.md new file mode 100644 index 00000000000..6f40f8f93c3 --- /dev/null +++ b/sdk/solana/connect/guides/use-wallet-adapter.md @@ -0,0 +1,56 @@ +--- +sidebar_label: Use the Wallet Adapter +--- + +# Use the Solana Wallet Adapter + +If your dApp uses the [Solana Wallet Adapter](https://github.com/solana-labs/wallet-adapter), you just have to add the Solflare Wallet Adapter to the list of supported wallets. + +Install the latest *wallets* package with + +```sh +npm install @solana/wallet-adapter-wallets@latest +``` + +Then update the code to look like this: + +```javascript +import { + SolflareWalletAdapter, + /* ... other adapters ... */ +} from '@solana/wallet-adapter-wallets'; + +const wallets = useMemo( + () => [ + new SolflareWalletAdapter(), + // ... other adapters ... + ], + [] +); + + +``` + +Alternatively, you can install only the Solflare adapter directly + +```sh +npm install @solana/wallet-adapter-solflare@latest +``` + +Then update the code: + +```javascript +import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare'; +import { /* ... other adapters ... */ } from '@solana/wallet-adapter-wallets'; + + +const wallets = useMemo( + () => [ + new SolflareWalletAdapter(), + // ... other adapters ... + ], + [] +); + + +``` From e02092aa435e0f8d62122f859a42eef3448948fc Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Wed, 22 Oct 2025 18:20:32 -0700 Subject: [PATCH 10/17] Update terminology and restructure EVM docs --- .../smart-account-quickstart/eip7702.md | 2 +- docusaurus.config.js | 6 +- .../smart-account-quickstart/eip7702.md | 2 +- sdk-sidebar.js | 95 ++-- sdk/about.md | 10 +- sdk/evm/connect/guides/batch-requests.md | 160 ------- sdk/evm/connect/guides/connect-extension.md | 443 ------------------ .../connect/{partners => guides}/dynamic.md | 14 +- .../guides/javascript/batch-requests.md | 82 ++++ .../best-practices/display.md | 0 .../best-practices/production-readiness.md | 10 +- .../best-practices/run-devnet.md | 0 .../guides/{ => javascript}/display-tokens.md | 6 +- .../javascript/index.md} | 28 +- .../interact-with-contracts.md | 115 +---- .../{ => javascript}/manage-networks.md | 92 +--- .../{ => javascript}/manage-user-accounts.md | 85 +--- .../send-transactions/batch-transactions.md | 18 +- .../send-transactions/index.md | 115 +---- .../{ => javascript}/sign-data/index.md | 8 +- .../guides/{ => javascript}/sign-data/siwe.md | 8 +- .../guides/{ => javascript}/use-deeplinks.md | 0 .../{get-started => guides}/rainbowkit.md | 14 +- .../{get-started => guides}/react-native.md | 2 +- .../wagmi.md => guides/wagmi/index.md} | 24 +- .../guides/wagmi/interact-with-contracts.md | 221 +++++++++ .../connect/guides/wagmi/manage-networks.md | 126 +++++ .../guides/wagmi/manage-user-accounts.md | 132 ++++++ .../connect/guides/wagmi/send-transactions.md | 142 ++++++ .../connect/{partners => guides}/web3auth.md | 12 +- sdk/evm/connect/reference/methods.md | 6 +- sdk/evm/connect/reference/provider-api.md | 8 +- sdk/evm/index.md | 30 +- sdk/index.md | 14 +- sdk/reference/options.md | 6 +- src/components/SubNavBar/configs.ts | 2 +- .../NavigationOverlay/NavigationFlow.tsx | 4 +- src/pages/quickstart/builder/choices.ts | 2 +- .../react/stepContent/initialization.mdx | 4 +- .../react/stepContent/installation.mdx | 4 +- .../react/stepContent/reactQuickStart.mdx | 4 +- .../builder/metamask-sdk/react/steps.ts | 6 +- src/pages/tutorials/create-wallet-ai-agent.md | 10 +- .../tutorials/upgrade-eoa-to-smart-account.md | 10 +- src/utils/tutorials-map.tsx | 4 +- 45 files changed, 934 insertions(+), 1152 deletions(-) delete mode 100644 sdk/evm/connect/guides/batch-requests.md delete mode 100644 sdk/evm/connect/guides/connect-extension.md rename sdk/evm/connect/{partners => guides}/dynamic.md (89%) create mode 100644 sdk/evm/connect/guides/javascript/batch-requests.md rename sdk/evm/connect/guides/{ => javascript}/best-practices/display.md (100%) rename sdk/evm/connect/guides/{ => javascript}/best-practices/production-readiness.md (78%) rename sdk/evm/connect/guides/{ => javascript}/best-practices/run-devnet.md (100%) rename sdk/evm/connect/guides/{ => javascript}/display-tokens.md (94%) rename sdk/evm/connect/{get-started/javascript.md => guides/javascript/index.md} (73%) rename sdk/evm/connect/guides/{ => javascript}/interact-with-contracts.md (64%) rename sdk/evm/connect/guides/{ => javascript}/manage-networks.md (64%) rename sdk/evm/connect/guides/{ => javascript}/manage-user-accounts.md (66%) rename sdk/evm/connect/guides/{ => javascript}/send-transactions/batch-transactions.md (90%) rename sdk/evm/connect/guides/{ => javascript}/send-transactions/index.md (60%) rename sdk/evm/connect/guides/{ => javascript}/sign-data/index.md (97%) rename sdk/evm/connect/guides/{ => javascript}/sign-data/siwe.md (86%) rename sdk/evm/connect/guides/{ => javascript}/use-deeplinks.md (100%) rename sdk/evm/connect/{get-started => guides}/rainbowkit.md (86%) rename sdk/evm/connect/{get-started => guides}/react-native.md (98%) rename sdk/evm/connect/{get-started/wagmi.md => guides/wagmi/index.md} (80%) create mode 100644 sdk/evm/connect/guides/wagmi/interact-with-contracts.md create mode 100644 sdk/evm/connect/guides/wagmi/manage-networks.md create mode 100644 sdk/evm/connect/guides/wagmi/manage-user-accounts.md create mode 100644 sdk/evm/connect/guides/wagmi/send-transactions.md rename sdk/evm/connect/{partners => guides}/web3auth.md (89%) diff --git a/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md b/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md index bd354978985..c8eaf905a60 100644 --- a/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md +++ b/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md @@ -168,4 +168,4 @@ const userOperationHash = await bundlerClient.sendUserOperation({ - To grant specific permissions to other accounts from your smart account, [create a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md). - To quickly bootstrap a MetaMask Smart Accounts project, [use the CLI](../use-the-cli.md). -- You can also [use MetaMask Wallet SDK to upgrade a MetaMask account to a smart account](/tutorials/upgrade-eoa-to-smart-account). +- You can also [use MM Connect to upgrade a MetaMask account to a smart account](/tutorials/upgrade-eoa-to-smart-account). diff --git a/docusaurus.config.js b/docusaurus.config.js index 7359acb38e7..e6798ee1a35 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -301,7 +301,7 @@ const config = { { name: 'keywords', content: - 'MetaMask, Embedded Wallets, Quickstart, Web3 Development, SDK, Wallet Integration, API, Dapp Development, Blockchain Development, Ethereum Development, Smart Contract, Account Abstraction, Snaps, Crypto Wallet, DeFi, NFT, Infura, Services, Dashboard', + 'MetaMask, Embedded Wallets, Quickstart, Web3 Development, SDK, MM Connect, Wallet Integration, API, Dapp Development, Blockchain Development, Ethereum Development, Smart Contract, Account Abstraction, Snaps, Crypto Wallet, DeFi, NFT, Infura, Services, Dashboard', }, // Twitter-specific meta tags { @@ -420,7 +420,7 @@ const config = { title: 'Documentation', items: [ { - label: 'MetaMask Wallet SDK', + label: 'MM Connect', to: '/sdk', }, { @@ -457,7 +457,7 @@ const config = { href: 'https://github.com/MetaMask/metamask-extension/', }, { - label: 'MetaMask Wallet SDK GitHub', + label: 'MM Connect GitHub', href: 'https://github.com/MetaMask/metamask-sdk/', }, { diff --git a/gator_versioned_docs/version-0.13.0/get-started/smart-account-quickstart/eip7702.md b/gator_versioned_docs/version-0.13.0/get-started/smart-account-quickstart/eip7702.md index bd354978985..c8eaf905a60 100644 --- a/gator_versioned_docs/version-0.13.0/get-started/smart-account-quickstart/eip7702.md +++ b/gator_versioned_docs/version-0.13.0/get-started/smart-account-quickstart/eip7702.md @@ -168,4 +168,4 @@ const userOperationHash = await bundlerClient.sendUserOperation({ - To grant specific permissions to other accounts from your smart account, [create a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md). - To quickly bootstrap a MetaMask Smart Accounts project, [use the CLI](../use-the-cli.md). -- You can also [use MetaMask Wallet SDK to upgrade a MetaMask account to a smart account](/tutorials/upgrade-eoa-to-smart-account). +- You can also [use MM Connect to upgrade a MetaMask account to a smart account](/tutorials/upgrade-eoa-to-smart-account). diff --git a/sdk-sidebar.js b/sdk-sidebar.js index 3754deb8a2d..362ba72b7f9 100644 --- a/sdk-sidebar.js +++ b/sdk-sidebar.js @@ -49,72 +49,75 @@ const sdkSidebar = { ], evm: [ 'evm/index', - { - type: 'category', - label: 'Get started', - collapsible: false, - collapsed: false, - items: [ - 'evm/connect/get-started/wagmi', - 'evm/connect/get-started/javascript', - 'evm/connect/get-started/rainbowkit', - 'evm/connect/get-started/react-native', - ], - }, { 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', + label: 'JavaScript', collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/guides/send-transactions/index" }, + link: { type: "doc", id: "evm/connect/guides/javascript/index" }, items: [ - 'evm/connect/guides/send-transactions/batch-transactions', + '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', + ], + }, ], }, { type: 'category', - label: 'Sign data', + label: 'Wagmi', collapsible: true, collapsed: true, - link: { type: "doc", id: "evm/connect/guides/sign-data/index" }, + link: { type: "doc", id: "evm/connect/guides/wagmi/index" }, items: [ - 'evm/connect/guides/sign-data/siwe', + '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/batch-requests', - 'evm/connect/guides/interact-with-contracts', - 'evm/connect/guides/use-deeplinks', - 'evm/connect/guides/display-tokens', - 'evm/connect/guides/connect-extension', - { - 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', - ], - }, - ], - }, - { - type: 'category', - label: 'Partner guides', - collapsible: false, - collapsed: false, - items: [ - 'evm/connect/partners/dynamic', - 'evm/connect/partners/web3auth', + 'evm/connect/guides/rainbowkit', + 'evm/connect/guides/react-native', + 'evm/connect/guides/dynamic', + 'evm/connect/guides/web3auth', ], }, { diff --git a/sdk/about.md b/sdk/about.md index 888b785cfa5..f913987cb85 100644 --- a/sdk/about.md +++ b/sdk/about.md @@ -1,15 +1,15 @@ -# About the SDK +# About MM Connect ## Supported platforms -With MetaMask Wallet SDK, you can connect your dapp to MetaMask in the following ways: +With MM Connect, you can connect your dapp to MetaMask in the following ways: - **Desktop web dapps** - Automatically connect to the MetaMask extension, or connect to the MetaMask mobile app using a QR code. -- **Mobile dapps** - The SDK generates a deeplink that takes users directly to the MetaMask mobile app. +- **Mobile dapps** - MM Connect generates a deeplink that takes users directly to the MetaMask mobile app. -The following table expands on the SDK's connection methods: +The following table expands on the supported connection methods: -| Dapp location | User wallet location | Connection method | MetaMask Wallet SDK | Other SDKs | +| Dapp location | User wallet location | Connection method | MM Connect | Other SDKs | |---------------|-------------|------------------|--------------------------|--------------------------| | Desktop web | Wallet browser extension | Automatic connection via browser extension | Supported | Supported | | Desktop web | Wallet mobile app | QR code scan with wallet mobile app | Supported | Limited | diff --git a/sdk/evm/connect/guides/batch-requests.md b/sdk/evm/connect/guides/batch-requests.md deleted file mode 100644 index 52a3415afed..00000000000 --- a/sdk/evm/connect/guides/batch-requests.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -description: Batch multiple JSON-RPC requests using MetaMask Wallet SDK or Wagmi. -keywords: [SDK, Wagmi, batch, JSON-RPC, RPC, requests, methods, dapp] ---- - -# Batch requests - -MetaMask Wallet SDK provides mechanisms to send multiple JSON-RPC requests in a single call. -However, "batching" can be used in a few different contexts: - -- [**Wagmi batching for contract reads**](#use-wagmi-usereadcontracts) - Wagmi does not support MetaMask's generic batching mechanism. - Instead, it provides the [`useReadContracts`](https://wagmi.sh/react/api/hooks/useReadContracts) hook to perform multiple contract read operations in a single hook call. - This is specialized for retrieving data from smart contracts and returns an array of results corresponding to each read call. - `useReadContracts` does not support batching JSON-RPC methods. - -- [**Vanilla JavaScript batching with `metamask_batch`**](#use-vanilla-javascript-metamask_batch) - - This approach uses MetaMask Wallet SDK's `metamask_batch` method to group any JSON-RPC requests together, whether they are contract calls or other JSON-RPC methods (for example, signing messages or sending transactions). - 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 [sending atomic batch transactions](send-transactions/batch-transactions.md) in MetaMask. -Use the methods introduced by EIP-5792 to send atomic batches. -::: - -## Use Wagmi (`useReadContracts`) - -When using Wagmi, you can perform multiple contract read operations in a single hook call using `useReadContracts`. -This method is designed specifically for contract calls and batches them together internally, returning the results as an array. -It is not a generic JSON-RPC batching tool but rather a specialized solution for reading from smart contracts. - -For more information, see the [Wagmi documentation](https://wagmi.sh/react/api/hooks/useReadContracts). - -The following is an example of batching read operations using `useReadContracts`: - -```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. - -:::tip -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. -::: - -## Use Vanilla JavaScript (`metamask_batch`) - -If you're not using Wagmi, you can directly use MetaMask Wallet SDK's `metamask_batch` method to group multiple JSON-RPC requests into a single HTTP call. - -Use cases include: - -- **Batching multiple signatures** - Send multiple signing requests in one batch. -- **Switching networks** - Switch the EVM network, perform an action such as sending a transaction, and switch back, all in one batch. -- **Mixed transactions and signatures** - Combine transaction sending and signing requests in one batch. - -:::note -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 { MetaMaskSDK } from "@metamask/sdk"; - -const MMSDK = new MetaMaskSDK(); -const provider = MMSDK.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: "eth_sendTransaction", - params: [ - { - from: "0x1234...", - to: "0xABCD...", - // Additional transaction parameters. - }, - ], - }, - ]; - - try { - const results = await provider.request({ - method: "metamask_batch", - params: [requests], - }); - console.log("Batch Results:", results); - } catch (err) { - console.error("Batch request failed:", err); - } -} - -document.getElementById("batchBtn").addEventListener("click", handleBatchRequests); -``` - -The following HTML displays a **Send Batch** button: - -```html - -``` - -:::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/connect-extension.md b/sdk/evm/connect/guides/connect-extension.md deleted file mode 100644 index a87a2090ab8..00000000000 --- a/sdk/evm/connect/guides/connect-extension.md +++ /dev/null @@ -1,443 +0,0 @@ ---- -description: Connect to the MetaMask extension using the Wallet API and EIP-6963. -sidebar_label: Connect to the extension -toc_max_heading_level: 4 -keywords: [extension, API] ---- - -# Connect to the MetaMask extension - -:::tip Building a cross-platform or mobile dapp? -For cross-platform development, mobile integration, or advanced features like QR codes and -deeplinking, connect to MetaMask using [**MetaMask Wallet SDK**](../get-started/wagmi.md) instead. -::: - -You can connect your dapp to users' MetaMask wallets by detecting MetaMask in their browsers and -connecting to their accounts. -This page provides instructions for connecting to MetaMask using the wallet detection mechanism -introduced by [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963). -This approach allows you to detect multiple installed wallets and connect to them without conflicts. - -You can connect to the MetaMask browser extension [using third-party libraries](#connect-to-metamask-using-third-party-libraries) -or [directly using Vite](#connect-to-metamask-directly-using-vite). - -:::note -Using the Wallet API enables your dapp to work both with the MetaMask extension and in the in-app browser of the MetaMask mobile app. -However, we recommend using the SDK for a more consistent mobile connection. -::: - -## Connect to MetaMask using third-party libraries - -You can connect to MetaMask using the following third-party libraries that support EIP-6963: - -- [Wagmi 2+](/sdk/connect/javascript-wagmi) -- [Reown AppKit](https://docs.reown.com/appkit/overview) -- [MIPD Store](https://github.com/wevm/mipd) -- [RainbowKit](https://rainbowkit.com/docs/introduction) -- [Web3-Onboard](https://onboard.blocknative.com) -- [ConnectKit](https://docs.family.co/connectkit) - -## Connect to MetaMask directly using Vite - -To connect to MetaMask directly, we recommend implementing support for EIP-6963 using the -[Vite](https://vitejs.dev/) build tool with [vanilla TypeScript](#vanilla-typescript) or -[React TypeScript](#react-typescript). - -### Vanilla TypeScript - -Follow these steps for creating a vanilla TypeScript project to connect to MetaMask: - -#### 1. Create a project - -[Create a Vite project](https://v3.vitejs.dev/guide/#scaffolding-your-first-vite-project) using the -template for vanilla TypeScript: - -```bash -npm create vite@latest vanilla-ts-6963 -- --template vanilla-ts -``` - -#### 2. Set up the project - -In your Vite project, update `src/vite-env.d.ts` with the EIP-6963 interfaces: - -```typescript title="vite-env.d.ts" -/// - -interface EIP6963ProviderInfo { - rdns: string - uuid: string - name: string - icon: string -} - -interface EIP6963ProviderDetail { - info: EIP6963ProviderInfo - provider: EIP1193Provider -} - -type EIP6963AnnounceProviderEvent = { - detail: { - info: EIP6963ProviderInfo - provider: Readonly - } -} - -interface EIP1193Provider { - isStatus?: boolean - host?: string - path?: string - sendAsync?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - send?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - request: (request: { - method: string - params?: Array - }) => Promise -} -``` - -:::note -In addition to the EIP-6963 interfaces, you need a `EIP1193Provider` interface (defined by -[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)), which is the foundational structure for -Ethereum wallet providers, and represents the essential properties and methods for interacting with -MetaMask and other Ethereum wallets in JavaScript. -::: - -#### 3. Update `main.ts` - -Update `src/main.ts` with the following code: - -```typescript title="main.ts" -import "./style.css" -import { listProviders } from "./providers.ts" - -document.querySelector("#app")!.innerHTML = ` -
    -
    -
    -` - -listProviders(document.querySelector("#providerButtons")!) -``` - -The `querySelector` finds and returns the first HTML element that matches the CSS selector `app`, -and sets its `innerHTML`. -You need to include a basic HTML structure with an inner `div` to inject a list of buttons, each -representing a detected wallet provider. - -You'll create the `listProviders` function in the next step, and pass an argument which represents -the `div` element. - -#### 4. Connect to wallets - -Create a file `src/providers.ts` with the following code: - -```ts title="providers.ts" -declare global { - interface WindowEventMap { - "eip6963:announceProvider": CustomEvent - } -} - -// Connect to the selected provider using eth_requestAccounts. -const connectWithProvider = async ( - wallet: EIP6963AnnounceProviderEvent["detail"] -) => { - try { - await wallet.provider.request({ method: "eth_requestAccounts" }) - } catch (error) { - console.error("Failed to connect to provider:", error) - } -} - -// Display detected providers as connect buttons. -export function listProviders(element: HTMLDivElement) { - window.addEventListener( - "eip6963:announceProvider", - (event: EIP6963AnnounceProviderEvent) => { - const button = document.createElement("button") - - button.innerHTML = ` - ${event.detail.info.name} -
    ${event.detail.info.name}
    - ` - - // Call connectWithProvider when a user selects the button. - button.onclick = () => connectWithProvider(event.detail) - element.appendChild(button) - } - ) - - // Notify event listeners and other parts of the dapp that a provider is requested. - window.dispatchEvent(new Event("eip6963:requestProvider")) -} -``` - -The `connectWithProvider` function connects the user to the selected provider using -[`eth_requestAccounts`](../reference/json-rpc-api/index.md). -The `wallet` object is passed as an argument to the function, indicating the argument type. - -The `listProviders` function uses a simplified approach. -Instead of mapping and joining an entire block of HTML, it directly passes the `event.detail` object -to the `connectWithProvider` function when a provider is announced. - -#### 5. View the project - -Run the following command to view and test the Vite project in your browser: - -```bash -npm run dev -``` - -#### Example - -See the [vanilla TypeScript example](https://github.com/MetaMask/vite-vanilla-ts-eip-6963) for more information. -You can clone the repository and run the example locally using `npm i && npm run dev`. - -### React TypeScript - -Follow these steps for creating a React TypeScript project to connect to MetaMask: - -#### 1. Create a project - -[Create a Vite project](https://v3.vitejs.dev/guide/#scaffolding-your-first-vite-project) using the -template for React TypeScript: - -```bash -npm create vite@latest react-ts-6963 -- --template react-ts -``` - -#### 2. Set up the project - -In your Vite project, update `src/vite-env.d.ts` with the EIP-6963 interfaces: - -```typescript title="vite-env.d.ts" -/// - -interface EIP6963ProviderInfo { - rdns: string - uuid: string - name: string - icon: string -} - -interface EIP6963ProviderDetail { - info: EIP6963ProviderInfo - provider: EIP1193Provider -} - -type EIP6963AnnounceProviderEvent = { - detail: { - info: EIP6963ProviderInfo - provider: Readonly - } -} - -interface EIP1193Provider { - isStatus?: boolean - host?: string - path?: string - sendAsync?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - send?: ( - request: { method: string; params?: Array }, - callback: (error: Error | null, response: unknown) => void - ) => void - request: (request: { - method: string - params?: Array - }) => Promise -} -``` - -:::note -In addition to the EIP-6963 interfaces, you need a `EIP1193Provider` interface (defined by -[EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)), which is the foundational structure for -Ethereum wallet providers, and represents the essential properties and methods for interacting with -MetaMask and other Ethereum wallets in JavaScript. -::: - -#### 3. Update `App.tsx` - -Update `src/App.tsx` with the following code: - -```ts title="App.tsx" -import "./App.css" -import { DiscoverWalletProviders } from "./components/WalletProviders" - -function App() { - return ( - - ) -} - -export default App -``` - -This code renders the `WalletProviders` component that you'll create in the next step, which -contains the logic for detecting and connecting to wallet providers. - -#### 4. Detect and connect to wallets - -Create a `src/components` directory and add a file `WalletProviders.tsx` with the following code: - -```ts title="WalletProviders.tsx" -import { useState } from "react" -import { useSyncProviders } from "../hooks/useSyncProviders" -import { formatAddress } from "../utils" - -export const DiscoverWalletProviders = () => { - const [selectedWallet, setSelectedWallet] = useState() - const [userAccount, setUserAccount] = useState("") - const providers = useSyncProviders() - - // Connect to the selected provider using eth_requestAccounts. - const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => { - const accounts: string[] | undefined = - await ( - providerWithInfo.provider - .request({ method: "eth_requestAccounts" }) - .catch(console.error) - ) as string[] | undefined; - - if (accounts?.[0]) { - setSelectedWallet(providerWithInfo) - setUserAccount(accounts?.[0]) - } - } - - // Display detected providers as connect buttons. - return ( - <> -

    Wallets Detected:

    -
    - { - providers.length > 0 ? providers?.map((provider: EIP6963ProviderDetail) => ( - - )) : -
    - No Announced Wallet Providers -
    - } -
    -
    -

    {userAccount ? "" : "No "}Wallet Selected

    - {userAccount && -
    -
    - {selectedWallet.info.name} -
    {selectedWallet.info.name}
    -
    ({formatAddress(userAccount)})
    -
    -
    - } - - ) -} -``` - -In this code: - -- `selectedWallet` is a state variable that holds the user's most recently selected wallet. -- `userAccount` is a state variable that holds the user's connected wallet's address. -- `useSyncProviders` is a custom hook that returns the providers array (wallets installed in the browser). - -The `handleConnect` function takes a `providerWithInfo`, which is an `EIP6963ProviderDetail` object. -That object is used to request the user's accounts from the provider using -[`eth_requestAccounts`](../reference/json-rpc-api/index.md). - -If the request succeeds, the `selectedWallet` and `userAccount` local state variables are set. - -Then, the component maps over the providers array and renders a button for each detected provider. - -Finally, if the `userAccount` state variable is not empty, the selected wallet icon, name, and -address are displayed. - -#### 5. Add React hooks - -Create a `src/hooks` directory and add a file `store.ts` with the following code: - -```ts title="hooks/store.ts" -declare global { - interface WindowEventMap { - "eip6963:announceProvider": CustomEvent - } -} - -// An array to store the detected wallet providers. -let providers: EIP6963ProviderDetail[] = [] - -export const store = { - value: () => providers, - subscribe: (callback: () => void) => { - function onAnnouncement(event: EIP6963AnnounceProviderEvent) { - if (providers.map((p) => p.info.uuid).includes(event.detail.info.uuid)) - return - providers = [...providers, event.detail] - callback() - } - - // Listen for eip6963:announceProvider and call onAnnouncement when the event is triggered. - window.addEventListener("eip6963:announceProvider", onAnnouncement) - - // Dispatch the event, which triggers the event listener in the MetaMask wallet. - window.dispatchEvent(new Event("eip6963:requestProvider")) - - // Return a function that removes the event listener. - return () => - window.removeEventListener("eip6963:announceProvider", onAnnouncement) - }, -} -``` - -Also, add a file `useSyncProviders.ts` with the following code to the `hooks` directory: - -```ts title="hooks/useSyncProviders.ts" -import { useSyncExternalStore } from "react" -import { store } from "./store" - -export const useSyncProviders = () => - useSyncExternalStore(store.subscribe, store.value, store.value) -``` - -This hook allows you to subscribe to MetaMask events, read updated values, and update components. -It uses the `store.value` and `store.subscribe` methods defined in the `store.ts` hook. - -#### 6. Create utility functions - -Create a `src/utils` directory and add a file `index.ts` with the following code: - -```ts title="index.ts" -export const formatBalance = (rawBalance: string) => { - const balance = (parseInt(rawBalance) / 1000000000000000000).toFixed(2) - return balance -} - -export const formatChainAsNum = (chainIdHex: string) => { - const chainIdNum = parseInt(chainIdHex) - return chainIdNum -} - -export const formatAddress = (addr: string) => { - const upperAfterLastTwo = addr.slice(0, 2) + addr.slice(2) - return `${upperAfterLastTwo.substring(0, 5)}...${upperAfterLastTwo.substring(39)}` -} -``` - -This is a good place to store utility functions that you might need to reuse throughout your dapp. -This example only uses the `formatAddress` function, but the others might be useful for other applications. - -#### Example - -See the [React TypeScript example](https://github.com/MetaMask/vite-react-ts-eip-6963) for more information. -You can clone the repository and run the example locally using `npm i && npm run dev`. diff --git a/sdk/evm/connect/partners/dynamic.md b/sdk/evm/connect/guides/dynamic.md similarity index 89% rename from sdk/evm/connect/partners/dynamic.md rename to sdk/evm/connect/guides/dynamic.md index 8aab9bf37db..21354ed2f45 100644 --- a/sdk/evm/connect/partners/dynamic.md +++ b/sdk/evm/connect/guides/dynamic.md @@ -1,14 +1,14 @@ --- sidebar_label: Dynamic SDK -description: Quickstart guide for using MetaMask Wallet SDK and Dynamic SDK. +description: Quickstart guide for using MM Connect and Dynamic SDK. toc_max_heading_level: 2 keywords: [connect, MetaMask, Dynamic, SDK, dapp, Wallet SDK] --- # Connect to MetaMask using Dynamic SDK -Get started with MetaMask Wallet SDK and [Dynamic SDK](https://docs.dynamic.xyz/introduction/welcome). -You can use MetaMask Wallet SDK features directly within Dynamic SDK. +Get started with MM Connect and [Dynamic SDK](https://docs.dynamic.xyz/introduction/welcome). +You can use MM Connect features directly within Dynamic SDK. You can [download the quickstart template](#set-up-using-a-template) or [manually set up the SDKs](#set-up-manually) in an existing dapp.

    @@ -26,7 +26,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up using a template -1. Download the [MetaMask Wallet SDK + Dynamic SDK template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/partners/dynamic): +1. Download the [MM Connect + Dynamic SDK template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/partners/dynamic): ```bash npx degit MetaMask/metamask-sdk-examples/partners/dynamic metamask-dynamic @@ -45,7 +45,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall `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 MetaMask Wallet SDK examples repository and navigate into the `partners/dynamic` directory: + To do so, clone the MM Connect examples repository and navigate into the `partners/dynamic` directory: ```bash git clone https://github.com/MetaMask/metamask-sdk-examples @@ -79,7 +79,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall pnpm dev ``` -You've successfully set up MetaMask Wallet SDK and Dynamic SDK. +You've successfully set up MM Connect and Dynamic SDK. See how to [use the combined SDKs](#usage). ## Set up manually @@ -191,7 +191,7 @@ Before deploying your project to production: 1. Update your `next.config.ts` with production domains. 2. Set up proper environment variables. 3. Configure your Dynamic SDK environment ID. -4. Ensure MetaMask Wallet SDK is properly initialized. +4. Ensure MM Connect is properly initialized. ## Troubleshoot diff --git a/sdk/evm/connect/guides/javascript/batch-requests.md b/sdk/evm/connect/guides/javascript/batch-requests.md new file mode 100644 index 00000000000..f541c311c4a --- /dev/null +++ b/sdk/evm/connect/guides/javascript/batch-requests.md @@ -0,0 +1,82 @@ +--- +description: Batch multiple JSON-RPC requests using MM Connect. +keywords: [SDK, batch, JSON-RPC, RPC, requests, methods, dapp] +--- + +# Batch requests + +MM Connect provides a `metamask_batch` method to send multiple JSON-RPC requests in a single call. +These requests can be contract calls or other JSON-RPC methods (for example, signing messages or sending transactions). +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. +::: + +## Batch JSON-RPC requests + +You can directly use MM Connect's `metamask_batch` method to group multiple JSON-RPC requests into a single HTTP call. + +Use cases include: + +- **Batching multiple signatures** - Send multiple signing requests in one batch. +- **Switching networks** - Switch the EVM network, perform an action such as sending a transaction, and switch back, all in one batch. +- **Mixed transactions and signatures** - Combine transaction sending and signing requests in one batch. + +:::note +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 { MetaMaskSDK } from "@metamask/sdk"; + +const MMSDK = new MetaMaskSDK(); +const provider = MMSDK.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: "eth_sendTransaction", + params: [ + { + from: "0x1234...", + to: "0xABCD...", + // Additional transaction parameters. + }, + ], + }, + ]; + + try { + const results = await provider.request({ + method: "metamask_batch", + params: [requests], + }); + console.log("Batch Results:", results); + } catch (err) { + console.error("Batch request failed:", err); + } +} + +document.getElementById("batchBtn").addEventListener("click", handleBatchRequests); +``` + +The following HTML displays a **Send Batch** button: + +```html + +``` + +:::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/best-practices/display.md b/sdk/evm/connect/guides/javascript/best-practices/display.md similarity index 100% rename from sdk/evm/connect/guides/best-practices/display.md rename to sdk/evm/connect/guides/javascript/best-practices/display.md diff --git a/sdk/evm/connect/guides/best-practices/production-readiness.md b/sdk/evm/connect/guides/javascript/best-practices/production-readiness.md similarity index 78% rename from sdk/evm/connect/guides/best-practices/production-readiness.md rename to sdk/evm/connect/guides/javascript/best-practices/production-readiness.md index 2c62b34f6ed..7b414191acb 100644 --- a/sdk/evm/connect/guides/best-practices/production-readiness.md +++ b/sdk/evm/connect/guides/javascript/best-practices/production-readiness.md @@ -1,12 +1,12 @@ --- -description: MetaMask-specific production readiness checklist for dapps using MetaMask Wallet SDK. +description: MetaMask-specific production readiness checklist for dapps using MM Connect. keywords: [SDK, production, readiness, checklist, compatibility, errors, dapp] toc_max_heading_level: 2 --- # Production readiness -When using MetaMask Wallet SDK, ensure your dapp is production ready by focusing on these key areas unique to MetaMask: +When using MM Connect, ensure your dapp is production ready by focusing on these key areas unique to MetaMask: - [Wallet connection and mobile compatibility](#wallet-connection-and-mobile-compatibility) - [Reliable RPC endpoints](#reliable-rpc-endpoints) @@ -23,7 +23,7 @@ When using MetaMask Wallet SDK, ensure your dapp is production ready by focusing - **Custom RPC setup** - Use production-grade RPC endpoints and custom API keys by signing up on [MetaMask Developer](https://developer.metamask.io/). This improves reliability over public nodes. -- **Configuration** - Configure your Wagmi (or MetaMask Wallet SDK) setup with your custom RPC URL using environment variables. +- **Configuration** - Configure your Wagmi (or MM Connect) setup with your custom RPC URL using environment variables. For example: ```tsx title="Configure custom RPC endpoint" @@ -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/best-practices/run-devnet.md b/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md similarity index 100% rename from sdk/evm/connect/guides/best-practices/run-devnet.md rename to sdk/evm/connect/guides/javascript/best-practices/run-devnet.md diff --git a/sdk/evm/connect/guides/display-tokens.md b/sdk/evm/connect/guides/javascript/display-tokens.md similarity index 94% rename from sdk/evm/connect/guides/display-tokens.md rename to sdk/evm/connect/guides/javascript/display-tokens.md index dd206182296..6a826b82280 100644 --- a/sdk/evm/connect/guides/display-tokens.md +++ b/sdk/evm/connect/guides/javascript/display-tokens.md @@ -14,7 +14,7 @@ Manually adding tokens involves the user interacting with contract addresses, an You can improve the security and experience of displaying your [ERC-20 token](#display-an-erc-20-token) or users' [NFTs](#display-nfts) in MetaMask by using the -[`wallet_watchAsset`](../reference/json-rpc-api/index.md) RPC method. +[`wallet_watchAsset`](../../reference/json-rpc-api/index.md) RPC method. `wallet_watchAsset` provides a friendly interface that prompts users to register tokens to their MetaMask wallet, without having to interact with contract addresses. @@ -97,10 +97,10 @@ The add NFT interfaces look like the following:

    - NFT confirmation + NFT confirmation
    - Multiple NFTs confirmation + Multiple NFTs confirmation
    diff --git a/sdk/evm/connect/get-started/javascript.md b/sdk/evm/connect/guides/javascript/index.md similarity index 73% rename from sdk/evm/connect/get-started/javascript.md rename to sdk/evm/connect/guides/javascript/index.md index fd93dce7043..dbc6092b646 100644 --- a/sdk/evm/connect/get-started/javascript.md +++ b/sdk/evm/connect/guides/javascript/index.md @@ -1,5 +1,5 @@ --- -description: Quickstart guide for using MetaMask Wallet SDK with a JavaScript dapp. +description: Quickstart guide for using MM Connect with a JavaScript dapp. sidebar_label: JavaScript keywords: [connect, MetaMask, JavaScript, SDK, dapp, Wallet SDK] --- @@ -9,12 +9,12 @@ import TabItem from "@theme/TabItem"; # Connect to MetaMask using JavaScript -Get started with MetaMask Wallet SDK in your JavaScript dapp. +Get started with MM Connect in your JavaScript dapp. You can [download the quickstart template](#set-up-using-a-template) or [manually set up the SDK](#set-up-manually) in an existing dapp.

    - JavaScript SDK Quickstart + JavaScript SDK Quickstart

    @@ -27,7 +27,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up using a template -1. Download the [MetaMask Wallet SDK JavaScript template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/javascript): +1. Download the [MM Connect JavaScript template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/javascript): ```bash npx degit MetaMask/metamask-sdk-examples/quickstarts/javascript metamask-javascript @@ -46,7 +46,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall `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 MetaMask Wallet SDK examples repository and navigate into the `quickstarts/javascript` directory: + 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 @@ -80,7 +80,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall pnpm dev ``` -You've successfully set up MetaMask Wallet SDK. +You've successfully set up MM Connect. ## Set up manually @@ -136,8 +136,8 @@ const MMSDK = new MetaMaskSDK({ These examples configure the SDK with the following options: -- [`dappMetadata`](../../../reference/options.md#dappmetadata) - Ensures trust by showing your dapp's `name`, `url`, and `iconUrl` during connection. -- [`infuraAPIKey`](../../../reference/options.md#infuraapikey) - Enables read-only RPC and load‑balancing. +- [`dappMetadata`](../../../../reference/options.md#dappmetadata) - Ensures trust by showing your dapp's `name`, `url`, and `iconUrl` during connection. +- [`infuraAPIKey`](../../../../reference/options.md#infuraapikey) - Enables read-only RPC and load‑balancing. Set this option to your [Infura API key](/developer-tools/dashboard/get-started/create-api). ### 3. Connect and use provider @@ -159,17 +159,17 @@ console.log("eth_accounts result:", result) `MMSDK.connect()` handles cross-platform connection (desktop and mobile), including deeplinking. -Use `provider.request()` for arbitrary [JSON-RPC requests](../reference/json-rpc-api/index.md) like `eth_chainId` or `eth_getBalance`, or for [batching requests](../guides/batch-requests.md) via `metamask_batch`. +Use `provider.request()` for arbitrary [JSON-RPC requests](../../reference/json-rpc-api/index.md) like `eth_chainId` or `eth_getBalance`, or for [batching requests](batch-requests.md) via `metamask_batch`. ## Common SDK methods at a glance | Method | Description | | --------------------------------------------------------------------------------- | -------------------------------------------------------- | -| [`connect()`](../reference/methods.md#connect) | Triggers wallet connection flow | -| [`connectAndSign({ msg: "..." })`](../reference/methods.md#connectandsign) | Connects and prompts user to sign a message | -| [`getProvider()`](../reference/methods.md#getprovider) | Returns the provider object for RPC requests | -| [`provider.request({ method, params })`](../reference/provider-api.md#request) | Calls any Ethereum JSON‑RPC method | -| [Batched RPC](../guides/batch-requests.md) | Use `metamask_batch` to group multiple JSON-RPC requests | +| [`connect()`](../../reference/methods.md#connect) | Triggers wallet connection flow | +| [`connectAndSign({ msg: "..." })`](../../reference/methods.md#connectandsign) | Connects and prompts user to sign a message | +| [`getProvider()`](../../reference/methods.md#getprovider) | Returns the provider object for RPC requests | +| [`provider.request({ method, params })`](../../reference/provider-api.md#request) | Calls any Ethereum JSON‑RPC method | +| [Batched RPC](batch-requests.md) | Use `metamask_batch` to group multiple JSON-RPC requests | ## Usage example diff --git a/sdk/evm/connect/guides/interact-with-contracts.md b/sdk/evm/connect/guides/javascript/interact-with-contracts.md similarity index 64% rename from sdk/evm/connect/guides/interact-with-contracts.md rename to sdk/evm/connect/guides/javascript/interact-with-contracts.md index 5bb47020e74..17f7a85e797 100644 --- a/sdk/evm/connect/guides/interact-with-contracts.md +++ b/sdk/evm/connect/guides/javascript/interact-with-contracts.md @@ -1,13 +1,13 @@ --- -description: Interact with contracts with the SDK in your Wagmi or Vanilla JavaScript dapp. -keywords: [SDK, Wagmi, JavaScript, read, write, smart, contract, contracts, dapp] +description: Interact with contracts with the SDK 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 [Wagmi](#use-wagmi) or [Vanilla JavaScript](#use-vanilla-javascript) dapp. +Interact with smart contracts in your JavaScript dapp. With the SDK, you can: - **Read data** from smart contracts. @@ -16,108 +16,11 @@ With the SDK, you can: - **Manage transaction states**. - **Handle contract errors**. -## Use Wagmi +## Read and write to contracts -Wagmi provides dedicated hooks for smart contract interactions. -The following are examples of using these hooks. +You can implement smart contract interactions directly in JavaScript. -Read contract data: - -```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()}
    -} -``` - -Write to contracts: - -```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}
    } -
    - ) -} -``` - -## Use Vanilla JavaScript - -You can implement smart contract interactions directly in Vanilla JavaScript. - -The following example reads contract data using the [`eth_call`](../reference/json-rpc-api/index.md) RPC method: +The following example reads contract data using the [`eth_call`](../../reference/json-rpc-api/index.md) RPC method: ```javascript async function getBalance(contractAddress, userAddress) { @@ -157,9 +60,9 @@ async function displayBalance() { } ``` -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) +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 diff --git a/sdk/evm/connect/guides/manage-networks.md b/sdk/evm/connect/guides/javascript/manage-networks.md similarity index 64% rename from sdk/evm/connect/guides/manage-networks.md rename to sdk/evm/connect/guides/javascript/manage-networks.md index d7b3f73c13b..deb3d50b584 100644 --- a/sdk/evm/connect/guides/manage-networks.md +++ b/sdk/evm/connect/guides/javascript/manage-networks.md @@ -1,12 +1,12 @@ --- -description: Manage networks with the SDK in your Wagmi or Vanilla JavaScript dapp. -keywords: [SDK, Wagmi, JavaScript, detect, switch, add, network, networks, dapp] +description: Manage networks with the SDK in your JavaScript dapp. +keywords: [SDK, JavaScript, detect, switch, add, network, networks, dapp] toc_max_heading_level: 2 --- # Manage networks -Manage networks in your [Wagmi](#use-wagmi) or [Vanilla JavaScript](#use-vanilla-javascript) dapp. +Manage networks in your JavaScript dapp. With the SDK, you can: - **Detect the current network** and monitor network changes. @@ -16,87 +16,17 @@ With the SDK, you can:

    - Switch Networks + Switch Networks

    -## Use Wagmi +## 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 -} -``` - -## Use Vanilla JavaScript - -You can implement network management directly in Vanilla JavaScript. +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: +[`eth_chainId`](../../reference/json-rpc-api/index.md) RPC method and +[`chainChanged`](../../reference/provider-api.md#chainchanged) provider event: ```javascript // Get current chain ID @@ -121,8 +51,8 @@ ethereum.on("chainChanged", (chainId) => { ``` The following example switches networks using the -[`wallet_switchEthereumChain`](../reference/json-rpc-api/index.md) -and [`wallet_addEthereumChain`](../reference/json-rpc-api/index.md) +[`wallet_switchEthereumChain`](../../reference/json-rpc-api/index.md) +and [`wallet_addEthereumChain`](../../reference/json-rpc-api/index.md) RPC methods: ```javascript @@ -211,7 +141,7 @@ 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. | +| `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. | diff --git a/sdk/evm/connect/guides/manage-user-accounts.md b/sdk/evm/connect/guides/javascript/manage-user-accounts.md similarity index 66% rename from sdk/evm/connect/guides/manage-user-accounts.md rename to sdk/evm/connect/guides/javascript/manage-user-accounts.md index 1436d457aa4..e00c2e00cce 100644 --- a/sdk/evm/connect/guides/manage-user-accounts.md +++ b/sdk/evm/connect/guides/javascript/manage-user-accounts.md @@ -1,13 +1,12 @@ --- -description: Authenticate users with the SDK in your Wagmi or Vanilla JavaScript dapp. -keywords: [SDK, Wagmi, JavaScript, authenticate, connect, sign, accounts, wallet, dapp] +description: Authenticate users with the SDK 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 [Wagmi](#use-wagmi) or -[Vanilla JavaScript](#use-vanilla-javascript) dapp. +Connect and manage user wallet sessions in your JavaScript dapp. With the SDK, you can: - **Connect users' wallets** to your dapp. @@ -20,76 +19,15 @@ With the SDK, you can:

    - Connect to MetaMask + Connect to MetaMask

    -## Use Wagmi +## 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...
    -} -``` - -## Use Vanilla JavaScript - -You can implement user authentication directly in Vanilla JavaScript, using the -[`eth_requestAccounts`](../reference/json-rpc-api/index.md) RPC method -and [`accountsChanged`](../reference/provider-api.md#accountschanged) provider event. +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 @@ -162,10 +100,9 @@ Display connect and disconnect buttons in HTML: ``` -### Connect and sign +## Connect and sign -If you're not using Wagmi, you can access MetaMask Wallet SDK's [`connectAndSign`](../reference/methods.md#connectandsign) method, -which requests wallet access and signs the message in a single user interaction. +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 @@ -195,7 +132,7 @@ The following HTML displays a **Connect & Sign** button: ``` :::tip -This one-step flow is unique to MetaMask Wallet SDK's `connectAndSign` method. +This one-step flow is unique to MM Connect's `connectAndSign` method. It's not part of Wagmi or other wallet libraries. ::: diff --git a/sdk/evm/connect/guides/send-transactions/batch-transactions.md b/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md similarity index 90% rename from sdk/evm/connect/guides/send-transactions/batch-transactions.md rename to sdk/evm/connect/guides/javascript/send-transactions/batch-transactions.md index 5779ec706b7..6fee0c79820 100644 --- a/sdk/evm/connect/guides/send-transactions/batch-transactions.md +++ b/sdk/evm/connect/guides/javascript/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: @@ -109,14 +109,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" @@ -161,7 +161,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: diff --git a/sdk/evm/connect/guides/send-transactions/index.md b/sdk/evm/connect/guides/javascript/send-transactions/index.md similarity index 60% rename from sdk/evm/connect/guides/send-transactions/index.md rename to sdk/evm/connect/guides/javascript/send-transactions/index.md index 75bb7cc4bd9..78924855dbc 100644 --- a/sdk/evm/connect/guides/send-transactions/index.md +++ b/sdk/evm/connect/guides/javascript/send-transactions/index.md @@ -1,12 +1,12 @@ --- -description: Handle transactions with the SDK in your Wagmi or Vanilla JavaScript dapp. -keywords: [SDK, Wagmi, JavaScript, send, transaction, transactions, status, estimate, gas, dapp] +description: Handle transactions with the SDK 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 [Wagmi](#use-wagmi) or [Vanilla JavaScript](#use-vanilla-javascript) dapp. +Handle EVM transactions in your JavaScript dapp. With the SDK, you can: - **Send transactions**. @@ -15,106 +15,15 @@ With the SDK, you can: - **Handle transaction errors** gracefully. - **Manage complex transaction patterns**. -## Use Wagmi - -Wagmi provides hooks for sending transactions and tracking their status. -The following are examples of sending a [basic transaction](#basic-transaction) and an -[advanced transaction with gas estimation](#advanced-transaction-with-gas-estimation). - -### 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}
    } -
    - ) -} -``` - -### 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 -} -``` - -## Use Vanilla JavaScript - -You can implement transaction handling directly in Vanilla JavaScript. -The following are examples of sending a [basic transaction](#basic-transaction-1) and an -[advanced transaction with gas estimation](#advanced-transaction-with-gas-estimation-1). +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). -### Basic transaction +## 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) +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 @@ -213,9 +122,9 @@ async function handleSend() { ``` -### Advanced transaction with gas estimation +## Send an advanced transaction with gas estimation -To add gas estimation, use the [`eth_estimateGas`](../../reference/json-rpc-api/index.md) +To add gas estimation, use the [`eth_estimateGas`](../../../reference/json-rpc-api/index.md) RPC method. ```javascript diff --git a/sdk/evm/connect/guides/sign-data/index.md b/sdk/evm/connect/guides/javascript/sign-data/index.md similarity index 97% rename from sdk/evm/connect/guides/sign-data/index.md rename to sdk/evm/connect/guides/javascript/sign-data/index.md index 58f114b1863..d6ae6034be1 100644 --- a/sdk/evm/connect/guides/sign-data/index.md +++ b/sdk/evm/connect/guides/javascript/sign-data/index.md @@ -26,7 +26,7 @@ sign data using an unsupported method, in which case we recommend using your sta ## Use `eth_signTypedData_v4` -[`eth_signTypedData_v4`](../../reference/json-rpc-api/index.md) +[`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. @@ -35,7 +35,7 @@ account names in place of addresses).

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

    @@ -184,14 +184,14 @@ See the [live example](https://metamask.github.io/test-dapp/#signTypedDataV4) an ## Use `personal_sign` -[`personal_sign`](../../reference/json-rpc-api/index.md) is the +[`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) +![Personal sign](../../../_assets/personal_sign.png)

    diff --git a/sdk/evm/connect/guides/sign-data/siwe.md b/sdk/evm/connect/guides/javascript/sign-data/siwe.md similarity index 86% rename from sdk/evm/connect/guides/sign-data/siwe.md rename to sdk/evm/connect/guides/javascript/sign-data/siwe.md index 96d7bbe8cf4..246e338a777 100644 --- a/sdk/evm/connect/guides/sign-data/siwe.md +++ b/sdk/evm/connect/guides/javascript/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,17 +31,17 @@ 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" const siweSign = async (siweMessage) => { diff --git a/sdk/evm/connect/guides/use-deeplinks.md b/sdk/evm/connect/guides/javascript/use-deeplinks.md similarity index 100% rename from sdk/evm/connect/guides/use-deeplinks.md rename to sdk/evm/connect/guides/javascript/use-deeplinks.md diff --git a/sdk/evm/connect/get-started/rainbowkit.md b/sdk/evm/connect/guides/rainbowkit.md similarity index 86% rename from sdk/evm/connect/get-started/rainbowkit.md rename to sdk/evm/connect/guides/rainbowkit.md index 68eb5ef7c6d..a5a0f036e19 100644 --- a/sdk/evm/connect/get-started/rainbowkit.md +++ b/sdk/evm/connect/guides/rainbowkit.md @@ -1,13 +1,13 @@ --- -description: Quickstart guide for using MetaMask Wallet SDK with a JavaScript and RainbowKit dapp. +description: Quickstart guide for using MM Connect with a JavaScript and RainbowKit dapp. toc_max_heading_level: 2 -sidebar_label: JavaScript + RainbowKit +sidebar_label: RainbowKit keywords: [connect, MetaMask, JavaScript, RainbowKit, SDK, dapp, Wallet SDK] --- # Connect to MetaMask using JavaScript + RainbowKit -Get started with MetaMask Wallet SDK in a JavaScript and RainbowKit dapp. +Get started with MM Connect in a JavaScript and RainbowKit dapp. You can [download the quickstart template](#set-up-using-a-template) or [manually set up the SDK](#set-up-manually) in an existing dapp.

    @@ -25,7 +25,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up using a template -1. Download the [MetaMask Wallet SDK RainbowKit template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/rainbowkit): +1. Download the [MM Connect RainbowKit template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/rainbowkit): ```bash npx degit MetaMask/metamask-sdk-examples/quickstarts/rainbowkit metamask-rainbowkit @@ -44,7 +44,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall `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 MetaMask Wallet SDK examples repository and navigate into the `quickstarts/rainbowkit` directory: + To do so, clone the MM Connect examples repository and navigate into the `quickstarts/rainbowkit` directory: ```bash git clone https://github.com/MetaMask/metamask-sdk-examples @@ -82,7 +82,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ### 1. Install the SDK -Install MetaMask Wallet SDK along with its peer dependencies to an existing React project: +Install MM Connect along with its peer dependencies to an existing React project: ```bash npm2yarn npm install @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query @@ -108,7 +108,7 @@ In the following example, replace `` with your WalletConnect pr ```jsx const config = getDefaultConfig({ - appName: "MetaMask Wallet SDK RainbowKit Quickstart", + appName: "MM Connect RainbowKit Quickstart", projectId: "", chains: [mainnet, linea, sepolia, lineaSepolia], wallets: [ diff --git a/sdk/evm/connect/get-started/react-native.md b/sdk/evm/connect/guides/react-native.md similarity index 98% rename from sdk/evm/connect/get-started/react-native.md rename to sdk/evm/connect/guides/react-native.md index 3d8cb9c5e01..8b5fde5c340 100644 --- a/sdk/evm/connect/get-started/react-native.md +++ b/sdk/evm/connect/guides/react-native.md @@ -9,7 +9,7 @@ import TabItem from "@theme/TabItem"; # Connect to MetaMask using React Native -Get started with MetaMask Wallet SDK in your React Native or Expo dapp. +Get started with MM Connect in your React Native or Expo dapp. ## Steps diff --git a/sdk/evm/connect/get-started/wagmi.md b/sdk/evm/connect/guides/wagmi/index.md similarity index 80% rename from sdk/evm/connect/get-started/wagmi.md rename to sdk/evm/connect/guides/wagmi/index.md index f688f8da7dd..fad7b6eda5d 100644 --- a/sdk/evm/connect/get-started/wagmi.md +++ b/sdk/evm/connect/guides/wagmi/index.md @@ -1,18 +1,18 @@ --- -description: Quickstart guide for using MetaMask Wallet SDK with a JavaScript and Wagmi dapp. +description: Quickstart guide for using MM Connect with a JavaScript and Wagmi dapp. toc_max_heading_level: 2 -sidebar_label: JavaScript + Wagmi (recommended) +sidebar_label: Wagmi keywords: [connect, MetaMask, JavaScript, Wagmi, SDK, dapp, Wallet SDK] --- # Connect to MetaMask using JavaScript + Wagmi -Get started with MetaMask Wallet SDK in a JavaScript and Wagmi dapp. +Get started with MM Connect in a JavaScript and Wagmi dapp. You can [download the quickstart template](#set-up-using-a-template) or [manually set up the SDK](#set-up-manually) in an existing dapp.

    - Quickstart + Quickstart

    @@ -24,7 +24,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up using a template -1. Download the [MetaMask Wallet SDK Wagmi template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/wagmi): +1. Download the [MM Connect Wagmi template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/quickstarts/wagmi): ```bash npx degit MetaMask/metamask-sdk-examples/quickstarts/wagmi metamask-wagmi @@ -43,7 +43,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall `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 MetaMask Wallet SDK examples repository and navigate into the `quickstarts/wagmi` directory: + To do so, clone the MM Connect examples repository and navigate into the `quickstarts/wagmi` directory: ```bash git clone https://github.com/MetaMask/metamask-sdk-examples @@ -69,7 +69,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ### 1. Install the SDK -Install MetaMask Wallet SDK along with its peer dependencies to an existing React project: +Install MM Connect along with its peer dependencies to an existing React project: ```bash npm2yarn npm install @metamask/sdk wagmi viem@2.x @tanstack/react-query @@ -89,7 +89,7 @@ import { metaMask } from "wagmi/connectors" ### 3. Configure your project Set up your configuration with the desired chains and connectors. -In the following example, set the [`infuraAPIKey`](../../../reference/options.md#infuraapikey) option to your [Infura API key](/developer-tools/dashboard/get-started/create-api) to use for RPC requests: +In the following example, set the [`infuraAPIKey`](../../../../reference/options.md#infuraapikey) option to your [Infura API key](/developer-tools/dashboard/get-started/create-api) to use for RPC requests: ```jsx const config = createConfig({ @@ -179,10 +179,10 @@ const config = createConfig({ After completing the basic setup, you can follow these guides to add your own functionality: -- [Manage user accounts](../guides/manage-user-accounts.md) -- [Manage networks](../guides/manage-networks.md) -- [Send transactions](../guides/send-transactions/index.md) -- [Interact with smart contracts](../guides/interact-with-contracts.md) +- [Manage user accounts](manage-user-accounts.md) +- [Manage networks](manage-networks.md) +- [Send transactions](send-transactions.md) +- [Interact with smart contracts](interact-with-contracts.md) ## Live example diff --git a/sdk/evm/connect/guides/wagmi/interact-with-contracts.md b/sdk/evm/connect/guides/wagmi/interact-with-contracts.md new file mode 100644 index 00000000000..05775f41a84 --- /dev/null +++ b/sdk/evm/connect/guides/wagmi/interact-with-contracts.md @@ -0,0 +1,221 @@ +--- +description: Interact with contracts with the SDK 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 the SDK, 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 new file mode 100644 index 00000000000..fd2eb70c3b6 --- /dev/null +++ b/sdk/evm/connect/guides/wagmi/manage-networks.md @@ -0,0 +1,126 @@ +--- +description: Manage networks with the SDK 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 the SDK, 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 new file mode 100644 index 00000000000..12cbf3fde92 --- /dev/null +++ b/sdk/evm/connect/guides/wagmi/manage-user-accounts.md @@ -0,0 +1,132 @@ +--- +description: Authenticate users with the SDK 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 the SDK, 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 new file mode 100644 index 00000000000..38e7d793d32 --- /dev/null +++ b/sdk/evm/connect/guides/wagmi/send-transactions.md @@ -0,0 +1,142 @@ +--- +description: Handle transactions with the SDK 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 the SDK, 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). + +## 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/partners/web3auth.md b/sdk/evm/connect/guides/web3auth.md similarity index 89% rename from sdk/evm/connect/partners/web3auth.md rename to sdk/evm/connect/guides/web3auth.md index 81853b145cc..d1c768c249a 100644 --- a/sdk/evm/connect/partners/web3auth.md +++ b/sdk/evm/connect/guides/web3auth.md @@ -1,15 +1,15 @@ --- sidebar_label: Embedded Wallets SDK -description: Quickstart guide for using MetaMask Wallet SDK and Embedded Wallets SDK. +description: Quickstart guide for using MM Connect and Embedded Wallets SDK. toc_max_heading_level: 2 keywords: [connect, MetaMask, Embedded Wallets, SDK, dapp, Wallet SDK] --- # Connect to MetaMask using Embedded Wallets SDK -Get started with MetaMask Wallet SDK and [Embedded Wallets SDK (previously Web3Auth)](/embedded-wallets), +Get started with MM Connect and [Embedded Wallets SDK (previously Web3Auth)](/embedded-wallets), enabling users to sign in with an email or social media account. -You can use MetaMask Wallet SDK features directly within Embedded Wallets SDK. +You can use MM Connect features directly within Embedded Wallets SDK. You can [download the quickstart template](#set-up-using-a-template) or [manually set up the SDKs](#set-up-manually) in an existing dapp.

    @@ -27,7 +27,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up using a template -1. Download the [MetaMask Wallet SDK + Web3Auth SDK template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/partners/web3auth): +1. Download the [MM Connect + Web3Auth SDK template](https://github.com/MetaMask/metamask-sdk-examples/tree/main/partners/web3auth): ```bash npx degit MetaMask/metamask-sdk-examples/partners/web3auth metamask-web3auth @@ -46,7 +46,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall `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 MetaMask Wallet SDK examples repository and navigate into the `partners/web3auth` directory: + To do so, clone the MM Connect examples repository and navigate into the `partners/web3auth` directory: ```bash git clone https://github.com/MetaMask/metamask-sdk-examples @@ -80,7 +80,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall pnpm dev ``` -You've successfully set up MetaMask Wallet SDK and MetaMask Embedded Wallets. +You've successfully set up MM Connect and MetaMask Embedded Wallets. See how to [use Embedded Wallets](#usage). ## Set up manually diff --git a/sdk/evm/connect/reference/methods.md b/sdk/evm/connect/reference/methods.md index 5f20930554e..88a91a36f4b 100644 --- a/sdk/evm/connect/reference/methods.md +++ b/sdk/evm/connect/reference/methods.md @@ -1,12 +1,12 @@ --- -description: Methods reference for MetaMask Wallet SDK. +description: Methods reference for MM Connect. keywords: [SDK, method, methods, dapp] toc_max_heading_level: 2 --- -# SDK methods +# MM Connect methods -MetaMask Wallet SDK provides several convenience methods for connecting to and interacting with MetaMask, including the following. +MM Connect provides several convenience methods for connecting to and interacting with MetaMask, including the following. ## `connect` diff --git a/sdk/evm/connect/reference/provider-api.md b/sdk/evm/connect/reference/provider-api.md index c13d3d94650..152c3250637 100644 --- a/sdk/evm/connect/reference/provider-api.md +++ b/sdk/evm/connect/reference/provider-api.md @@ -13,7 +13,7 @@ MetaMask supports [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963), which int alternative wallet detection mechanism to the `window.ethereum` injected provider. This alternative mechanism enables dapps to support wallet interoperability by discovering multiple injected wallet providers in a user's browser. -We recommend [using this mechanism to connect to MetaMask](../guides/connect-extension.md). +We recommend using this mechanism to connect to MetaMask. You can access the provider API using the selected EIP-6963 provider object. Throughout this documentation, we refer to the selected provider using `provider`. @@ -45,7 +45,7 @@ If the provider isn't connected, the page must be reloaded to re-establish the c See the [`connect`](#connect) and [`disconnect`](#disconnect) events for more information. :::note -This method is unrelated to [managing user accounts](../guides/manage-user-accounts.md). +This method is unrelated to [managing user accounts](../guides/javascript/manage-user-accounts.md). In the provider interface, "connected" and "disconnected" refer to whether the provider can make RPC requests to the current chain. ::: @@ -174,7 +174,7 @@ Callers are identified by their URL origin, which means that all sites with the the same permissions. This means that the provider emits `accountsChanged` when the user's exposed account address changes. -Listen to this event to [handle accounts](../guides/manage-user-accounts.md). +Listen to this event to [handle accounts](../guides/javascript/manage-user-accounts.md). ### `chainChanged` @@ -184,7 +184,7 @@ provider // Or window.ethereum if you don't support EIP-6963. ``` The provider emits this event when the currently connected chain changes. -Listen to this event to [detect a user's network](../guides/manage-networks.md). +Listen to this event to [detect a user's network](../guides/javascript/manage-networks.md). ### `connect` diff --git a/sdk/evm/index.md b/sdk/evm/index.md index 5c5f165a7aa..4a07b3d75b1 100644 --- a/sdk/evm/index.md +++ b/sdk/evm/index.md @@ -8,45 +8,45 @@ import CardList from '@site/src/components/CardList' ## Supported platforms and libraries -MetaMask Wallet SDK is available in a variety of ways to make integration as easy as possible. +MM Connect is available in a variety of ways to make integration as easy as possible. You can access it directly via npm, through popular developer libraries like Wagmi, or as part of popular convenience libraries. -## Why use the SDK? +## Why use MM Connect? -MetaMask Wallet SDK gives your dapp a powerful upgrade: +MM Connect gives your dapp a powerful upgrade: - **Cross-platform, cross-browser support** - One integration covers both desktop and mobile, all major browsers, and the MetaMask mobile app—streamlining your user onboarding and eliminating edge cases. - **Mobile connection that just works** - Say goodbye to clunky "open in in-app browser" flows. The SDK enables a native connection from any mobile browser (Safari, Chrome, etc.) directly to the MetaMask mobile app, using secure deeplinking and session management. -- **Production-ready, battle-tested** - MetaMask Wallet SDK is used in high-volume dapps across DeFi, NFTs, gaming, and more—ensuring stability, speed, and a smooth developer experience. -- **Multichain by design** - Today, the SDK supports all EVM networks and Solana. +- **Production-ready, battle-tested** - MM Connect is used in high-volume dapps across DeFi, NFTs, gaming, and more—ensuring stability, speed, and a smooth developer experience. +- **Multichain by design** - Today, MM Connect supports all EVM networks and Solana. Coming soon: Seamless connection to more ecosystems like Bitcoin and Tron. Futureproof your dapp with a single integration. diff --git a/sdk/reference/options.md b/sdk/reference/options.md index 287308691b8..72210de4f0a 100644 --- a/sdk/reference/options.md +++ b/sdk/reference/options.md @@ -1,14 +1,14 @@ --- -description: Configuration options reference for MetaMask Wallet SDK. +description: Configuration options reference for MM Connect. keywords: [SDK, configure, configuration, option, options, dapp] --- import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; -# SDK options +# MM Connect options -MetaMask Wallet SDK takes the following configuration options. +MM Connect takes the following configuration options. ### `checkInstallationImmediately` diff --git a/src/components/SubNavBar/configs.ts b/src/components/SubNavBar/configs.ts index 4ec4773a142..fe9e9462b84 100644 --- a/src/components/SubNavBar/configs.ts +++ b/src/components/SubNavBar/configs.ts @@ -49,7 +49,7 @@ export const EMBEDDED_WALLETS_SUBNAV_CONFIG: SubNavBarConfig = { export const SDK_SUBNAV_CONFIG: SubNavBarConfig = { pathPattern: '/sdk', - sectionName: 'MetaMask Wallet SDK', + sectionName: 'MM Connect', links: [ { key: 'overview', diff --git a/src/pages/quickstart/NavigationOverlay/NavigationFlow.tsx b/src/pages/quickstart/NavigationOverlay/NavigationFlow.tsx index ecc327fca37..274d4e6e7dd 100644 --- a/src/pages/quickstart/NavigationOverlay/NavigationFlow.tsx +++ b/src/pages/quickstart/NavigationOverlay/NavigationFlow.tsx @@ -27,7 +27,7 @@ const navigationOptions: NavigationOption[] = [ { id: 'mm-sdk', title: "I want to connect to users' MetaMask wallets", - description: 'MetaMask Wallet SDK', + description: 'MM Connect', product: METAMASK_SDK, }, { @@ -148,7 +148,7 @@ const NavigationFlow: React.FC = ({ onSelect }) => { Quick Links

    - 📖 MetaMask Wallet SDK Docs + 📖 MM Connect Docs 💳 Embedded Wallets Docs diff --git a/src/pages/quickstart/builder/choices.ts b/src/pages/quickstart/builder/choices.ts index ea142c9dde5..b8e6f4fd5fe 100644 --- a/src/pages/quickstart/builder/choices.ts +++ b/src/pages/quickstart/builder/choices.ts @@ -6,7 +6,7 @@ export const METAMASK_SDK = 'METAMASK_SDK' // Product choices export const PRODUCTS: DisplayChoice[] = [ - { key: METAMASK_SDK, displayName: 'MetaMask Wallet SDK' }, + { key: METAMASK_SDK, displayName: 'MM Connect' }, { key: EMBEDDED_WALLETS, displayName: 'Embedded Wallets' }, ] diff --git a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/initialization.mdx b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/initialization.mdx index 5b26ea245b7..7e24b459b12 100644 --- a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/initialization.mdx +++ b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/initialization.mdx @@ -1,6 +1,6 @@ -### Initialize MetaMask Wallet SDK +### Initialize MM Connect -Initialize MetaMask Wallet SDK with these options: +Initialize MM Connect with these options: - `dappMetadata` - Displays your dapp's name, URL, and icon URL during connection. - `infuraAPIKey` - Enables the read-only RPC and load‑balancing. diff --git a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/installation.mdx b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/installation.mdx index e44806b8931..3ead8859cbc 100644 --- a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/installation.mdx +++ b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/installation.mdx @@ -1,6 +1,6 @@ -### Install MetaMask Wallet SDK +### Install MM Connect -Install MetaMask Wallet SDK in your React project. +Install MM Connect in your React project. ```bash npm2yarn npm install --save @metamask/sdk diff --git a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/reactQuickStart.mdx b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/reactQuickStart.mdx index 96ade3d92c8..5b9f99980d8 100644 --- a/src/pages/quickstart/builder/metamask-sdk/react/stepContent/reactQuickStart.mdx +++ b/src/pages/quickstart/builder/metamask-sdk/react/stepContent/reactQuickStart.mdx @@ -1,7 +1,7 @@ -### MetaMask Wallet SDK - React quickstart +### MM Connect - React quickstart This quickstart shows you how to connect a React application to the MetaMask wallet using -MetaMask Wallet SDK. +MM Connect. Clone the React quickstart application. diff --git a/src/pages/quickstart/builder/metamask-sdk/react/steps.ts b/src/pages/quickstart/builder/metamask-sdk/react/steps.ts index a3db209f85e..960e7eda04b 100644 --- a/src/pages/quickstart/builder/metamask-sdk/react/steps.ts +++ b/src/pages/quickstart/builder/metamask-sdk/react/steps.ts @@ -12,7 +12,7 @@ export default function getSteps(steps, files, replacementAggregator) { pointer: replacementAggregator.highlightRange( qsFileLinks.MMSDK_REACT_APP_TSX, files[qsFileLinks.MMSDK_REACT_APP_TSX], - 'MetaMask Wallet SDK Import' + 'MM Connect Import' ), }, { @@ -20,7 +20,7 @@ export default function getSteps(steps, files, replacementAggregator) { pointer: replacementAggregator.highlightRange( qsFileLinks.MMSDK_REACT_PACKAGE_JSON, files[qsFileLinks.MMSDK_REACT_PACKAGE_JSON], - 'Install MetaMask Wallet SDK' + 'Install MM Connect' ), }, { @@ -28,7 +28,7 @@ export default function getSteps(steps, files, replacementAggregator) { pointer: replacementAggregator.highlightRange( qsFileLinks.MMSDK_REACT_APP_TSX, files[qsFileLinks.MMSDK_REACT_APP_TSX], - 'Initialize MetaMask Wallet SDK' + 'Initialize MM Connect' ), }, { diff --git a/src/pages/tutorials/create-wallet-ai-agent.md b/src/pages/tutorials/create-wallet-ai-agent.md index ad78791ef0e..ea840eb9ca6 100644 --- a/src/pages/tutorials/create-wallet-ai-agent.md +++ b/src/pages/tutorials/create-wallet-ai-agent.md @@ -1,8 +1,8 @@ --- -title: Create an AI agent using MetaMask Wallet SDK +title: Create an AI agent using MM Connect image: 'img/tutorials/tutorials-banners/create-wallet-ai-agent.png' -description: Create a wallet AI agent using MetaMask Wallet SDK and Vercel's AI SDK. -tags: [metamask wallet sdk, AI agent, Vercel, Wagmi, Next.js, OpenAI] +description: Create a wallet AI agent using MM Connect and Vercel's AI SDK. +tags: [mm connect, AI agent, Vercel, Wagmi, Next.js, OpenAI] date: May 2, 2025 author: MetaMask Developer Relations --- @@ -11,7 +11,7 @@ import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; This tutorial walks you through creating an AI agent dapp that can display your wallet balance and initiate transactions from your wallet, on the Linea Sepolia network. -You will use a provided template, which sets up MetaMask Wallet SDK and [Vercel's AI SDK](https://sdk.vercel.ai/) with a [Next.js](https://nextjs.org/docs) and [Wagmi](https://wagmi.sh/) dapp. +You will use a provided template, which sets up MM Connect and [Vercel's AI SDK](https://sdk.vercel.ai/) with a [Next.js](https://nextjs.org/docs) and [Wagmi](https://wagmi.sh/) dapp. ## Prerequisites @@ -59,7 +59,7 @@ You will use a provided template, which sets up MetaMask Wallet SDK and [Vercel' ### 2. Create the dapp interface -In `app/page.tsx`, use the `useAccount`, `useConnect`, and `useDisconnect` hooks from Wagmi, along with the Wagmi [MetaMask Wallet SDK connector](https://wagmi.sh/react/api/connectors/metaMask) to create a button to connect and disconnect your MetaMask wallet. +In `app/page.tsx`, use the `useAccount`, `useConnect`, and `useDisconnect` hooks from Wagmi, along with the Wagmi [MetaMask connector](https://wagmi.sh/react/api/connectors/metaMask) to create a button to connect and disconnect your MetaMask wallet. Use the `Chat` component to display the AI agent chat interface. diff --git a/src/pages/tutorials/upgrade-eoa-to-smart-account.md b/src/pages/tutorials/upgrade-eoa-to-smart-account.md index 2b7b688b4f8..a0390b820c7 100644 --- a/src/pages/tutorials/upgrade-eoa-to-smart-account.md +++ b/src/pages/tutorials/upgrade-eoa-to-smart-account.md @@ -1,14 +1,14 @@ --- title: Upgrade an EOA to a smart account -description: Upgrade a MetaMask EOA to a smart account using MetaMask Wallet SDK and Wagmi. +description: Upgrade a MetaMask EOA to a smart account using MM Connect and Wagmi. image: 'img/tutorials/tutorials-banners/upgrade-eoa-to-smart-account.png' -tags: [metamask wallet sdk, wagmi, EOA, smart account, EIP-7702, EIP-5792] +tags: [mm connect, wagmi, EOA, smart account, EIP-7702, EIP-5792] date: Aug 22, 2025 author: MetaMask Developer Relations --- This tutorial walks you through upgrading a MetaMask externally owned account (EOA) to a [MetaMask smart account](/delegation-toolkit/concepts/smart-accounts) via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), and sending an [atomic batch transaction](/wallet/how-to/send-transactions/send-batch-transactions/#about-atomic-batch-transactions) via [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792). -You will use a provided template, which sets up MetaMask Wallet SDK with a [Next.js](https://nextjs.org/docs) and [Wagmi](https://wagmi.sh/) dapp. +You will use a provided template, which sets up MM Connect with a [Next.js](https://nextjs.org/docs) and [Wagmi](https://wagmi.sh/) dapp. ## Prerequisites @@ -62,7 +62,7 @@ Add a `NEXT_PUBLIC_INFURA_API_KEY` environment variable, replacing ` ``` -In `src/providers/AppProvider.tsx`, configure the Wagmi [MetaMask Wallet SDK connector](https://wagmi.sh/react/api/connectors/metaMask) using your Infura API key: +In `src/providers/AppProvider.tsx`, configure the Wagmi [MetaMask connector](https://wagmi.sh/react/api/connectors/metaMask) using your Infura API key: ```tsx title="AppProvider.tsx" "use client"; @@ -88,7 +88,7 @@ import { metaMask } from "wagmi/connectors"; ### 3. Create a connect and disconnect button -In `src/app/page.tsx`, use the `useAccount`, `useConnect`, and `useDisconnect` hooks from Wagmi, along with the MetaMask Wallet SDK connector, to create a button to connect and disconnect your MetaMask wallet, and display the connection status: +In `src/app/page.tsx`, use the `useAccount`, `useConnect`, and `useDisconnect` hooks from Wagmi, along with the MetaMask connector, to create a button to connect and disconnect your MetaMask wallet, and display the connection status: ```tsx title="page.tsx" "use client"; diff --git a/src/utils/tutorials-map.tsx b/src/utils/tutorials-map.tsx index cac7e4b2eba..94705ddcaea 100644 --- a/src/utils/tutorials-map.tsx +++ b/src/utils/tutorials-map.tsx @@ -1,7 +1,7 @@ export const tags = { web3Auth: 'web3auth', embeddedWallet: 'embedded wallets', - metamaskSdk: 'metamask wallet sdk', + metamaskSdk: 'mm connect', delegationToolkit: 'delegation toolkit', infura: 'infura', snaps: 'snaps', @@ -90,7 +90,7 @@ export const platformMap = [ export const productMap = [ { - label: 'MetaMask Wallet SDK', + label: 'MM Connect', value: tags.metamaskSdk, }, { From 956c09a4e8f971ba6e54239d8ea3014948c91766 Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Wed, 22 Oct 2025 18:47:45 -0700 Subject: [PATCH 11/17] fix broken links --- .../javascript/best-practices/run-devnet.md | 3 +-- sdk/evm/connect/reference/methods.md | 2 +- sdk/index.md | 5 ----- .../connect/guides/send-legacy-transaction.md | 2 +- .../guides/send-versioned-transaction.md | 4 ++-- snaps/features/custom-evm-accounts/index.md | 1 - snaps/features/transaction-insights.md | 4 ---- snaps/how-to/connect-to-a-snap.md | 3 +-- snaps/how-to/request-permissions.md | 4 ++-- snaps/learn/about-snaps/apis.md | 22 +++++++++---------- snaps/learn/about-snaps/index.md | 2 +- snaps/learn/tutorials/transaction-insights.md | 6 ++--- snaps/reference/entry-points.md | 3 +-- snaps/reference/keyring-api/chain-methods.md | 5 ----- snaps/reference/permissions.md | 8 +++---- snaps/reference/snaps-api.md | 2 +- snaps/reference/wallet-api-for-snaps.md | 7 +----- .../tutorials/upgrade-eoa-to-smart-account.md | 4 ++-- 18 files changed, 31 insertions(+), 56 deletions(-) diff --git a/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md b/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md index da125869f8f..e115a60b998 100644 --- a/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md +++ b/sdk/evm/connect/guides/javascript/best-practices/run-devnet.md @@ -83,8 +83,7 @@ Follow these steps to connect MetaMask to Hardhat Network. :::tip Alternatively, you can add Hardhat Network to MetaMask using - [`wallet_addEthereumChain`](/wallet/reference/json-rpc-methods/wallet_addethereumchain/?AddEthereumChainParameter[rpcUrls][0]=http://127.0.0.1:8545&AddEthereumChainParameter[chainId]=0x539&AddEthereumChainParameter[chainName]=Hardhat&AddEthereumChainParameter[nativeCurrency][name]=testEth&AddEthereumChainParameter[nativeCurrency][symbol]=testEth&AddEthereumChainParameter[nativeCurrency][decimals]=18) - in the interactive API playground. + [`wallet_addEthereumChain`](../../../reference/json-rpc-api/index.md). ::: ## Reset your local nonce calculation diff --git a/sdk/evm/connect/reference/methods.md b/sdk/evm/connect/reference/methods.md index 88a91a36f4b..31054fb5d6e 100644 --- a/sdk/evm/connect/reference/methods.md +++ b/sdk/evm/connect/reference/methods.md @@ -46,7 +46,7 @@ console.log("Signature:", signature); ## `connectWith` -Connects to MetaMask and executes a specific [JSON-RPC method](/wallet/reference/json-rpc-methods). +Connects to MetaMask and executes a specific [JSON-RPC method](json-rpc-api/index.md). ### Parameters diff --git a/sdk/index.md b/sdk/index.md index 8e42f0fdda9..2fa1548f69f 100644 --- a/sdk/index.md +++ b/sdk/index.md @@ -28,11 +28,6 @@ description: 'Connect to Ethereum and other EVM networks.', href: '/sdk/solana', title: 'Solana', description: 'Connect to Solana.', -}, -{ -href: '/sdk/starknet', -title: 'Starknet', -description: 'Connect to Starknet.', } ]} /> diff --git a/sdk/solana/connect/guides/send-legacy-transaction.md b/sdk/solana/connect/guides/send-legacy-transaction.md index 34228c2a7d4..3c37b1e9012 100644 --- a/sdk/solana/connect/guides/send-legacy-transaction.md +++ b/sdk/solana/connect/guides/send-legacy-transaction.md @@ -69,7 +69,7 @@ await connection.getSignatureStatuses(signatures); The following methods are also supported, but are not recommended over `signAndSendTransaction`. It is safer for users, and a simpler API for developers, for Phantom to submit the transaction immediately after signing it instead of relying on the application to do so. :::warning -The following methods are not supported in the [wallet standard](#) implementation and may be removed in a future release. These methods are only available via the [window.solana object](/solana/detecting-the-provider). +The following methods are not supported in the [wallet standard](#) implementation and may be removed in a future release. These methods are only available via the [window.solana object]. ::: ## Sign a transaction (without sending) diff --git a/sdk/solana/connect/guides/send-versioned-transaction.md b/sdk/solana/connect/guides/send-versioned-transaction.md index 8c0d445d36f..f4cbcc36037 100644 --- a/sdk/solana/connect/guides/send-versioned-transaction.md +++ b/sdk/solana/connect/guides/send-versioned-transaction.md @@ -1,6 +1,6 @@ # Send a versioned transaction -The Solana runtime supports two types of transactions: `legacy` (see [Send a legacy transaction](/solana/sending-a-transaction)) and `v0` (transactions that can include Address Lookup Tables or LUTs). +The Solana runtime supports two types of transactions: `legacy` (see [Send a legacy transaction]) and `v0` (transactions that can include Address Lookup Tables or LUTs). The goal of `v0` is to increase the maximum size of a transaction, and hence the number of accounts that can fit in a single atomic transaction. With LUTs, developers can now build transactions with a maximum of 256 accounts, as compared to the limit of 35 accounts in legacy transactions that do not utilize LUTs. @@ -18,7 +18,7 @@ On this page, we'll go over the following: ## Build a versioned transaction -Versioned transactions are built in a very similar fashion to [legacy transactions](sending-a-transaction). The only difference is that developers should use the `VersionedTransaction` class rather than the `Transaction` class. +Versioned transactions are built in a very similar fashion to [legacy transactions](send-legacy-transaction.md). The only difference is that developers should use the `VersionedTransaction` class rather than the `Transaction` class. The following example shows how to build a simple transfer instruction. Once the transfer instruction is made, a `MessageV0` formatted transaction message is constructed with the transfer instruction. Finally, a new `VersionedTransaction` is created, parsing in the `v0` compatible message. diff --git a/snaps/features/custom-evm-accounts/index.md b/snaps/features/custom-evm-accounts/index.md index 8e061f1e8e6..d5caeaad16e 100644 --- a/snaps/features/custom-evm-accounts/index.md +++ b/snaps/features/custom-evm-accounts/index.md @@ -285,7 +285,6 @@ externally owned accounts (EOAs): - [`personal_sign`](../../reference/keyring-api/chain-methods.md#personal_sign) - [`eth_signTypedData_v4`](../../reference/keyring-api/chain-methods.md#eth_signtypeddata_v4) - [`eth_signTransaction`](../../reference/keyring-api/chain-methods.md#eth_signtransaction) -- [Deprecated signing methods](/wallet/concepts/signing-methods/#deprecated-signing-methods) ## Account abstraction (ERC-4337) diff --git a/snaps/features/transaction-insights.md b/snaps/features/transaction-insights.md index c3050722bb9..f8e1315f5db 100644 --- a/snaps/features/transaction-insights.md +++ b/snaps/features/transaction-insights.md @@ -100,10 +100,6 @@ export const onTransaction: OnTransactionHandler = async ({ -:::note -Learn more about the [parameters of a submitted transaction](/wallet/how-to/send-transactions#transaction-parameters). -::: - The Snap tab in the transaction confirmation window displays the transaction insights:

    diff --git a/snaps/how-to/connect-to-a-snap.md b/snaps/how-to/connect-to-a-snap.md index e4c7d9e5c1b..7bb558f6c1f 100644 --- a/snaps/how-to/connect-to-a-snap.md +++ b/snaps/how-to/connect-to-a-snap.md @@ -15,14 +15,13 @@ This is possible because Snaps can expose a [custom JSON-RPC API](../learn/about ## Detect wallet To connect to a Snap, dapps must first detect MetaMask in the user's browser. -See the Wallet API documentation on [how to connect to the MetaMask extension](/wallet/how-to/connect-extension). ### Detect MetaMask Flask When developing your Snap, you might need to require [MetaMask Flask](../get-started/install-flask.md) in your dapp. We recommend detecting MetaMask Flask using the -[multi-wallet detection mechanism](/wallet/concepts/wallet-interoperability) specified by EIP-6963. +multi-wallet detection mechanism specified by EIP-6963. Alternatively, you can use the `window.ethereum` injected provider, but this might fail if the user is running multiple wallet extensions simultaneously. diff --git a/snaps/how-to/request-permissions.md b/snaps/how-to/request-permissions.md index dba44fba7a0..a2d7706bca8 100644 --- a/snaps/how-to/request-permissions.md +++ b/snaps/how-to/request-permissions.md @@ -59,8 +59,8 @@ permission, add the following to the manifest file: Dynamic permissions are not requested in the manifest file. Instead, your Snap can acquire dynamic permissions during its lifecycle. -For example, request permission to call the [`eth_accounts`](/wallet/reference/json-rpc-methods/eth_accounts) -MetaMask JSON-RPC API method by calling [`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts). +For example, request permission to call the [`eth_accounts`](/sdk/evm/connect/reference/json-rpc-api) +MetaMask JSON-RPC API method by calling [`eth_requestAccounts`](/sdk/evm/connect/reference/json-rpc-api). See the [`eth_accounts` dynamic permission](../reference/permissions.md#eth_accounts) for more information. ## Request permissions from a dapp diff --git a/snaps/learn/about-snaps/apis.md b/snaps/learn/about-snaps/apis.md index 3ca308e3ca4..6209138517a 100644 --- a/snaps/learn/about-snaps/apis.md +++ b/snaps/learn/about-snaps/apis.md @@ -89,7 +89,7 @@ Snaps can also call some Wallet JSON-RPC API methods using the `ethereum` global To expose `ethereum` to the Snap execution environment, a Snap must first request the [`endowment:ethereum-provider`](../../reference/permissions.md#endowmentethereum-provider) permission. -For example, to call [`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts), first request +For example, to call [`eth_requestAccounts`](/sdk/evm/connect/reference/json-rpc-api), first request the required permission: ```json title="snap.manifest.json" @@ -108,16 +108,16 @@ The `ethereum` global available to Snaps has fewer capabilities than `window.eth Snaps can only use it to make read requests, not to write to the blockchain or initiate transactions. Snaps can call all Wallet JSON-RPC API methods **except** the following: -- [`wallet_requestPermissions`](/wallet/reference/json-rpc-methods/wallet_requestpermissions) -- [`wallet_revokePermissions`](/wallet/reference/json-rpc-methods/wallet_revokepermissions) -- [`wallet_addEthereumChain`](/wallet/reference/json-rpc-methods/wallet_addethereumchain) -- [`wallet_switchEthereumChain`](/wallet/reference/json-rpc-methods/wallet_switchethereumchain) -- [`wallet_watchAsset`](/wallet/reference/json-rpc-methods/wallet_watchasset) -- [`wallet_registerOnboarding`](/wallet/reference/json-rpc-methods/wallet_registeronboarding) -- [`wallet_scanQRCode`](/wallet/reference/json-rpc-methods/wallet_scanqrcode) -- [`eth_sendTransaction`](/wallet/reference/json-rpc-methods/eth_sendtransaction) -- [`eth_decrypt`](/wallet/reference/json-rpc-methods/eth_decrypt) -- [`eth_getEncryptionPublicKey`](/wallet/reference/json-rpc-methods/eth_getencryptionpublickey) +- `wallet_requestPermissions` +- `wallet_revokePermissions` +- `wallet_addEthereumChain` +- `wallet_switchEthereumChain` +- `wallet_watchAsset` +- `wallet_registerOnboarding` +- `wallet_scanQRCode` +- `eth_sendTransaction` +- `eth_decrypt` +- `eth_getEncryptionPublicKey` ## Custom JSON-RPC APIs diff --git a/snaps/learn/about-snaps/index.md b/snaps/learn/about-snaps/index.md index 2bac953cb3e..f85f97e1e32 100644 --- a/snaps/learn/about-snaps/index.md +++ b/snaps/learn/about-snaps/index.md @@ -45,7 +45,7 @@ of MetaMask core unless given permission to do so. ### APIs A Snap can communicate with MetaMask using the [Snaps API](../../reference/snaps-api.md) and some -[MetaMask JSON-RPC API](/wallet/reference/json-rpc-methods) methods. +[MetaMask JSON-RPC API](/sdk/evm/connect/reference/json-rpc-api) methods. The Snaps API allows Snaps to extend or modify the functionality of MetaMask, and communicate with other Snaps. diff --git a/snaps/learn/tutorials/transaction-insights.md b/snaps/learn/tutorials/transaction-insights.md index 9002596bc5d..8d82df42f5f 100644 --- a/snaps/learn/tutorials/transaction-insights.md +++ b/snaps/learn/tutorials/transaction-insights.md @@ -11,7 +11,7 @@ import TabItem from "@theme/TabItem"; This tutorial walks you through creating a Snap that calculates the percentage of gas fees they would pay for their transaction. -It gets the current gas price by calling the [`eth_gasPrice`](/wallet/reference/json-rpc-methods/eth_gasprice) RPC +It gets the current gas price by calling the [`eth_gasPrice`](/sdk/evm/connect/reference/json-rpc-api) RPC method using the global Ethereum provider made available to Snaps, and displays this as a percentage of gas fees in a tab in MetaMask's transaction confirmation window. @@ -303,9 +303,7 @@ export const onTransaction: OnTransactionHandler = async ({ transaction }) => { :::note Notes -- Learn more about the [parameters of a submitted transaction](/wallet/how-to/send-transactions#transaction-parameters). - -- If you have previously developed a dapp, you're likely familiar with accessing the Ethereum provider +If you have previously developed a dapp, you're likely familiar with accessing the Ethereum provider using `window.ethereum`. In a Snap, the `window` object is not available. Instead, when you request the `endowment:ethereum-provider` permission, your Snap is granted access to the [`ethereum` global object](../about-snaps/apis.md#snap-requests). diff --git a/snaps/reference/entry-points.md b/snaps/reference/entry-points.md index f5589b57776..927f9197346 100644 --- a/snaps/reference/entry-points.md +++ b/snaps/reference/entry-points.md @@ -399,7 +399,7 @@ module.exports.onRpcRequest = async ({ origin, request }) => { To provide [signature insights](../features/signature-insights.md) before a user signs a message, a Snap must expose the `onSignature` entry point. -Whenever a [signing method](/wallet/concepts/signing-methods) is called, such as `personal_sign` or +Whenever a signing method is called, such as `personal_sign` or `eth_signTypedData_v4`, MetaMask passes the raw unsigned signature payload to the `onSignature` handler method. @@ -498,7 +498,6 @@ For MetaMask to call the Snap's `onTransaction` method, you must request the An object containing: - `transaction` - The raw transaction payload. - Learn more about the [parameters of a submitted transaction](/wallet/how-to/send-transactions#transaction-parameters). - `chainId` - The [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) chain ID. - `transactionOrigin` - The transaction origin if diff --git a/snaps/reference/keyring-api/chain-methods.md b/snaps/reference/keyring-api/chain-methods.md index 6c4105138b7..6cd96f7ac34 100644 --- a/snaps/reference/keyring-api/chain-methods.md +++ b/snaps/reference/keyring-api/chain-methods.md @@ -240,11 +240,6 @@ Signature: `string` - Hex-encoded signature. -### Deprecated methods - -Snaps can also implement [deprecated signing -methods](/wallet/concepts/signing-methods/#deprecated-signing-methods) that some dapps might use. - ## ERC-4337 methods :::flaskOnly diff --git a/snaps/reference/permissions.md b/snaps/reference/permissions.md index ace7d708296..43762b8a8da 100644 --- a/snaps/reference/permissions.md +++ b/snaps/reference/permissions.md @@ -74,7 +74,7 @@ Specify this permission in the manifest file as follows: To communicate with a node using MetaMask, a Snap must request the `endowment:ethereum-provider` permission. This permission exposes the `ethereum` global to the Snap execution environment, allowing Snaps to -call some [MetaMask JSON-RPC API](/wallet/reference/json-rpc-methods) methods. +call some [MetaMask JSON-RPC API](/sdk/evm/connect/reference/json-rpc-api) methods. This global is an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) provider. Specify this permission in the manifest file as follows: @@ -348,8 +348,8 @@ The following endowments accept this caveat: ### `eth_accounts` -A Snap can request permission to call the [`eth_accounts`](/wallet/reference/json-rpc-methods/eth_accounts) MetaMask -JSON-RPC API method by calling [`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts). +A Snap can request permission to call the [`eth_accounts`](/sdk/evm/connect/reference/json-rpc-api) MetaMask +JSON-RPC API method by calling [`eth_requestAccounts`](/sdk/evm/connect/reference/json-rpc-api). Calling `eth_requestAccounts` requires the [`endowment:ethereum-provider`](#endowmentethereum-provider) permission: @@ -373,7 +373,7 @@ await ethereum.request({ method: "eth_requestAccounts" }) You can check the presence of the permission by calling -[`wallet_getPermissions`](/wallet/reference/json-rpc-methods/wallet_getpermissions). +[`wallet_getPermissions`](/sdk/evm/connect/reference/json-rpc-api). If the permission is present, the result contains a permission with a `parentCapability` of `eth_accounts`. The permission contains a `restrictReturnedAccounts` caveat, an array of all the accounts the user allows for this Snap. diff --git a/snaps/reference/snaps-api.md b/snaps/reference/snaps-api.md index 9892fed0d90..bf96f698d2b 100644 --- a/snaps/reference/snaps-api.md +++ b/snaps/reference/snaps-api.md @@ -287,7 +287,7 @@ An object containing `coinType`, the BIP-44 coin type to get the entropy for. Coin type 60 is reserved for MetaMask externally owned accounts and blocked for Snaps. To connect to MetaMask accounts in a Snap, use [`endowment:ethereum-provider`](../reference/permissions.md#endowmentethereum-provider) and -[`eth_requestAccounts`](/wallet/reference/json-rpc-methods/eth_requestaccounts). +[`eth_requestAccounts`](/sdk/evm/connect/reference/json-rpc-api). ::: #### Returns diff --git a/snaps/reference/wallet-api-for-snaps.md b/snaps/reference/wallet-api-for-snaps.md index e697384beae..32bf6ded3fa 100644 --- a/snaps/reference/wallet-api-for-snaps.md +++ b/snaps/reference/wallet-api-for-snaps.md @@ -10,14 +10,9 @@ import TabItem from "@theme/TabItem"; # Wallet API for Snaps Dapps can install and communicate with Snaps using a subset of the -[Wallet JSON-RPC API](/wallet/concepts/wallet-api/#json-rpc-api). +[Wallet JSON-RPC API](/sdk/evm/connect/reference/json-rpc-api). This page is a reference for those Snaps-specific methods. -:::note -See the [Wallet JSON-RPC API interactive reference](/wallet/reference/json-rpc-methods) for the other -methods dapps can call. -::: - ## `wallet_getSnaps` Returns the IDs of the dapp's permitted Snaps and some relevant metadata. diff --git a/src/pages/tutorials/upgrade-eoa-to-smart-account.md b/src/pages/tutorials/upgrade-eoa-to-smart-account.md index a0390b820c7..1cf16d4ba65 100644 --- a/src/pages/tutorials/upgrade-eoa-to-smart-account.md +++ b/src/pages/tutorials/upgrade-eoa-to-smart-account.md @@ -7,7 +7,7 @@ date: Aug 22, 2025 author: MetaMask Developer Relations --- -This tutorial walks you through upgrading a MetaMask externally owned account (EOA) to a [MetaMask smart account](/delegation-toolkit/concepts/smart-accounts) via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), and sending an [atomic batch transaction](/wallet/how-to/send-transactions/send-batch-transactions/#about-atomic-batch-transactions) via [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792). +This tutorial walks you through upgrading a MetaMask externally owned account (EOA) to a [MetaMask smart account](/delegation-toolkit/concepts/smart-accounts) via [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702), and sending an [atomic batch transaction](/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions) via [EIP-5792](https://eips.ethereum.org/EIPS/eip-5792). You will use a provided template, which sets up MM Connect with a [Next.js](https://nextjs.org/docs) and [Wagmi](https://wagmi.sh/) dapp. ## Prerequisites @@ -199,7 +199,7 @@ When connected, the interface displays your connected wallet address: ### 4. Handle and send batch transactions -In `src/app/page.tsx`, use the [`useSendCalls`](https://wagmi.sh/react/api/hooks/useSendCalls) hook from Wagmi to handle and send [atomic batch transactions](/wallet/how-to/send-transactions/send-batch-transactions). +In `src/app/page.tsx`, use the [`useSendCalls`](https://wagmi.sh/react/api/hooks/useSendCalls) hook from Wagmi to handle and send [atomic batch transactions](/sdk/evm/connect/guides/javascript/send-transactions/batch-transactions). Also use React's `useState` hook to handle the transaction state. The following example sends 0.001 and 0.0001 ETH in a batch transaction. Replace `` with recipient addresses of your choice: From f70f9405bd5aa998024bc992700cf9dfea967dcd Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Mon, 3 Nov 2025 18:34:33 -0800 Subject: [PATCH 12/17] fix broken links --- .../guides/erc7715/execute-on-metamask-users-behalf.md | 2 +- .../guides/erc7715/execute-on-metamask-users-behalf.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/delegation-toolkit/guides/erc7715/execute-on-metamask-users-behalf.md b/delegation-toolkit/guides/erc7715/execute-on-metamask-users-behalf.md index 9e636dcbe43..acd2b937cb5 100644 --- a/delegation-toolkit/guides/erc7715/execute-on-metamask-users-behalf.md +++ b/delegation-toolkit/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 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](/wallet/how-to/send-transactions/send-batch-transactions/#about-atomic-batch-transactions) or ask the +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 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 8386d747a8d..e3647bf74c0 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 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](/wallet/how-to/send-transactions/send-batch-transactions/#about-atomic-batch-transactions) or ask the +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 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? From e1e0008d624fd775402a8c449f287ad9fab0af0f Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo <12214231+alexandratran@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:54:36 -0800 Subject: [PATCH 13/17] Add/update Solana Wallet Adapter docs (#2443) --- .../connect/guides/send-legacy-transaction.md | 133 +++++----------- .../guides/send-versioned-transaction.md | 142 +++++++----------- .../connect/guides/use-wallet-adapter.md | 110 +++++++++----- sdk/solana/index.md | 6 +- 4 files changed, 164 insertions(+), 227 deletions(-) diff --git a/sdk/solana/connect/guides/send-legacy-transaction.md b/sdk/solana/connect/guides/send-legacy-transaction.md index 3c37b1e9012..6df4acd9b24 100644 --- a/sdk/solana/connect/guides/send-legacy-transaction.md +++ b/sdk/solana/connect/guides/send-legacy-transaction.md @@ -1,130 +1,71 @@ -# Send a legacy transaction - -Once a web application is connected to Phantom, it can prompt the user for permission to send transactions on their behalf. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -In order to send a transaction, a web application must: - -1. Create an unsigned transaction. -2. Have the transaction be signed and submitted to the network by the user's Phantom wallet. -3. Optionally await network confirmation using a Solana JSON RPC connection. +# Send a legacy transaction -:::info -For more information about the nature of Solana transactions, refer to the [solana-web3.js](https://solana-foundation.github.io/solana-web3.js/) documentation and the [Solana Cookbook](https://solanacookbook.com/core-concepts/transactions.html#transactions). -::: +Solana supports [legacy transactions and versioned transactions](https://solana.com/developers/guides/advanced/versions). +Unlike versioned transactions, legacy transactions cannot include Address Lookup Tables, so they are capped at 32 addresses per transaction. -For a sample Phantom transaction, check out our [sandbox](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L160). +After connecting to MetaMask, your dapp can prompt a user to sign and send one or more Solana legacy transactions. +See the [Solana documentation](https://solana.com/docs/core/transactions) for more information about Solana transactions and how to create them. ## Sign and send a transaction -Once a transaction is created, the web application may ask the user's Phantom wallet to sign and send the transaction. If accepted, Phantom will sign the transaction with the user's private key and submit it via a Solana JSON RPC connection. By far the **easiest** and most **recommended** way of doing this is by using the `signAndSendTransaction` method on the provider, but it is also possible to do with `request`. In both cases, the call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the `signature`. +After creating an unsigned legacy transaction, you can ask the user's MetaMask wallet to sign and send the transaction. +Use the `signAndSendTransaction` method on the provider, or use `signAndSendTransaction` with the provider's `request` method. +You can specify an optional [`SendOptions`](https://solana-foundation.github.io/solana-web3.js/types/SendOptions.html) object as a second parameter for `signAndSendTransaction`, or as an `options` parameter when using `request`. -### signAndSendTransaction() +The method returns a promise for an object containing the `signature`. -```javascript theme={null} -const provider = getProvider(); // see "Detecting the Provider" -const network = ""; + + + +```javascript +const provider = getProvider(); // TO DO: replace with provider snippet +const network = ''; const connection = new Connection(network); const transaction = new Transaction(); const { signature } = await provider.signAndSendTransaction(transaction); await connection.getSignatureStatus(signature); ``` -### request() + + -```javascript theme={null} -const provider = getProvider(); // see "Detecting the Provider" -const network = ""; +```javascript +const provider = getProvider(); // TO DO: replace with provider snippet +const network = ''; const connection = new Connection(network); const transaction = new Transaction(); const { signature } = await provider.request({ - method: "signAndSendTransaction", - params: { - message: bs58.encode(transaction.serializeMessage()), - }, + method: 'signAndSendTransaction', + params: { + message: bs58.encode(transaction.serializeMessage()), + }, }); await connection.getSignatureStatus(signature); ``` -You can also specify a `SendOptions` [object](https://solana-foundation.github.io/solana-web3.js/modules.html#SendOptions) as a second argument into `signAndSendTransaction` or as an `options` parameter when using `request`. - -For a live demo of `signAndSendTransaction`, refer to [handleSignAndSendTransaction](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L160) in our sandbox. + + ## Sign and send multiple transactions -It is also possible to sign and send multiple transactions at once. This is exposed through the `signAndSendAllTransactions` method on the provider. This method accepts an array of Solana transactions, and will optionally accept a [SendOptions](https://solana-foundation.github.io/solana-web3.js/types/SendOptions.html) object as a second parameter. If successful, it will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the array of string `signatures` and the `publicKey` of the signer. +After creating multiple unsigned legacy transactions, you can ask the user's MetaMask wallet to sign and send all the transactions at once. +Use the `signAndSendAllTransactions` method on the provider. +This method accepts an array of Solana transactions, and an optional [`SendOptions`](https://solana-foundation.github.io/solana-web3.js/types/SendOptions.html) object as a second parameter. -### signAndSendAllTransactions() +The method returns a promise for an object containing an array of `signatures` and the `publicKey` of the signer. -```typescript theme={null} -const provider = getProvider(); // see "Detecting the Provider" -const network = ""; +```typescript +const provider = getProvider(); // TO DO: replace with provider snippet +const network = ''; const connection = new Connection(network); const transactions = [new Transaction(), new Transaction()]; const { signatures, publicKey } = await provider.signAndSendAllTransactions(transactions); await connection.getSignatureStatuses(signatures); ``` -## Other signing methods - -The following methods are also supported, but are not recommended over `signAndSendTransaction`. It is safer for users, and a simpler API for developers, for Phantom to submit the transaction immediately after signing it instead of relying on the application to do so. - -:::warning -The following methods are not supported in the [wallet standard](#) implementation and may be removed in a future release. These methods are only available via the [window.solana object]. -::: - -## Sign a transaction (without sending) - -Once a transaction is created, a web application may ask the user's Phantom wallet to sign the transaction *without* also submitting it to the network. The easiest and most recommended way of doing this is via the `signTransaction` method on the provider, but it is also possible to do via `request`. In both cases, the call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for the signed transaction. After the transaction has been signed, an application may submit the transaction itself using [sendRawTransaction](https://solana-foundation.github.io/solana-web3.js/classes/Connection.html#sendRawTransaction) in web3.js. - -### signTransaction() - -```javascript theme={null} -const provider = getProvider(); -const network = ""; -const connection = new Connection(network); -const transaction = new Transaction(); -const signedTransaction = await provider.signTransaction(transaction); -const signature = await connection.sendRawTransaction(signedTransaction.serialize()); -``` - -### request() - -```javascript theme={null} -const provider = getProvider(); -const network = ""; -const connection = new Connection(network); -const transaction = new Transaction(); -const signedTransaction = await provider.request({ - method: "signTransaction", - params: { - message: bs58.encode(transaction.serializeMessage()), - }, -}); -const signature = await connection.sendRawTransaction(signedTransaction.serialize()); -``` - -For an example of `signTransaction`, refer to [handleSignTransaction](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L187) in our sandbox. - -## Sign multiple transactions - -For legacy integrations, Phantom supports signing multiple transactions at once without sending them. This is exposed through the `signAllTransactions` method on the provider. This method is **not recommended** for new integrations. Instead, developers should make use of `signAndSendAllTransactions`. - -### signAllTransactions() - -```javascript theme={null} -const signedTransactions = await provider.signAllTransactions(transactions); -``` - -### request() - -```javascript theme={null} -const message = transactions.map(transaction => { - return bs58.encode(transaction.serializeMessage()); -}); -const signedTransactions = await provider.request({ - method: "signAllTransactions", - params: { message }, -}); -``` +## Next steps -For an example of `signAllTransactions`, refer to [handleSignAllTransactions](https://github.com/phantom-labs/sandbox/blob/b57fdd0e65ce4f01290141a01e33d17fd2f539b9/src/App.tsx#L213) in our sandbox. +To efficiently load more addresses in a single transaction, learn how to [send a versioned transaction](send-versioned-transaction.md) with Address Lookup Tables. diff --git a/sdk/solana/connect/guides/send-versioned-transaction.md b/sdk/solana/connect/guides/send-versioned-transaction.md index f4cbcc36037..b87635e7c91 100644 --- a/sdk/solana/connect/guides/send-versioned-transaction.md +++ b/sdk/solana/connect/guides/send-versioned-transaction.md @@ -1,31 +1,23 @@ -# Send a versioned transaction - -The Solana runtime supports two types of transactions: `legacy` (see [Send a legacy transaction]) and `v0` (transactions that can include Address Lookup Tables or LUTs). - -The goal of `v0` is to increase the maximum size of a transaction, and hence the number of accounts that can fit in a single atomic transaction. With LUTs, developers can now build transactions with a maximum of 256 accounts, as compared to the limit of 35 accounts in legacy transactions that do not utilize LUTs. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -:::info -For a dive deep on versioned transactions, LUTs, and how the above changes affect the anatomy of a transaction, see [Versioned Transactions - Anvit Mangal's Blog](https://anvit.hashnode.dev/versioned-transactions). -::: - -On this page, we'll go over the following: +# Send a versioned transaction -1. Building a versioned tansaction. -2. Signing and sending a versioned transaction. -3. Building an Address LUT. -4. Extending an Address LUT. -5. Signing and sending a versioned transaction using a LUT. +Solana supports [legacy transactions and versioned transactions](https://solana.com/developers/guides/advanced/versions). +Unlike legacy transactions, versioned transactions (`v0`) can include [Address Lookup Tables](https://solana.com/developers/guides/advanced/lookup-tables) (ALTs or Address LUTs), enabling you to efficiently load up to 256 addresses in a single transaction. -## Build a versioned transaction +After connecting to MetaMask, your dapp can prompt a user to sign and send a Solana versioned transaction. -Versioned transactions are built in a very similar fashion to [legacy transactions](send-legacy-transaction.md). The only difference is that developers should use the `VersionedTransaction` class rather than the `Transaction` class. +## Create a versioned transaction -The following example shows how to build a simple transfer instruction. Once the transfer instruction is made, a `MessageV0` formatted transaction message is constructed with the transfer instruction. Finally, a new `VersionedTransaction` is created, parsing in the `v0` compatible message. +Solana versioned transactions are created in a similar way to [legacy transactions](https://solana.com/docs/core/transactions). +The only difference is to use the `VersionedTransaction` class instead of the `Transaction` class. -### createTransactionV0() +The following example shows how to create a simple transfer instruction, format the instruction into a `v0`-compatible transaction message, and create a versioned transaction that parses the message: -```typescript theme={null} -// create array of instructions +```typescript +// Create an array of instructions. +// This example uses a simple transfer instruction. const instructions = [ SystemProgram.transfer({ fromPubkey: publicKey, @@ -34,60 +26,49 @@ const instructions = [ }), ]; -// create v0 compatible message +// Create a v0-compatible message. const messageV0 = new TransactionMessage({ payerKey: publicKey, recentBlockhash: blockhash, instructions, }).compileToV0Message(); -// make a versioned transaction +// Create a versioned transaction. const transactionV0 = new VersionedTransaction(messageV0); ``` -For a live example of creating a versioned transaction, refer to [createTransferTransactionV0](https://github.com/phantom-labs/sandbox/blob/main/src/utils/createTransferTransactionV0.ts) in our sandbox. - ## Sign and send a versioned transaction -Once a Versioned transaction is created, it can be signed and sent via Phantom using the `signAndSendTransaction` method on the provider. The call will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) for an object containing the `signature`. This is the same way a legacy transaction is sent via the Phantom provider. +After creating an unsigned versioned transaction, you can ask the user's MetaMask wallet to sign and send the transaction. +Use the `signAndSendTransaction` method on the provider, which accepts an optional [`SendOptions`](https://solana-foundation.github.io/solana-web3.js/types/SendOptions.html) object as a second parameter. -### signAndSendTransaction() +The method returns a promise for an object containing the `signature`. -```javascript theme={null} -const provider = getProvider(); // see "Detecting the Provider" -const network = ""; +```javascript +const provider = getProvider(); // TO DO: replace with provider snippet +const network = ''; const connection = new Connection(network); -const versionedTransaction = new VersionedTransaction(); -const { signature } = await provider.signAndSendTransaction(versionedTransaction); +const { signature } = await provider.signAndSendTransaction(transactionV0); await connection.getSignatureStatus(signature); ``` -You can also specify a `SendOptions` [object](https://solana-foundation.github.io/solana-web3.js/modules.html#SendOptions) as a second argument into `signAndSendTransaction()` or as an `options` parameter when using `request`. - -For a live demo of signing and sending a versioned transaction, refer to [handleSignAndSendTransactionV0](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L191) in our sandbox. - -## Build an Address LUT - -Address LUTs can be used to load accounts into table-like data structures. These structures can then be referenced to significantly increase the number of accounts that can be loaded in a single transaction. - -This lookup method effectively "*compresses*" a 32-byte address into a 1-byte index value. This "*compression*" enables storing up to 256 address in a single LUT for use inside any given transaction. - -With the `@solana/web3.js` [createLookupTable](https://solana-foundation.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#createLookupTable) method, developers can construct the instruction needed to create a new LUT, as well as determine its address. Once we have the LUT instruction, we can construct a transaction, sign it, and send it to create a LUT on-chain. Address LUTs can be created with either a `v0` transaction or a `legacy` transaction. However, the Solana runtime can only retrieve and handle the additional addresses within a LUT while using `v0` transactions. +## Create an Address Lookup Table -The following is a code snippet that creates a LUT. +Create an [Address Lookup Table (ALT)](https://solana.com/developers/guides/advanced/lookup-tables) to efficiently load addresses into tables, significantly increasing the number of addresses that can be used in a single transaction. -### createAddressLookupTable() +Use the [createLookupTable](https://solana-foundation.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#createlookuptable) method to create the instruction needed to create a new ALT and determine its address. +With this instruction, you can create a transaction, sign it, and send it to create an ALT onchain. +For example: ```typescript theme={null} -// create an Address Lookup Table +// Create an Address Lookup Table. const [lookupTableInst, lookupTableAddress] = AddressLookupTableProgram.createLookupTable({ authority: publicKey, payer: publicKey, recentSlot: slot, }); -// To create the Address Lookup Table on chain: -// send the `lookupTableInst` instruction in a transaction +// To create the ALT onchain, send the lookupTableInst instruction in a transaction. const lookupMessage = new TransactionMessage({ payerKey: publicKey, recentBlockhash: blockhash, @@ -98,16 +79,14 @@ const lookupTransaction = new VersionedTransaction(lookupMessage); const lookupSignature = await signAndSendTransaction(provider, lookupTransaction); ``` -For a live demo of creating a LUT, refer to [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. +## Extend an Address Lookup Table -## Extend an Address LUT +After creating an ALT, you can extend it by appending addresses to the table. +Use the [extendLookupTable](https://solana-foundation.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#extendlookuptable) method to create a new extend instruction, and send it in a transaction. +For example: -Once an Address LUT is created, it can then be extended, which means that accounts can be appended to the table. Using the `@solana/web3.js` library, you can create a new `extend` instruction using the [extendLookupTable](https://solana-labs.github.io/solana-web3.js/classes/AddressLookupTableProgram.html#extendLookupTable) method. Once the extend instruction is created, it can be sent in a transaction. - -### extendAddressLookupTable() - -```typescript theme={null} -// add addresses to the `lookupTableAddress` table via an `extend` instruction +```typescript +// Add addresses to the lookupTableAddress table via an extend instruction. const extendInstruction = AddressLookupTableProgram.extendLookupTable({ payer: publicKey, authority: publicKey, @@ -115,12 +94,12 @@ const extendInstruction = AddressLookupTableProgram.extendLookupTable({ addresses: [ publicKey, SystemProgram.programId, - // more `publicKey` addresses can be listed here + // More publicKey addresses can be listed here. ], }); -// Send this `extendInstruction` in a transaction to the cluster -// to insert the listing of `addresses` into your lookup table with address `lookupTableAddress` +// Send this extendInstruction in a transaction to the cluster to insert +// the listing of addresses into your ALT with address lookupTableAddress. const extensionMessageV0 = new TransactionMessage({ payerKey: publicKey, recentBlockhash: blockhash, @@ -131,46 +110,35 @@ const extensionTransactionV0 = new VersionedTransaction(extensionMessageV0); const extensionSignature = await signAndSendTransaction(provider, extensionTransactionV0); ``` -For a live demo of extending a LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) function in our sandbox. - -## Sign and send a versioned transaction using a LUT - -Up until now, we have: - -1. Learned how to create a `VersionedTransaction`. -2. Created an Address LUT. -3. Extended the Address LUT. +## Create, sign, and send a versioned transaction with an ALT -At this point, we are now ready to sign and send a `VersionedTransaction` using an Address LUT. +After creating an ALT, you can create a versioned transaction with the ALT and ask the user's MetaMask wallet to sign and send it. -First, we need to fetch the account of the created Address LUT. +First, use the [`getAddressLookupTable`](https://solana-foundation.github.io/solana-web3.js/classes/Connection.html#getaddresslookuptable) method to fetch the account of the created ALT: -### getAddressLookupTable() - -```typescript theme={null} -// get the table from the cluster +```typescript +// Get the table from the cluster. const lookupTableAccount = await connection.getAddressLookupTable(lookupTableAddress).then((res) => res.value); -// `lookupTableAccount` will now be a `AddressLookupTableAccount` object +// lookupTableAccount will now be an AddressLookupTableAccount object. console.log('Table address from cluster:', lookupTableAccount.key.toBase58()); ``` -We can also parse and read all the addresses currently stores in the fetched Address LUT. - -## Parse and read addresses +Then, parse and read all the addresses currently stored in the fetched ALT: -```typescript theme={null} -// Loop through and parse all the address stored in the table +```typescript +// Loop through and parse all the address stored in the table. for (let i = 0; i < lookupTableAccount.state.addresses.length; i++) { const address = lookupTableAccount.state.addresses[i]; console.log(i, address.toBase58()); } ``` -We can now create the instructions array with an arbitrary transfer instruction, just the way we did while creating the `VersionedTransaction` earlier. This `VersionedTransaction` can then be sent using the `signAndSendTransaction()` provider function. +The following example creates a simple transfer instruction, formats the instruction into a `v0`-compatible transaction message using the ALT's account, and creates a versioned transaction that parses the message. +Sign and send the transaction using the `signAndSendTransaction` method on the provider. -```typescript theme={null} -// create an array with your desired `instructions` -// in this case, just a transfer instruction +```typescript +// Create an array of instructions. +// This example uses a simple transfer instruction. const instructions = [ SystemProgram.transfer({ fromPubkey: publicKey, @@ -179,16 +147,14 @@ const instructions = [ }), ]; -// create v0 compatible message +// Create a v0-compatible message. const messageV0 = new TransactionMessage({ payerKey: publicKey, recentBlockhash: blockhash, instructions, }).compileToV0Message([lookupTableAccount]); -// make a versioned transaction +// Create a versioned transaction. const transactionV0 = new VersionedTransaction(messageV0); const signature = await signAndSendTransaction(provider, transactionV0); ``` - -For a live demo of of signing and sending a versioned transaction using an Address LUT, refer to the [handleSignAndSendTransactionV0WithLookupTable](https://github.com/phantom-labs/sandbox/blob/78dc35fe140140a961345a6af30a058e1e19a7aa/src/App.tsx#L218) in our sandbox. diff --git a/sdk/solana/connect/guides/use-wallet-adapter.md b/sdk/solana/connect/guides/use-wallet-adapter.md index 6f40f8f93c3..90e278ea7c8 100644 --- a/sdk/solana/connect/guides/use-wallet-adapter.md +++ b/sdk/solana/connect/guides/use-wallet-adapter.md @@ -1,56 +1,88 @@ ---- -sidebar_label: Use the Wallet Adapter ---- +# Use the Wallet Adapter -# Use the Solana Wallet Adapter +To connect to Solana in MetaMask from your dapp, set up Solana's [Wallet Adapter](https://github.com/solana-labs/wallet-adapter). -If your dApp uses the [Solana Wallet Adapter](https://github.com/solana-labs/wallet-adapter), you just have to add the Solflare Wallet Adapter to the list of supported wallets. +You can use the [`create-solana-dapp`](https://github.com/solana-foundation/create-solana-dapp) CLI tool to quickly generate a Solana dapp with the Wallet Adapter built in, or follow this guide to manually set up the Wallet Adapter in an existing React dapp. -Install the latest *wallets* package with +:::info +See the [Solana documentation](https://solana.com/developers/cookbook/wallets/connect-wallet-react) for more information. +::: -```sh -npm install @solana/wallet-adapter-wallets@latest +## Steps + +### 1. Install dependencies + +Install the following dependencies: + +```bash +npm install @solana/web3.js \ + @solana/wallet-adapter-base \ + @solana/wallet-adapter-react \ + @solana/wallet-adapter-react-ui \ + @solana/wallet-adapter-wallets ``` -Then update the code to look like this: +### 2. Create the Solana provider -```javascript -import { - SolflareWalletAdapter, - /* ... other adapters ... */ -} from '@solana/wallet-adapter-wallets'; +Create a `SolanaProvider` that will be used to provide the Solana context to the application: -const wallets = useMemo( - () => [ - new SolflareWalletAdapter(), - // ... other adapters ... - ], - [] -); +```typescript title='components/SolanaProvider.tsx' +'use client'; - -``` +import React, { FC, ReactNode, useMemo } from 'react'; +import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react'; +import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; +import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; +import { clusterApiUrl } from '@solana/web3.js'; -Alternatively, you can install only the Solflare adapter directly +// Default styles that can be overridden by your dapp. +import '@solana/wallet-adapter-react-ui/styles.css'; -```sh -npm install @solana/wallet-adapter-solflare@latest -``` +interface SolanaProviderProps { + children: ReactNode; +} -Then update the code: +export const SolanaProvider: FC = ({ children }) => { + // The network can be set to devnet, testnet, or mainnet-beta. + const network = WalletAdapterNetwork.Devnet; -```javascript -import { SolflareWalletAdapter } from '@solana/wallet-adapter-solflare'; -import { /* ... other adapters ... */ } from '@solana/wallet-adapter-wallets'; + // You can also provide a custom RPC endpoint. + const endpoint = useMemo(() => clusterApiUrl(network), [network]); + return ( + + + {children} + + + ); +}; +``` + +### 3. Wrap the application in the Solana Provider + +Add the `SolanaProvider` to the `RootLayout` in the `app` directory: -const wallets = useMemo( - () => [ - new SolflareWalletAdapter(), - // ... other adapters ... - ], - [] -); +```typescript +import './globals.css'; +import '@solana/wallet-adapter-react-ui/styles.css'; +import { SolanaProvider } from '@/components/SolanaProvider'; - +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} ``` + +## Next steps + +See how to send a [legacy transaction](send-legacy-transaction.md) and a [versioned transaction](send-versioned-transaction.md). diff --git a/sdk/solana/index.md b/sdk/solana/index.md index e3420ca7a66..ed629803946 100644 --- a/sdk/solana/index.md +++ b/sdk/solana/index.md @@ -15,9 +15,7 @@ After adding Solana to MetaMask Flask, [show test networks](https://support.meta ## Wallet Standard -MetaMask implements the [Wallet Standard](https://github.com/wallet-standard/wallet-standard), so MetaMask is supported out-of-the-box for Solana dapps that use the Wallet Standard or that integrate Solana's [Wallet Adapter](https://github.com/anza-xyz/wallet-adapter/blob/master/APP.md). - -Learn more in Solana's [Interact With Wallets](https://solana.com/developers/courses/intro-to-solana/interact-with-wallets) documentation. +MetaMask implements the [Wallet Standard](https://github.com/wallet-standard/wallet-standard), so MetaMask is supported out-of-the-box for Solana dapps that use the Wallet Standard or that integrate Solana's [Wallet Adapter](https://github.com/anza-xyz/wallet-adapter). :::note With the Wallet Standard, MetaMask does not appear as a connection option for users that don't already have MetaMask installed. @@ -31,4 +29,4 @@ Several third-party libraries for Solana dapps detect and handle MetaMask out-of - [Dynamic](https://docs.dynamic.xyz/introduction/welcome) - [Privy](https://docs.privy.io/welcome) - [Reown](https://docs.reown.com/overview) -- [Web3Auth](https://web3auth.io/docs) +- [Embedded Wallets](/embedded-wallets) From 30d0439946c0776a488f6bc376bc0595a8aac334 Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo Date: Mon, 10 Nov 2025 12:50:27 -0800 Subject: [PATCH 14/17] Update mobile products menu --- src/config/mobileProductsMenu.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/config/mobileProductsMenu.ts b/src/config/mobileProductsMenu.ts index d256944af74..68053ecf4a7 100644 --- a/src/config/mobileProductsMenu.ts +++ b/src/config/mobileProductsMenu.ts @@ -12,12 +12,8 @@ export interface MobileProductItem { export const mobileProductsMenu: MobileProductItem[] = [ { - label: 'MetaMask SDK', - href: '/sdk/', - }, - { - label: 'Wallet API', - href: '/wallet/', + label: 'MM Connect', + href: '/sdk', }, { label: 'Embedded Wallets', From 3db005f24e8a3f09dae13b7d8c8f580acd903d8a Mon Sep 17 00:00:00 2001 From: Alexandra Carrillo <12214231+alexandratran@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:21:02 -0800 Subject: [PATCH 15/17] Update MM Connect EVM JS code samples with potential new usage (#2454) * Update MM Connect EVM JS code samples with potential new usage * remove initialize sdk comment * add provider to code snippets * fix cursor bugs * remove /evm from wagmi install --- sdk/evm/connect/guides/connectkit.md | 2 +- sdk/evm/connect/guides/dynamic.md | 2 +- .../guides/javascript/batch-requests.md | 6 +- .../guides/javascript/display-tokens.md | 69 +++++++++-------- sdk/evm/connect/guides/javascript/index.md | 28 +++---- .../javascript/interact-with-contracts.md | 13 +++- .../guides/javascript/manage-networks.md | 13 +++- .../guides/javascript/manage-user-accounts.md | 16 ++-- .../send-transactions/batch-transactions.md | 76 ++++++++++--------- .../javascript/send-transactions/index.md | 18 ++++- .../guides/javascript/sign-data/index.md | 73 ++++++++++-------- .../guides/javascript/sign-data/siwe.md | 14 ++-- sdk/evm/connect/guides/rainbowkit.md | 2 +- sdk/evm/connect/guides/react-native.md | 2 +- sdk/evm/connect/guides/wagmi/index.md | 4 +- .../connect/guides/wagmi/send-transactions.md | 5 ++ sdk/evm/connect/guides/web3auth.md | 2 +- .../json-rpc-api/eth_signTypedData_v4.mdx | 64 ++++++++-------- .../connect/reference/json-rpc-api/index.md | 16 +--- .../json-rpc-api/wallet_sendCalls.mdx | 48 ++++++------ sdk/evm/connect/reference/methods.md | 16 ++-- 21 files changed, 262 insertions(+), 227 deletions(-) diff --git a/sdk/evm/connect/guides/connectkit.md b/sdk/evm/connect/guides/connectkit.md index 3c25f2ab873..814056687ad 100644 --- a/sdk/evm/connect/guides/connectkit.md +++ b/sdk/evm/connect/guides/connectkit.md @@ -80,7 +80,7 @@ You can [download the quickstart template](#set-up-using-a-template) or [manuall ## Set up manually -### 1. Install the SDK +### 1. Install MM Connect Install MM Connect along with its peer dependencies to an existing React project: diff --git a/sdk/evm/connect/guides/dynamic.md b/sdk/evm/connect/guides/dynamic.md index 408719972bc..be6e8e49213 100644 --- a/sdk/evm/connect/guides/dynamic.md +++ b/sdk/evm/connect/guides/dynamic.md @@ -86,7 +86,7 @@ See how to [use the combined SDKs](#usage). ### 1. Install dependencies -Install the SDK and the required dependencies to an existing project: +Install MM Connect and the required dependencies to an existing project: ```bash npm2yarn npm install @dynamic-labs/sdk-react-core @dynamic-labs/ethereum @dynamic-labs/wagmi-connector wagmi viem @tanstack/react-query diff --git a/sdk/evm/connect/guides/javascript/batch-requests.md b/sdk/evm/connect/guides/javascript/batch-requests.md index f541c311c4a..ed1b777ea8d 100644 --- a/sdk/evm/connect/guides/javascript/batch-requests.md +++ b/sdk/evm/connect/guides/javascript/batch-requests.md @@ -34,10 +34,10 @@ When using `metamask_batch`, keep in mind the following: The following is an example of batching JSON-RPC requests using `metamask_batch`: ```js -import { MetaMaskSDK } from "@metamask/sdk"; +import { createEVMClient } from "@metamask/connect/evm"; -const MMSDK = new MetaMaskSDK(); -const provider = MMSDK.getProvider(); +const evmClient = createEVMClient(); +const provider = evmClient.getProvider(); async function handleBatchRequests() { // Example batch: one personal_sign call and one eth_sendTransaction call. diff --git a/sdk/evm/connect/guides/javascript/display-tokens.md b/sdk/evm/connect/guides/javascript/display-tokens.md index 6a826b82280..63471b28168 100644 --- a/sdk/evm/connect/guides/javascript/display-tokens.md +++ b/sdk/evm/connect/guides/javascript/display-tokens.md @@ -38,6 +38,11 @@ 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"; + +const evmClient = createEVMClient(); +const provider = evmClient.getProvider(); + const tokenAddress = "0xd00981105e61274c8a5cd5a88fe7e037d935b513" const tokenSymbol = "TUT" const tokenDecimals = 18 @@ -45,23 +50,22 @@ 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 // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_watchAsset", - params: { - type: "ERC20", - options: { - // The address of the token. - address: tokenAddress, - // A ticker symbol or shorthand, up to 5 characters. - symbol: tokenSymbol, - // The number of decimals in the token. - decimals: tokenDecimals, - // A string URL of the token logo. - image: tokenImage, - }, + const wasAdded = await provider.request({ + method: "wallet_watchAsset", + params: { + type: "ERC20", + options: { + // The address of the token. + address: tokenAddress, + // A ticker symbol or shorthand, up to 5 characters. + symbol: tokenSymbol, + // The number of decimals in the token. + decimals: tokenDecimals, + // A string URL of the token logo. + image: tokenImage, }, - }) + }, + }) if (wasAdded) { console.log("Thanks for your interest!") @@ -110,21 +114,25 @@ 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"; + +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 // Or window.ethereum if you don't support EIP-6963. - .request({ - method: "wallet_watchAsset", - params: { - type: "ERC721", // Or "ERC1155". - options: { - // The address of the token. - address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", - // ERC-721 or ERC-1155 token ID. - tokenId: "1", - }, + const wasAdded = await provider.request({ + method: "wallet_watchAsset", + params: { + type: "ERC721", // Or "ERC1155". + options: { + // The address of the token. + address: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e", + // ERC-721 or ERC-1155 token ID. + tokenId: "1", }, - }) + }, + }) if (wasAdded) { console.log("User successfully added the token!") @@ -143,8 +151,7 @@ To prompt users to add multiple NFTs, use `sendAsync()` instead of For example: ```javascript -provider // Or window.ethereum if you don't support EIP-6963. - .sendAsync([{ +provider.sendAsync([{ method: "wallet_watchAsset", params: { type: "ERC721", @@ -164,5 +171,5 @@ provider // Or window.ethereum if you don't support EIP-6963. }, }, ... - ]) +]) ``` diff --git a/sdk/evm/connect/guides/javascript/index.md b/sdk/evm/connect/guides/javascript/index.md index f6eb7026120..6bdb709ac9d 100644 --- a/sdk/evm/connect/guides/javascript/index.md +++ b/sdk/evm/connect/guides/javascript/index.md @@ -84,25 +84,25 @@ You've successfully set up MM Connect. ## Set up manually -### 1. Install the SDK +### 1. Install MM Connect -Install the SDK in an existing JavaScript project: +Install MM Connect in an existing JavaScript project: ```bash npm2yarn -npm install @metamask/sdk +npm install @metamask/connect/evm ``` -### 2. Initialize the SDK +### 2. Initialize MM Connect -The following are examples of using the SDK in various JavaScript environments: +The following are examples of using MM Connect in various JavaScript environments: ```javascript -import { MetaMaskSDK } from "@metamask/sdk" +import { createEVMClient } from "@metamask/connect/evm" -const MMSDK = new MetaMaskSDK({ +const evmClient = createEVMClient({ dappMetadata: { name: "Example JavaScript dapp", url: window.location.href, @@ -119,7 +119,7 @@ const MMSDK = new MetaMaskSDK({