diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36abbd33b..c9b731e4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,8 @@ jobs: cd demo/vue-app-new npm install npm run build - env: + env: + VITE_SOLANA_MAINNET_RPC: ${{ secrets.VITE_SOLANA_MAINNET_RPC }} VITE_APP_PIMLICO_API_KEY: ${{ secrets.PIMLICO_API_KEY }} # Copy the files from build folder to the S3 bucket diff --git a/demo/vue-app-new/.env.development b/demo/vue-app-new/.env.development new file mode 100644 index 000000000..e73dba66b --- /dev/null +++ b/demo/vue-app-new/.env.development @@ -0,0 +1 @@ +# VITE_SOLANA_MAINNET_RPC="enter your rpc endpoint here" \ No newline at end of file diff --git a/demo/vue-app-new/package-lock.json b/demo/vue-app-new/package-lock.json index 0767f65ac..769593c61 100644 --- a/demo/vue-app-new/package-lock.json +++ b/demo/vue-app-new/package-lock.json @@ -34,12 +34,14 @@ "@web3auth/torus-solana-adapter": "file:../../packages/adapters/torus-solana-adapter", "@web3auth/wallet-connect-v2-adapter": "file:../../packages/adapters/wallet-connect-v2-adapter", "@web3auth/wallet-services-plugin": "file:../../packages/plugins/wallet-services-plugin", + "bs58": "^5.0.0", "ethers": "^6.13.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1" }, "devDependencies": { "@toruslabs/eslint-config-vue": "^3.3.1", + "@types/bs58": "^4.0.4", "@vitejs/plugin-vue": "^5.0.5", "autoprefixer": "^10.4.19", "eslint": "^8.54.0", @@ -210,16 +212,22 @@ "version": "9.1.0", "license": "ISC", "dependencies": { + "@solana/web3.js": "^1.95.3", + "@toruslabs/base-controllers": "^6.1.2", "@walletconnect/sign-client": "^2.16.1", "@walletconnect/types": "^2.16.1", "@walletconnect/utils": "^2.16.1", "@web3auth/auth": "^9.3.3", "@web3auth/base": "^9.0.2", - "@web3auth/base-evm-adapter": "^9.0.2", "@web3auth/base-provider": "^9.0.2", "@web3auth/ethereum-provider": "^9.0.2", + "@web3auth/solana-provider": "^9.0.2", + "bs58": "^5.0.0", "deepmerge": "^4.3.1" }, + "devDependencies": { + "@types/bs58": "^4.0.4" + }, "engines": { "node": ">=18.x", "npm": ">=9.x" @@ -1150,6 +1158,15 @@ "superstruct": "^2.0.2" } }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/@swc/helpers": { "version": "0.5.13", "license": "Apache-2.0", @@ -1416,6 +1433,17 @@ "vue": "^3.x" } }, + "node_modules/@types/bs58": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.4.tgz", + "integrity": "sha512-0IEpMFXXQi2zXaXl9GJ3sRwQo0uEkD+yFOv+FnAU5lkPtcu6h61xb7jc2CFPEZ5BUOaiP13ThuGc9HD4R8lR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "base-x": "^3.0.6" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "license": "MIT", @@ -2684,6 +2712,15 @@ "text-encoding-utf-8": "^1.0.2" } }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/bowser": { "version": "2.11.0", "license": "MIT" @@ -2740,12 +2777,20 @@ } }, "node_modules/bs58": { - "version": "4.0.1", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", + "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", "license": "MIT", "dependencies": { - "base-x": "^3.0.2" + "base-x": "^4.0.0" } }, + "node_modules/bs58/node_modules/base-x": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", + "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==", + "license": "MIT" + }, "node_modules/buffer": { "version": "6.0.3", "funding": [ diff --git a/demo/vue-app-new/package.json b/demo/vue-app-new/package.json index ffd80e84d..4f16d4584 100644 --- a/demo/vue-app-new/package.json +++ b/demo/vue-app-new/package.json @@ -36,12 +36,14 @@ "@web3auth/torus-solana-adapter": "file:../../packages/adapters/torus-solana-adapter", "@web3auth/wallet-connect-v2-adapter": "file:../../packages/adapters/wallet-connect-v2-adapter", "@web3auth/wallet-services-plugin": "file:../../packages/plugins/wallet-services-plugin", + "bs58": "^5.0.0", "ethers": "^6.13.2", "vue": "^3.4.31", "vue-i18n": "^9.13.1" }, "devDependencies": { "@toruslabs/eslint-config-vue": "^3.3.1", + "@types/bs58": "^4.0.4", "@vitejs/plugin-vue": "^5.0.5", "autoprefixer": "^10.4.19", "eslint": "^8.54.0", diff --git a/demo/vue-app-new/src/components/AppDashboard.vue b/demo/vue-app-new/src/components/AppDashboard.vue index 689ae0bd8..9505c2133 100644 --- a/demo/vue-app-new/src/components/AppDashboard.vue +++ b/demo/vue-app-new/src/components/AppDashboard.vue @@ -1,25 +1,18 @@ @@ -203,7 +265,7 @@ const onSignPersonalMsg = async () => { {{ t("app.buttons.btnGetBalance") }} - @@ -216,6 +278,7 @@ const onSignPersonalMsg = async () => { +
Sample Transaction
@@ -224,13 +287,14 @@ const onSignPersonalMsg = async () => { - +
] : [ { name: "torus-solana-adapter", value: "torus-solana" }, + { name: "wallet-connect-v2-adapter", value: "wallet-connect-v2" }, { name: "injected-adapters", value: "injected-solana" }, ] ); diff --git a/demo/vue-app-new/src/config.ts b/demo/vue-app-new/src/config.ts index 600d29ce5..5e7a8ae5f 100644 --- a/demo/vue-app-new/src/config.ts +++ b/demo/vue-app-new/src/config.ts @@ -50,6 +50,7 @@ export const chainConfigs: Record = { }, ], [CHAIN_NAMESPACES.SOLANA]: [ + // Ref: https://namespaces.chainagnostic.org/solana/caip10 { chainNamespace: CHAIN_NAMESPACES.SOLANA, rpcTarget: "https://api.devnet.solana.com", @@ -57,7 +58,16 @@ export const chainConfigs: Record = { logo: "https://cryptologos.cc/logos/solana-sol-logo.png", chainId: "0x3", ticker: "SOL", - tickerName: "Solana", + tickerName: "Solana Devnet", + }, + { + chainNamespace: CHAIN_NAMESPACES.SOLANA, + rpcTarget: import.meta.env.VITE_SOLANA_MAINNET_RPC, + blockExplorerUrl: "https://explorer.solana.com", + logo: "https://cryptologos.cc/logos/solana-sol-logo.png", + chainId: "0x1", + ticker: "SOL", + tickerName: "Solana Mainnet", }, ], [CHAIN_NAMESPACES.CASPER]: [], diff --git a/demo/vue-app-new/src/services/solHandlers.ts b/demo/vue-app-new/src/services/solHandlers.ts index 8aeee8db6..7c8ac28a1 100644 --- a/demo/vue-app-new/src/services/solHandlers.ts +++ b/demo/vue-app-new/src/services/solHandlers.ts @@ -2,6 +2,7 @@ import { Connection, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction } from "@solana/web3.js"; import { CustomChainConfig, IProvider, log } from "@web3auth/base"; import { SolanaWallet } from "@web3auth/solana-provider"; +import base58 from "bs58"; const getConnection = async (provider: IProvider): Promise => { const solanaWallet = new SolanaWallet(provider); @@ -77,6 +78,7 @@ export const signTransaction = async (provider: IProvider, uiConsole: any) => { const conn = await getConnection(provider); const solWeb3 = new SolanaWallet(provider); const pubKey = await solWeb3.requestAccounts(); + log.info("pubKey", pubKey); const block = await conn.getLatestBlockhash("finalized"); const transactionInstruction = SystemProgram.transfer({ @@ -92,8 +94,7 @@ export const signTransaction = async (provider: IProvider, uiConsole: any) => { }).add(transactionInstruction); const signedTx = await solWeb3.signTransaction(transaction); - - // const res = await conn.sendRawTransaction(signedTx.serialize()); + log.info("signedTx", signedTx); uiConsole("signature", signedTx); return { signature: signedTx }; } catch (error) { @@ -108,7 +109,8 @@ export const signMessage = async (provider: IProvider, uiConsole: any) => { const solWeb3 = new SolanaWallet(provider); const msg = Buffer.from("Test Signing Message ", "utf8"); const res = await solWeb3.signMessage(new Uint8Array(msg)); - uiConsole(res); + const parsedResult = base58.encode(res); + uiConsole("solana signed message", parsedResult); } catch (error) { log.error("Error", error); uiConsole("error", error); diff --git a/package-lock.json b/package-lock.json index fdc4929a7..125163b79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26803,16 +26803,22 @@ "version": "9.1.0", "license": "ISC", "dependencies": { + "@solana/web3.js": "^1.95.3", + "@toruslabs/base-controllers": "^6.1.2", "@walletconnect/sign-client": "^2.16.1", "@walletconnect/types": "^2.16.1", "@walletconnect/utils": "^2.16.1", "@web3auth/auth": "^9.3.3", "@web3auth/base": "^9.0.2", - "@web3auth/base-evm-adapter": "^9.0.2", "@web3auth/base-provider": "^9.0.2", "@web3auth/ethereum-provider": "^9.0.2", + "@web3auth/solana-provider": "^9.0.2", + "bs58": "^5.0.0", "deepmerge": "^4.3.1" }, + "devDependencies": { + "@types/bs58": "^4.0.4" + }, "engines": { "node": ">=18.x", "npm": ">=9.x" diff --git a/packages/adapters/wallet-connect-v2-adapter/package.json b/packages/adapters/wallet-connect-v2-adapter/package.json index fd455bf69..206e13b8a 100644 --- a/packages/adapters/wallet-connect-v2-adapter/package.json +++ b/packages/adapters/wallet-connect-v2-adapter/package.json @@ -35,16 +35,22 @@ "@babel/runtime": "^7.x" }, "dependencies": { + "@solana/web3.js": "^1.95.3", + "@toruslabs/base-controllers": "^6.1.2", "@walletconnect/sign-client": "^2.16.1", "@walletconnect/types": "^2.16.1", "@walletconnect/utils": "^2.16.1", "@web3auth/auth": "^9.3.3", "@web3auth/base": "^9.0.2", - "@web3auth/base-evm-adapter": "^9.0.2", "@web3auth/base-provider": "^9.0.2", "@web3auth/ethereum-provider": "^9.0.2", + "@web3auth/solana-provider": "^9.0.2", + "bs58": "^5.0.0", "deepmerge": "^4.3.1" }, + "devDependencies": { + "@types/bs58": "^4.0.4" + }, "lint-staged": { "!(*d).ts": [ "eslint --cache --fix", diff --git a/packages/adapters/wallet-connect-v2-adapter/src/WalletConnectV2Provider.ts b/packages/adapters/wallet-connect-v2-adapter/src/WalletConnectV2Provider.ts index 15631f9a7..1745bcdfe 100644 --- a/packages/adapters/wallet-connect-v2-adapter/src/WalletConnectV2Provider.ts +++ b/packages/adapters/wallet-connect-v2-adapter/src/WalletConnectV2Provider.ts @@ -1,17 +1,18 @@ import type { ISignClient, SignClientTypes } from "@walletconnect/types"; import { getAccountsFromNamespaces, parseAccountId } from "@walletconnect/utils"; import { JRPCEngine, JRPCMiddleware, providerErrors, providerFromEngine } from "@web3auth/auth"; -import { CHAIN_NAMESPACES, CustomChainConfig, getChainConfig, log, WalletInitializationError, WalletLoginError } from "@web3auth/base"; +import { CHAIN_NAMESPACES, CustomChainConfig, getChainConfig, log, WalletLoginError } from "@web3auth/base"; import { BaseProvider, BaseProviderConfig, BaseProviderState } from "@web3auth/base-provider"; import { AddEthereumChainParameter, createChainSwitchMiddleware, createEthMiddleware, - createJsonRpcClient, - IChainSwitchHandlers, + createJsonRpcClient as createEthJsonRpcClient, + IChainSwitchHandlers as IEthChainSwitchHandlers, } from "@web3auth/ethereum-provider"; +import { createJsonRpcClient as createSolJsonRpcClient, createSolanaMiddleware } from "@web3auth/solana-provider"; -import { addChain, getAccounts, getProviderHandlers, switchChain } from "./walletConnectV2Utils"; +import { addChain, getAccounts, getEthProviderHandlers, getSolProviderHandlers, switchChain } from "./walletConnectV2Utils"; export interface WalletConnectV2ProviderConfig extends BaseProviderConfig { chainConfig: CustomChainConfig; @@ -22,8 +23,6 @@ export interface WalletConnectV2ProviderState extends BaseProviderState { } export class WalletConnectV2Provider extends BaseProvider { - readonly PROVIDER_CHAIN_NAMESPACE = CHAIN_NAMESPACES.EIP155; - private connector: ISignClient | null = null; constructor({ config, state, connector }: { config: WalletConnectV2ProviderConfig; state?: BaseProviderState; connector?: ISignClient }) { @@ -52,8 +51,6 @@ export class WalletConnectV2Provider extends BaseProvider { - const { chainNamespace } = this.config.chainConfig; - if (chainNamespace !== this.PROVIDER_CHAIN_NAMESPACE) throw WalletInitializationError.incompatibleChainNameSpace("Invalid chain namespace"); this.onConnectorStateUpdate(connector); await this.setupEngine(connector); } @@ -107,18 +104,27 @@ export class WalletConnectV2Provider extends BaseProvider { + if (this.config.chainConfig.chainNamespace === CHAIN_NAMESPACES.EIP155) { + return this.setupEthEngine(connector); + } else if (this.config.chainConfig.chainNamespace === CHAIN_NAMESPACES.SOLANA) { + return this.setupSolEngine(connector); + } + throw new Error(`Unsupported chainNamespace: ${this.config.chainConfig.chainNamespace}`); + } + + private async setupEthEngine(connector: ISignClient): Promise { const { chainId } = this.config.chainConfig; const numChainId = parseInt(chainId, 16); - const providerHandlers = getProviderHandlers({ connector, chainId: numChainId }); + const providerHandlers = getEthProviderHandlers({ connector, chainId: numChainId }); const jrpcRes = await getAccounts(connector); this.update({ accounts: jrpcRes || [], }); const ethMiddleware = createEthMiddleware(providerHandlers); - const chainSwitchMiddleware = this.getChainSwitchMiddleware(); + const chainSwitchMiddleware = this.getEthChainSwitchMiddleware(); const engine = new JRPCEngine(); - const { networkMiddleware } = createJsonRpcClient(this.config.chainConfig as CustomChainConfig); + const { networkMiddleware } = createEthJsonRpcClient(this.config.chainConfig as CustomChainConfig); engine.push(ethMiddleware); engine.push(chainSwitchMiddleware); engine.push(networkMiddleware); @@ -126,8 +132,25 @@ export class WalletConnectV2Provider extends BaseProvider { - const chainSwitchHandlers: IChainSwitchHandlers = { + private async setupSolEngine(connector: ISignClient): Promise { + const { chainId } = this.config.chainConfig; + const providerHandlers = getSolProviderHandlers({ connector, chainId }); + const jrpcRes = await getAccounts(connector); + + this.update({ + accounts: jrpcRes || [], + }); + const solMiddleware = createSolanaMiddleware(providerHandlers); + const engine = new JRPCEngine(); + const { networkMiddleware } = createSolJsonRpcClient(this.config.chainConfig as CustomChainConfig); + engine.push(solMiddleware); + engine.push(networkMiddleware); + const provider = providerFromEngine(engine); + this.updateProviderEngineProxy(provider); + } + + private getEthChainSwitchMiddleware(): JRPCMiddleware { + const chainSwitchHandlers: IEthChainSwitchHandlers = { addChain: async (params: AddEthereumChainParameter): Promise => { const { chainId, chainName, rpcUrls, blockExplorerUrls, nativeCurrency, iconUrls } = params; this.addChain({ diff --git a/packages/adapters/wallet-connect-v2-adapter/src/config.ts b/packages/adapters/wallet-connect-v2-adapter/src/config.ts index 080e6c344..18946a58a 100644 --- a/packages/adapters/wallet-connect-v2-adapter/src/config.ts +++ b/packages/adapters/wallet-connect-v2-adapter/src/config.ts @@ -15,11 +15,27 @@ export enum DEFAULT_EIP155_METHODS { SWITCH_ETHEREUM_CHAIN = "wallet_switchEthereumChain", } +export enum DEFAULT_SOLANA_METHODS { + SIGN_TRANSACTION = "solana_signTransaction", + SIGN_MESSAGE = "solana_signMessage", +} + export enum DEFAULT_EIP_155_EVENTS { ETH_CHAIN_CHANGED = "chainChanged", ETH_ACCOUNTS_CHANGED = "accountsChanged", } +export enum DEFAULT_SOLANA_EVENTS { + SOL_CHAIN_CHANGED = "chainChanged", + SOL_ACCOUNTS_CHANGED = "accountsChanged", +} + +export const SOLANA_CAIP_CHAIN_MAP: Record = { + "0x1": "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", + "0x2": "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z", + "0x3": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1", +}; + /** * Extracts a name for the site from the DOM */ @@ -107,6 +123,8 @@ export const getSupportedMethodsByNamespace = (namespace: string) => { switch (namespace) { case CHAIN_NAMESPACES.EIP155: return Object.values(DEFAULT_EIP155_METHODS); + case CHAIN_NAMESPACES.SOLANA: + return Object.values(DEFAULT_SOLANA_METHODS); default: throw new Error(`No default methods for namespace: ${namespace}`); } @@ -116,6 +134,8 @@ export const getSupportedEventsByNamespace = (namespace: string) => { switch (namespace) { case CHAIN_NAMESPACES.EIP155: return Object.values(DEFAULT_EIP_155_EVENTS); + case CHAIN_NAMESPACES.SOLANA: + return Object.values(DEFAULT_SOLANA_EVENTS); default: throw new Error(`No default events for namespace: ${namespace}`); } @@ -143,7 +163,7 @@ export const getWalletConnectV2Settings = async ( adapterSettings: IAdapterSettings; loginSettings: EngineTypes.ConnectParams; }> => { - if (namespace === CHAIN_NAMESPACES.EIP155) { + if (namespace === CHAIN_NAMESPACES.EIP155 || namespace === CHAIN_NAMESPACES.SOLANA) { const appMetadata = await getSiteMetadata(); const adapterSettings: IAdapterSettings = { walletConnectInitOptions: { @@ -159,7 +179,7 @@ export const getWalletConnectV2Settings = async ( }; const chainNamespaces = chainIds.map((chainId) => { - return `${namespace}:${parseInt(chainId, 16)}`; + return `${namespace}:${namespace === CHAIN_NAMESPACES.SOLANA ? SOLANA_CAIP_CHAIN_MAP[chainId] : parseInt(chainId, 16)}`; }); const loginSettings: EngineTypes.ConnectParams = { diff --git a/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2Utils.ts b/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2Utils.ts index f627a32a8..d87e13913 100644 --- a/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2Utils.ts +++ b/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2Utils.ts @@ -2,7 +2,17 @@ import type { ISignClient, SessionTypes } from "@walletconnect/types"; import { getAccountsFromNamespaces, parseAccountId } from "@walletconnect/utils"; import { type JRPCRequest, providerErrors, rpcErrors } from "@web3auth/auth"; import { WalletLoginError } from "@web3auth/base"; -import type { AddEthereumChainParameter, IProviderHandlers, MessageParams, TransactionParams, TypedMessageParams } from "@web3auth/ethereum-provider"; +import type { + AddEthereumChainParameter, + IProviderHandlers as EthProviderHandlers, + MessageParams, + TransactionParams, + TypedMessageParams, +} from "@web3auth/ethereum-provider"; +import type { IProviderHandlers as SolProviderHandlers, TransactionOrVersionedTransaction } from "@web3auth/solana-provider"; +import base58 from "bs58"; + +import { SOLANA_CAIP_CHAIN_MAP } from "./config"; async function getLastActiveSession(signClient: ISignClient): Promise { if (signClient.session.length) { @@ -16,7 +26,11 @@ function isMobileDevice() { return /Mobi|Android|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i.test(window.navigator.userAgent); } -export async function sendJrpcRequest(signClient: ISignClient, chainId: number, method: string, params: U): Promise { +function isSolanaChain(chainId: string) { + return chainId.startsWith("solana:"); +} + +export async function sendJrpcRequest(signClient: ISignClient, chainId: string, method: string, params: U): Promise { const session = await getLastActiveSession(signClient); if (!session) { throw providerErrors.disconnected(); @@ -30,10 +44,15 @@ export async function sendJrpcRequest(signClient: ISignClient, chainId: nu return signClient.request({ topic: session.topic, - chainId: `eip155:${chainId}`, + chainId, request: { method, - params, + params: isSolanaChain(chainId) + ? { + ...params, + pubkey: session.self.publicKey, + } + : params, }, }); } @@ -56,7 +75,7 @@ export async function getAccounts(signClient: ISignClient): Promise { throw WalletLoginError.connectionError("Failed to get accounts"); } -export function getProviderHandlers({ connector, chainId }: { connector: ISignClient; chainId: number }): IProviderHandlers { +export function getEthProviderHandlers({ connector, chainId }: { connector: ISignClient; chainId: number }): EthProviderHandlers { return { getPrivateKey: async () => { throw rpcErrors.methodNotSupported(); @@ -68,28 +87,83 @@ export function getProviderHandlers({ connector, chainId }: { connector: ISignCl return getAccounts(connector); }, processTransaction: async (txParams: TransactionParams, _: JRPCRequest): Promise => { - const methodRes = await sendJrpcRequest(connector, chainId, "eth_sendTransaction", [txParams]); + const methodRes = await sendJrpcRequest(connector, `eip155:${chainId}`, "eth_sendTransaction", [txParams]); return methodRes; }, processSignTransaction: async (txParams: TransactionParams, _: JRPCRequest): Promise => { - const methodRes = await sendJrpcRequest(connector, chainId, "eth_signTransaction", [txParams]); + const methodRes = await sendJrpcRequest(connector, `eip155:${chainId}`, "eth_signTransaction", [txParams]); return methodRes; }, processEthSignMessage: async (msgParams: MessageParams, _: JRPCRequest): Promise => { - const methodRes = await sendJrpcRequest(connector, chainId, "eth_sign", [msgParams.from, msgParams.data]); + const methodRes = await sendJrpcRequest(connector, `eip155:${chainId}`, "eth_sign", [msgParams.from, msgParams.data]); return methodRes; }, processPersonalMessage: async (msgParams: MessageParams, _: JRPCRequest): Promise => { - const methodRes = await sendJrpcRequest(connector, chainId, "personal_sign", [msgParams.data, msgParams.from]); + const methodRes = await sendJrpcRequest(connector, `eip155:${chainId}`, "personal_sign", [msgParams.data, msgParams.from]); return methodRes; }, processTypedMessageV4: async (msgParams: TypedMessageParams): Promise => { - const methodRes = await sendJrpcRequest(connector, chainId, "eth_signTypedData_v4", [msgParams.from, msgParams.data]); + const methodRes = await sendJrpcRequest(connector, `eip155:${chainId}`, "eth_signTypedData_v4", [ + msgParams.from, + msgParams.data, + ]); return methodRes; }, }; } +export function getSolProviderHandlers({ connector, chainId }: { connector: ISignClient; chainId: string }): SolProviderHandlers { + return { + requestAccounts: async (_: JRPCRequest) => { + return getAccounts(connector); + }, + getPrivateKey: async () => { + throw rpcErrors.methodNotSupported(); + }, + getSecretKey: async () => { + throw rpcErrors.methodNotSupported(); + }, + getPublicKey: async () => { + throw rpcErrors.methodNotSupported(); + }, + getAccounts: async (_: JRPCRequest) => { + return getAccounts(connector); + }, + signAllTransactions: async (_: JRPCRequest) => { + throw rpcErrors.methodNotSupported(); + }, + signAndSendTransaction: async (_: JRPCRequest) => { + throw rpcErrors.methodNotSupported(); + }, + signMessage: async (req: JRPCRequest<{ message: Uint8Array }>): Promise => { + const methodRes = await sendJrpcRequest<{ signature: string }, { message: string }>( + connector, + `solana:${SOLANA_CAIP_CHAIN_MAP[chainId]}`, + "solana_signMessage", + { + message: base58.encode(req.params.message), + } + ); + return base58.decode(methodRes.signature); + }, + signTransaction: async (req: JRPCRequest<{ message: TransactionOrVersionedTransaction }>): Promise => { + const [{ PublicKey }, accounts] = await Promise.all([import("@solana/web3.js"), getAccounts(connector)]); + if (accounts.length === 0) { + throw providerErrors.disconnected(); + } + const methodRes = await sendJrpcRequest<{ signature: string }, { transaction: string }>( + connector, + `solana:${SOLANA_CAIP_CHAIN_MAP[chainId]}`, + "solana_signTransaction", + { transaction: req.params.message.serialize({ requireAllSignatures: false }).toString("base64") } + ); + const finalTransaction = req.params.message; + finalTransaction.addSignature(new PublicKey(accounts[0]), Buffer.from(base58.decode(methodRes.signature))); + return finalTransaction; + }, + }; +} + export async function switchChain({ connector, chainId, @@ -99,7 +173,7 @@ export async function switchChain({ chainId: number; newChainId: string; }): Promise { - await sendJrpcRequest(connector, chainId, "wallet_switchEthereumChain", [{ chainId: newChainId }]); + await sendJrpcRequest(connector, `eip155:${chainId}`, "wallet_switchEthereumChain", [{ chainId: newChainId }]); } export async function addChain({ @@ -111,5 +185,5 @@ export async function addChain({ chainId: number; chainConfig: AddEthereumChainParameter; }): Promise { - await sendJrpcRequest(connector, chainId, "wallet_addEthereumChain", [chainConfig]); + await sendJrpcRequest(connector, `eip155:${chainId}`, "wallet_addEthereumChain", [chainConfig]); } diff --git a/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2adapter.ts b/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2adapter.ts index f35f82bff..7fd7313f7 100644 --- a/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2adapter.ts +++ b/packages/adapters/wallet-connect-v2-adapter/src/walletConnectV2adapter.ts @@ -1,3 +1,4 @@ +import { signChallenge, verifySignedChallenge } from "@toruslabs/base-controllers"; import Client from "@walletconnect/sign-client"; import { SessionTypes } from "@walletconnect/types"; import { getSdkError, isValidArray } from "@walletconnect/utils"; @@ -10,12 +11,17 @@ import { ADAPTER_STATUS_TYPE, AdapterInitOptions, AdapterNamespaceType, + BaseAdapter, CHAIN_NAMESPACES, ChainNamespaceType, + checkIfTokenIsExpired, CONNECTED_EVENT_DATA, CustomChainConfig, + getSavedToken, IProvider, log, + saveToken, + UserAuthInfo, UserInfo, WALLET_ADAPTERS, WalletConnectV2Data, @@ -23,19 +29,19 @@ import { WalletLoginError, Web3AuthError, } from "@web3auth/base"; -import { BaseEvmAdapter } from "@web3auth/base-evm-adapter"; +import base58 from "bs58"; import deepmerge from "deepmerge"; import { getWalletConnectV2Settings } from "./config"; import { WalletConnectV2AdapterOptions } from "./interface"; import { WalletConnectV2Provider } from "./WalletConnectV2Provider"; -class WalletConnectV2Adapter extends BaseEvmAdapter { +class WalletConnectV2Adapter extends BaseAdapter { readonly name: string = WALLET_ADAPTERS.WALLET_CONNECT_V2; - readonly adapterNamespace: AdapterNamespaceType = ADAPTER_NAMESPACES.EIP155; + readonly adapterNamespace: AdapterNamespaceType = ADAPTER_NAMESPACES.MULTICHAIN; - readonly currentChainNamespace: ChainNamespaceType = CHAIN_NAMESPACES.EIP155; + readonly currentChainNamespace: ChainNamespaceType = CHAIN_NAMESPACES.OTHER; readonly type: ADAPTER_CATEGORY_TYPE = ADAPTER_CATEGORY.EXTERNAL; @@ -75,7 +81,6 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { } async init(options: AdapterInitOptions): Promise { - await super.init(); super.checkInitializationRequirements(); const projectId = this.adapterOptions.adapterSettings?.walletConnectInitOptions?.projectId; if (!projectId) { @@ -92,7 +97,6 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { } this.adapterOptions.adapterSettings = deepmerge(wc2Settings.adapterSettings || {}, this.adapterOptions.adapterSettings || {}); - const { adapterSettings } = this.adapterOptions; this.connector = await Client.init(adapterSettings?.walletConnectInitOptions); this.wcProvider = new WalletConnectV2Provider({ @@ -189,11 +193,13 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { return {}; } - async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise { - await super.disconnectSession(); + async disconnect( + options: { cleanup?: boolean; sessionRemovedByWallet?: boolean } = { cleanup: false, sessionRemovedByWallet: false } + ): Promise { const { cleanup } = options; if (!this.connector || !this.connected || !this.activeSession?.topic) throw WalletLoginError.notConnectedError("Not connected with wallet"); - await this.connector.disconnect({ topic: this.activeSession?.topic, reason: getSdkError("USER_DISCONNECTED") }); + if (!options.sessionRemovedByWallet) + await this.connector.disconnect({ topic: this.activeSession?.topic, reason: getSdkError("USER_DISCONNECTED") }); this.rehydrated = false; if (cleanup) { this.connector = null; @@ -204,7 +210,52 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { this.status = ADAPTER_STATUS.READY; } this.activeSession = null; - await super.disconnect(); + this.emit(ADAPTER_EVENTS.DISCONNECTED); + } + + async authenticateUser(): Promise { + if (!this.provider || this.status !== ADAPTER_STATUS.CONNECTED) throw WalletLoginError.notConnectedError(); + const { chainNamespace, chainId } = this.chainConfig; + const accounts = await this.provider.request({ + method: chainNamespace === CHAIN_NAMESPACES.EIP155 ? "eth_accounts" : "getAccounts", + }); + if (accounts && accounts.length > 0) { + const existingToken = getSavedToken(accounts[0] as string, this.name); + if (existingToken) { + const isExpired = checkIfTokenIsExpired(existingToken); + if (!isExpired) { + return { idToken: existingToken }; + } + } + + const payload = { + domain: window.location.origin, + uri: window.location.href, + address: accounts[0], + chainId: parseInt(chainId, 16), + version: "1", + nonce: Math.random().toString(36).slice(2), + issuedAt: new Date().toISOString(), + }; + + const challenge = await signChallenge(payload, chainNamespace); + const signedMessage = await this._getSignedMessage(challenge, accounts, chainNamespace); + + const idToken = await verifySignedChallenge( + chainNamespace, + signedMessage as string, + challenge, + this.name, + this.sessionTime, + this.clientId, + this.web3AuthNetwork + ); + saveToken(accounts[0] as string, this.name, idToken); + return { + idToken, + }; + } + throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first"); } public async enableMFA(): Promise { @@ -246,7 +297,6 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { await this.connector.disconnect({ topic: this.activeSession?.topic, reason: getSdkError("USER_DISCONNECTED") }); } - log.debug("creating new session for web3auth wallet connect"); const { uri, approval } = await this.connector.connect(this.adapterOptions.loginSettings); const qrcodeModal = this.adapterOptions?.adapterSettings?.qrcodeModal; @@ -298,6 +348,7 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { private async onConnectHandler() { if (!this.connector || !this.wcProvider) throw WalletInitializationError.notReady("Wallet adapter is not ready yet"); if (!this.chainConfig) throw WalletInitializationError.invalidParams("Chain config is not set"); + this.subscribeEvents(); if (this.adapterOptions.adapterSettings?.qrcodeModal) { this.wcProvider = new WalletConnectV2Provider({ config: { @@ -308,7 +359,6 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { }); } await this.wcProvider.setupProvider(this.connector); - this.subscribeEvents(); this.cleanupPendingPairings(); this.status = ADAPTER_STATUS.CONNECTED; this.emit(ADAPTER_EVENTS.CONNECTED, { @@ -333,9 +383,17 @@ class WalletConnectV2Adapter extends BaseEvmAdapter { this.connector.events.on("session_delete", () => { // Session was deleted -> reset the dapp state, clean up from user session, etc. + this.disconnect({ sessionRemovedByWallet: true }); + }); + } - this.disconnect(); + private async _getSignedMessage(challenge: string, accounts: string[], chainNamespace: ChainNamespaceType): Promise { + const signedMessage = await this.provider.request({ + method: chainNamespace === CHAIN_NAMESPACES.EIP155 ? "personal_sign" : "signMessage", + params: chainNamespace === CHAIN_NAMESPACES.EIP155 ? [challenge, accounts[0]] : { message: Buffer.from(challenge) }, }); + if (chainNamespace === CHAIN_NAMESPACES.SOLANA) return base58.encode(signedMessage as Uint8Array); + return signedMessage as string; } } diff --git a/packages/adapters/wallet-connect-v2-adapter/torus.config.js b/packages/adapters/wallet-connect-v2-adapter/torus.config.js index 6df6a4932..927b75e03 100644 --- a/packages/adapters/wallet-connect-v2-adapter/torus.config.js +++ b/packages/adapters/wallet-connect-v2-adapter/torus.config.js @@ -1 +1 @@ -module.exports = require("../../../torus.config"); +module.exports = require('../../../torus.config.js'); diff --git a/packages/providers/solana-provider/src/index.ts b/packages/providers/solana-provider/src/index.ts index eb3d71de4..d1e438257 100644 --- a/packages/providers/solana-provider/src/index.ts +++ b/packages/providers/solana-provider/src/index.ts @@ -1,3 +1,4 @@ export * from "./interface"; export * from "./providers"; +export * from "./rpc"; export * from "./solanaWallet"; diff --git a/packages/providers/solana-provider/src/providers/injectedProviders/base/baseInjectedProvider.ts b/packages/providers/solana-provider/src/providers/injectedProviders/base/baseInjectedProvider.ts index 720ce94f1..0ab112f96 100644 --- a/packages/providers/solana-provider/src/providers/injectedProviders/base/baseInjectedProvider.ts +++ b/packages/providers/solana-provider/src/providers/injectedProviders/base/baseInjectedProvider.ts @@ -2,8 +2,9 @@ import { JRPCEngine, JRPCMiddleware, providerFromEngine } from "@web3auth/auth"; import { CustomChainConfig, WalletLoginError } from "@web3auth/base"; import { BaseProvider, BaseProviderConfig, BaseProviderState } from "@web3auth/base-provider"; +import { IProviderHandlers } from "../../../rpc"; import { createConfigMiddleware } from "../../../rpc/JrpcClient"; -import { createSolanaMiddleware, IProviderHandlers } from "../../../rpc/solanaRpcMiddlewares"; +import { createSolanaMiddleware } from "../../../rpc/solanaRpcMiddlewares"; export abstract class BaseInjectedProvider

extends BaseProvider { constructor({ config, state }: { config: BaseProviderConfig; state?: BaseProviderState }) { diff --git a/packages/providers/solana-provider/src/providers/injectedProviders/base/providerHandlers.ts b/packages/providers/solana-provider/src/providers/injectedProviders/base/providerHandlers.ts index 73b3ffc9e..e250a52fe 100644 --- a/packages/providers/solana-provider/src/providers/injectedProviders/base/providerHandlers.ts +++ b/packages/providers/solana-provider/src/providers/injectedProviders/base/providerHandlers.ts @@ -2,7 +2,7 @@ import { JRPCRequest, rpcErrors } from "@web3auth/auth"; import bs58 from "bs58"; import { IBaseWalletProvider, TransactionOrVersionedTransaction } from "../../../interface"; -import { IProviderHandlers } from "../../../rpc/solanaRpcMiddlewares"; +import { IProviderHandlers } from "../../../rpc"; export const getBaseProviderHandlers = (injectedProvider: IBaseWalletProvider): IProviderHandlers => { const providerHandlers: IProviderHandlers = { diff --git a/packages/providers/solana-provider/src/providers/injectedProviders/torus/providerHandlers.ts b/packages/providers/solana-provider/src/providers/injectedProviders/torus/providerHandlers.ts index cd0edf24f..c9efa87da 100644 --- a/packages/providers/solana-provider/src/providers/injectedProviders/torus/providerHandlers.ts +++ b/packages/providers/solana-provider/src/providers/injectedProviders/torus/providerHandlers.ts @@ -1,7 +1,7 @@ import { JRPCRequest, rpcErrors } from "@web3auth/auth"; import { ITorusWalletProvider, TransactionOrVersionedTransaction } from "../../../interface"; -import { IProviderHandlers } from "../../../rpc/solanaRpcMiddlewares"; +import { IProviderHandlers } from "../../../rpc"; export const getTorusHandlers = (injectedProvider: ITorusWalletProvider): IProviderHandlers => { const providerHandlers: IProviderHandlers = { diff --git a/packages/providers/solana-provider/src/providers/injectedProviders/walletStandardProvider.ts b/packages/providers/solana-provider/src/providers/injectedProviders/walletStandardProvider.ts index ba46fa454..c24870c64 100644 --- a/packages/providers/solana-provider/src/providers/injectedProviders/walletStandardProvider.ts +++ b/packages/providers/solana-provider/src/providers/injectedProviders/walletStandardProvider.ts @@ -1,5 +1,5 @@ import { IWalletStandardProviderHandler } from "../../interface"; -import { IProviderHandlers } from "../../rpc/solanaRpcMiddlewares"; +import { IProviderHandlers } from "../../rpc"; import { BaseInjectedProvider } from "./base/baseInjectedProvider"; import { getBaseProviderHandlers } from "./base/providerHandlers"; diff --git a/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyProvider.ts b/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyProvider.ts index 773075fa0..09671649e 100644 --- a/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyProvider.ts +++ b/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyProvider.ts @@ -2,15 +2,9 @@ import { getED25519Key, JRPCEngine, JRPCMiddleware, JRPCRequest, providerErrors, import { CHAIN_NAMESPACES, CustomChainConfig, WalletInitializationError } from "@web3auth/base"; import { BaseProvider, BaseProviderConfig, BaseProviderState } from "@web3auth/base-provider"; +import { AddSolanaChainParameter, IChainSwitchHandlers } from "../../rpc"; import { createJsonRpcClient } from "../../rpc/JrpcClient"; -import { - AddSolanaChainParameter, - createAccountMiddleware, - createChainSwitchMiddleware, - createSolanaMiddleware, - IAccountHandlers, - IChainSwitchHandlers, -} from "../../rpc/solanaRpcMiddlewares"; +import { createAccountMiddleware, createChainSwitchMiddleware, createSolanaMiddleware, IAccountHandlers } from "../../rpc/solanaRpcMiddlewares"; import { getProviderHandlers } from "./solanaPrivateKeyUtils"; export interface SolanaPrivKeyProviderConfig extends BaseProviderConfig { diff --git a/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyUtils.ts b/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyUtils.ts index ae507854f..9cff0a049 100644 --- a/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyUtils.ts +++ b/packages/providers/solana-provider/src/providers/privateKeyProvider/solanaPrivateKeyUtils.ts @@ -5,7 +5,7 @@ import { SafeEventEmitterProvider, WalletInitializationError } from "@web3auth/b import bs58 from "bs58"; import { TransactionOrVersionedTransaction } from "../../interface"; -import { IProviderHandlers } from "../../rpc/solanaRpcMiddlewares"; +import { IProviderHandlers } from "../../rpc"; export async function getProviderHandlers({ privKey, diff --git a/packages/providers/solana-provider/src/rpc/index.ts b/packages/providers/solana-provider/src/rpc/index.ts new file mode 100644 index 000000000..532554f34 --- /dev/null +++ b/packages/providers/solana-provider/src/rpc/index.ts @@ -0,0 +1,3 @@ +export * from "./interfaces"; +export * from "./JrpcClient"; +export * from "./solanaRpcMiddlewares"; diff --git a/packages/providers/solana-provider/src/rpc/interfaces.ts b/packages/providers/solana-provider/src/rpc/interfaces.ts new file mode 100644 index 000000000..100af4806 --- /dev/null +++ b/packages/providers/solana-provider/src/rpc/interfaces.ts @@ -0,0 +1,33 @@ +import { JRPCRequest } from "@web3auth/auth"; + +import { TransactionOrVersionedTransaction } from "../interface"; + +export interface AddSolanaChainParameter { + chainId: string; // A 0x-prefixed hexadecimal string + chainName: string; + nativeCurrency: { + name: string; + symbol: string; // 2-6 characters long + decimals: 18; + }; + rpcUrls: string[]; + blockExplorerUrls?: string[]; + iconUrls?: string[]; +} + +export interface IChainSwitchHandlers { + addNewChainConfig: (req: JRPCRequest) => Promise; + switchSolanaChain: (req: JRPCRequest<{ chainId: string }>) => Promise; +} + +export interface IProviderHandlers { + requestAccounts: (req: JRPCRequest) => Promise; + getAccounts: (req: JRPCRequest) => Promise; + getPublicKey: (req: JRPCRequest) => Promise; + getPrivateKey: (req: JRPCRequest) => Promise; + signTransaction: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction }>) => Promise; + signAllTransactions: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction[] }>) => Promise; + signAndSendTransaction: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction }>) => Promise<{ signature: string }>; + getSecretKey: (req: JRPCRequest) => Promise; + signMessage: (req: JRPCRequest<{ message: Uint8Array; display?: string }>) => Promise; +} diff --git a/packages/providers/solana-provider/src/rpc/solanaRpcMiddlewares.ts b/packages/providers/solana-provider/src/rpc/solanaRpcMiddlewares.ts index b920667ad..2a8e9e0b5 100644 --- a/packages/providers/solana-provider/src/rpc/solanaRpcMiddlewares.ts +++ b/packages/providers/solana-provider/src/rpc/solanaRpcMiddlewares.ts @@ -1,18 +1,7 @@ import { createAsyncMiddleware, JRPCMiddleware, JRPCRequest, mergeMiddleware } from "@web3auth/auth"; import { TransactionOrVersionedTransaction } from "../interface"; - -export interface IProviderHandlers { - requestAccounts: (req: JRPCRequest) => Promise; - getAccounts: (req: JRPCRequest) => Promise; - getPublicKey: (req: JRPCRequest) => Promise; - getPrivateKey: (req: JRPCRequest) => Promise; - signTransaction: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction }>) => Promise; - signAllTransactions: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction[] }>) => Promise; - signAndSendTransaction: (req: JRPCRequest<{ message: TransactionOrVersionedTransaction }>) => Promise<{ signature: string }>; - getSecretKey: (req: JRPCRequest) => Promise; - signMessage: (req: JRPCRequest<{ message: Uint8Array; display?: string }>) => Promise; -} +import { AddSolanaChainParameter, IChainSwitchHandlers, IProviderHandlers } from "./interfaces"; export function createGetAccountsMiddleware({ getAccounts }: { getAccounts: IProviderHandlers["getAccounts"] }): JRPCMiddleware { return createAsyncMiddleware(async (request, response, next) => { @@ -110,23 +99,7 @@ export function createSolanaMiddleware(providerHandlers: IProviderHandlers): JRP createGenericJRPCMiddleware("solanaSecretKey", getSecretKey) as JRPCMiddleware, ]); } -export interface AddSolanaChainParameter { - chainId: string; // A 0x-prefixed hexadecimal string - chainName: string; - nativeCurrency: { - name: string; - symbol: string; // 2-6 characters long - decimals: 18; - }; - rpcUrls: string[]; - blockExplorerUrls?: string[]; - iconUrls?: string[]; -} -export interface IChainSwitchHandlers { - addNewChainConfig: (req: JRPCRequest) => Promise; - switchSolanaChain: (req: JRPCRequest<{ chainId: string }>) => Promise; -} export function createChainSwitchMiddleware({ addNewChainConfig, switchSolanaChain }: IChainSwitchHandlers): JRPCMiddleware { return mergeMiddleware([ createGenericJRPCMiddleware("addSolanaChain", addNewChainConfig) as JRPCMiddleware,