Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}

// 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,64 @@ 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,
// get the chainId from the original provider (i.e. metamask)
get chainId() {
return provider.chainId;
},
// 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.
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

/**
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