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
226 changes: 132 additions & 94 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/modal/src/assets/base-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "@web3auth/no-modal/connectors/base-account-connector";
2 changes: 2 additions & 0 deletions packages/modal/src/ui/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const DEFAULT_LOGO_DARK = "https://images.web3auth.io/web3auth-logo-w-lig

export const WALLET_CONNECT_LOGO = "https://images.web3auth.io/login-wallet-connect.svg";

export const BASE_ACCOUNT_LOGO = "https://images.web3auth.io/login-base-account.svg";

export const DEFAULT_PRIMARY_COLOR = "#0364FF";
export const DEFAULT_ON_PRIMARY_COLOR = "#FFFFFF";

Expand Down
13 changes: 13 additions & 0 deletions packages/no-modal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@
],
"peerDependencies": {
"@babel/runtime": "^7.x",
"@base-org/account": "^2.5.1",
"@coinbase/wallet-sdk": "^4.3.x",
"react": ">=18",
"viem": ">=2.29",
"vue": "^3.x"
},
"peerDependenciesMeta": {
"@base-org/account": {
"optional": true
},
"@coinbase/wallet-sdk": {
"optional": true
},
Expand Down Expand Up @@ -92,6 +96,7 @@
"xrpl": "^2.14.0"
},
"devDependencies": {
"@base-org/account": "^2.5.1",
"@coinbase/wallet-sdk": "^4.3.7",
"@types/elliptic": "6.4.18",
"@types/json-rpc-random-id": "^1.0.3",
Expand Down Expand Up @@ -139,6 +144,11 @@
"require": "./dist/lib.cjs/vue/wagmi/index.js",
"types": "./dist/lib.cjs/types/vue/wagmi/index.d.ts"
},
"./connectors/base-account-connector": {
"import": "./dist/lib.esm/connectors/base-account-connector/index.js",
"require": "./dist/lib.cjs/connectors/base-account-connector/index.js",
"types": "./dist/lib.cjs/types/connectors/base-account-connector/index.d.ts"
},
"./connectors/coinbase-connector": {
"import": "./dist/lib.esm/connectors/coinbase-connector/index.js",
"require": "./dist/lib.cjs/connectors/coinbase-connector/index.js",
Expand Down Expand Up @@ -172,6 +182,9 @@
"vue/solana": [
"./dist/lib.cjs/types/vue/solana/index.d.ts"
],
"connectors/base-account-connector": [
"./dist/lib.cjs/types/connectors/base-account-connector/index.d.ts"
],
"connectors/coinbase-connector": [
"./dist/lib.cjs/types/connectors/coinbase-connector/index.d.ts"
],
Expand Down
2 changes: 2 additions & 0 deletions packages/no-modal/src/base/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const SOLANA_CONNECTORS = {

export const EVM_CONNECTORS = {
COINBASE: "coinbase",
BASE_ACCOUNT: "base-account",
...MULTI_CHAIN_CONNECTORS,
} as const;

Expand All @@ -27,5 +28,6 @@ export const CONNECTOR_NAMES = {
[MULTI_CHAIN_CONNECTORS.AUTH]: "Auth",
[MULTI_CHAIN_CONNECTORS.WALLET_CONNECT_V2]: "Wallet Connect v2",
[EVM_CONNECTORS.COINBASE]: "Coinbase Smart Wallet",
[EVM_CONNECTORS.BASE_ACCOUNT]: "Base Account",
[EVM_CONNECTORS.METAMASK]: "MetaMask",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import type { AppMetadata, Preference } from "@base-org/account";

import {
BaseConnectorLoginParams,
BaseConnectorSettings,
CHAIN_NAMESPACES,
ChainNamespaceType,
CONNECTED_EVENT_DATA,
CONNECTOR_CATEGORY,
CONNECTOR_CATEGORY_TYPE,
CONNECTOR_EVENTS,
CONNECTOR_NAMESPACES,
CONNECTOR_STATUS,
CONNECTOR_STATUS_TYPE,
ConnectorFn,
ConnectorInitOptions,
ConnectorNamespaceType,
ConnectorParams,
IdentityTokenInfo,
IProvider,
UserInfo,
WALLET_CONNECTOR_TYPE,
WALLET_CONNECTORS,
WalletLoginError,
Web3AuthError,
} from "../../base";
import { BaseEvmConnector } from "../base-evm-connector";
import { getSiteIcon, getSiteName } from "../utils";

export type BaseAccountSDKOptions = Partial<AppMetadata & { preference?: Preference; paymasterUrls?: Record<number, string> }>;

export interface BaseAccountConnectorOptions extends BaseConnectorSettings {
connectorSettings?: BaseAccountSDKOptions;
}

interface BaseAccountProvider {
request<T>(args: { method: string; params?: unknown[] }): Promise<T>;
on(event: string, listener: (...args: unknown[]) => void): void;
once(event: string, listener: (...args: unknown[]) => void): void;
removeAllListeners(): void;
}

interface ProviderRpcError extends Error {
code: number;
}

class BaseAccountConnector extends BaseEvmConnector<void> {
readonly connectorNamespace: ConnectorNamespaceType = CONNECTOR_NAMESPACES.EIP155;

readonly currentChainNamespace: ChainNamespaceType = CHAIN_NAMESPACES.EIP155;

readonly type: CONNECTOR_CATEGORY_TYPE = CONNECTOR_CATEGORY.EXTERNAL;

readonly name: WALLET_CONNECTOR_TYPE = WALLET_CONNECTORS.BASE_ACCOUNT;

public status: CONNECTOR_STATUS_TYPE = CONNECTOR_STATUS.NOT_READY;

private baseAccountProvider: BaseAccountProvider | null = null;

private baseAccountOptions: BaseAccountSDKOptions = { appName: "Web3Auth" };

constructor(connectorOptions: BaseAccountConnectorOptions) {
super(connectorOptions);
this.baseAccountOptions = { ...this.baseAccountOptions, ...connectorOptions.connectorSettings };
}

get provider(): IProvider | null {
if (this.status !== CONNECTOR_STATUS.NOT_READY && this.baseAccountProvider) {
return this.baseAccountProvider as unknown as IProvider;
}
return null;
}

set provider(_: IProvider | null) {
throw new Error("Not implemented");
}

async init(options: ConnectorInitOptions): Promise<void> {
await super.init(options);
const chainConfig = this.coreOptions.chains.find((x) => x.chainId === options.chainId);
super.checkInitializationRequirements({ chainConfig });

const { createBaseAccountSDK } = await import("@base-org/account");

// Derive defaults from site metadata if available
let appName = this.baseAccountOptions.appName || "Web3Auth";
let appLogoUrl = this.baseAccountOptions.appLogoUrl || "";

if (typeof window !== "undefined") {
if (!this.baseAccountOptions.appName) {
appName = getSiteName(window) || "Web3Auth";
}
if (!this.baseAccountOptions.appLogoUrl) {
appLogoUrl = (await getSiteIcon(window)) || "";
}
}

// Derive appChainIds from all EIP155 chains
const appChainIds = this.coreOptions.chains
.filter((x) => x.chainNamespace === CHAIN_NAMESPACES.EIP155)
.map((x) => Number.parseInt(x.chainId, 16));

const sdk = createBaseAccountSDK({
...this.baseAccountOptions,
appName,
appLogoUrl: appLogoUrl || null,
appChainIds: this.baseAccountOptions.appChainIds || appChainIds,
});

this.baseAccountProvider = sdk.getProvider() as unknown as BaseAccountProvider;
this.status = CONNECTOR_STATUS.READY;
this.emit(CONNECTOR_EVENTS.READY, WALLET_CONNECTORS.BASE_ACCOUNT);

try {
if (options.autoConnect) {
this.rehydrated = true;
const provider = await this.connect({ chainId: options.chainId, getIdentityToken: options.getIdentityToken });
if (!provider) {
this.rehydrated = false;
throw WalletLoginError.connectionError("Failed to rehydrate.");
}
}
} catch (error) {
this.emit(CONNECTOR_EVENTS.REHYDRATION_ERROR, error as Web3AuthError);
}
}

async connect({ chainId, getIdentityToken }: BaseConnectorLoginParams): Promise<IProvider | null> {
super.checkConnectionRequirements();
if (!this.baseAccountProvider) throw WalletLoginError.notConnectedError("Connector is not initialized");

this.status = CONNECTOR_STATUS.CONNECTING;
this.emit(CONNECTOR_EVENTS.CONNECTING, { connector: WALLET_CONNECTORS.BASE_ACCOUNT });

try {
const chainConfig = this.coreOptions.chains.find((x) => x.chainId === chainId);
if (!chainConfig) throw WalletLoginError.connectionError("Chain config is not available");

await this.baseAccountProvider.request({ method: "eth_requestAccounts" });
const currentChainId = await this.baseAccountProvider.request<string>({ method: "eth_chainId" });

if (currentChainId !== chainConfig.chainId) {
await this.switchChain(chainConfig, true);
}

this.status = CONNECTOR_STATUS.CONNECTED;
if (!this.provider) throw WalletLoginError.notConnectedError("Failed to connect with provider");

this.provider.once("disconnect", () => {
this.disconnect();
});

let identityTokenInfo: IdentityTokenInfo | undefined;

this.emit(CONNECTOR_EVENTS.CONNECTED, {
connector: WALLET_CONNECTORS.BASE_ACCOUNT,
reconnected: this.rehydrated,
provider: this.provider,
identityTokenInfo,
} as CONNECTED_EVENT_DATA);

if (getIdentityToken) {
identityTokenInfo = await this.getIdentityToken();
}

return this.provider;
} catch (error) {
this.status = CONNECTOR_STATUS.READY;
if (!this.rehydrated) this.emit(CONNECTOR_EVENTS.ERRORED, error as Web3AuthError);
this.rehydrated = false;
if (error instanceof Web3AuthError) throw error;
throw WalletLoginError.connectionError("Failed to login with Base Account", error);
}
}

async disconnect(options: { cleanup: boolean } = { cleanup: false }): Promise<void> {
await super.disconnectSession();
this.provider?.removeAllListeners();
if (options.cleanup) {
this.status = CONNECTOR_STATUS.NOT_READY;
this.baseAccountProvider = null;
} else {
this.status = CONNECTOR_STATUS.READY;
}
await super.disconnect();
}

async getUserInfo(): Promise<Partial<UserInfo>> {
if (!this.canAuthorize) throw WalletLoginError.notConnectedError("Not connected with wallet, Please login/connect first");
return {};
}

public async switchChain(params: { chainId: string }, init = false): Promise<void> {
super.checkSwitchChainRequirements(params, init);
try {
await this.baseAccountProvider?.request({ method: "wallet_switchEthereumChain", params: [{ chainId: params.chainId }] });
} catch (switchError: unknown) {
if ((switchError as ProviderRpcError).code === 4902) {
const chainConfig = this.coreOptions.chains.find((x) => x.chainId === params.chainId);
if (!chainConfig) throw WalletLoginError.connectionError("Chain config is not available");
await this.baseAccountProvider?.request({
method: "wallet_addEthereumChain",
params: [
{
chainId: chainConfig.chainId,
rpcUrls: [chainConfig.rpcTarget],
chainName: chainConfig.displayName,
nativeCurrency: { name: chainConfig.tickerName, symbol: chainConfig.ticker, decimals: chainConfig.decimals || 18 },
blockExplorerUrls: [chainConfig.blockExplorerUrl],
iconUrls: [chainConfig.logo],
},
],
});
return;
}
throw switchError;
}
}

public async enableMFA(): Promise<void> {
throw new Error("Method Not implemented");
}

public async manageMFA(): Promise<void> {
throw new Error("Method Not implemented");
}
}

export const baseAccountConnector = (params?: BaseAccountSDKOptions): ConnectorFn => {
return ({ coreOptions }: ConnectorParams) => {
return new BaseAccountConnector({
connectorSettings: params,
coreOptions,
});
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./baseAccountConnector";
1 change: 1 addition & 0 deletions packages/no-modal/src/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./auth-connector";
export * from "./base-account-connector";
export * from "./base-evm-connector";
export * from "./base-solana-connector";
export * from "./injected-evm-connector";
Expand Down
6 changes: 5 additions & 1 deletion packages/no-modal/src/noModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -865,7 +865,11 @@ export class Web3AuthNoModal extends SafeEventEmitter<Web3AuthNoModalEvents> imp
const isAaSupportedForCurrentChain =
this.currentChain?.chainNamespace === CHAIN_NAMESPACES.EIP155 &&
accountAbstractionConfig?.chains?.some((chain) => chain.chainId === this.currentChain?.chainId);
if (isAaSupportedForCurrentChain && (data.connector === WALLET_CONNECTORS.AUTH || this.coreOptions.useAAWithExternalWallet)) {
// Skip AA wrapping for Base Account connector as it's already a smart account provider
const shouldApplyAA =
data.connector === WALLET_CONNECTORS.AUTH ||
(this.coreOptions.useAAWithExternalWallet && data.connector !== WALLET_CONNECTORS.BASE_ACCOUNT);
if (isAaSupportedForCurrentChain && shouldApplyAA) {
const { accountAbstractionProvider, toEoaProvider } = await import("./providers/account-abstraction-provider");
// for embedded wallets, we use ws-embed provider which is AA provider, need to derive EOA provider
const eoaProvider: IProvider = data.connector === WALLET_CONNECTORS.AUTH ? await toEoaProvider(provider) : provider;
Expand Down