Package Exports
- sui.ski
- sui.ski/assets/au.png
- sui.ski/assets/btc-icon.svg
- sui.ski/assets/diamond-192.png
- sui.ski/assets/iusd-256.png
- sui.ski/assets/iusd.png
- sui.ski/assets/iusd.svg
- sui.ski/assets/ski-192.png
- sui.ski/assets/ski-512.png
- sui.ski/assets/ski-idle.gif
- sui.ski/assets/ski.svg
- sui.ski/assets/sol-icon.svg
- sui.ski/assets/sui-drop.svg
- sui.ski/assets/sui-icon.svg
- sui.ski/assets/sui-ski-qr.svg
- sui.ski/assets/superteam-poster.jpg
- sui.ski/assets/tbai.svg
- sui.ski/assets/waap-icon.svg
- sui.ski/assets/waap-qr.svg
- sui.ski/styles
Readme
.SKI — .Sui Key-In
.SKI once, everywhere.
Native Cross-Chain Wallets via IKA dWallets
Real Bitcoin, Ethereum, and Solana addresses controlled by your Sui account — no bridges, no wrapping, no custodians. Powered by IKA's 2PC-MPC threshold signatures.
▶ Watch the demo — streamed from Walrus, Sui's decentralized storage protocol.
What One Sui Account Controls
| Curve | Chains | Address Format |
|---|---|---|
| secp256k1 (1 DKG) | Bitcoin, Ethereum, Base, Polygon, Arbitrum, Optimism | bc1q..., 0x... |
| ed25519 (1 DKG) | Solana | base58 |
Two DKG ceremonies. Two dWallets. Seven chains. One Sui account.
Why This Matters
- No bridges — BTC stays on Bitcoin, SOL stays on Solana. IKA generates real native addresses whose signing is governed by Sui smart contracts.
- Non-collusive security — 2PC-MPC means neither the user nor the network can sign alone. 100+ mainnet operators with Byzantine threshold.
- Quantum-ready architecture — Sui's flag-byte signature scheme (
flag || sig || pk) lets the network add post-quantum primitives via a new flag byte — no hard fork, no address migration. IKA's per-curve DKG is inherently modular, so post-quantum dWallets slot in alongside existing ones. See Mysten Labs' research on post-quantum readiness in EdDSA chains and our full analysis indocs/ika-quantum-resistance.md.
Domain structure
| Domain | Purpose |
|---|---|
sui.ski |
Root — main application, embeddable widget, API endpoints |
<name>.sui.ski |
SuiNS profile pages (e.g. brando.sui.ski for brando.sui) |
All subdomains with 3+ characters are reserved for SuiNS profile pages. There are no dedicated subdomains for the app, API, or RPC — everything routes through the root domain.
A cross-domain session cookie (ski:xdomain) is set on domain=sui.ski so authentication persists across the root and all profile subdomains.
UI overview
The .SKI header bar renders up to four elements in a row:
Dot button (ski-dot)
Small status indicator on the far left. Shows the connected wallet's shape (diamond = address only, blue square = has SuiNS name, green circle = unconnected). Clicking it opens the SKI modal when disconnected, or toggles the SKI menu when connected. The dot carries a Splash drop overlay when a sponsor is active.
Profile pill (ski-profile)
Displays the connected wallet icon, social badge (WaaP provider), SuiNS name, and live balance. Clicking the pill opens the wallet's .sui.ski profile page in a new tab when a SuiNS name is set. The balance cycles between SUI-primary and USD-primary on click.
SKI button (ski-btn)
The main branded button showing the SKI logo and optionally the wallet shape, SuiNS name, and Splash drop. When disconnected, clicking it opens the SKI modal. When connected, it toggles the SKI menu. The modal and menu are mutually exclusive — opening one always closes the other.
Balance pill
Shows the live USD-equivalent balance with a dollar icon. Click to cycle between SUI and USD display.
SKI modal
A single-column overlay anchored below the SKI button, right-aligned with it. The modal has a fixed header (brand logo, balance, QR code) and a scrollable body containing:
- Key detail pane — the currently selected wallet with SuiNS name, address, balance, network badges, and Ika dWallet status. Defaults to the first blue-square wallet on open, falls back to the first black-diamond, then to WaaP.
- Splash legend — every key that has ever connected from this device, grouped by shape tier (diamond > blue square > green circle) with collapsible group arrows
- + new WaaP row — always present in the green tier; clicking it shows a placeholder detail pane, and clicking that detail header disconnects any active WaaP session and opens a fresh WaaP OAuth modal
- Wallet list (alternative layout) — one row per installed wallet extension with shape badges and social icons
Each legend row shows the key shape, SuiNS name badge, truncated hex address (right-justified), and the wallet provider icon (Phantom, Backpack, WaaP social icon, Slush, Suiet, etc.).
Long-press lock — hold a row for 2.2 s to lock the detail pane to that wallet. An amber ring indicates locked state. Long-press again to unlock.
Layout toggle — the Splash/List switch at the bottom lets users choose between the splash legend view and the plain wallet list. Preference is persisted in localStorage.
SKI menu
A dropdown menu beneath the SKI button (mutually exclusive with the modal). Contains:
- SuiNS name management — register new
.suinames, set default, view owned names with renewal dates - Shade orders — privacy-preserving grace-period domain sniping with commitment-reveal
- Disconnect button
- Manage Keys — opens the modal from the menu
SuiNS integration
Full SuiNS lifecycle from the SKI menu:
- Register — search and register
.suinames with instant tier pricing, pay with SUI, USDC, or NS tokens - Set default — change your primary SuiNS name (updates the SKI button and profile instantly)
- Target address — view and copy the address a name points to, with color-coded status (purple = self, green = available, orange = kiosk-listed)
- Owned names — scrollable chip grid showing all names owned by the connected wallet, with grace-period expiry warnings and renewal cost estimates
Session layer
After connecting, .SKI requests one personal message signature to prove key ownership. The signed proof is tied to a FingerprintJS visitorId (device fingerprint) and stored in localStorage. On reload the session is restored silently — no re-signing required until it expires (7 days for software wallets, 24 hours for hardware/Keystone).
Session format: { address, signature, bytes, visitorId, expiresAt } — stored under ski:session.
Splash sponsorship
Splash is a device-level gas sponsor system. A wallet owner activates Splash to cover gas fees for every key connected from the same device. Sponsored transactions use Sui's sponsored transaction flow.
- Activate via the Splash button in the modal header or the detail card
- Devices that connect through
?splash={address}are enrolled automatically - SuiNS names work as the sponsor parameter:
?splash=brando.sui - The drop badge appears on keys covered by an active sponsor
Shade
Privacy-preserving SuiNS grace-period domain sniping via on-chain commitment-reveal. A Move contract stores only keccak256(domain || execute_after_ms || target_address || salt) — the domain name, target address, and execution timestamp remain hidden until reveal at execution time.
- ShadeOrder — a shared object holding the owner address, escrowed SUI balance, and opaque commitment hash
execute()— permissionless; anyone with the preimage can call, enabling keeper botscancel()— owner-only; returns escrowed SUI- ShadeExecutorAgent — Cloudflare Durable Object that auto-executes orders at grace-period expiry via DO Alarms, using a dedicated keeper address with its own gas
- Three execution routes: SUI->NS (DeepBook), SUI->USDC->NS (two-hop), or SUI direct fallback
Contract: 0xfcd0b2b4f69758cd3ed0d35a55335417cac6304017c3c5d9a5aaff75c367aaff
See SHIELD.md for the full security model and threat analysis.
WaaP (social login)
WaaP provides custodial wallet creation via social login, email, phone, and biometrics — no browser extension required. A static placeholder wallet is always present in the SKI modal so the "+ new WaaP" row renders instantly on page load, before the WaaP SDK finishes initializing.
| Auth method | Status |
|---|---|
| Social (X, Google, Discord, GitHub) | Active |
| Active | |
| Phone | Active |
| Biometrics | Active |
WaaP accounts appear in the legend with their social provider icon (X, Google, Discord, or email envelope). Clicking an existing WaaP blue-square row reconnects via encrypted device proof (zero-modal). Clicking "+ new WaaP" forces a full disconnect and opens a fresh OAuth flow.
Install
npm install sui.ski
# or
bun add sui.skiEmbed via script tag (CDN)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sui.ski/public/styles.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/sui.ski/public/dist/ski.js"></script>Add the widget markup anywhere in your <body>:
<div class="ski-header">
<div class="ski-wallet" id="ski-wallet">
<button class="ski-btn ski-dot" id="ski-dot" style="display:none"></button>
<div id="ski-profile"></div>
<button class="ski-btn" id="ski-btn" style="display:none"></button>
<div id="ski-menu"></div>
</div>
</div>
<div id="ski-modal"></div>The script auto-initializes on load — no further JS required.
Embed via bundler
import 'sui.ski';Same DOM markup as above is required.
Events
window.addEventListener('ski:wallet-connected', (e: CustomEvent) => {
const { address, walletName } = e.detail;
});
window.addEventListener('ski:wallet-disconnected', () => {});
// Request sign-in (opens modal, then triggers sign + redirect to sui.ski)
window.dispatchEvent(new CustomEvent('ski:request-signin'));Requesting a transaction
import { Transaction } from '@mysten/sui/transactions';
const tx = new Transaction();
// ... build your transaction
window.dispatchEvent(new CustomEvent('ski:sign-and-execute-transaction', {
detail: { transaction: tx, requestId: 'my-req-1' }
}));
window.addEventListener('ski:transaction-result', (e: CustomEvent) => {
const { requestId, success, digest, error } = e.detail;
});If a Splash sponsor is active, the sponsored flow is used automatically.
Modal API
import { openModal } from 'sui.ski';
import { setModalLayout, type ModalLayout } from 'sui.ski';
// Layouts: 'splash' (default), 'list' (wallet list only), 'layout2' (no Splash strip)
setModalLayout('list');Supported wallets
Any wallet implementing the Sui Wallet Standard:
| Wallet | Shape | Notes |
|---|---|---|
| Phantom | Diamond/Blue | |
| Backpack | Diamond/Blue | Keystone hardware via Backpack supported (24 h session) |
| Slush | Green | |
| Suiet | Green | |
| Keystone | Diamond | Direct extension; auto-prompts on connect |
| WaaP | Diamond/Blue/Green | Social/email/phone/biometrics wallet; provider badge displayed |
| Any Sui Wallet Standard extension | Varies |
Shape is determined by state: green circle = never connected, black diamond = connected (no SuiNS), blue square = connected with SuiNS name.
Self-hosting / Cloudflare Worker deploy
bun install
npx wrangler login
bun run build && npx wrangler deployThe worker hosts four Durable Objects:
| Binding | Purpose |
|---|---|
SessionAgent |
Verifies signed sessions server-side |
SponsorAgent |
Manages Splash sponsor state |
SplashDeviceAgent |
Tracks per-device Splash activation (keyed by FingerprintJS visitorId) |
ShadeExecutorAgent |
Auto-executes Shade orders at grace-period expiry via DO Alarms |
API routes served by the worker:
| Route | Purpose |
|---|---|
/agents/* |
WebSocket upgrade for Durable Object agents |
/api/health |
Health check |
/api/shade/* |
Shade order management (poke, status, schedule) |
/api/tradeport/listing/:label |
TradePort listing proxy |
IKA dWallet Integration
Native cross-chain addresses via IKA's 2PC-MPC threshold signatures. Two dWallets (secp256k1 + ed25519) control real Bitcoin, Ethereum, and Solana addresses — no bridges, no wrapping, no custodians.
Architecture
| Layer | File | Purpose |
|---|---|---|
| Chain registry | src/client/chains.ts |
CAIP-2 chain → IKA curve/algo/derivation mapping |
| Address derivation | src/client/ika.ts |
Extract pubkey from dWallet → derive chain addresses |
| Multi-curve DKG | src/client/ika.ts |
Curve.SECP256K1 (BTC/EVM) + Curve.ED25519 (Solana) |
| Signing ceremony | src/client/ika-signing.ts |
5-phase 2PC-MPC signing with browser presign pool |
| gRPC adapter | src/server/grpc-7k-adapter.ts |
Wraps SuiGrpcClient for 7K SDK compatibility |
| DKG provisioning | src/server/ika-provision.ts |
Server-side sponsored dWallet creation |
| SuiNS gate | src/server/index.ts |
Gas sponsorship gated by SuiNS NFT ownership |
| Quantum analysis | docs/ika-quantum-resistance.md |
Signature schemes, Shor's algorithm impact, migration paths |
How it works
- Sign in → check for existing dWallets via
getCrossChainStatus()(scans all caps, detects curve) - No dWallet + has SuiNS → sponsored DKG tx (SUI→IKA swap via Cetus CLMM in same PTB), user co-signs
- Network selector → choose Sui, Bitcoin, or Solana view
- Create → runs DKG for the correct curve (secp256k1 for BTC, ed25519 for Solana), skips if already exists
- dWallet active → derive native address from public output (P2WPKH, EVM keccak, or base58)
Post-Quantum Readiness
Both secp256k1 and ed25519 are vulnerable to Shor's algorithm. The architecture is designed for the transition:
- Sui's flag-byte scheme — adding post-quantum signatures = new flag byte + verifier, no hard fork
- IKA's per-curve DKG — post-quantum dWallets coexist with existing ones, users migrate at their own pace
- Defense in depth — even before target chains go post-quantum, Sui's authorization layer can adopt quantum-safe auth independently
- Mysten Labs research — ePrint 2025/1368 describes backward-compatible quantum-safe migration for EdDSA chains (Sui, Solana, Near, Stellar, Cosmos)
Full analysis: docs/ika-quantum-resistance.md
Credits
Chain registry and signing ceremony adapted from inkwell-finance/ows-ika (MIT) — Inkwell Finance's OpenWallet Standard adapter for IKA dWallets.
Stack
@mysten/sui^2.5.1,@mysten/suins^1.0.2,@human.tech/waap-sdk^1.2.2,@ika.xyz/sdk^0.3.1- Transport:
SuiGrpcClientprimary withSuiGraphQLClientfallback (no JSON-RPC) - Crypto:
@noble/hashes(SHA-256, RIPEMD-160),@scure/base(bech32) - Build:
bun build src/ski.ts --outdir public/dist --target browser - Deploy: Cloudflare Workers + Wrangler 4.77
Local development
bun install
bun run dev # watches src/ski.ts, rebuilds on change
# in a second terminal:
bun run dev:wrangler # wrangler dev with hot reloadOpen http://localhost:8787 — always use http://localhost (not file://) so wallet extensions have a valid origin.
License
MIT
