Skip to main content

Wallet Integration

The CIFER SDK is wallet-agnostic—it works with any wallet that can sign messages. This guide shows you how to connect popular wallet solutions.

The SignerAdapter Interface

All wallet integrations implement this simple interface:

interface SignerAdapter {
getAddress(): Promise<string>;
signMessage(message: string): Promise<string>; // EIP-191 personal_sign
sendTransaction?(txRequest: TxIntent): Promise<TxExecutionResult>; // optional
}

The SDK provides a built-in Eip1193SignerAdapter that works with any EIP-1193 compatible provider, plus you can create custom signers.


MetaMask

MetaMask injects window.ethereum which is EIP-1193 compatible.

import { createCiferSdk, Eip1193SignerAdapter } from 'cifer-sdk';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Check if MetaMask is installed
if (typeof window.ethereum === 'undefined') {
throw new Error('MetaMask is not installed');
}

// Request account access
await window.ethereum.request({ method: 'eth_requestAccounts' });

// Create signer
const signer = new Eip1193SignerAdapter(window.ethereum);
const address = await signer.getAddress();

console.log('Connected:', address);

Handling Account Changes

// Listen for account changes
window.ethereum.on('accountsChanged', (accounts: string[]) => {
if (accounts.length === 0) {
console.log('Wallet disconnected');
} else {
// Clear cached address and reconnect
signer.clearCache();
console.log('Switched to:', accounts[0]);
}
});

// Listen for chain changes
window.ethereum.on('chainChanged', (chainId: string) => {
console.log('Chain changed to:', parseInt(chainId, 16));
// You may want to reload or update your app state
});

WalletConnect

WalletConnect works with mobile wallets by scanning a QR code. Use WalletConnect's provider with the SDK.

Using WalletConnect v2

npm install @walletconnect/modal @walletconnect/ethereum-provider
import { createCiferSdk, Eip1193SignerAdapter } from 'cifer-sdk';
import { EthereumProvider } from '@walletconnect/ethereum-provider';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Create WalletConnect provider
const provider = await EthereumProvider.init({
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID', // Get from cloud.walletconnect.com
chains: [752025], // Ternoa mainnet
optionalChains: [1, 11155111], // Ethereum mainnet, Sepolia
showQrModal: true,
metadata: {
name: 'My CIFER App',
description: 'Quantum-resistant encryption',
url: 'https://myapp.com',
icons: ['https://myapp.com/icon.png'],
},
});

// Connect (shows QR modal)
await provider.connect();

// Create signer from WalletConnect provider
const signer = new Eip1193SignerAdapter(provider);
const address = await signer.getAddress();

console.log('Connected via WalletConnect:', address);

Disconnecting

// Disconnect WalletConnect session
await provider.disconnect();

Thirdweb

Thirdweb provides a unified wallet SDK that supports multiple wallet types.

Using Thirdweb Connect

npm install thirdweb
import { createCiferSdk, Eip1193SignerAdapter } from 'cifer-sdk';
import { createThirdwebClient, defineChain } from 'thirdweb';
import { createWallet, injectedProvider } from 'thirdweb/wallets';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Create Thirdweb client
const thirdwebClient = createThirdwebClient({
clientId: 'YOUR_THIRDWEB_CLIENT_ID', // Get from thirdweb.com/dashboard
});

// Define your chain (if not built-in)
const ternoa = defineChain({
id: 752025,
name: 'Ternoa',
nativeCurrency: { name: 'CAPS', symbol: 'CAPS', decimals: 18 },
rpcUrls: {
default: { http: ['https://mainnet.ternoa.network'] },
},
});

// Create and connect a wallet
const wallet = createWallet('io.metamask'); // or 'walletConnect', 'coinbaseWallet', etc.
const account = await wallet.connect({
client: thirdwebClient,
chain: ternoa,
});

// Get the EIP-1193 provider from Thirdweb
const provider = injectedProvider('io.metamask');

// Create CIFER signer
const signer = new Eip1193SignerAdapter(provider);
const address = await signer.getAddress();

console.log('Connected via Thirdweb:', address);

Using Thirdweb's In-App Wallet

import { inAppWallet } from 'thirdweb/wallets';

// Create an in-app wallet (email/social login)
const wallet = inAppWallet();

// Connect with email
const account = await wallet.connect({
client: thirdwebClient,
chain: ternoa,
strategy: 'email',
email: 'user@example.com',
});

// For in-app wallets, create a custom signer
const signer = {
async getAddress() {
return account.address;
},
async signMessage(message: string) {
return account.signMessage({ message });
},
};

Private Key (Server-Side)

For backend services or scripts, you can sign directly with a private key.

Security

Never expose private keys in frontend code. This approach is only for trusted server environments.

Using ethers.js

npm install ethers
import { createCiferSdk } from 'cifer-sdk';
import { Wallet } from 'ethers';
import type { SignerAdapter } from 'cifer-sdk';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Create ethers wallet from private key
const privateKey = process.env.PRIVATE_KEY!;
const wallet = new Wallet(privateKey);

// Create custom signer adapter
const signer: SignerAdapter = {
async getAddress() {
return wallet.address;
},
async signMessage(message: string) {
return wallet.signMessage(message);
},
};

console.log('Server signer:', await signer.getAddress());

Using viem

npm install viem
import { createCiferSdk } from 'cifer-sdk';
import { privateKeyToAccount } from 'viem/accounts';
import type { SignerAdapter } from 'cifer-sdk';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Create account from private key
const privateKey = process.env.PRIVATE_KEY as `0x${string}`;
const account = privateKeyToAccount(privateKey);

