diff --git a/example/src/App.tsx b/example/src/App.tsx index c9803a8..7badc06 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,27 +1,35 @@ import "./App.css" +import * as util from "./lib" import { type ConnectOptions, type DisconnectOptions, + StarknetWindowObject, connect, disconnect, } from "get-starknet" import { useState } from "react" function App() { - const [walletName, setWalletName] = useState("") + const [walletInfo, setWalletInfo] = useState("") + const [wallet, setWallet] = useState(null) function handleConnect(options?: ConnectOptions) { return async () => { const res = await connect(options) - console.log(res) - setWalletName(res?.name || "") + console.log("wallet", res) + setWallet(res) + setWalletInfo( + `Name: ${res?.name || ""}, Address: ${ + res?.selectedAddress || "" + }, ChainId: ${res?.chainId || ""}`, + ) } } function handleDisconnect(options?: DisconnectOptions) { return async () => { await disconnect(options) - setWalletName("") + setWalletInfo("") } } @@ -55,13 +63,99 @@ function App() { Disconnect and reset - {walletName && ( + {walletInfo && (

- Selected Wallet:
{walletName}
+ Selected Wallet:
{walletInfo}

)} + ) } diff --git a/example/src/lib.ts b/example/src/lib.ts new file mode 100644 index 0000000..f4df246 --- /dev/null +++ b/example/src/lib.ts @@ -0,0 +1,197 @@ +import { StarknetWindowObject } from "get-starknet" + +enum StarknetChainId { + SN_MAIN = "0x534e5f4d41494e", + SN_GOERLI = "0x534e5f474f45524c49", +} +const signMessagePayload = { + types: { + StarkNetDomain: [ + { name: "name", type: "felt" }, + { name: "version", type: "felt" }, + { name: "chainId", type: "felt" }, + ], + Person: [ + { name: "name", type: "felt" }, + { name: "wallet", type: "felt" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "felt" }, + ], + }, + primaryType: "Mail", + domain: { + name: "StarkNet Mail", + version: "1", + chainId: 1, + }, + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +} + +const transactionPayload = { + contractAddress: + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + calldata: [ + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "10", + "0", + ], + entrypoint: "transfer", +} + +const signTransactionsDetail = { + walletAddress: + "0x00b28a089e7fb83debee4607b6334d687918644796b47d9e9e38ea8213833137", + chainId: StarknetChainId.SN_GOERLI, + cairoVersion: "0", + nonce: "0x1", + version: "0x0", + maxFee: 100, +} + +const signDeployAccountTransactionPayload = { + classHash: + "0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", + contractAddress: + "0x00b28a089e7fb83debee4607b6334d687918644796b47d9e9e38ea8213833137", + constructorCalldata: [], + addressSalt: + "0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", + chainId: "0x534e5f474f45524c49", + nonce: "0x1", + version: "0x0", + maxFee: 100, +} + +const deployContractPayload = { + classHash: + "0x189ce59d98d8d3883a5a9fc7026cc94519ca099147196680734ec46aee5e750", + constructorCalldata: [], +} + +const delcareContractPayload = { + classHash: + "0x025ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918", + senderAddress: + "0x00b28a089e7fb83debee4607b6334d687918644796b47d9e9e38ea8213833137", + chainId: StarknetChainId.SN_GOERLI, + nonce: "0x1", + version: "0x0", + maxFee: 100, +} + +export const sendErc20Transaction = async (wallet: StarknetWindowObject) => { + try { + const response = await wallet.account?.execute(transactionPayload) + console.log("response is ----> ", response) + } catch (err) { + console.log("error", err) + } +} + +export const deployContract = async (wallet: StarknetWindowObject) => { + try { + const response = await wallet.account?.deployContract( + deployContractPayload, + {}, + ) + console.log("response is ----> ", response) + } catch (err) { + console.log("error", err) + } +} + +export const getPubKey = async (wallet: StarknetWindowObject) => { + try { + const response = await wallet.account?.signer.getPubKey() + console.log("response is ----> ", response) + } catch (err) { + console.log("error", err) + } +} + +export const signMessage = async (wallet: StarknetWindowObject) => { + try { + const response = await wallet.account?.signMessage(signMessagePayload) + console.log("response is ----> ", response) + } catch (err) { + console.log("error", err) + } +} + +export const signMessageSlient = async (wallet: StarknetWindowObject) => { + try { + const response = await wallet.account?.signer.signMessage( + signMessagePayload, + wallet.account?.address, + ) + console.log("sign is ----> ", response) + + if (response) { + const verify = await wallet.account?.verifyMessage( + signMessagePayload, + response as any, + ) + + console.log("verify result is ----> ", verify) + } + } catch (err) { + console.log("error", err) + } +} + +export const signTransaction = async (wallet: StarknetWindowObject) => { + const response = await wallet.account?.signer.signTransaction( + [transactionPayload], + signTransactionsDetail as any, + ) + console.log("sign is ----> ", response) +} + +export const signDeployAccountTransaction = async ( + wallet: StarknetWindowObject, +) => { + const response = await wallet.account?.signer.signDeployAccountTransaction( + signDeployAccountTransactionPayload as any, + ) + console.log("sign is ----> ", response) +} + +export const signDeclareTransaction = async (wallet: StarknetWindowObject) => { + const response = await wallet.account?.signer.signDeclareTransaction( + delcareContractPayload as any, + ) + + console.log("sign is ----> ", response) +} + +export const getNonce = async (wallet: StarknetWindowObject) => { + const response = await wallet.account?.getNonce() + console.log("response is ----> ", response) +} + +export const switchNetwork = async (wallet: StarknetWindowObject) => { + const chainId = + wallet.chainId == StarknetChainId.SN_MAIN + ? StarknetChainId.SN_GOERLI + : StarknetChainId.SN_MAIN + const response = await wallet.request({ + type: "wallet_switchStarknetChain", + params: { + chainId, + }, + }) + console.log("response is ----> ", response) +} diff --git a/example/vite.config.ts b/example/vite.config.ts index dc52590..8a495a6 100644 --- a/example/vite.config.ts +++ b/example/vite.config.ts @@ -3,5 +3,9 @@ import { defineConfig } from "vite" // https://vitejs.dev/config/ export default defineConfig({ + build: { + target: "es2020", + }, plugins: [react()], + optimizeDeps: { esbuildOptions: { target: "es2020" } }, }) diff --git a/packages/core/package.json b/packages/core/package.json index 0cec766..1fea02f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,6 +29,10 @@ "dev": "vite build --watch", "test": "vitest" }, + "dependencies": { + "@consensys/get-starknet": "1.0.0-dev-fc04076-202312205118", + "@metamask/detect-provider": "^2.0.0" + }, "devDependencies": { "c8": "^7.12.0", "happy-dom": "^6.0.4", diff --git a/packages/core/src/__test__/main.test.ts b/packages/core/src/__test__/main.test.ts index 50d3830..f332559 100644 --- a/packages/core/src/__test__/main.test.ts +++ b/packages/core/src/__test__/main.test.ts @@ -131,7 +131,7 @@ describe("getDiscoveryWallets()", () => { it("should return all discovery wallets", async () => { const sn = getWallet({}) const discoveryWallets = await sn.getDiscoveryWallets() - expect(discoveryWallets.length).toBe(2) + expect(discoveryWallets.length).toBe(3) expect(discoveryWallets.map((w) => w.id)).contains(ArgentXMock.id) expect(discoveryWallets.map((w) => w.id)).contains(BraavosMock.id) }) diff --git a/packages/core/src/discovery.ts b/packages/core/src/discovery.ts index c7a25d8..f445039 100644 --- a/packages/core/src/discovery.ts +++ b/packages/core/src/discovery.ts @@ -30,6 +30,17 @@ const wallets: WalletProvider[] = [ edge: "https://microsoftedge.microsoft.com/addons/detail/braavos-wallet/hkkpjehhcnhgefhbdcgfkeegglpjchdc", }, }, + { + id: "metamask", + name: "MetaMask", + icon: `data:image/svg+xml;utf8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTIiIGhlaWdodD0iMTg5IiB2aWV3Qm94PSIwIDAgMjEyIDE4OSI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cG9seWdvbiBmaWxsPSIjQ0RCREIyIiBwb2ludHM9IjYwLjc1IDE3My4yNSA4OC4zMTMgMTgwLjU2MyA4OC4zMTMgMTcxIDkwLjU2MyAxNjguNzUgMTA2LjMxMyAxNjguNzUgMTA2LjMxMyAxODAgMTA2LjMxMyAxODcuODc1IDg5LjQzOCAxODcuODc1IDY4LjYyNSAxNzguODc1Ii8+PHBvbHlnb24gZmlsbD0iI0NEQkRCMiIgcG9pbnRzPSIxMDUuNzUgMTczLjI1IDEzMi43NSAxODAuNTYzIDEzMi43NSAxNzEgMTM1IDE2OC43NSAxNTAuNzUgMTY4Ljc1IDE1MC43NSAxODAgMTUwLjc1IDE4Ny44NzUgMTMzLjg3NSAxODcuODc1IDExMy4wNjMgMTc4Ljg3NSIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjU2LjUgMCkiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjkwLjU2MyAxNTIuNDM4IDg4LjMxMyAxNzEgOTEuMTI1IDE2OC43NSAxMjAuMzc1IDE2OC43NSAxMjMuNzUgMTcxIDEyMS41IDE1Mi40MzggMTE3IDE0OS42MjUgOTQuNSAxNTAuMTg4Ii8+PHBvbHlnb24gZmlsbD0iI0Y4OUMzNSIgcG9pbnRzPSI3NS4zNzUgMjcgODguODc1IDU4LjUgOTUuMDYzIDE1MC4xODggMTE3IDE1MC4xODggMTIzLjc1IDU4LjUgMTM2LjEyNSAyNyIvPjxwb2x5Z29uIGZpbGw9IiNGODlEMzUiIHBvaW50cz0iMTYuMzEzIDk2LjE4OCAuNTYzIDE0MS43NSAzOS45MzggMTM5LjUgNjUuMjUgMTM5LjUgNjUuMjUgMTE5LjgxMyA2NC4xMjUgNzkuMzEzIDU4LjUgODMuODEzIi8+PHBvbHlnb24gZmlsbD0iI0Q4N0MzMCIgcG9pbnRzPSI0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi4zNzUgODcuMTg4IDEyNiA2NS4yNSAxMjAuMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VBOEQzQSIgcG9pbnRzPSI0Ni4xMjUgMTAxLjgxMyA2NS4yNSAxMTkuODEzIDY1LjI1IDEzNy44MTMiLz48cG9seWdvbiBmaWxsPSIjRjg5RDM1IiBwb2ludHM9IjY1LjI1IDEyMC4zNzUgODcuNzUgMTI2IDk1LjA2MyAxNTAuMTg4IDkwIDE1MyA2NS4yNSAxMzguMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSI2NS4yNSAxMzguMzc1IDYwLjc1IDE3My4yNSA5MC41NjMgMTUyLjQzOCIvPjxwb2x5Z29uIGZpbGw9IiNFQThFM0EiIHBvaW50cz0iOTIuMjUgMTAyLjM3NSA5NS4wNjMgMTUwLjE4OCA4Ni42MjUgMTI1LjcxOSIvPjxwb2x5Z29uIGZpbGw9IiNEODdDMzAiIHBvaW50cz0iMzkuMzc1IDEzOC45MzggNjUuMjUgMTM4LjM3NSA2MC43NSAxNzMuMjUiLz48cG9seWdvbiBmaWxsPSIjRUI4RjM1IiBwb2ludHM9IjEyLjkzOCAxODguNDM4IDYwLjc1IDE3My4yNSAzOS4zNzUgMTM4LjkzOCAuNTYzIDE0MS43NSIvPjxwb2x5Z29uIGZpbGw9IiNFODgyMUUiIHBvaW50cz0iODguODc1IDU4LjUgNjQuNjg4IDc4Ljc1IDQ2LjEyNSAxMDEuMjUgOTIuMjUgMTAyLjkzOCIvPjxwb2x5Z29uIGZpbGw9IiNERkNFQzMiIHBvaW50cz0iNjAuNzUgMTczLjI1IDkwLjU2MyAxNTIuNDM4IDg4LjMxMyAxNzAuNDM4IDg4LjMxMyAxODAuNTYzIDY4LjA2MyAxNzYuNjI1Ii8+PHBvbHlnb24gZmlsbD0iI0RGQ0VDMyIgcG9pbnRzPSIxMjEuNSAxNzMuMjUgMTUwLjc1IDE1Mi40MzggMTQ4LjUgMTcwLjQzOCAxNDguNSAxODAuNTYzIDEyOC4yNSAxNzYuNjI1IiB0cmFuc2Zvcm09Im1hdHJpeCgtMSAwIDAgMSAyNzIuMjUgMCkiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjcwLjMxMyAxMTIuNSA2NC4xMjUgMTI1LjQzOCA4Ni4wNjMgMTE5LjgxMyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMTUwLjE4OCAwKSIvPjxwb2x5Z29uIGZpbGw9IiNFODhGMzUiIHBvaW50cz0iMTIuMzc1IC41NjMgODguODc1IDU4LjUgNzUuOTM4IDI3Ii8+PHBhdGggZmlsbD0iIzhFNUEzMCIgZD0iTTEyLjM3NTAwMDIsMC41NjI1MDAwMDggTDIuMjUwMDAwMDMsMzEuNTAwMDAwNSBMNy44NzUwMDAxMiw2NS4yNTAwMDEgTDMuOTM3NTAwMDYsNjcuNTAwMDAxIEw5LjU2MjUwMDE0LDcyLjU2MjUgTDUuMDYyNTAwMDgsNzYuNTAwMDAxMSBMMTEuMjUsODIuMTI1MDAxMiBMNy4zMTI1MDAxMSw4NS41MDAwMDEzIEwxNi4zMTI1MDAyLDk2Ljc1MDAwMTQgTDU4LjUwMDAwMDksODMuODEyNTAxMiBDNzkuMTI1MDAxMiw2Ny4zMTI1MDA0IDg5LjI1MDAwMTMsNTguODc1MDAwMyA4OC44NzUwMDEzLDU4LjUwMDAwMDkgQzg4LjUwMDAwMTMsNTguMTI1MDAwOSA2My4wMDAwMDA5LDM4LjgxMjUwMDYgMTIuMzc1MDAwMiwwLjU2MjUwMDAwOCBaIi8+PGcgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjExLjUgMCkiPjxwb2x5Z29uIGZpbGw9IiNGODlEMzUiIHBvaW50cz0iMTYuMzEzIDk2LjE4OCAuNTYzIDE0MS43NSAzOS45MzggMTM5LjUgNjUuMjUgMTM5LjUgNjUuMjUgMTE5LjgxMyA2NC4xMjUgNzkuMzEzIDU4LjUgODMuODEzIi8+PHBvbHlnb24gZmlsbD0iI0Q4N0MzMCIgcG9pbnRzPSI0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi4zNzUgODcuMTg4IDEyNiA2NS4yNSAxMjAuMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VBOEQzQSIgcG9pbnRzPSI0Ni4xMjUgMTAxLjgxMyA2NS4yNSAxMTkuODEzIDY1LjI1IDEzNy44MTMiLz48cG9seWdvbiBmaWxsPSIjRjg5RDM1IiBwb2ludHM9IjY1LjI1IDEyMC4zNzUgODcuNzUgMTI2IDk1LjA2MyAxNTAuMTg4IDkwIDE1MyA2NS4yNSAxMzguMzc1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSI2NS4yNSAxMzguMzc1IDYwLjc1IDE3My4yNSA5MCAxNTMiLz48cG9seWdvbiBmaWxsPSIjRUE4RTNBIiBwb2ludHM9IjkyLjI1IDEwMi4zNzUgOTUuMDYzIDE1MC4xODggODYuNjI1IDEyNS43MTkiLz48cG9seWdvbiBmaWxsPSIjRDg3QzMwIiBwb2ludHM9IjM5LjM3NSAxMzguOTM4IDY1LjI1IDEzOC4zNzUgNjAuNzUgMTczLjI1Ii8+PHBvbHlnb24gZmlsbD0iI0VCOEYzNSIgcG9pbnRzPSIxMi45MzggMTg4LjQzOCA2MC43NSAxNzMuMjUgMzkuMzc1IDEzOC45MzggLjU2MyAxNDEuNzUiLz48cG9seWdvbiBmaWxsPSIjRTg4MjFFIiBwb2ludHM9Ijg4Ljg3NSA1OC41IDY0LjY4OCA3OC43NSA0Ni4xMjUgMTAxLjI1IDkyLjI1IDEwMi45MzgiLz48cG9seWdvbiBmaWxsPSIjMzkzOTM5IiBwb2ludHM9IjcwLjMxMyAxMTIuNSA2NC4xMjUgMTI1LjQzOCA4Ni4wNjMgMTE5LjgxMyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMTUwLjE4OCAwKSIvPjxwb2x5Z29uIGZpbGw9IiNFODhGMzUiIHBvaW50cz0iMTIuMzc1IC41NjMgODguODc1IDU4LjUgNzUuOTM4IDI3Ii8+PHBhdGggZmlsbD0iIzhFNUEzMCIgZD0iTTEyLjM3NTAwMDIsMC41NjI1MDAwMDggTDIuMjUwMDAwMDMsMzEuNTAwMDAwNSBMNy44NzUwMDAxMiw2NS4yNTAwMDEgTDMuOTM3NTAwMDYsNjcuNTAwMDAxIEw5LjU2MjUwMDE0LDcyLjU2MjUgTDUuMDYyNTAwMDgsNzYuNTAwMDAxMSBMMTEuMjUsODIuMTI1MDAxMiBMNy4zMTI1MDAxMSw4NS41MDAwMDEzIEwxNi4zMTI1MDAyLDk2Ljc1MDAwMTQgTDU4LjUwMDAwMDksODMuODEyNTAxMiBDNzkuMTI1MDAxMiw2Ny4zMTI1MDA0IDg5LjI1MDAwMTMsNTguODc1MDAwMyA4OC44NzUwMDEzLDU4LjUwMDAwMDkgQzg4LjUwMDAwMTMsNTguMTI1MDAwOSA2My4wMDAwMDA5LDM4LjgxMjUwMDYgMTIuMzc1MDAwMiwwLjU2MjUwMDAwOCBaIi8+PC9nPjwvZz48L3N2Zz4=`, + downloads: { + chrome: + "https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn", + firefox: "https://addons.mozilla.org/en-US/firefox/addon/ether-metamask/", + edge: "https://microsoftedge.microsoft.com/addons/detail/metamask/ejbalbakoplchlghecdalmeeeajnimhm?hl=en-US", + }, + }, ] export default wallets diff --git a/packages/core/src/main.ts b/packages/core/src/main.ts index 16241b9..48102d5 100644 --- a/packages/core/src/main.ts +++ b/packages/core/src/main.ts @@ -7,6 +7,7 @@ import { IStorageWrapper, LocalStorageWrapper } from "./localStorageStore" import { pipe } from "./utils" import { FilterList, filterBy, filterByPreAuthorized } from "./wallet/filter" import { isWalletObj } from "./wallet/isWalletObject" +import { bootstrapMetamaskBridge } from "./wallet/metamaskBridge" import { scanObjectForWallets } from "./wallet/scan" import { Sort, sortBy } from "./wallet/sort" @@ -75,6 +76,8 @@ export function getStarknet( } const lastConnectedStore = storageFactoryImplementation("gsw-last") + bootstrapMetamaskBridge() + return { getAvailableWallets: async (options = {}) => { const availableWallets = scanObjectForWallets( diff --git a/packages/core/src/wallet/metamaskBridge.ts b/packages/core/src/wallet/metamaskBridge.ts new file mode 100644 index 0000000..7f80262 --- /dev/null +++ b/packages/core/src/wallet/metamaskBridge.ts @@ -0,0 +1,60 @@ +import type { MetaMaskProvider } from "@consensys/get-starknet" +import { MetaMaskSnapWallet } from "@consensys/get-starknet" +import detectEthereumProvider from "@metamask/detect-provider" + +async function waitForEthereumProvider( + options: { timeout?: number; retries?: number } = {}, +): Promise { + const { timeout = 3000, retries = 0 } = options + + let provider: MetaMaskProvider | null = null + try { + provider = await detectEthereumProvider({ timeout }) + } catch { + // Silent error - do nothing + } + + if (provider) { + return provider + } + + if (retries === 0) { + return null + } + + provider = await waitForEthereumProvider({ timeout, retries: retries - 1 }) + return provider +} + +async function hasSnapSupport(provider: MetaMaskProvider) { + try { + await provider.request({ + method: "wallet_getSnaps", + }) + return true + } catch { + return false + } +} + +async function bootstrapMetamaskBridge() { + if (window.hasOwnProperty("starknet_metamask")) { + return + } + + let provider: MetaMaskProvider | null = null + + provider = await waitForEthereumProvider({ retries: 3 }) + if (!provider) { + return + } + + const snapSupport = await hasSnapSupport(provider) + if (!snapSupport) { + return + } + + window["starknet_metamask"] = new MetaMaskSnapWallet(provider) +} + +export { bootstrapMetamaskBridge } diff --git a/packages/core/vite.config.ts b/packages/core/vite.config.ts index dd2321f..9c8528c 100644 --- a/packages/core/vite.config.ts +++ b/packages/core/vite.config.ts @@ -17,6 +17,7 @@ export default defineConfig({ // the proper extensions will be added fileName: "core", }, + target: "es2020", }, plugins: [ dts({ @@ -31,4 +32,5 @@ export default defineConfig({ exclude: ["**/node_modules/**", "**/*.mock.ts"], }, }, + optimizeDeps: { esbuildOptions: { target: "es2020" } }, }) diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index 36a0ff0..549883c 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ // the proper extensions will be added fileName: "ui", }, + target: "es2020", }, plugins: [ svelte({ emitCss: false }), @@ -21,4 +22,5 @@ export default defineConfig({ insertTypesEntry: true, }), ], + optimizeDeps: { esbuildOptions: { target: "es2020" } }, })