Skip to content
14 changes: 10 additions & 4 deletions demo/vue-app-new/src/components/X402Tester.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useSwitchChain } from "@wagmi/vue";
import { useX402Fetch } from "@web3auth/modal/x402/vue";
import { useChain, useWeb3Auth } from "@web3auth/modal/vue";
import { CHAIN_NAMESPACES } from "@web3auth/no-modal";
import { ref, watch } from "vue";
import { computed, ref, watch } from "vue";

const BASE_SEPOLIA_CHAIN_ID = "0x14a34"; // 84532
const SOLANA_DEVNET_CHAIN_ID = "0x67"; // 103
const SOLANA_DEVNET_CAIP_CHAIN_ID = `solana:${Number(SOLANA_DEVNET_CHAIN_ID)}`;
const DEFAULT_X402_URL = import.meta.env.VITE_APP_X402_TEST_CONTENT_URL || "https://x402.org/protected";
const DEFAULT_X402_URL = "https://web3auth-dev-demo-x420.sapphire-dev-2-1.authnetwork.dev";
Comment thread
lwin-kyaw marked this conversation as resolved.

const { isConnected, connection, web3Auth } = useWeb3Auth();
const { chainId, chainNamespace } = useChain();
Expand All @@ -19,6 +19,8 @@ const emit = defineEmits<{
(e: "print-to-console", title: string, payload?: unknown): void;
}>();

const eip155Chains = computed(() => web3Auth.value?.coreOptions.chains?.filter((c) => c.chainNamespace === CHAIN_NAMESPACES.EIP155) || []);

const url = ref(DEFAULT_X402_URL);
const fetchLoading = ref(false);

Expand All @@ -45,9 +47,13 @@ watch(
const onSwitchToBaseSepolia = async () => {
fetchLoading.value = true;
try {
await switchChainAsync({ chainId: parseInt(BASE_SEPOLIA_CHAIN_ID, 16) });
const newChain = eip155Chains.value.find((c) => c.chainId === BASE_SEPOLIA_CHAIN_ID);
if (!newChain) throw new Error(`Unsupported chainId: ${BASE_SEPOLIA_CHAIN_ID}`);
const data = await switchChainAsync({ chainId: Number(newChain.chainId) });
emit("print-to-console", "switchedChain", { chainId: data.id });
} catch (err) {
emit("print-to-console", "x402 network error", err instanceof Error ? err.message : String(err));
console.error("Error", err);
emit("print-to-console", "switchedChain error", err instanceof Error ? err.message : String(err));
} finally {
fetchLoading.value = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import { getErrorAnalyticsProperties, signChallenge } from "@toruslabs/base-cont
import { bytesToHexPrefixedString, utf8ToBytes } from "@toruslabs/metadata-helpers";
import type { Wallet } from "@wallet-standard/base";
import { StandardConnect, StandardConnectFeature } from "@wallet-standard/features";
import {
createScaffoldMiddlewareV2,
JRPCEngineV2,
type JRPCRequest,
type MiddlewareConstraint,
type MiddlewareParams,
providerFromEngineV2,
rpcErrors,
} from "@web3auth/auth";
import { EVM_METHOD_TYPES } from "@web3auth/ws-embed";
import { generateSiweNonce } from "viem/siwe";

Expand Down Expand Up @@ -214,7 +223,7 @@ class MetaMaskConnector extends BaseConnector<void> {
},
});

this.evmProvider = this.evmClient.getProvider() as unknown as IProvider;
this.evmProvider = this.createEvmProviderBridge(this.evmClient.getProvider() as unknown as IProvider);
Comment thread
chaitanyapotti marked this conversation as resolved.
Outdated
}

// Create the Solana client only when Solana chains are configured
Expand Down Expand Up @@ -457,24 +466,7 @@ class MetaMaskConnector extends BaseConnector<void> {
throw WalletLoginError.unsupportedOperation("switchChain requires an EVM client, but no EVM chains are configured.");
}