// Create custom signer adapter
const signer: SignerAdapter = {
async getAddress() {
return account.address;
},
async signMessage(message: string) {
return account.signMessage({ message });
},
};

console.log('Server signer:', await signer.getAddress());

Pure Node.js (No Dependencies)

For minimal setups without wallet libraries:

import { createCiferSdk } from 'cifer-sdk';
import { createSign, createHash } from 'crypto';
import { secp256k1 } from '@noble/curves/secp256k1';
import type { SignerAdapter } from 'cifer-sdk';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

const privateKeyHex = process.env.PRIVATE_KEY!.replace('0x', '');
const privateKeyBytes = Buffer.from(privateKeyHex, 'hex');

// Derive address from private key
const publicKey = secp256k1.getPublicKey(privateKeyBytes, false);
const addressHash = createHash('keccak256')
.update(Buffer.from(publicKey.slice(1)))
.digest();
const address = '0x' + addressHash.slice(-20).toString('hex');

// Create custom signer
const signer: SignerAdapter = {
async getAddress() {
return address;
},
async signMessage(message: string) {
// EIP-191 prefix
const prefix = `\x19Ethereum Signed Message:\n${message.length}`;
const prefixedMessage = prefix + message;

// Hash with keccak256
const messageHash = createHash('keccak256')
.update(prefixedMessage)
.digest();

// Sign with secp256k1
const signature = secp256k1.sign(messageHash, privateKeyBytes);
const r = signature.r.toString(16).padStart(64, '0');
const s = signature.s.toString(16).padStart(64, '0');
const v = (signature.recovery + 27).toString(16).padStart(2, '0');

return `0x${r}${s}${v}`;
},
};

console.log('Minimal signer:', await signer.getAddress());

wagmi (React)

If you're using wagmi for React apps, you can easily get an EIP-1193 provider.

npm install wagmi viem @tanstack/react-query
import { createCiferSdk, Eip1193SignerAdapter } from 'cifer-sdk';
import { useAccount, useConnectorClient } from 'wagmi';

function useCiferSigner() {
const { address, isConnected } = useAccount();
const { data: connectorClient } = useConnectorClient();

const getSigner = async () => {
if (!isConnected || !connectorClient) {
throw new Error('Wallet not connected');
}

// Get the underlying provider
const provider = await connectorClient.transport;

// Create CIFER signer
return new Eip1193SignerAdapter(provider);
};

return { getSigner, address, isConnected };
}

// Usage in a component
function EncryptButton() {
const { getSigner, isConnected } = useCiferSigner();

const handleEncrypt = async () => {
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

const signer = await getSigner();

const encrypted = await blackbox.payload.encryptPayload({
chainId: 752025,
secretId: 123n,
plaintext: 'Hello from wagmi!',
signer,
readClient: sdk.readClient,
blackboxUrl: sdk.blackboxUrl,
});

console.log('Encrypted:', encrypted);
};

return (
<button onClick={handleEncrypt} disabled={!isConnected}>
Encrypt Data
</button>
);
}

Coinbase Wallet

Coinbase Wallet also provides an EIP-1193 provider.

npm install @coinbase/wallet-sdk
import { createCiferSdk, Eip1193SignerAdapter } from 'cifer-sdk';
import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk';

// Initialize SDK
const sdk = await createCiferSdk({
blackboxUrl: 'https://cifer-blackbox.ternoa.dev:3010',
});

// Create Coinbase Wallet SDK
const coinbaseWallet = new CoinbaseWalletSDK({
appName: 'My CIFER App',
appLogoUrl: 'https://myapp.com/logo.png',
});

// Create provider for Ternoa
const provider = coinbaseWallet.makeWeb3Provider({
options: 'all', // or 'smartWalletOnly', 'eoaOnly'
});

// Request connection
await provider.request({ method: 'eth_requestAccounts' });

// Create signer
const signer = new Eip1193SignerAdapter(provider);
const address = await signer.getAddress();

console.log('Connected via Coinbase:', address);

Best Practices

1. Handle Connection States

function WalletStatus({ signer }: { signer: SignerAdapter | null }) {
const [address, setAddress] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (signer) {
signer.getAddress()
.then(setAddress)
.catch((e) => setError(e.message));
}
}, [signer]);

if (error) return <div>Error: {error}</div>;
if (!address) return <div>Connecting...</div>;
return <div>Connected: {address}</div>;
}

2. Wrap Signing Errors

async function safeSign(signer: SignerAdapter, message: string) {
try {
return await signer.signMessage(message);
} catch (error) {
if (error.code === 4001) {
throw new Error('User rejected the signature request');
}
throw error;
}
}

3. Support Multiple Wallets

type WalletType = 'metamask' | 'walletconnect' | 'coinbase';

async function createSigner(type: WalletType): Promise<SignerAdapter> {
switch (type) {
case 'metamask':
return new Eip1193SignerAdapter(window.ethereum);

case 'walletconnect':
const wcProvider = await EthereumProvider.init({ /* ... */ });
await wcProvider.connect();
return new Eip1193SignerAdapter(wcProvider);

case 'coinbase':
const cbProvider = coinbaseWallet.makeWeb3Provider();
await cbProvider.request({ method: 'eth_requestAccounts' });
return new Eip1193SignerAdapter(cbProvider);
}
}

Next Steps