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,50 @@ 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 engineProvider;
}
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