Coinbase Wallet provides unique features like passkey/WebAuthn support alongside standard wallet functionality. OnchainTestKit offers comprehensive automation for all Coinbase Wallet features.
Coinbase Wallet-Specific Actions
In addition to the common wallet actions, Coinbase Wallet supports these specific actions:
enum CoinbaseSpecificActionType {
LOCK = "lock", // Lock the wallet
UNLOCK = "unlock", // Unlock the wallet
ADD_TOKEN = "addToken", // Add a custom token
ADD_ACCOUNT = "addAccount", // Create a new account
SWITCH_ACCOUNT = "switchAccount", // Switch between accounts
ADD_NETWORK = "addNetwork", // Add a custom network
SEND_TOKENS = "sendTokens", // Send tokens (not yet implemented)
HANDLE_PASSKEY_POPUP = "handlePasskeyPopup", // Handle WebAuthn/Passkey authentication
}
Passkey/WebAuthn Support
One of Coinbase Wallet’s unique features is support for passkey authentication, providing enhanced security through WebAuthn.
Passkey Configuration
interface PasskeyConfig {
name: string; // Display name for the passkey
rpId: string; // Relying Party ID (domain)
rpName: string; // Relying Party Name
userId: string; // User identifier
isUserVerified?: boolean; // Whether user verification is required
}
Passkey Registration
Register a new passkey for wallet authentication:
test('register passkey', async ({ page, coinbase }) => {
// Trigger passkey registration
const [popup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByTestId('register-passkey').click(),
]);
// Handle passkey registration
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: popup,
passkeyAction: 'register',
passkeyConfig: {
name: 'My Test Passkey',
rpId: 'localhost',
rpName: 'My dApp',
userId: 'test-user-123',
isUserVerified: true,
},
});
});
Passkey Authentication
Use passkey for transaction approval:
test('approve transaction with passkey', async ({ page, coinbase }) => {
// Trigger transaction
const [txPopup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByRole('button', { name: 'Send Transaction' }).click(),
]);
// Approve with passkey
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: txPopup,
passkeyAction: 'approve',
});
});
Complete Passkey Flow Example
test('complete passkey flow', async ({ page, coinbase }) => {
if (!coinbase) throw new Error('Coinbase fixture is required');
// Connect wallet
await page.getByTestId('connect-wallet').click();
await page.getByRole('button', { name: 'Coinbase' }).click();
await coinbase.handleAction(BaseActionType.CONNECT_TO_DAPP);
// Register passkey
const [registerPopup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByTestId('setup-passkey').click(),
]);
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: registerPopup,
passkeyAction: 'register',
passkeyConfig: {
name: 'Test Passkey',
rpId: 'localhost',
rpName: 'Test dApp',
userId: 'user-' + Date.now(),
isUserVerified: true,
},
});
// Use passkey for transaction
await page.getByRole('button', { name: 'Send ETH' }).click();
const [txPopup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByRole('button', { name: 'Confirm' }).click(),
]);
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: txPopup,
passkeyAction: 'approve',
});
// Verify transaction success
await expect(page.getByText('Transaction successful')).toBeVisible();
});
Account Management
Add New Account
await coinbase.handleAction(CoinbaseSpecificActionType.ADD_ACCOUNT, {
accountName: 'DeFi Account',
});
Switch Between Accounts
await coinbase.handleAction(CoinbaseSpecificActionType.SWITCH_ACCOUNT, {
accountName: 'DeFi Account',
});
Account Management Example
test('manage Coinbase accounts', async ({ page, coinbase }) => {
// Add multiple accounts
await coinbase.handleAction(CoinbaseSpecificActionType.ADD_ACCOUNT, {
accountName: 'Trading Account',
});
await coinbase.handleAction(CoinbaseSpecificActionType.ADD_ACCOUNT, {
accountName: 'Savings Account',
});
// Switch to specific account
await coinbase.handleAction(CoinbaseSpecificActionType.SWITCH_ACCOUNT, {
accountName: 'Trading Account',
});
});
Network Management
Add Custom Network
await coinbase.handleAction(CoinbaseSpecificActionType.ADD_NETWORK, {
network: {
name: 'Base Mainnet',
rpcUrl: 'https://mainnet.base.org',
chainId: 8453,
symbol: 'ETH',
},
});
Wallet Lock/Unlock
Lock Wallet
await coinbase.handleAction(CoinbaseSpecificActionType.LOCK);
Unlock Wallet
await coinbase.handleAction(CoinbaseSpecificActionType.UNLOCK, {
password: 'YourWalletPassword',
});
Coinbase Wallet Configuration
Basic Configuration
import { configure } from '@coinbase/onchaintestkit';
const coinbaseConfig = configure()
.withCoinbase()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'COMPLEXPASSWORD1',
})
.build();
Advanced Configuration
const coinbaseConfig = configure()
.withCoinbase()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'COMPLEXPASSWORD1',
})
.withNetwork({
name: 'Base Sepolia',
chainId: 84532,
symbol: 'ETH',
rpcUrl: 'https://sepolia.base.org',
})
.withCustomSetup(async (wallet, context) => {
// Add custom tokens
await wallet.handleAction(CoinbaseSpecificActionType.ADD_TOKEN, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
tokenSymbol: 'USDC',
tokenDecimals: 6,
});
// Add additional accounts
await wallet.handleAction(CoinbaseSpecificActionType.ADD_ACCOUNT, {
accountName: 'DeFi Operations',
});
})
.build();
Complete Coinbase Wallet Test Example
Here’s a comprehensive example demonstrating various Coinbase Wallet features:
import { createOnchainTest, CoinbaseSpecificActionType, BaseActionType } from '@coinbase/onchaintestkit';
import { configure } from '@coinbase/onchaintestkit';
const config = configure()
.withLocalNode({
chainId: 84532,
forkUrl: process.env.E2E_TEST_FORK_URL,
})
.withCoinbase()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'COMPLEXPASSWORD1',
})
.build();
const test = createOnchainTest(config);
test('comprehensive Coinbase test', async ({ page, coinbase, node }) => {
if (!coinbase) throw new Error('Coinbase fixture is required');
// Navigate to dApp
await page.goto('https://app.example.com');
// Connect wallet
await page.getByTestId('connect-wallet').click();
await page.getByRole('button', { name: 'Coinbase' }).click();
await coinbase.handleAction(BaseActionType.CONNECT_TO_DAPP);
// Setup passkey
const [passkeyPopup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByTestId('enable-passkey').click(),
]);
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: passkeyPopup,
passkeyAction: 'register',
passkeyConfig: {
name: 'Test Passkey',
rpId: 'localhost',
rpName: 'Example dApp',
userId: 'test-user',
isUserVerified: true,
},
});
// Add custom network
await coinbase.handleAction(CoinbaseSpecificActionType.ADD_NETWORK, {
network: {
name: 'Local Test Network',
rpcUrl: `http://localhost:${node?.port}`,
chainId: 84532,
symbol: 'ETH',
},
});
// Switch to the network
await coinbase.handleAction(BaseActionType.SWITCH_NETWORK, {
networkName: 'Local Test Network',
});
// Perform transaction with passkey approval
await page.getByRole('button', { name: 'Send Transaction' }).click();
const [txPopup] = await Promise.all([
page.context().waitForEvent('page'),
page.getByRole('button', { name: 'Confirm' }).click(),
]);
await coinbase.handleAction(CoinbaseSpecificActionType.HANDLE_PASSKEY_POPUP, {
mainPage: page,
popup: txPopup,
passkeyAction: 'approve',
});
// Verify success
await expect(page.getByText('Transaction complete')).toBeVisible();
});
PasskeyAuthenticator Class
Coinbase Wallet uses the PasskeyAuthenticator
class internally to manage WebAuthn credentials:
class PasskeyAuthenticator {
constructor(page: Page);
async setup(options: PasskeySetupOptions): Promise<void>;
async createCredential(options: CredentialCreationOptions): Promise<PublicKeyCredential>;
async getCredential(options: CredentialRequestOptions): Promise<PublicKeyCredential>;
async cleanup(): Promise<void>;
}
Best Practices
Always handle popups when working with passkeys - they open in separate browser windows
Passkey credentials are tied to specific domains (rpId). Use consistent values across tests
Coinbase Wallet requires stronger passwords than some other wallets. Use complex passwords with numbers and special characters
Next Steps