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 @@ -214,7 +214,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 +457,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 +533,56 @@ 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 {
return new Proxy(provider, {
get: (target, prop, receiver) => {
if (prop === "request") {
Comment thread
lwin-kyaw marked this conversation as resolved.
Outdated
return async <S, R>(args: { method: string; params?: S }) => {
// handle `wallet_switchEthereumChain` request from the clients which uses the EVM provider directly (e.g. wagmi actions)
// we already handle this method, `wallet_switchEthereumChain` in other connectors, but metamask lacks of this
if (args?.method === "wallet_switchEthereumChain") {
const chainParams = Array.isArray(args.params) ? args.params[0] : undefined;
const chainId =
chainParams && typeof chainParams === "object" && "chainId" in chainParams ? (chainParams.chainId as string | undefined) : undefined;

if (chainId && this.evmClient) {
const chainConfiguration = this.getEvmChainConfiguration(chainId);
await this.evmClient.switchChain({ chainId: chainId as Hex, chainConfiguration });
return null as R;
}
}

return target.request(args);
};
}

const value = Reflect.get(target, prop, receiver);
return typeof value === "function" ? value.bind(target) : value;
},
}) as IProvider;
}
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
type AccountAbstractionMultiChainConfig,
type BiconomySmartAccountConfig,
type BundlerConfig,
EIP_5792_METHODS,
EIP_7702_METHODS,
type ISmartAccount,
type KernelSmartAccountConfig,
type MetamaskSmartAccountConfig,
Expand Down Expand Up @@ -80,9 +82,10 @@ class AccountAbstractionProvider extends BaseProvider<AccountAbstractionProvider
}

public async setupProvider(eoaProvider: IProvider): Promise<void> {
const { currentChain } = this;
const { chainNamespace } = currentChain;
if (chainNamespace !== this.PROVIDER_CHAIN_NAMESPACE) throw WalletInitializationError.incompatibleChainNameSpace("Invalid chain namespace");
const currentChain = this.currentChain;
const chainNamespace = currentChain?.chainNamespace;
if (chainNamespace && chainNamespace !== this.PROVIDER_CHAIN_NAMESPACE)
throw WalletInitializationError.incompatibleChainNameSpace("Invalid chain namespace");
Comment thread
chaitanyapotti marked this conversation as resolved.
Outdated
const bundlerAndPaymasterConfig = this.config.smartAccountChainsConfig.find((config) => config.chainId === currentChain.chainId);
if (!bundlerAndPaymasterConfig)
throw WalletInitializationError.invalidProviderConfigError(`Bundler and paymaster config not found for chain ${currentChain.chainId}`);
Expand Down Expand Up @@ -154,7 +157,19 @@ class AccountAbstractionProvider extends BaseProvider<AccountAbstractionProvider
eoaProvider,
handlers: providerHandlers,
});
const eoaMiddleware = providerAsMiddleware(eoaProvider);

// override EIP-5792 and EIP-7702 methods to throw unsupported method error
// 4437 AA Account will not support the EIP7702/5792 methods
const overrideEip5792And7702MethodsHandlers = Object.fromEntries(
[...Object.values(EIP_5792_METHODS), ...Object.values(EIP_7702_METHODS)].map((method) => [
method,
async () => {
throw providerErrors.unsupportedMethod(`${method} is not supported for account abstraction provider`);
},
])
);

const eoaMiddleware = providerAsMiddleware(eoaProvider, overrideEip5792And7702MethodsHandlers);

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.

no. don't change provider as middleware.
add these above middlewares under the engine below in between aaMiddleware and eoaMiddleware

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.

Addressed here, 0f8553a

const engine = JRPCEngineV2.create({ middleware: [aaMiddleware, eoaMiddleware] });
const provider = providerFromEngineV2(engine);
this.updateProviderEngineProxy(provider);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { METHOD_TYPES } from "@toruslabs/ethereum-controllers";
import { createScaffoldMiddlewareV2, type JRPCRequest, type MiddlewareConstraint, type MiddlewareParams, rpcErrors } from "@web3auth/auth";
import {
cloneDeep,
createScaffoldMiddlewareV2,
type JRPCRequest,
Json,
type MiddlewareConstraint,
type MiddlewareParams,
rpcErrors,
} from "@web3auth/auth";

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

export function providerAsMiddleware(provider: IProvider): MiddlewareConstraint {
/**
* Creates a middleware that forwards the request to the given provider.
*
* If `overrideHandlers` is provided, the middleware will check if the request method is in the `overrideHandlers` object.
* If it is, the middleware will call the handler function with the request and return the result.
* If it is not, the middleware will forward the request to the given provider.
*
* @param provider - The provider to use.
* @param overrideHandlers - The handlers to override.
* @returns The middleware.
*/
export function providerAsMiddleware(
provider: IProvider,
overrideHandlers?: Record<string, (request: JRPCRequest<unknown>) => Promise<unknown>>
): MiddlewareConstraint {
return async ({ request }) => {
if (overrideHandlers) {
const handler = overrideHandlers[request.method as keyof IEthProviderHandlers];
if (handler) {
const mutableRequest = cloneDeep(request);
return handler(mutableRequest as JRPCRequest<unknown>) as Promise<void | Json>;
}
}
return provider.request({ method: request.method, params: request.params });
};
}
Loading