Skip to content

Instantly share code, notes, and snippets.

@MatiFalcone
Created June 24, 2025 15:05
Show Gist options
  • Select an option

  • Save MatiFalcone/79cdc94a5f89006bceafbd886aaefb3c to your computer and use it in GitHub Desktop.

Select an option

Save MatiFalcone/79cdc94a5f89006bceafbd886aaefb3c to your computer and use it in GitHub Desktop.
'use client';
import React, {
createContext,
useContext,
useState,
useCallback,
useRef,
ReactNode,
} from 'react';
import type { AirUserDetails as AirKitUserDetails } from '@mocanetwork/airkit';
// Define the context shape
interface AirKitContextType {
// Core connection state
isAirKitConnected: boolean;
// Authentication
connectWithAirKit: () => Promise<void>;
logout: () => Promise<void>;
clearInit: () => void;
// User information
getUserInformation: () => Promise<AirKitUserDetails | undefined>;
// Wallet management
getProvider: () => Promise<any>; // Returns Eip1193Provider compatible with web3.js, ethers.js, viem, etc.
preloadWallet: () => Promise<void>;
isSmartAccountDeployed: () => Promise<boolean>;
deploySmartAccount: () => Promise<void>;
// MFA and security
setupOrUpdateMfa: () => Promise<void>;
// Air ID
claimAirId: () => Promise<void>;
// Partner integration
goToPartner: (partnerUrl: string) => Promise<{ urlWithToken: string }>;
getAccessToken: () => Promise<object | null>;
// Event handling
on: (event: string, callback: (...args: any[]) => void) => void;
off: (event: string, callback: (...args: any[]) => void) => void;
// New API methods
/**
* Add a new user to the Motorverse Hub
* This should be called first after successful Airkit authentication
* @returns The created user data
*/
addUser: () => Promise<any>;
/**
* Add an email to the current user's profile
* @param email The email address to add
* @returns The updated user data
*/
addEmail: (email: string) => Promise<any>;
/**
* Add a wallet to the current user's profile
* @param address The wallet address to add
* @param signature The signature of the message
* @param message The message that was signed
* @param walletType The type of wallet (e.g., 'metamask')
* @returns The updated user data
*/
addWallet: (address: string, signature: string, message: string, walletType?: string) => Promise<any>;
/**
* Remove a wallet from the current user's profile
* @param walletId The ID of the wallet to remove
* @returns True if the wallet was successfully removed
*/
deleteWallet: (walletId: string) => Promise<boolean>;
getUserWallets: () => Promise<Array<{ address: string; chainId: number }>>;
/**
* Set a wallet as the default wallet for the user
* @param walletId The ID of the wallet to set as default
* @returns True if the operation was successful
*/
setDefaultWallet: (walletId: string) => Promise<boolean>;
/**
* Get the current user's information
* @returns The user's data
*/
getUser: () => Promise<any>;
}
const AirKitContext = createContext<AirKitContextType | undefined>(undefined);
// Import the singleton instance creator
import { getAirService, AirKitApi } from '@/lib/airkitApi';
export function AirKitProvider({ children }: { children: ReactNode }) {
const [isAirKitConnected, setIsAirKitConnected] = useState(false);
const serviceRef = useRef<any>(null);
const airkitApiRef = useRef<any>(null);
const [isInitialized, setIsInitialized] = useState(false);
// Initialize the service once
if (!serviceRef.current) {
serviceRef.current = getAirService();
// Create a new instance of AirKitApi
airkitApiRef.current = new AirKitApi();
}
// Check for existing session on mount
React.useEffect(() => {
const checkSession = async () => {
if (!serviceRef.current || isInitialized) return;
try {
// First initialize with rehydration enabled
await serviceRef.current.init({
buildEnv: "staging",
enableLogging: true,
skipRehydration: false // Enable rehydration to restore session
});
// After initialization, check if we have a valid session
// by trying to get the user info
try {
const userInfo = await serviceRef.current.getUserInfo();
const hasValidSession = !!userInfo && typeof userInfo === 'object' && Object.keys(userInfo).length > 0;
if (hasValidSession) {
console.log('User session restored:', userInfo);
setIsAirKitConnected(true);
} else {
console.log('No active session found');
setIsAirKitConnected(false);
}
} catch (err) {
console.log('No active session or error getting user info:', err);
setIsAirKitConnected(false);
}
} catch (err) {
console.error("Error initializing AirKit:", err);
setIsAirKitConnected(false);
} finally {
setIsInitialized(true);
}
};
checkSession();
}, [isInitialized]);
// Connect and login function
const connectWithAirKit = useCallback(async () => {
if (!serviceRef.current) return;
try {
await serviceRef.current.init({
buildEnv: "staging",
enableLogging: true,
skipRehydration: false // Enable rehydration to maintain session
});
await serviceRef.current.login();
setIsAirKitConnected(true);
} catch (err) {
console.error("AIR Kit login failed:", err);
setIsAirKitConnected(false);
throw err; // Re-throw to allow error handling in the component
}
}, []);
// Get user information function
const getUserInformation = useCallback(async (): Promise<AirKitUserDetails | undefined> => {
if (!serviceRef.current) return;
try {
// @ts-ignore - We know getUserInfo exists on the service
const userInfo = await serviceRef.current.getUserInfo();
console.log('User info received:', userInfo);
return userInfo as AirKitUserDetails;
} catch (err) {
console.error("AIR Kit getUserInfo failed:", err);
throw err;
}
}, []);
const goToPartner = useCallback(async (partnerUrl: string) => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
return serviceRef.current.goToPartner(partnerUrl);
}, []);
const getProvider = useCallback(async () => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
return await serviceRef.current.getProvider();
}, []);
const getAccessToken = useCallback(async (): Promise<object | null> => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
return await serviceRef.current.getAccessToken();
}, []);
const preloadWallet = useCallback(async () => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
await serviceRef.current.preloadWallet();
}, []);
const setupOrUpdateMfa = useCallback(async () => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
await serviceRef.current.setupOrUpdateMfa();
}, []);
const claimAirId = useCallback(async () => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
// Assuming there's a claimAirId method in the SDK
// @ts-ignore - If the method doesn't exist, this will be a no-op
if (typeof serviceRef.current.claimAirId === 'function') {
// @ts-ignore
await serviceRef.current.claimAirId();
} else {
console.warn('claimAirId method not available in the current SDK version');
}
}, []);
const isSmartAccountDeployed = useCallback(async (): Promise<boolean> => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
return serviceRef.current.isSmartAccountDeployed();
}, []);
const deploySmartAccount = useCallback(async () => {
if (!serviceRef.current) throw new Error("AirKit service not initialized");
// Assuming there's a deploySmartAccount method in the SDK
// @ts-ignore - If the method doesn't exist, this will be a no-op
if (typeof serviceRef.current.deploySmartAccount === 'function') {
// @ts-ignore
await serviceRef.current.deploySmartAccount();
} else {
console.warn('deploySmartAccount method not available in the current SDK version');
}
}, []);
const logout = useCallback(async () => {
if (!serviceRef.current) return; // Early return if not initialized
console.log('Explicit logout called');
try {
// @ts-ignore - Check if logout method exists
if (typeof serviceRef.current.logout === 'function') {
// @ts-ignore
await serviceRef.current.logout();
}
setIsAirKitConnected(false);
} catch (error) {
console.error('Error during logout:', error);
throw error; // Re-throw to allow error handling in the calling component
}
}, []);
const clearInit = useCallback(() => {
// This would typically clear any initialization state
// Implementation depends on SDK requirements
console.log('Clear init called');
}, []);
const on = useCallback((event: string, callback: (...args: any[]) => void) => {
if (!serviceRef.current) return;
// @ts-ignore - If the method doesn't exist, this will be a no-op
if (typeof serviceRef.current.on === 'function') {
// @ts-ignore
serviceRef.current.on(event, callback);
}
}, []);
const off = useCallback((event: string, callback: (...args: any[]) => void) => {
if (!serviceRef.current) return;
// @ts-ignore - If the method doesn't exist, this will be a no-op
if (typeof serviceRef.current.off === 'function') {
// @ts-ignore
serviceRef.current.off(event, callback);
}
}, []);
const addUser = useCallback(async () => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.addUser();
}, []);
const addEmail = useCallback(async (email: string) => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.addEmail(email);
}, []);
const addWallet = useCallback(async (address: string, signature: string, message: string, walletType: string = 'metamask') => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.addWallet(address, signature, message, walletType);
}, []);
const deleteWallet = useCallback(async (walletId: string) => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.deleteWallet(walletId);
}, []);
const getUserWallets = useCallback(async () => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.getUserWallets();
}, []);
const getUser = useCallback(async () => {
try {
if (!airkitApiRef.current) {
console.error('AirKit API not initialized - attempting to initialize...');
// Try to initialize if not already done
if (!serviceRef.current) {
serviceRef.current = getAirService();
airkitApiRef.current = new AirKitApi();
}
if (!airkitApiRef.current) {
throw new Error('Failed to initialize AirKit API');
}
}
console.log('Calling getUser on AirKit API...');
const result = await airkitApiRef.current.getUser();
console.log('getUser result:', result);
return result;
} catch (error) {
console.error('Error in getUser:', {
error: error instanceof Error ? error.message : 'Unknown error',
hasAirKitApi: !!airkitApiRef.current,
hasAirService: !!serviceRef.current,
isAirKitConnected,
timestamp: new Date().toISOString()
});
throw error;
}
}, [isAirKitConnected]);
const setDefaultWallet = useCallback(async (walletId: string) => {
if (!airkitApiRef.current) throw new Error("AirKit API not initialized");
return airkitApiRef.current.setDefaultWallet(walletId);
}, []);
const contextValue = {
isAirKitConnected,
connectWithAirKit,
getUserInformation,
goToPartner,
getProvider,
getAccessToken,
preloadWallet,
setupOrUpdateMfa,
claimAirId,
isSmartAccountDeployed,
deploySmartAccount,
logout,
clearInit,
on,
off,
addUser,
addEmail,
addWallet,
deleteWallet,
getUserWallets,
setDefaultWallet,
getUser,
};
return (
<AirKitContext.Provider value={contextValue}>
{children}
</AirKitContext.Provider>
);
}
export function useAirKit() {
const context = useContext(AirKitContext);
if (context === undefined) {
throw new Error('useAirKit must be used within an AirKitProvider');
}
return context;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment