Nuxt 3
Integrate XRPL-Connect into your Nuxt 3 application with the Composition API.
Installation
npm install xrpl-connect xrplBasic Setup
Create a composable for wallet management:
// composables/useWallet.ts
import { ref, onMounted } from 'vue';
import { WalletManager,XamanAdapter;CrossmarkAdapter } from 'xrpl-connect';
import type { Account, WalletError, WalletAdapter } from 'xrpl-connect';
interface UseWalletOptions {
adapters: WalletAdapter[];
network?: 'mainnet' | 'testnet' | 'devnet';
}
export const useWallet = (options: UseWalletOptions) => {
const { adapters, network = 'testnet' } = options;
const walletManager = ref<WalletManager | null>(null);
const account = ref<Account | null>(null);
const connected = ref(false);
const error = ref<WalletError | null>(null);
const loading = ref(true);
const connectorRef = ref<HTMLElement | null>(null);
onMounted(() => {
const manager = new WalletManager({
adapters,
network,
autoConnect: true,
});
manager.on('connect', (acc: Account) => {
account.value = acc;
connected.value = true;
error.value = null;
});
manager.on('disconnect', () => {
account.value = null;
connected.value = false;
});
manager.on('error', (err: WalletError) => {
error.value = err;
});
walletManager.value = manager;
loading.value = false;
if (connectorRef.value) {
connectorRef.value.setWalletManager(manager);
}
});
const disconnect = async () => {
if (walletManager.value) {
await walletManager.value.disconnect();
}
};
return {
walletManager,
account,
connected,
error,
loading,
connectorRef,
disconnect,
};
};Creating a Plugin
Create a Nuxt plugin to provide wallet globally:
// plugins/wallet.client.ts
import { defineNuxtPlugin } from '#app';
import { WalletManager,XamanAdapter;CrossmarkAdapter } from 'xrpl-connect';
import type { Account, WalletError } from 'xrpl-connect';
declare module '#app' {
interface NuxtApp {
$wallet: {
manager: WalletManager | null;
account: any;
connected: boolean;
error: WalletError | null;
};
}
}
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
const manager = new WalletManager({
adapters: [
new XamanAdapter({
apiKey: config.public.xamanApiKey || '',
}),
new CrossmarkAdapter(),
],
network: 'testnet',
autoConnect: true,
});
const state = reactive({
manager,
account: null as Account | null,
connected: false,
error: null as WalletError | null,
});
manager.on('connect', (account: Account) => {
state.account = account;
state.connected = true;
state.error = null;
});
manager.on('disconnect', () => {
state.account = null;
state.connected = false;
});
manager.on('error', (error: WalletError) => {
state.error = error;
});
return {
provide: {
wallet: state,
},
};
});Add to your nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
public: {
xamanApiKey: process.env.NUXT_PUBLIC_XAMAN_API_KEY,
},
},
});Using the Plugin
Access the wallet in any component:
<template>
<div>
<xrpl-wallet-connector ref="connectorRef" primary-wallet="xaman" />
<div v-if="$wallet.error" style="color: red">
Error: {{ $wallet.error.message }}
</div>
<div v-if="$wallet.account" style="margin-top: 20px">
<h3>Connected Account</h3>
<p><strong>Address:</strong> {{ $wallet.account.address }}</p>
<p><strong>Network:</strong> {{ $wallet.account.network.name }}</p>
<button @click="handleDisconnect">Disconnect</button>
</div>
</div>
</template>
<script setup lang="ts">
const connectorRef = ref<HTMLElement | null>(null);
const { $wallet } = useNuxtApp();
onMounted(() => {
if (connectorRef.value && $wallet.manager) {
connectorRef.value.setWalletManager($wallet.manager);
}
});
const handleDisconnect = async () => {
if ($wallet.manager) {
await $wallet.manager.disconnect();
}
};
</script>Composable with Plugin
Combine composable + plugin for maximum flexibility:
// composables/useWalletManager.ts
export const useWalletManager = () => {
const { $wallet } = useNuxtApp();
const connectorRef = ref<HTMLElement | null>(null);
onMounted(() => {
if (connectorRef.value && $wallet.manager) {
connectorRef.value.setWalletManager($wallet.manager);
}
});
return {
...toRefs($wallet),
connectorRef,
};
};Usage:
<script setup lang="ts">
const { account, connected, error, connectorRef, manager } = useWalletManager();
const handlePayment = async () => {
if (!manager?.connected) return;
try {
const result = await manager.signAndSubmit({
TransactionType: 'Payment',
Account: account.value.address,
Destination: 'rN7n7otQDd6FczFgLdlqtyMVrn3HMfXoQT',
Amount: '1000000',
});
console.log('Payment sent:', result.hash);
} catch (error) {
console.error('Payment failed:', error);
}
};
</script>Signing Transactions
Handle transactions in Nuxt:
<template>
<form @submit.prevent="handleSubmit">
<button type="submit" :disabled="loading || !$wallet.connected">
{{ loading ? 'Sending...' : 'Send Payment' }}
</button>
<p v-if="errorMsg" style="color: red">{{ errorMsg }}</p>
<p v-if="result" style="color: green">Success! Hash: {{ result.hash }}</p>
</form>
</template>
<script setup lang="ts">
const { $wallet } = useNuxtApp();
const loading = ref(false);
const errorMsg = ref('');
const result = ref<any>(null);
const handleSubmit = async () => {
if (!$wallet.manager?.connected) {
errorMsg.value = 'Wallet not connected';
return;
}
loading.value = true;
errorMsg.value = '';
try {
const txResult = await $wallet.manager.signAndSubmit({
TransactionType: 'Payment',
Account: $wallet.account.address,
Destination: 'rN7n7otQDd6FczFgLdlqtyMVrn3HMfXoQT',
Amount: '1000000',
});
result.value = txResult;
} catch (error: any) {
errorMsg.value = error.message;
} finally {
loading.value = false;
}
};
</script>Middleware for Protected Routes
Protect routes that require wallet connection:
// middleware/requireWallet.ts
export default defineRouteMiddleware((to, from) => {
const { $wallet } = useNuxtApp();
if (!$wallet.connected) {
return navigateTo('/');
}
});Use in a page:
<!-- pages/transactions.vue -->
<script setup lang="ts">
definePageMeta({
middleware: 'requireWallet',
});
</script>
<template>
<div>Protected content here</div>
</template>Pinia Store Integration
For larger apps, use Pinia with XRPL-Connect:
// stores/wallet.ts
import { defineStore } from 'pinia';
import { WalletManager, XamanAdapter,} from 'xrpl-connect';
import type { Account, WalletError } from 'xrpl-connect';
export const useWalletStore = defineStore('wallet', () => {
const manager = ref<WalletManager | null>(null);
const account = ref<Account | null>(null);
const connected = ref(false);
const error = ref<WalletError | null>(null);
const initializeWallet = (adapters: any[]) => {
const newManager = new WalletManager({
adapters,
network: 'testnet',
autoConnect: true,
});
newManager.on('connect', (acc: Account) => {
account.value = acc;
connected.value = true;
error.value = null;
});
newManager.on('disconnect', () => {
account.value = null;
connected.value = false;
});
newManager.on('error', (err: WalletError) => {
error.value = err;
});
manager.value = newManager;
};
const disconnect = async () => {
if (manager.value) {
await manager.value.disconnect();
}
};
const signAndSubmit = async (transaction: any) => {
if (!manager.value?.connected) {
throw new Error('Wallet not connected');
}
return manager.value.signAndSubmit(transaction);
};
return {
manager,
account,
connected,
error,
initializeWallet,
disconnect,
signAndSubmit,
};
});Use in a page:
<script setup lang="ts">
import { useWalletStore } from '~/stores/wallet';
import { XamanAdapter } from 'xrpl-connect';
const wallet = useWalletStore();
const config = useRuntimeConfig();
onMounted(() => {
if (!wallet.manager) {
wallet.initializeWallet([
new XamanAdapter({ apiKey: config.public.xamanApiKey }),
]);
}
});
const handlePayment = async () => {
try {
const result = await wallet.signAndSubmit({
TransactionType: 'Payment',
Account: wallet.account.address,
Destination: 'rN7n7otQDd6FczFgLdlqtyMVrn3HMfXoQT',
Amount: '1000000',
});
console.log('Payment sent:', result.hash);
} catch (error) {
console.error('Payment failed:', error);
}
};
</script>Error Handling
Handle errors gracefully:
<template>
<div>
<xrpl-wallet-connector ref="connectorRef" />
<ErrorAlert v-if="$wallet.error" :error="$wallet.error" />
</div>
</template>
<script setup lang="ts">
const ErrorAlert = defineAsyncComponent(
() => import('~/components/ErrorAlert.vue')
);
</script>Create the error component:
<!-- components/ErrorAlert.vue -->
<template>
<div class="error-alert">
{{ getErrorMessage(error) }}
</div>
</template>
<script setup lang="ts">
interface Props {
error: any;
}
const props = defineProps<Props>();
const getErrorMessage = (error: any) => {
if (error.code === 'WALLET_NOT_FOUND') {
return 'Please install a wallet to continue';
} else if (error.code === 'CONNECTION_FAILED') {
return 'Failed to connect. Please try again.';
} else if (error.code === 'SIGN_FAILED') {
return 'You rejected the transaction';
}
return error.message;
};
</script>
<style scoped>
.error-alert {
padding: 10px;
background: #fee;
color: #c00;
border-radius: 4px;
}
</style>Server Routes for Backend Operations
Use Nuxt server routes for secure operations:
// server/api/transactions/sign.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
try {
// Validate transaction on backend
if (!body.transaction.Account || !body.transaction.Destination) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid transaction',
});
}
// Process transaction
// You could integrate server-side xrpl operations here
return { success: true };
} catch (error: any) {
throw createError({
statusCode: 500,
statusMessage: error.message,
});
}
});Use in your component:
const submitTransaction = async () => {
const result = await $fetch('/api/transactions/sign', {
method: 'POST',
body: {
transaction: {
TransactionType: 'Payment',
Account: $wallet.account.address,
Destination: 'rN7n7otQDd6FczFgLdlqtyMVrn3HMfXoQT',
Amount: '1000000',
},
},
});
};Environment Variables
Set up your .env file:
NUXT_PUBLIC_XAMAN_API_KEY=your_api_key
NUXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_idBest Practices
Use Plugins - Initialize wallet in a plugin for app-wide access
Composables - Create composables for component-level wallet logic
Error Handling - Handle errors gracefully in all wallet operations
Middleware - Protect routes with middleware when needed
Pinia for Complex State - Use Pinia for larger applications
Type Safety - Use TypeScript for better type checking
Environment Variables - Keep secrets in
.envfilesSSR Considerations - Use
client-onlycomponents for wallet UI
<!-- Prevent SSR for web components -->
<ClientOnly>
<xrpl-wallet-connector ref="connectorRef" />
</ClientOnly>Full Example: Complete Nuxt Page
<!-- pages/wallet.vue -->
<template>
<div class="wallet-container">
<h1>XRPL-Connect Demo</h1>
<ClientOnly>
<xrpl-wallet-connector ref="connectorRef" primary-wallet="xaman" />
</ClientOnly>
<div v-if="$wallet.error" class="error">
{{ getErrorMessage($wallet.error) }}
</div>
<div v-if="$wallet.account" class="account-info">
<h2>Connected Account</h2>
<p><strong>Address:</strong> {{ shortAddress }}</p>
<p><strong>Network:</strong> {{ $wallet.account.network.name }}</p>
<button @click="handleDisconnect">Disconnect</button>
</div>
<form v-if="$wallet.connected" @submit.prevent="handlePayment" class="payment-form">
<h2>Send Payment</h2>
<button type="submit" :disabled="loading">
{{ loading ? 'Sending...' : 'Send 1 XRP' }}
</button>
<p v-if="paymentResult" class="success">
Success! Hash: {{ paymentResult.hash }}
</p>
</form>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const { $wallet } = useNuxtApp();
const connectorRef = ref<HTMLElement | null>(null);
const loading = ref(false);
const paymentResult = ref<any>(null);
onMounted(() => {
if (connectorRef.value && $wallet.manager) {
connectorRef.value.setWalletManager($wallet.manager);
}
});
const shortAddress = computed(() => {
if (!$wallet.account) return null;
const addr = $wallet.account.address;
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
});
const handleDisconnect = async () => {
if ($wallet.manager) {
await $wallet.manager.disconnect();
}
};
const handlePayment = async () => {
if (!$wallet.manager?.connected) return;
loading.value = true;
try {
const result = await $wallet.manager.signAndSubmit({
TransactionType: 'Payment',
Account: $wallet.account.address,
Destination: 'rN7n7otQDd6FczFgLdlqtyMVrn3HMfXoQT',
Amount: '1000000',
});
paymentResult.value = result;
} catch (error) {
console.error('Payment failed:', error);
} finally {
loading.value = false;
}
};
const getErrorMessage = (error: any) => {
if (error.code === 'WALLET_NOT_FOUND') {
return 'Please install a wallet to continue';
}
return error.message;
};
</script>
<style scoped>
.wallet-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.error {
padding: 10px;
background: #fee;
color: #c00;
border-radius: 4px;
margin: 20px 0;
}
.account-info {
margin: 20px 0;
padding: 15px;
background: #f5f5f5;
border-radius: 8px;
}
.payment-form {
margin: 20px 0;
padding: 15px;
background: #e8f4f8;
border-radius: 8px;
}
.success {
color: green;
word-break: break-all;
}
</style>