const chainConfig = this.coreOptions.chains.find(
(x) => x.chainId === params.chainId && ([CHAIN_NAMESPACES.EIP155] as ChainNamespaceType[]).includes(x.chainNamespace)
);

const chainConfiguration = chainConfig
? {
chainId: params.chainId,
chainName: chainConfig.displayName,
rpcUrls: [chainConfig.rpcTarget],
blockExplorerUrls: chainConfig.blockExplorerUrl ? [chainConfig.blockExplorerUrl] : undefined,
nativeCurrency: {
name: chainConfig.tickerName,
symbol: chainConfig.ticker,
decimals: chainConfig.decimals || 18,
},
iconUrls: chainConfig.logo ? [chainConfig.logo] : undefined,
}
: undefined;
const chainConfiguration = this.getEvmChainConfiguration(params.chainId);

await this.evmClient.switchChain({ chainId: params.chainId as Hex, chainConfiguration });
}
Expand Down Expand Up @@ -550,6 +542,60 @@ class MetaMaskConnector extends BaseConnector<void> {
}
await this.initializationPromise;
}

private getEvmChainConfiguration(chainId: string) {
const chainConfig = this.coreOptions.chains.find(
(x) => x.chainId === chainId && ([CHAIN_NAMESPACES.EIP155] as ChainNamespaceType[]).includes(x.chainNamespace)
);

return chainConfig
? {
chainId,
chainName: chainConfig.displayName,
rpcUrls: [chainConfig.rpcTarget],
blockExplorerUrls: chainConfig.blockExplorerUrl ? [chainConfig.blockExplorerUrl] : undefined,
nativeCurrency: {
name: chainConfig.tickerName,
symbol: chainConfig.ticker,
decimals: chainConfig.decimals || 18,
},
iconUrls: chainConfig.logo ? [chainConfig.logo] : undefined,
}
: undefined;
}

