@vechain/react-native-wallet-link
v1.0.9
Published
Vechain wallet link helps you link Vechain's wallet VeWorld with any ecosystem React Native dApp.
Downloads
740
Readme
@vechain/react-native-wallet-link
A React Native library that enables seamless integration with VeChain's VeWorld wallet, allowing dApps to connect, sign messages, and send transactions through the VeWorld mobile app.
Features
- ✅ Wallet Connection: Connect to VeWorld wallet with secure key exchange
- ✍️ Message Signing: Sign certificates and typed data messages
- 💸 Transaction Signing: Sign and send transactions to the VeChain network via VeWorld
- 🔐 Secure Communication: End-to-end encryption using NaCl cryptography
- 🔗 Deep Link Integration: Seamless app-to-app communication via deep links
- 🌐 Network Support: Support for VeChain mainnet, testnet, and solo networks
Installation
npm install @vechain/react-native-wallet-linkor
yarn add @vechain/react-native-wallet-linkPeer Dependencies
This library requires the following peer dependencies:
npm install @vechain/sdk-core @vechain/sdk-networkor
yarn add @vechain/sdk-core @vechain/sdk-network Quick Start
1. Setup the VeWorldProvider
Wrap your app with the VeWorldProvider to enable wallet functionality:
import React from 'react';
import { VeWorldProvider } from '@vechain/react-native-wallet-link';
import { TESTNET_URL } from '@vechain/sdk-network';
import * as Linking from 'expo-linking';
export default function App() {
return (
<VeWorldProvider
appName="Your App Name"
appUrl="https://yourapp.com"
redirectUrl={Linking.createURL('/', { scheme: 'yourapp' })}
node={TESTNET_URL}
config={{
onVeWorldConnected: (response) => {
// Handle wallet connection
},
onVeWorldDisconnected: (response) => {
// Handle wallet disconnection
},
onVeWorldSentTransaction: (response) => {
// Handle transaction response
},
onVeWorldSignedCertificate: (response) => {
// Handle certificate signing response
},
onVeWorldSignedTypedData: (response) => {
// Handle typed data signing response
},
}}
>
{/* Your app components */}
</VeWorldProvider>
);
}With Expo Router
After you wrapped the app with the provider at the root level of your App folder create a new file named +native-intent.tsx since Expo router don't allow to use deep links to a route that doesn't exists and add the following content:
import { LinkEvent, isVeWorldResponse, processResponse } from "@vechain/react-native-wallet-link"
export function redirectSystemPath({
path,
}: {
path: string;
initial: boolean;
}) {
try {
if (isVeWorldResponse(path)) {
return processResponse(path)
.then((response) => {
switch (response.event) {
case LinkEvent.OnVeWorldConnected:
return "/your-custom-route";
case LinkEvent.OnVeWorldDisconnected:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedTransaction:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedCertificate:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedTypedData:
return "/your-custom-route";
default:
return "/your-custom-route";
}
})
// Will throw error if the response from veworld is an error
.catch((err) => {
switch (err.event) {
case LinkEvent.OnVeWorldConnected:
return "/your-custom-route";
case LinkEvent.OnVeWorldDisconnected:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedTransaction:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedCertificate:
return "/your-custom-route";
case LinkEvent.OnVeWorldSignedTypedData:
return "/your-custom-route";
default:
return "/error-or-custom-route";
}
});
}
return path;
} catch {
return "/error-or-custom-route";
}
}This file will handle the redirect to a specific route inside the app. You can take a look at Expo docs for further details
Inside here you can also manipulate/read data that you got from VeWolrd the response.
2. Configure Deep Links
Add deep link configuration to your app:
// app.json (Expo) or Info.plist (iOS) / AndroidManifest.xml (Android)
{
"expo": {
"scheme": "yourapp",
"linking": {
"prefixes": ["yourapp://", "https://yourapp.com"]
}
}
}3. Use the Wallet Hook
import React, { useState, useEffect } from 'react';
import { useVeWorldWallet, decryptPayload } from '@vechain/react-native-wallet-link';
import { encodeBase64 } from 'tweetnacl-util';
export function WalletComponent() {
const { generateKeyPair, connect, disconnect, signCertificate, signTypedData, signAndSendTransaction } = useVeWorldWallet();
// State management - you need to store these values
const [keyPair, setKeyPair] = useState<{secretKey: string, publicKey: string} | null>(null);
const [veWorldPublicKey, setVeWorldPublicKey] = useState<string | null>(null);
const [address, setAddress] = useState<string | null>(null);
const [session, setSession] = useState<string | null>(null);
// Generate key pair on component mount
useEffect(() => {
if (!keyPair) {
const newKeyPair = generateKeyPair();
setKeyPair({
secretKey: encodeBase64(newKeyPair.secretKey),
publicKey: encodeBase64(newKeyPair.publicKey),
});
}
}, [keyPair, generateKeyPair]);
const handleConnect = () => {
if (keyPair) {
connect(keyPair.publicKey);
}
};
const handleDisconnect = () => {
if (keyPair && veWorldPublicKey && session) {
disconnect(keyPair, veWorldPublicKey, session);
}
};
return (
<View>
{address ? (
<Button title="Disconnect" onPress={handleDisconnect} />
) : (
<Button title="Connect to VeWorld" onPress={handleConnect} />
)}
</View>
);
}Required Data Storage
The library requires you to manage the following data in your application state:
Essential Data to Store
interface VeWorldState {
// Key pair for encryption/decryption (generate once, store securely)
keyPair: {
secretKey: string; // Base64 encoded secret key
publicKey: string; // Base64 encoded public key
} | null;
// VeWorld connection data (set after successful connection)
veWorldPublicKey: string | null; // VeWorld's public key for encryption
// VeWorld's public key for encryption
address: string | null; // Connected wallet address
session: string | null; // Session token for authenticated requests
}State Management Options
You can use any state management solution:
- React Context + useState
- Redux
- Zustand (as shown in the example)
- AsyncStorage for persistence
- React Query for server state
API Reference
VeWorldProvider Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| appName | string | ✅ | Your app's display name |
| appUrl | string | ✅ | Your app's URL |
| redirectUrl | string | ✅ | Deep link URL for redirects |
| node | string | ✅ | VeChain network node URL |
| config | VeWorldConfig | ✅ | Event handlers configuration |
useVeWorldWallet Hook
Returns an object with the following methods:
generateKeyPair()
Generates a new encryption key pair for secure communication.
const keyPair = generateKeyPair();
// Returns: { secretKey: Uint8Array, publicKey: Uint8Array }connect(publicKey: string)
Initiates connection to VeWorld wallet.
connect(keyPair.publicKey);disconnect(keyPair, veWorldPublicKey, session, description?)
Disconnects from VeWorld wallet.
disconnect(keyPair, veWorldPublicKey, session, "Disconnect from app");signCertificate(keyPair, veWorldPublicKey, session, certificate, description?)
Signs a certificate message.
const certificate = {
purpose: 'identification',
payload: { type: 'text', content: 'Hello, world!' }
};
signCertificate(keyPair, veWorldPublicKey, session, certificate);signTypedData(keyPair, veWorldPublicKey, session, typedData, description?)
Signs typed data (EIP-712 style).
const typedData = {
domain: { name: 'My DApp', version: '1.0.0', chainId: 1 },
origin: 'https://mydapp.com',
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' }
]
},
value: {
name: 'John Doe',
wallet: '0x9199828f14cf883c8d311245bec34ec0b51fedcb'
}
};
signTypedData(keyPair, veWorldPublicKey, session, typedData);signAndSendTransaction(keyPair, veWorldPublicKey, session, transaction, description?)
Signs and sends a transaction.
Multi-clauses transactions are supported
import { Clause, VET, Address } from '@vechain/sdk-core';
const transaction = [
Clause.transferVET(
Address.of('0x9199828f14cf883c8d311245bec34ec0b51fedcb'),
VET.of(0.1)
)
];
signAndSendTransaction(keyPair, veWorldPublicKey, session, transaction);Event Handlers
onVeWorldConnected
Called when wallet connection is successful.
onVeWorldConnected: (response) => {
if ('errorCode' in response) {
// Handle error
console.error(response.errorMessage);
return;
}
// Decrypt the response
const payload = decryptPayload<OnVeWorldConnectedData>(
keyPair.secretKey,
response.publicKey,
response.nonce,
response.data
);
// Update your state
setAddress(payload.address);
setSession(payload.session);
setVeWorldPublicKey(response.publicKey);
}onVeWorldDisconnected
Called when wallet disconnection occurs.
onVeWorldDisconnected: (response) => {
if (response && 'errorCode' in response) {
console.error(response.errorMessage);
return;
}
// Clear your state
setAddress(null);
setSession(null);
setVeWorldPublicKey(null);
}onVeWorldSentTransaction
Called when a transaction is signed and sent.
onVeWorldSentTransaction: (response) => {
if ('errorCode' in response) {
console.error(response.errorMessage);
return;
}
const payload = decryptPayload<OnVeWorldSignedTransactionData>(
keyPair.secretKey,
response.publicKey,
response.nonce,
response.data
);
console.log('Transaction ID:', payload.transaction.id);
}onVeWorldSignedCertificate
Called when a certificate is signed.
onVeWorldSignedCertificate: (response) => {
if ('errorCode' in response) {
console.error(response.errorMessage);
return;
}
const payload = decryptPayload<OnVeWorldSignedData>(
keyPair.secretKey,
response.publicKey,
response.nonce,
response.data
);
console.log('Signature:', payload.signature);
}onVeWorldSignedTypedData
Called when typed data is signed.
onVeWorldSignedTypedData: (response) => {
if ('errorCode' in response) {
console.error(response.errorMessage);
return;
}
const payload = decryptPayload<OnVeWorldSignedData>(
keyPair.secretKey,
response.publicKey,
response.nonce,
response.data
);
console.log('Signature:', payload.signature);
}Network Configuration
Supported Networks
import { MAINNET_URL, TESTNET_URL } from '@vechain/sdk-network';
// Mainnet
node={MAINNET_URL}
// Testnet
node={TESTNET_URL}
// Custom node
node="https://your-custom-node.com"Error Handling
All event handlers receive either a success response or an error response:
type VeWorldResponse = {
data: string;
nonce: string;
publicKey: string;
};
type VeWorldError = {
errorCode: string;
errorMessage: string;
};Always check for errors before processing responses:
if ('errorCode' in response) {
// Handle error
console.error('VeWorld Error:', response.errorCode, response.errorMessage);
} else {
// Process success response
const payload = decryptPayload(/* ... */);
}Security Considerations
- Key Storage: Store encryption keys securely using your platform's secure storage
- Session Management: Sessions don't expires but can be invalidated
Troubleshooting
Common Issues
- Deep links not working: Ensure your app scheme is properly configured
- VeWorld not opening: Check that VeWorld app is installed on the device
- Connection failures: Verify your app configuration and network settings
- Decryption errors: Ensure you're using the correct key pair for the session
Contributing
License
MIT
