Wallet-to-Wallet Calls
Make audio and video calls directly between wallet addresses—no phone numbers required.
Starting a Call
Audio Call
const call = await userDialer.calls.start({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
type: 'audio'
});
console.log('Call ID:', call.id);Video Call
const call = await userDialer.calls.start({
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
type: 'video',
video: {
resolution: '720p',
frameRate: 30
}
});Call Object
interface Call {
id: string;
from: string; // Caller wallet address
to: string; // Recipient wallet address
type: 'audio' | 'video';
status: 'ringing' | 'active' | 'ended' | 'missed' | 'declined';
startedAt?: Date;
endedAt?: Date;
duration?: number; // in seconds
}Answering a Call
// Listen for incoming calls
userDialer.on('call:incoming', async (call) => {
// Get caller's profile info
const callerProfile = await userDialer.profile.getProfile({
walletAddress: call.from
});
console.log(`Incoming call from ${callerProfile.displayName || call.from}`);
// Answer the call
await userDialer.calls.answer(call.id);
});Declining a Call
await userDialer.calls.decline(call.id, {
reason: 'busy' // 'busy', 'declined', or custom message
});Ending a Call
await userDialer.calls.end(call.id);Media Controls
Mute/Unmute
// Mute your microphone
await userDialer.calls.mute(call.id);
// Unmute
await userDialer.calls.unmute(call.id);
// Toggle
await userDialer.calls.toggleMute(call.id);Video Controls
// Disable video
await userDialer.calls.disableVideo(call.id);
// Enable video
await userDialer.calls.enableVideo(call.id);
// Toggle video
await userDialer.calls.toggleVideo(call.id);Speaker Controls
// Enable speaker
await userDialer.calls.setSpeaker(call.id, true);
// Use earpiece
await userDialer.calls.setSpeaker(call.id, false);Call Events
Subscribe to call events for real-time updates:
// Incoming call
userDialer.on('call:incoming', (call) => {
console.log('Incoming call from:', call.from);
});
// Call answered
userDialer.on('call:answered', (call) => {
console.log('Call answered:', call.id);
});
// Call ended
userDialer.on('call:ended', (call) => {
console.log('Call ended. Duration:', call.duration);
});
// Call declined
userDialer.on('call:declined', (call) => {
console.log('Call declined by:', call.to);
});
// Call missed
userDialer.on('call:missed', (call) => {
console.log('Missed call from:', call.from);
});Media Streams
Accessing Local Stream
const localStream = userDialer.calls.getLocalStream(call.id);
// Attach to video element
const videoElement = document.getElementById('local-video');
videoElement.srcObject = localStream;Accessing Remote Stream
const remoteStream = userDialer.calls.getRemoteStream(call.id);
// Attach to video element
const videoElement = document.getElementById('remote-video');
videoElement.srcObject = remoteStream;Complete Example: React Component
import { useState, useEffect } from 'react';
import { DialClient } from '@dial/sdk';
export function CallInterface({ userDialer, recipient }) {
const [call, setCall] = useState(null);
const [isMuted, setIsMuted] = useState(false);
const [isVideoEnabled, setIsVideoEnabled] = useState(true);
useEffect(() => {
// Listen for incoming calls
userDialer.on('call:incoming', (incomingCall) => {
setCall(incomingCall);
});
userDialer.on('call:ended', () => {
setCall(null);
});
return () => {
userDialer.off('call:incoming');
userDialer.off('call:ended');
};
}, [userDialer]);
const startCall = async (type: 'audio' | 'video') => {
const newCall = await userDialer.calls.start({
to: recipient,
type
});
setCall(newCall);
};
const answerCall = async () => {
if (call) {
await userDialer.calls.answer(call.id);
}
};
const endCall = async () => {
if (call) {
await userDialer.calls.end(call.id);
setCall(null);
}
};
const toggleMute = async () => {
if (call) {
await userDialer.calls.toggleMute(call.id);
setIsMuted(!isMuted);
}
};
const toggleVideo = async () => {
if (call) {
await userDialer.calls.toggleVideo(call.id);
setIsVideoEnabled(!isVideoEnabled);
}
};
return (
<div>
{!call ? (
<div>
<button onClick={() => startCall('audio')}>
Audio Call
</button>
<button onClick={() => startCall('video')}>
Video Call
</button>
</div>
) : (
<div>
{call.status === 'ringing' && (
<button onClick={answerCall}>Answer</button>
)}
<video
ref={(el) => {
if (el) {
el.srcObject = userDialer.calls.getRemoteStream(call.id);
}
}}
autoPlay
/>
<button onClick={toggleMute}>
{isMuted ? 'Unmute' : 'Mute'}
</button>
{call.type === 'video' && (
<button onClick={toggleVideo}>
{isVideoEnabled ? 'Disable Video' : 'Enable Video'}
</button>
)}
<button onClick={endCall}>End Call</button>
</div>
)}
</div>
);
}Call Quality
Network Quality Monitoring
userDialer.on('call:quality', (quality) => {
console.log('Network quality:', quality.score); // 0-5
console.log('Latency:', quality.latency); // ms
console.log('Packet loss:', quality.packetLoss); // %
});Adaptive Bitrate
The SDK automatically adjusts video quality based on network conditions:
const call = await userDialer.calls.start({
to: '0x...',
type: 'video',
adaptiveBitrate: true, // default: true
minBitrate: 200, // kbps
maxBitrate: 2500 // kbps
});Advanced Options
Custom Ring Tones
userDialer.calls.setRingtone('/sounds/custom-ringtone.mp3');Call Recording
// Start recording
const recording = await userDialer.calls.startRecording(call.id);
// Stop recording
const recordedFile = await userDialer.calls.stopRecording(call.id);
console.log('Recording URL:', recordedFile.url);Caller ID & Profiles
All calls automatically include the caller’s Dial Profile information:
userDialer.on('call:incoming', async (call) => {
// Call includes embedded profile info
const profile = call.callerProfile;
console.log('Display Name:', profile.displayName);
console.log('Avatar:', profile.avatar);
console.log('Status:', profile.status);
// Or fetch full profile separately
const fullProfile = await userDialer.profile.getProfile({
walletAddress: call.from
});
});Users are identified by their wallet addresses, with display names and avatars provided by their Dial Profile.
Next Steps
Last updated on