private createEvmProviderBridge(provider: IProvider): IProvider {
const switchChainMiddleware = createScaffoldMiddlewareV2({
wallet_switchEthereumChain: async (params: MiddlewareParams<JRPCRequest<{ chainId: string }[]>>): Promise<null> => {
const chainParams = params.request.params?.length ? params.request.params[0] : undefined;
const chainId = chainParams?.chainId;

if (!chainId) throw rpcErrors.invalidParams("Missing chainId");
if (!this.evmClient) throw WalletLoginError.unsupportedOperation("MetaMask EVM client is not initialized");

const chainConfiguration = this.getEvmChainConfiguration(chainId);
await this.evmClient.switchChain({ chainId: chainId as Hex, chainConfiguration });
return null;
},
});
const forwardMiddleware: MiddlewareConstraint = async ({ request }) => {
return provider.request({ method: request.method, params: request.params });
};
const engine = JRPCEngineV2.create({ middleware: [switchChainMiddleware, forwardMiddleware] });
const engineProvider = providerFromEngineV2(engine) as IProvider;

return {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that metamaskConnector can handle provider requests from the dapp

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we wrapped the evmProvider with the engine, we will need to handle/forward the rest of the events, requests right?

...engineProvider,
// bind the provider events to the engine provider
// without the binding, the engine provider could not forward events to the original provider
on: provider.on.bind(provider),
once: provider.once.bind(provider),
removeListener: provider.removeListener.bind(provider),
off: typeof provider.off === "function" ? provider.off.bind(provider) : undefined,
emit: typeof provider.emit === "function" ? provider.emit.bind(provider) : undefined,
removeAllListeners: typeof provider.removeAllListeners === "function" ? provider.removeAllListeners.bind(provider) : undefined,
} as IProvider;
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { type BundlerClient, createBundlerClient, createPaymasterClient, type Pa

import { CHAIN_NAMESPACES, type CustomChainConfig, type IProvider, WalletInitializationError } from "../../../base";
import { BaseProvider, type BaseProviderConfig, type BaseProviderState } from "../../../providers/base-provider";
import { createAaMiddleware, createEoaMiddleware, providerAsMiddleware } from "../rpc/ethRpcMiddlewares";
import { createAaMiddleware, createEip7702And5792MiddlewareForAaProvider, createEoaMiddleware, providerAsMiddleware } from "../rpc/ethRpcMiddlewares";
import { getProviderHandlers } from "./utils";

interface AccountAbstractionProviderConfig extends BaseProviderConfig {
Expand Down Expand Up @@ -80,7 +80,10 @@ class AccountAbstractionProvider extends BaseProvider<AccountAbstractionProvider
}

public async setupProvider(eoaProvider: IProvider): Promise<void> {
const { currentChain } = this;
const currentChain = this.currentChain;
if (!currentChain) {
throw WalletInitializationError.invalidProviderConfigError(`AA chain config not found for chain ${this.chainId}`);
}
const { chainNamespace } = currentChain;
if (chainNamespace !== this.PROVIDER_CHAIN_NAMESPACE) throw WalletInitializationError.incompatibleChainNameSpace("Invalid chain namespace");
const bundlerAndPaymasterConfig = this.config.smartAccountChainsConfig.find((config) => config.chainId === currentChain.chainId);
Expand Down Expand Up @@ -154,8 +157,12 @@ class AccountAbstractionProvider extends BaseProvider<AccountAbstractionProvider
eoaProvider,
handlers: providerHandlers,
});

// middleware to handle EIP-7702 and EIP-5792 methods,
// currently, we do not support EIP-7702 and EIP-5792 methods for account abstraction provider
const eip7702And5792Middleware = await createEip7702And5792MiddlewareForAaProvider();
const eoaMiddleware = providerAsMiddleware(eoaProvider);
const engine = JRPCEngineV2.create({ middleware: [aaMiddleware, eoaMiddleware] });
const engine = JRPCEngineV2.create({ middleware: [aaMiddleware, eip7702And5792Middleware, eoaMiddleware] });
const provider = providerFromEngineV2(engine);
this.updateProviderEngineProxy(provider);
eoaProvider.once("chainChanged", (chainId) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { METHOD_TYPES } from "@toruslabs/ethereum-controllers";
import { createScaffoldMiddlewareV2, type JRPCRequest, type MiddlewareConstraint, type MiddlewareParams, rpcErrors } from "@web3auth/auth";
import { EIP_5792_METHODS, EIP_7702_METHODS, METHOD_TYPES } from "@toruslabs/ethereum-controllers";
import {
createScaffoldMiddlewareV2,
type JRPCRequest,
type MiddlewareConstraint,
type MiddlewareParams,
providerErrors,
rpcErrors,
} from "@web3auth/auth";

import { IProvider } from "../../../base";
import { IEthProviderHandlers, MessageParams, TransactionParams, TypedMessageParams } from "../../ethereum-provider";
Expand Down Expand Up @@ -210,6 +217,18 @@ export async function createEoaMiddleware({ aaProvider }: { aaProvider: IProvide
});
}

export async function createEip7702And5792MiddlewareForAaProvider(): Promise<MiddlewareConstraint> {
const eip5792Methods = Object.values(EIP_5792_METHODS);
const eip7702Methods = Object.values(EIP_7702_METHODS);
const eip7702And5792Methods: string[] = [...eip5792Methods, ...eip7702Methods];
return async ({ request, next }) => {
if (eip7702And5792Methods.includes(request.method as string)) {
throw providerErrors.unsupportedMethod(`${request.method} is not supported for account abstraction provider`);
}
return next(request);
};
}

export function providerAsMiddleware(provider: IProvider): MiddlewareConstraint {
return async ({ request }) => {
return provider.request({ method: request.method, params: request.params });
Expand Down
Loading