@cygnus-wealth/wallet-integration-system
Version:
Multi-chain wallet integration system for CygnusWealth
241 lines (240 loc) • 9.73 kB
JavaScript
import { Chain, IntegrationSource } from '@cygnus-wealth/data-models';
export class SuiWalletIntegration {
chain;
source;
provider = null;
walletStandard = null;
connected = false;
accounts = [];
activeAccountIndex = 0;
constructor(chain = Chain.SUI, source, _config) {
this.chain = chain;
this.source = source;
// Configuration can be used for future extensions if needed
}
async connect() {
if (typeof window === 'undefined') {
throw new Error('Window not available');
}
try {
// First, try to connect based on the source
if (this.source === IntegrationSource.SUIET || !this.source) {
// Try Suiet wallet (legacy approach)
if (window.suiet) {
return await this.connectSuiet();
}
}
// For Slush or if Suiet not found, try Wallet Standard
const wallet = await this.findWalletStandard();
if (wallet) {
return await this.connectWalletStandard(wallet);
}
// If we still haven't connected, throw appropriate error
if (this.source === IntegrationSource.SUIET) {
throw new Error('Suiet wallet not found. Please install the Suiet browser extension.');
}
else {
throw new Error('No Sui wallet found. Please install Slush or Suiet wallet.');
}
}
catch (error) {
this.connected = false;
throw error;
}
}
async connectSuiet() {
if (!window.suiet) {
throw new Error('Suiet wallet not found');
}
this.provider = window.suiet;
const hasPermission = await this.provider.hasPermissions?.(['viewAccount']) ?? true;
if (!hasPermission && this.provider.requestPermissions) {
await this.provider.requestPermissions();
}
const addresses = await this.provider.getAccounts();
if (addresses.length === 0) {
throw new Error('No accounts found');
}
// Create Account objects for all addresses
this.accounts = addresses.map((address, index) => ({
address,
index,
derivationPath: undefined, // Unknown - wallets don't expose derivation paths
label: index === 0 ? 'Active Account' : `Connected Account ${index}`
}));
this.activeAccountIndex = 0;
this.connected = true;
return {
address: this.accounts[0].address,
chain: this.chain,
source: IntegrationSource.SUIET,
connected: true,
connectedAt: new Date(),
accounts: this.accounts,
activeAccount: this.accounts[0]
};
}
async findWalletStandard() {
return new Promise((resolve) => {
// Set a timeout in case wallet is not available
const timeout = setTimeout(() => resolve(null), 2000);
// Listen for wallet standard ready event
const handleWalletReady = (event) => {
clearTimeout(timeout);
const wallets = new Map();
// Register function to collect wallets
const register = (wallet) => {
// Check if this is a Sui wallet
if (wallet.accounts.some(acc => acc.chains.includes('sui:mainnet'))) {
wallets.set(wallet.name, wallet);
}
};
// Call the app ready handler
if (event.detail && typeof event.detail.register === 'function') {
event.detail.register({ register });
}
// Find appropriate wallet based on source
let targetWallet = null;
// Look for Slush wallet (could be named "Slush" or "Sui Wallet")
for (const [name, wallet] of wallets) {
if (name.toLowerCase().includes('slush') || name.toLowerCase().includes('sui wallet')) {
targetWallet = wallet;
break;
}
}
// If no specific wallet found, use the first available Sui wallet
if (!targetWallet && wallets.size > 0) {
targetWallet = wallets.values().next().value || null;
}
resolve(targetWallet);
};
window.addEventListener('wallet-standard:app-ready', handleWalletReady);
// Dispatch event to trigger wallet registration
window.dispatchEvent(new CustomEvent('wallet-standard:app-ready', {
detail: {
register: (wallet) => {
if (wallet.register) {
handleWalletReady(new CustomEvent('wallet-standard:app-ready', {
detail: wallet
}));
}
}
}
}));
});
}
async connectWalletStandard(wallet) {
this.walletStandard = wallet;
// Get accounts from wallet
const accounts = wallet.accounts.filter(acc => acc.chains.includes('sui:mainnet'));
if (accounts.length === 0) {
throw new Error('No Sui accounts found in wallet');
}
// Use the connect feature if available
const connectFeature = wallet.features['standard:connect'];
if (connectFeature && typeof connectFeature.connect === 'function') {
await connectFeature.connect();
}
// Create Account objects
this.accounts = accounts.map((account, index) => ({
address: account.address,
index,
derivationPath: undefined, // Unknown - wallets don't expose derivation paths
label: index === 0 ? 'Active Account' : `Connected Account ${index}`
}));
this.activeAccountIndex = 0;
this.connected = true;
// Determine the actual source based on wallet name
let actualSource = this.source;
if (wallet.name.toLowerCase().includes('slush') || wallet.name.toLowerCase().includes('sui wallet')) {
actualSource = IntegrationSource.OTHER; // Since SLUSH is not in the enum yet
}
return {
address: this.accounts[0].address,
chain: this.chain,
source: actualSource,
connected: true,
connectedAt: new Date(),
accounts: this.accounts,
activeAccount: this.accounts[0]
};
}
async disconnect() {
// Disconnect wallet
if (this.provider && this.provider.disconnect) {
await this.provider.disconnect();
}
if (this.walletStandard) {
const disconnectFeature = this.walletStandard.features['standard:disconnect'];
if (disconnectFeature && typeof disconnectFeature.disconnect === 'function') {
await disconnectFeature.disconnect();
}
}
this.connected = false;
this.provider = null;
this.walletStandard = null;
this.accounts = [];
this.activeAccountIndex = 0;
}
async getAddress() {
if (!this.connected || this.accounts.length === 0) {
throw new Error('Wallet not connected');
}
return this.accounts[this.activeAccountIndex].address;
}
async getAllAccounts() {
if (!this.connected) {
throw new Error('Wallet not connected');
}
// Try to refresh accounts
if (this.provider && this.provider.getAccounts) {
try {
const addresses = await this.provider.getAccounts();
// Update accounts if changed
if (addresses.length !== this.accounts.length) {
this.accounts = addresses.map((address, index) => ({
address,
index,
derivationPath: undefined,
label: index === 0 ? 'Active Account' : `Connected Account ${index}`
}));
}
}
catch (error) {
console.error('Error fetching accounts:', error);
}
}
else if (this.walletStandard) {
// For wallet standard, accounts are static from initial connection
const accounts = this.walletStandard.accounts.filter(acc => acc.chains.includes('sui:mainnet'));
if (accounts.length !== this.accounts.length) {
this.accounts = accounts.map((account, index) => ({
address: account.address,
index,
derivationPath: undefined,
label: index === 0 ? 'Active Account' : `Connected Account ${index}`
}));
}
}
return [...this.accounts];
}
async switchAccount(address) {
if (!this.connected) {
throw new Error('Wallet not connected');
}
const accountIndex = this.accounts.findIndex(acc => acc.address === address);
if (accountIndex === -1) {
throw new Error(`Account ${address} not found`);
}
this.activeAccountIndex = accountIndex;
}
async getActiveAccount() {
if (!this.connected || this.accounts.length === 0) {
return null;
}
return this.accounts[this.activeAccountIndex];
}
isConnected() {
return this.connected;
}
}