Skip to content

Authentication

Vortum supports multiple authentication methods, allowing users to connect with their preferred blockchain wallet or identity provider.

Supported Authentication Methods

ICP Identity Providers

ProviderDescription
Internet IdentityICP's native decentralized identity with biometric/device authentication
NFIDEmail-based identity with Internet Identity compatibility
OISYICP wallet with transaction signing capabilities

Blockchain Wallets

BlockchainWalletsProtocol
SolanaPhantom, SolflareSign-In with Solana (SIWS)
BitcoinPhantom, UnisatSign-In with Bitcoin (SIWB)

Blockchain wallet authentication uses a "Sign-In with X" (SIWx) pattern - users sign a message with their wallet to prove ownership, and Vortum creates a delegated ICP identity linked to that wallet address.

ICP Identity Providers

Internet Identity

typescript
import { VortumClient } from '@vortum/sdk'

const client = await VortumClient.create()

// Login with Internet Identity
await client.auth.loginWithII()

// Check authentication
if (client.isAuthenticated()) {
  console.log('Principal:', client.getPrincipal())
}

// Logout
await client.auth.logout()

NFID

typescript
// Login with NFID (email-based)
await client.auth.loginWithNFID()

OISY Wallet

typescript
// Login with OISY wallet
await client.auth.loginWithOISY()

Blockchain Wallet Authentication

Blockchain wallet authentication is handled in the webapp through the Sign-In with Solana (SIWS) and Sign-In with Bitcoin (SIWB) protocols.

How It Works

  1. Connect Wallet: User connects their Solana or Bitcoin wallet (e.g., Phantom, Solflare, Unisat)
  2. Sign Message: The wallet signs a verification message proving ownership
  3. Create Delegation: Vortum creates a delegated ICP identity linked to the wallet address
  4. Session Established: User can now interact with the platform using their wallet identity

Solana Wallets

Supported wallets:

  • Phantom - Popular multi-chain wallet
  • Solflare - Solana-native wallet

Bitcoin Wallets

Supported wallets:

  • Phantom - Uses BIP-322 message signing
  • Unisat - Uses ECDSA message signing

Session Persistence

Sessions are automatically persisted. Check for existing session on page load:

typescript
const client = await VortumClient.create()

// Initialize auth (restores existing session)
await client.auth.initAuth()

if (client.isAuthenticated()) {
  // User is already logged in
  console.log('Welcome back!')
} else {
  // Show login UI
}

State Subscription

React to authentication changes:

typescript
const unsubscribe = client.subscribe(() => {
  if (client.isAuthenticated()) {
    console.log('Logged in:', client.getPrincipal())
  } else {
    console.log('Logged out')
  }
})

Error Handling

typescript
try {
  await client.auth.loginWithII()
} catch (error) {
  console.error('Login failed:', error)
}

// Or check error state
const error = client.auth.getError()
if (error) {
  console.error('Auth error:', error)
}

// Check if connecting
if (client.auth.isConnecting()) {
  console.log('Authentication in progress...')
}

Account Creation

After authentication, create or get your account:

typescript
await client.auth.loginWithII()

// Get existing account
let account = await client.account.get()

if (!account) {
  // Create new account
  await client.account.create({ name: 'Trader' })
  account = await client.account.get()
}

console.log('Account:', account)

Two-Factor Authentication (TOTP)

Enable TOTP-based 2FA for enhanced security. Vortum uses a Merkle tree commitment scheme - the server never stores your TOTP secret, only a cryptographic commitment.

Check 2FA Status

typescript
const status = await client.totp.getStatus()
console.log('2FA enabled:', status.enabled)
console.log('Account locked:', status.isLocked)
console.log('Recent verification:', status.hasRecentVerification)

Enable 2FA

typescript
if (!status.enabled) {
  // Step 1: Start setup - get encrypted secret
  const setup = await client.totp.startSetup()

  // Step 2: Decrypt and display QR code to user
  // (requires client-side VetKD decryption)

  // Step 3: Confirm with TOTP code and Merkle proof
  await client.totp.confirmSetup({
    code: totpCode,
    commitment: merkleRoot,
    treeStartWindow: currentWindow,
    treeSize: treeSize
  })
}

Verify 2FA

typescript
// Verify 2FA for sensitive operations (withdrawals, settings changes)
await client.totp.verify({
  code: totpCode,
  proof: merkleProof
})

Tree Refresh

The Merkle tree needs periodic refresh (every ~30 days):

typescript
if (status.needsTreeRefresh) {
  await client.totp.refreshTree({
    currentCode: totpCode,
    currentProof: currentProof,
    newCommitment: newMerkleRoot,
    newTreeStartWindow: newWindow,
    newTreeSize: newSize
  })
}

Security Considerations

  • Non-custodial: Your keys never leave your wallet
  • Delegated identity: Blockchain wallet logins create ICP delegations, not direct key access
  • Session expiry: Sessions expire after 7 days for security
  • 2FA protection: Enable TOTP for withdrawals and sensitive operations
  • Merkle commitments: 2FA uses zero-knowledge proofs - server never sees your TOTP secret