@inferenco/nova-wallet-adapter
v0.1.1
Published
Standalone Nova wallet adapter for Cedra dapps
Readme
Overview
This adapter allows Cedra dApps to connect to two Nova products:
- Nova Desk — Desktop application. The adapter connects directly to Nova Desk's local HTTP bridge at
localhost:21984. No external services required. - Nova Wallet — Mobile wallet app. The adapter connects through nova-service, a hosted relay that brokers end-to-end encrypted communication between the dApp and the wallet via deeplinks.
Both connections are handled transparently — the adapter detects the environment and uses the right transport automatically.
Features
- Nova Desk integration — Direct local HTTP bridge to the Nova Desk desktop application
- Nova Wallet integration — End-to-end encrypted relay via nova-service (X25519 + XChaCha20-Poly1305)
- Dual dApp integration — Plugin adapter (
NovaWallet) and AIP-62 wallet-standard (registerNovaWallet) - Injected provider — Auto-detects
window.inferenco,window.nova, and branded aliases - Deeplinks —
inferenco://URI scheme for wallet handoff on desktop and mobile - Session persistence — Reconnect without re-approval across page reloads
- Zero config — Works out of the box with sensible defaults, fully configurable when needed
Installation
npm install @inferenco/nova-wallet-adapteryarn add @inferenco/nova-wallet-adapterpnpm add @inferenco/nova-wallet-adapterQuick Start
AIP-62 Wallet-Standard (Recommended)
The simplest way to integrate — auto-register Nova as a wallet-standard wallet:
// Side-effect import — registers Nova wallet automatically
import "@inferenco/nova-wallet-adapter/auto-register";Or register manually with options:
import { registerNovaWallet } from "@inferenco/nova-wallet-adapter/aip62";
registerNovaWallet({
forceRegistration: true, // Register even without injected provider
});Plugin Adapter
For dApps using plugin-style adapters:
import { NovaWallet } from "@inferenco/nova-wallet-adapter";
const wallet = new NovaWallet();
// Connect
const account = await wallet.connect();
console.log(account.address);
// Sign a message
const response = await wallet.signMessage({
message: "Hello Nova!",
nonce: "unique-nonce-123",
});
// Sign and submit a transaction
const result = await wallet.signAndSubmitTransaction({
data: {
function: "0x1::coin::transfer",
typeArguments: ["0x1::aptos_coin::AptosCoin"],
functionArguments: ["0xrecipient", 1000],
},
});
// Disconnect
await wallet.disconnect();Using NovaClient Directly
For full control over the connection lifecycle:
import { NovaClient } from "@inferenco/nova-wallet-adapter";
const client = new NovaClient({
bridgeBaseUrl: "http://127.0.0.1:21984",
detectAliases: true,
});
const { account, network } = await client.connect();
const signed = await client.signTransaction(rawTransaction);
const result = await client.signAndSubmitTransaction(transactionPayload);WalletCore Resume Helper
If your dApp uses Cedra WalletCore, call the resume helper during provider bootstrap:
import {
NOVA_CONNECT_NAME,
tryResumeNovaWalletConnection,
} from "@inferenco/nova-wallet-adapter";
await tryResumeNovaWalletConnection(walletCore);This resumes pending mobile callback state after browser reloads and reconnects through stored sessions without app-specific bridge logic.
Architecture
The adapter provides two dApp integration surfaces backed by a shared NovaClient, which connects to Nova Desk or Nova Wallet depending on the environment:
┌─────────────────────────────────────────────────────────┐
│ Your dApp │
│ │
│ NovaWallet (plugin) ─┐ │
│ ├──▶ NovaClient (core logic) │
│ AIP-62 Bridge ────────┘ │ │
└───────────────────────────────────┼─────────────────────┘
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Injected │ │ Nova Desk │ │ Nova Wallet │
│ Provider │ │ (desktop) │ │ (mobile) │
│ │ │ │ │ │
│ window.nova │ │ localhost │ │ nova-service │
│ window. │ │ :21984 │ │ + deeplink │
│ inferenco │ │ HTTP bridge │ │ E2E encrypt │
└──────────────┘ └──────────────┘ └──────────────┘How Connections Work
Desktop — Nova Desk: The adapter connects to Nova Desk's local HTTP bridge at http://127.0.0.1:21984. Requests are initiated, then polled until the user approves in the Nova Desk UI. Sessions persist across page reloads.
Mobile — Nova Wallet: The adapter creates an encrypted pairing through nova-service, then launches an inferenco:// deeplink to hand off to Nova Wallet. The user approves in the app, and the result is returned through the relay. All communication is end-to-end encrypted.
Injected provider: When a Nova extension is installed, the adapter calls it directly — no bridge or relay needed.
Connection Order
- Injected provider —
window.inferenco/window.nova(instant, direct) - Stored session — validates and reuses a prior Nova Desk or Nova Wallet session
- Nova Desk bridge — local HTTP connection on desktop browsers
- Nova Wallet relay — encrypted relay + deeplink on mobile browsers
- Desktop deeplink —
inferenco://loginhandoff when Nova Desk is not running
Nova Desk and nova-service are independent. Nova Desk works without nova-service, and Nova Wallet works without Nova Desk.
For more detail, see Architecture docs.
API Reference
NovaWallet
The plugin adapter class, compatible with plugin-style wallet consumers.
| Method | Returns | Description |
|--------|---------|-------------|
| connect() | Promise<AccountInfo> | Connect to Nova Desk or Nova Wallet |
| account() | Promise<AccountInfo> | Get current account info |
| disconnect() | Promise<void> | Disconnect and clear session |
| signMessage(input) | Promise<CedraSignMessageOutput> | Sign an arbitrary message |
| signTransaction(tx, opts?) | Promise<Uint8Array \| { authenticator, rawTransaction?: AnyRawTransaction }> | Sign a transaction without submitting |
| signAndSubmitTransaction(tx, opts?) | Promise<CedraSignAndSubmitTransactionOutput> | Sign and submit a transaction |
| signAndSubmitBCSTransaction(tx, opts?) | Promise<CedraSignAndSubmitTransactionOutput> | Sign and submit (BCS variant) |
| onAccountChange(cb) | Promise<void> | Subscribe to account changes |
| onNetworkChange(cb) | Promise<void> | Subscribe to network changes |
| deeplinkProvider(url?) | string | Generate a deeplink URL |
signTransaction accepts prebuilt SDK transactions and Cedra wallet-standard v1.1
inputs, including secondarySigners and fee payer fields. When the wallet returns
a signed multi-agent transaction, the adapter preserves the SDK AnyRawTransaction
wrapper so secondary signer and fee payer metadata remains available for submission.
Properties:
| Property | Type | Description |
|----------|------|-------------|
| name | "Nova Connect" | Wallet display name |
| url | string | Wallet website URL |
| icon | string | Base64 SVG icon |
| readyState | NovaWalletReadyState | Current detection state |
| connecting | boolean | Connection in progress |
| connected | boolean | Currently connected |
| publicAccount | NovaAccountKeys | Cached public key info |
| network | NovaNetworkInfo | Current network info |
registerNovaWallet(options?)
Registers Nova as an AIP-62 wallet-standard wallet. Implements all Cedra standard features:
cedra:connect,cedra:disconnect,cedra:account,cedra:networkcedra:signMessage,cedra:signTransaction,cedra:signAndSubmitTransactioncedra:onAccountChange,cedra:onNetworkChangecedra:openInMobileApp
NovaClient
The core client powering both adapter surfaces. Use directly for advanced control.
| Method | Returns | Description |
|--------|---------|-------------|
| connect() | Promise<{ account, network }> | Connect to Nova Desk or Nova Wallet |
| disconnect() | Promise<void> | Disconnect and revoke session |
| getAccount() | Promise<AccountInfo> | Fetch account from provider or session |
| getNetwork() | Promise<NetworkInfo> | Fetch current network |
| signMessage(input) | Promise<CedraSignMessageOutput> | Sign message via active transport |
| signMessageAndVerify(input) | Promise<boolean> | Sign and verify locally |
| signTransaction(tx, opts?) | Promise<NovaSignTransactionResult> | Sign transaction |
| signAndSubmitTransaction(tx, opts?) | Promise<CedraSignAndSubmitTransactionOutput> | Sign and submit |
| hasProvider() | boolean | Check for injected provider |
| hasExternalSession() | boolean | Check for stored Nova Desk or Nova Wallet session |
For the complete type reference, see API Reference docs.
Configuration
All options are optional with sensible defaults:
const wallet = new NovaWallet({
// Identity & deeplinks
deeplinkBaseUrl: "inferenco://connect?callback=",
deeplinkScheme: "inferenco",
websiteUrl: "https://inferenco.com/nova-desk",
// Registration behavior
forceRegistration: false, // Register even without injected provider
desktopRegistration: true, // Register on desktop browsers
detectAliases: true, // Check window.cedra / window.aptos
// Network
networkOverride: undefined, // Force specific network
fullnodeUrl: undefined, // Custom fullnode for SDK operations
// Nova Desk (desktop bridge)
bridgeBaseUrl: "http://127.0.0.1:21984",
bridgeConnectTimeoutMs: 1200,
bridgePollIntervalMs: 250,
bridgePollTimeoutMs: 120000,
// Nova Wallet (mobile relay via nova-service)
relayBaseUrl: "https://nova-service-....run.app",
websocketBaseUrl: "wss://nova-service-....run.app/v1/ws",
mobilePollIntervalMs: 1000,
mobileRequestTimeoutMs: 180000,
mobileSocketTimeoutMs: 15000,
});See Configuration docs for detailed descriptions of each option.
Error Handling
All adapter errors are instances of NovaAdapterError with a typed error code:
import { NovaAdapterError, NovaErrorCode } from "@inferenco/nova-wallet-adapter";
try {
await wallet.connect();
} catch (error) {
if (error instanceof NovaAdapterError) {
switch (error.code) {
case NovaErrorCode.UserRejected:
console.log("User rejected the request");
break;
case NovaErrorCode.ConnectionTimeout:
console.log("Connection timed out");
break;
case NovaErrorCode.NotInstalled:
console.log("Nova Wallet not found");
break;
}
}
}| Error Code | Value | Description |
|------------|-------|-------------|
| UserRejected | USER_REJECTED | User declined the request |
| Unauthorized | UNAUTHORIZED | Session expired or invalid |
| Unsupported | UNSUPPORTED | Operation not supported by provider |
| NotInstalled | NOT_INSTALLED | No provider or bridge found |
| ConnectionTimeout | CONNECTION_TIMEOUT | Bridge/relay connection timed out |
| InvalidParams | INVALID_PARAMS | Invalid parameters provided |
| InvalidNetwork | INVALID_NETWORK | Network mismatch or unavailable |
| InternalError | INTERNAL_ERROR | Unexpected internal error |
Provider Detection
The adapter detects Nova providers in this priority order:
window.inferenco— primary namespacewindow.nova— secondary namespacewindow.cedra— only ifisNovaWallet === true(branded)window.aptos— only ifisNovaWallet === true(branded)
Unbranded providers on window.cedra / window.aptos are never wrapped. Alias detection can be disabled with detectAliases: false.
Session Management
Sessions are persisted in localStorage under the key inferenco:nova-session and automatically validated on reconnect:
- Nova Desk sessions are validated against the bridge's
/session/{id}endpoint - Nova Wallet sessions trust the stored encrypted credentials
- Invalid or expired sessions are automatically cleared, triggering a fresh connection flow
Nova Wallet Relay Security
Communication between the dApp and Nova Wallet through nova-service is end-to-end encrypted:
| Layer | Algorithm | Purpose |
|-------|-----------|---------|
| Key exchange | X25519 (ECDH) | Derive shared secret between dApp and wallet |
| Key derivation | HKDF-SHA256 | Deterministic key from shared secret with "nova-connect-relay" info |
| Encryption | XChaCha20-Poly1305 | Authenticated encryption of all request/response payloads |
| Nonce | 24 random bytes | Per-message nonce prevents replay attacks |
The relay server (nova-service) never sees plaintext request or response data.
Exports
The package ships three entry points:
| Entry Point | Import Path | Contents |
|-------------|-------------|----------|
| Main | @inferenco/nova-wallet-adapter | NovaWallet, NovaClient, types, utilities, errors |
| AIP-62 | @inferenco/nova-wallet-adapter/aip62 | createNovaAIP62Wallet, registerNovaWallet |
| Auto-register | @inferenco/nova-wallet-adapter/auto-register | Side-effect registration (import-only) |
Both ESM and CommonJS builds are included with full TypeScript declarations.
Nova Desk Bridge API
When Nova Desk is running locally, the adapter communicates via HTTP:
| Endpoint | Method | Description |
|----------|--------|-------------|
| /connect | GET | Initiate connection request |
| /sign-message | POST | Sign message request |
| /sign-transaction | POST | Sign transaction request |
| /transaction | POST | Sign and submit transaction |
| /request/{requestId} | GET | Poll for request status |
| /session/{sessionId} | GET | Validate existing session |
| /connection | DELETE | Revoke connection |
| /session/{sessionId} | DELETE | Revoke session |
All operations use a poll-based flow: initiate a request, receive a requestId, then poll until the user approves or rejects in Nova Desk.
Development
# Install dependencies
npm install
# Build (ESM + CJS + declarations)
npm run build
# Run tests
npm test
# Watch tests
npm run test:watch
# Type check
npm run typecheckDocumentation
| Document | Description | |----------|-------------| | Architecture | Transport mechanisms, connection flows, and system design | | API Reference | Complete type and method documentation | | Configuration | All options with defaults and examples | | Mobile Relay Protocol | Nova Wallet end-to-end encrypted communication | | Changelog | Version history and release notes |
Dependencies
| Package | Purpose |
|---------|---------|
| @cedra-labs/ts-sdk | Transaction building, network utilities, SDK operations |
| @cedra-labs/wallet-standard | AIP-62 wallet-standard types and registration |
| @noble/curves | X25519 ECDH key exchange (Nova Wallet relay) |
| @noble/hashes | SHA256, HKDF key derivation (Nova Wallet relay) |
| @noble/ciphers | XChaCha20-Poly1305 authenticated encryption (Nova Wallet relay) |
| eventemitter3 | Event emission for account/network change subscriptions |
