Status: This package is under active development and is not production‑ready yet. APIs and behavior may change without notice. Use for experimentation and development only.
A Svelte 5 wallet kit for the Sui blockchain. Ship wallet connection, multi‑account management, SuiNS names, SUI balance, transaction and message signing with simple components and typed utilities.
ConnectButton
and a responsive wallet ConnectModal
localStorage
$state
, $effect
, $props
) and full TypeScript typesyarn add sui-svelte-wallet-kit
# or
npm install sui-svelte-wallet-kit
Peer dependency:
yarn add svelte@^5.0.0
<script>
import { SuiModule, ConnectButton } from 'sui-svelte-wallet-kit';
const onConnect = () => {
console.log('Wallet connected');
};
</script>
<SuiModule {onConnect} autoConnect={true}>
<h1>My Sui dApp</h1>
<ConnectButton class="connect-btn" />
</SuiModule>
You can customize wallet display names and ordering using the walletConfig
prop:
<script>
import { SuiModule, ConnectButton } from 'sui-svelte-wallet-kit';
const walletConfig = {
// Custom display names for wallets
customNames: {
'Slush — A Sui wallet': 'Slush',
'Martian Sui Wallet': 'Martian',
'OKX Wallet': 'OKX',
'OneKey Wallet': 'OneKey',
'Surf Wallet': 'Surf',
'TokenPocket Wallet': 'TokenPocket'
},
// Custom ordering (wallets not listed will appear after these in alphabetical order)
ordering: [
'Slush — A Sui wallet', // Show Slush first
'OKX Wallet', // Then OKX
'Phantom', // Then Phantom
'Suiet', // Then Suiet
'Martian Sui Wallet', // Then Martian
'OneKey Wallet', // Then OneKey
'Surf Wallet', // Then Surf
'TokenPocket Wallet' // Then TokenPocket
]
};
</script>
<SuiModule {walletConfig} autoConnect={true}>
<ConnectButton />
</SuiModule>
Configuration Options:
customNames
: Object mapping original wallet names to custom display namesordering
: Array defining the preferred order of wallets in the connect modalNotes:
ordering
will appear after the ordered ones, sorted alphabeticallyProps:
onConnect?: () => void
autoConnect?: boolean
(default: false
)autoSuiNS?: boolean
(default: true
)autoSuiBalance?: boolean
(default: true
)walletConfig?: { customNames?: Record<string, string>; ordering?: string[] }
(optional wallet customization)Props:
class?: string
style?: string
onWalletSelection?: (payload: { wallet: any; installed: boolean; connected: boolean; alreadyConnected?: boolean }) => void
Behavior: toggles between Connect and Disconnect based on connection state. When not connected, clicking the button opens the modal and invokes onWalletSelection
with a payload describing the user selection so you can show a toast if the selected wallet is not installed.
Used internally by SuiModule
. You can access it via getConnectModal()
and call openAndWaitForResponse()
to let users reselect wallets while connected. For convenience, you can also use the switchWallet(options?)
helper (see API Reference).
UI notes:
<script>
import { switchWallet } from 'sui-svelte-wallet-kit';
// Simple programmatic switch (modal stays open until an installed wallet is picked or user cancels)
const simpleSwitch = async () => {
const res = await switchWallet();
if (res?.connected) {
console.log('Switched to', res.wallet?.name);
} else if (res?.cancelled) {
console.log('Switch cancelled');
}
};
</script>
Detecting not-installed wallets from the Connect button:
<script>
import { ConnectButton } from 'sui-svelte-wallet-kit';
const onWalletSelection = (payload) => {
const picked = payload?.wallet ?? payload;
const installed = typeof payload === 'object' ? !!payload?.installed : !!picked?.installed;
if (!installed) {
// Show your toast here
console.log('[Demo] Please install:', picked?.name);
}
};
</script>
<ConnectButton class="connect-btn" {onWalletSelection} />
Enable Google zkLogin via Enoki by passing the zkLoginGoogle
config to SuiModule
.
Requirements:
.apps.googleusercontent.com
)References:
Basic usage:
<script>
import { SuiModule, ConnectButton } from 'sui-svelte-wallet-kit';
const zkLoginGoogle = {
apiKey: 'ENOKI_API_KEY',
googleClientId: 'GOOGLE_CLIENT_ID.apps.googleusercontent.com',
// Optional: choose network: 'mainnet' | 'testnet' | 'devnet'
network: 'testnet'
};
</script>
<SuiModule {zkLoginGoogle} autoConnect={true}>
<ConnectButton />
</SuiModule>
Notes:
zkLoginGoogle
is provided and passes basic validation.GET /v1/app
for an early validity check.zkLoginGoogle.network
. Default is: mainnet
SuiModule
.Exports from sui-svelte-wallet-kit
:
SuiModule
, ConnectButton
, ConnectModal
connectWithModal(onSelection?)
, getConnectModal
, connect(wallet)
, disconnect
, switchWallet(options?)
signAndExecuteTransaction(transaction)
, signMessage(message)
, canSignMessage()
useCurrentWallet()
, lastWalletSelection
useCurrentAccount()
, useAccounts()
, activeAccountIndex
, switchAccount(selector)
, setAccountLabel(name)
, accountLoading
suiNames
, suiNamesLoading
, suiNamesByAddress
suiBalance
, suiBalanceLoading
, suiBalanceByAddress
, refreshSuiBalance(address?, { force?: boolean })
useSuiClient()
walletAdapters
, availableWallets
isZkLoginWallet()
, getZkLoginInfo()
Examples:
<script>
import {
useCurrentAccount,
useSuiClient,
accountLoading,
connectWithModal,
switchWallet,
disconnect,
signAndExecuteTransaction,
signMessage,
canSignMessage,
switchAccount,
suiBalance,
refreshSuiBalance,
isZkLoginWallet,
getZkLoginInfo
} from 'sui-svelte-wallet-kit';
import { Transaction } from '@mysten/sui/transactions';
// Use hooks for reactive state
let account = $derived(useCurrentAccount());
let suiClient = $derived(useSuiClient());
$effect(async () => {
if (account && isZkLoginWallet()) {
const info = await getZkLoginInfo();
console.log('zkLogin session/metadata:', info);
}
});
</script>
Override the modal by targeting its classes:
:global(.modal-overlay) {
background: rgba(0, 0, 0, 0.8);
}
:global(.modal-content) {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 16px;
}
:global(.wallet-button) {
background: #2a2a2a;
border: 1px solid #444;
color: white;
}
:global(.wallet-button:hover) {
border-color: #667eea;
background: #333;
}
Built on @suiet/wallet-sdk
and Wallet Radar. Detects popular Sui wallets such as Slush, Suiet, Sui Wallet, Ethos, Surf, Glass, and others available in the browser.
The package includes full TypeScript support with comprehensive type definitions for all components, utilities, and reactive stores.
Import types directly from the package:
import type {
SuiAccount,
SuiWallet,
WalletConfig,
ZkLoginGoogleConfig,
SignMessageResult,
WalletSelectionPayload,
ConnectionResult,
SwitchWalletOptions
} from 'sui-svelte-wallet-kit';
<script lang="ts">
import { SuiModule, ConnectButton } from 'sui-svelte-wallet-kit';
import type {
WalletConfig,
ZkLoginGoogleConfig,
WalletSelectionPayload
} from 'sui-svelte-wallet-kit';
// Type-safe configuration
const walletConfig: WalletConfig = {
ordering: ['Slush — A Sui wallet', 'OKX Wallet'],
customNames: {
'Slush — A Sui wallet': 'Slush'
}
};
const zkLoginGoogle: ZkLoginGoogleConfig = {
apiKey: 'your-api-key',
googleClientId: 'your-client-id.apps.googleusercontent.com',
network: 'testnet'
};
// Type-safe callback
const onWalletSelection = (payload: WalletSelectionPayload): void => {
console.log('Selected:', payload.wallet.name);
console.log('Installed:', payload.installed);
};
const onConnect = (): void => {
console.log('Connected!');
};
</script>
<SuiModule {walletConfig} {zkLoginGoogle} {onConnect} autoConnect={true}>
<ConnectButton {onWalletSelection} />
</SuiModule>
All reactive stores and functions are fully typed:
<script lang="ts">
import {
useCurrentAccount,
useSuiClient,
useCurrentWallet,
suiBalance,
switchAccount,
signMessage,
refreshSuiBalance
} from 'sui-svelte-wallet-kit';
import type { SuiAccount, SuiWallet, SignMessageResult, SuiClient } from 'sui-svelte-wallet-kit';
// Use hooks for reactive state
let account = $derived(useCurrentAccount());
let client = $derived(useSuiClient());
let currentWallet = $derived(useCurrentWallet());
// Reactive values are type-safe
$effect(() => {
const a: SuiAccount | undefined = account;
const c: SuiClient = client;
const w: SuiWallet | undefined = currentWallet;
const balance: string | null = suiBalance.value;
console.log('Account:', a?.address);
console.log('Client network:', c);
console.log('Wallet:', w?.name);
console.log('Balance:', balance);
});
const handleSwitchAccount = (index: number): boolean => switchAccount(index);
const handleSignMessage = async (message: string): Promise<void> => {
try {
const result: SignMessageResult = await signMessage(message);
console.log('Signature:', result.signature);
console.log('Message bytes:', result.messageBytes);
} catch (error) {
console.error('Signing failed:', error);
}
};
const handleRefreshBalance = async (address: string): Promise<void> => {
const balance: string | null = await refreshSuiBalance(address, { force: true, ttlMs: 5000 });
console.log('Refreshed balance:', balance);
};
const fetchBalance = async (): Promise<void> => {
if (!account) return;
const balance = await client.getBalance({ owner: account.address });
console.log('Balance:', balance);
};
</script>
<script lang="ts">
import { switchWallet } from 'sui-svelte-wallet-kit';
import type { SwitchWalletOptions, ConnectionResult, SuiWallet } from 'sui-svelte-wallet-kit';
const handleSwitchWallet = async (): Promise<void> => {
const options: SwitchWalletOptions = {
onSelection: ({ wallet, installed }) => {
console.log('Selected:', wallet.name, 'Installed:', installed);
},
shouldConnect: ({ selectedWallet, currentWallet }) => {
// Skip if selecting same wallet
return selectedWallet?.name !== currentWallet?.name;
},
onBeforeDisconnect: (current, selected) => {
console.log('Switching from', current?.name, 'to', selected?.name);
},
onConnected: (wallet) => {
console.log('Connected to:', wallet?.name);
},
onCancel: () => {
console.log('Switch cancelled');
}
};
const result: ConnectionResult = await switchWallet(options);
if (result.connected) {
console.log('Successfully switched to:', result.wallet?.name);
} else if (result.cancelled) {
console.log('User cancelled the switch');
}
};
</script>
Core Types:
SuiAccount
- Account information with address, chains, labelSuiWallet
- Wallet information with name, icon, adapterWalletConfig
- Configuration for wallet ordering and custom namesZkLoginGoogleConfig
- Configuration for Enoki zkLogin with GoogleZkLoginInfo
- zkLogin session and metadataResult Types:
SignMessageResult
- Message signature result with signature and messageBytesConnectionResult
- Connection/switch result with wallet info and statusWalletSelectionPayload
- Wallet selection payload with wallet and installed statusOptions Types:
SwitchWalletOptions
- Options for switchWallet()
with callbacksRefreshBalanceOptions
- Options for refreshSuiBalance()
with force and TTLStore Types:
ReadableStore<T>
- Base reactive store with value
getterAccountStore
- Account store with setAccount/removeAccount methodsSuiNamesStore
- SuiNS names store with clear methodLastWalletSelectionStore
- Last selection store with clear methodWith TypeScript enabled, you get:
Ensure your tsconfig.json
includes:
{
"compilerOptions": {
"moduleResolution": "bundler",
"strict": true
}
}
# Install deps
yarn install
# Build the package
yarn run prepack
# Lint package exports
yarn run lint:package
# Optional sanity check before publishing
./scripts/publish-check.sh
MIT — see LICENSE
.
https://github.com/teededung/sui-svelte-wallet-kit
https://github.com/teededung/sui-svelte-wallet-kit/issues
Below are a few practical examples adapted from the demo page (src/routes/+page.svelte
).
Connect, switch, disconnect with UX callbacks:
<script>
import {
ConnectButton,
connectWithModal,
switchWallet,
disconnect,
useCurrentWallet
} from 'sui-svelte-wallet-kit';
const onWalletSelection = (payload) => {
const picked = payload?.wallet ?? payload;
const installed = typeof payload === 'object' ? !!payload?.installed : !!picked?.installed;
if (!installed) alert('Please install the wallet: ' + picked?.name);
};
const onSwitchWallet = async () => {
const currentWallet = $derived(useCurrentWallet());
await switchWallet({
onSelection: onWalletSelection,
shouldConnect: ({ selectedWallet }) => {
// Example: skip reconnecting to the same wallet if it lacks native account picker
if (currentWallet?.name && selectedWallet?.name === currentWallet.name) return false;
return true;
}
});
};
</script>
<ConnectButton class="connect-btn" {onWalletSelection} />
<button onclick={onSwitchWallet}>Switch Wallet</button>
<button onclick={disconnect}>Disconnect</button>
Show account info, SuiNS, balance, and refresh balance:
<script>
import {
useCurrentAccount,
useAccounts,
suiNames,
suiNamesLoading,
suiBalance,
suiBalanceLoading,
refreshSuiBalance
} from 'sui-svelte-wallet-kit';
let account = $derived(useCurrentAccount());
const formatSui = (balance) => {
try {
const n = BigInt(balance);
const whole = n / 1000000000n;
const frac = n % 1000000000n;
const fracStr = frac.toString().padStart(9, '0').replace(/0+$/, '');
return fracStr ? `${whole}.${fracStr}` : whole.toString();
} catch (_) {
return balance ?? '0';
}
};
</script>
{#if account}
<p><strong>Address:</strong> {account.address}</p>
<p><strong>Chains:</strong> {account.chains?.join(', ') || 'N/A'}</p>
{#if suiNamesLoading.value}
<p><strong>SuiNS Names:</strong> Loading...</p>
{:else}
<p>
<strong>SuiNS Names:</strong>
{Array.isArray(suiNames.value) && suiNames.value.length > 0
? suiNames.value.join(', ')
: 'N/A'}
</p>
{/if}
<p>
<strong>SUI Balance:</strong>
{#if suiBalanceLoading.value}
Loading...
{:else}
{formatSui(suiBalance.value || '0')} SUI
{/if}
</p>
<button
onclick={() => refreshSuiBalance(account.address)}
disabled={!account || suiBalanceLoading.value}
>
Refresh Balance
</button>
{/if}
Sign and execute a simple transaction:
<script>
import { signAndExecuteTransaction, useCurrentAccount } from 'sui-svelte-wallet-kit';
import { Transaction } from '@mysten/sui/transactions';
let account = $derived(useCurrentAccount());
let isLoading = false;
let transactionResult = null;
let error = null;
const testTransaction = async () => {
if (!account) {
error = 'Please connect your wallet first';
return;
}
isLoading = true;
error = null;
transactionResult = null;
try {
const tx = new Transaction();
// Example: transfer 0 SUI to self (no-op); replace with your own commands
tx.transferObjects([tx.splitCoins(tx.gas, [0])], account.address);
transactionResult = await signAndExecuteTransaction(tx);
} catch (err) {
error = err?.message || 'Transaction failed';
} finally {
isLoading = false;
}
};
</script>
<button onclick={testTransaction} disabled={isLoading}>
{isLoading ? 'Signing Transaction...' : 'Test Transaction (0 SUI transfer)'}
</button>
{#if error}
<p style="color:#fca5a5">{error}</p>
{/if}
{#if transactionResult}
<pre>{JSON.stringify(transactionResult, null, 2)}</pre>
{/if}
Sign a message (works with wallets supporting sui:signMessage
or Enoki sui:signPersonalMessage
):
<script>
import { canSignMessage, signMessage, useCurrentAccount } from 'sui-svelte-wallet-kit';
let account = $derived(useCurrentAccount());
let message = 'Hello, Sui blockchain!';
let signatureResult = null;
let isSigningMessage = false;
let error = null;
const testSignMessage = async () => {
if (!account) {
error = 'Please connect your wallet first';
return;
}
isSigningMessage = true;
error = null;
signatureResult = null;
try {
signatureResult = await signMessage(message);
} catch (err) {
error = err?.message || 'Message signing failed';
} finally {
isSigningMessage = false;
}
};
</script>
{#if account}
{#if canSignMessage()}
<input bind:value={message} placeholder="Enter message to sign" />
<button onclick={testSignMessage} disabled={isSigningMessage}>
{isSigningMessage ? 'Signing Message...' : 'Sign Message'}
</button>
{:else}
<p>Current wallet does not support message signing.</p>
{/if}
{:else}
<p>Connect your wallet to sign messages</p>
{/if}
{#if signatureResult}
<p><strong>Signature:</strong> <code>{signatureResult.signature}</code></p>
{/if}
Show zkLogin (Enoki) session/metadata when connected via Google:
<script>
import { getZkLoginInfo, useCurrentAccount } from 'sui-svelte-wallet-kit';
let account = $derived(useCurrentAccount());
let zkInfo = null;
$effect(async () => {
zkInfo = account ? await getZkLoginInfo() : null;
});
</script>
{#if zkInfo}
<div>
<strong>zkLogin (Enoki)</strong>
{#if zkInfo.metadata}
<p><strong>Provider:</strong> {zkInfo.metadata?.provider || 'N/A'}</p>
{/if}
{#if zkInfo.session}
<p><strong>Session:</strong></p>
<pre>{JSON.stringify(zkInfo.session, null, 2)}</pre>
{:else}
<p>No zkLogin session info.</p>
{/if}
</div>
{/if}
Account switching by index:
<script>
import { accounts, activeAccountIndex, switchAccount } from 'sui-svelte-wallet-kit';
let selectedAccountIndex = -1;
$effect(() => {
selectedAccountIndex = activeAccountIndex.value;
});
const onAccountChange = () => switchAccount(Number(selectedAccountIndex));
</script>
{#if accounts.value.length > 1}
<select bind:value={selectedAccountIndex} onchange={onAccountChange}>
{#each accounts.value as acc, i}
<option value={i} selected={i === activeAccountIndex.value}>
#{i + 1} — {acc.address}
</option>
{/each}
</select>
{/if}