@guveno/wallet-sdk
Guveno Wallet SDK
Add secure crypto custody to your platform — create and manage multi-chain wallets, automate withdrawals, and receive real-time webhooks for Bitcoin, Ethereum, XRP, Polkadot, and more. Built for exchanges, fintechs, and platforms.
A typed TypeScript SDK with client-side key encryption and a fully offline signing path, so the server never holds plaintext key material.
Prefer the terminal? The
@guveno/clipackage wraps this SDK with the same features.
Install
npm install @guveno/wallet-sdk
Requires Node.js 20+.
How it works
There are two credentials, and they do different jobs:
- An API key (
gv_live_...) authenticates every request. Generate one in the Guveno dashboard. It carries your company and a role. - Your encryption password decrypts your recovery phrases. The server only ever stores them sealed to your account's encryption key; the password (set during dashboard onboarding) unlocks that key locally and never leaves your process.
Wallets, addresses, and the sealed secret all live on the server. Listing and reading metadata needs only the API key. Signing — withdrawing, deriving addresses, revealing a phrase — additionally needs your encryption password, because that's what decrypts the key.
Quick start
import { Guveno } from '@guveno/wallet-sdk';
const guveno = new Guveno({ apiKey: process.env.GUVENO_API_KEY });
// Browse — metadata only, no password needed.
const wallets = await guveno.listWallets();
// Load a wallet to sign with it: fetches the sealed secret and unlocks it locally.
const wallet = await guveno.loadWallet(wallets[0].id, process.env.GUVENO_ENCRYPTION_PASSWORD!);
// Derive and register the next receive address (uses the server-tracked index).
const { address } = await wallet.deriveAddress({ label: 'deposits' });
console.log('Deposit to', address.address);
const withdrawal = await wallet.withdraw({
// Omit addressId to auto-select the source: the server picks an address with
// enough balance — and for Bitcoin aggregates UTXOs across the wallet.
assetId: 1,
toAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
amount: '1.5'
});
console.log(withdrawal.status); // 'broadcast' — confirmation arrives via webhooks
wallet.lock(); // wipe key material from memory when done
loadWallet() accepts a numeric id, a wallet name, or a { name, chain, network } selector:
await guveno.loadWallet(wallets[0].id, password); // by id
await guveno.loadWallet('treasury', password); // by name
await guveno.loadWallet({ name: 'treasury', chain: 'bitcoin', network: 'mainnet' }, password);
Names are only unique within a (chain, network), so a bare name that matches wallets on more than one chain/network throws — pass the scoped { name, chain, network } selector or the id to disambiguate.
withdraw() runs the full prepare → sign → broadcast loop: the server returns an unsigned payload, the SDK signs it in process, and broadcasts the signed transaction. The recovery phrase never leaves your machine.
By default the source is auto-selected. Pass addressId to send from one specific address, or (Bitcoin only) sourceAddressIds to restrict UTXO aggregation to a chosen subset of the wallet's addresses. Account-based chains can't combine balances across addresses, so an auto withdrawal throws if no single address covers the amount.
Fees use the server's suggestion by default. Override per call with ethereumGas (Ethereum) or bitcoinFeeRate (Bitcoin, sat/vB). Bitcoin inputs are fixed at prepare time, so a fee rate much higher than suggested can fail to fit the selected inputs.
Creating and importing wallets
// Generate a new recovery phrase, derive the first address, seal it, and register it.
const { wallet, mnemonic, firstAddress } = await guveno.createWallet({
name: 'treasury-btc',
chain: 'bitcoin',
network: 'mainnet', // mainnet | testnet (Bitcoin) | sepolia (Ethereum) | paseo (Polkadot)
words: 24, // 12 or 24-word recovery phrase (defaults to 12)
encryptionPassword: process.env.GUVENO_ENCRYPTION_PASSWORD!
});
console.log(firstAddress.address); // bc1...
console.log('Back this up:', mnemonic); // shown once — store it safely
// Import an existing phrase as a new server-side wallet.
const imported = await guveno.importWallet({
name: 'restored-xrp',
chain: 'xrp',
network: 'mainnet',
mnemonic: 'test test test test test test test test test test test junk',
encryptionPassword: process.env.GUVENO_ENCRYPTION_PASSWORD!
});
Both return a Wallet that's already loaded and ready to use.
Using a loaded wallet
const wallet = await guveno.loadWallet(walletId, encryptionPassword);
await wallet.withdraw({ assetId, toAddress, amount }); // auto-select source
await wallet.withdraw({ addressId, assetId, toAddress, amount }); // specific source
const next = await wallet.deriveAddress({ label: 'deposits' }); // next address at the server index
const { items } = await wallet.listAddresses();
const phrase = await wallet.revealMnemonic(); // audited server-side
const status = await wallet.getWithdrawal(withdrawal.id);
wallet.id; wallet.name; wallet.chain; wallet.network; // metadata getters
- Per-source serialization — withdrawals from the same source (a specific address, or a wallet when auto-selecting) are queued so concurrent sends never collide on a nonce or reuse a UTXO; different sources run in parallel.
- Idempotency — an
idempotencyKeyis auto-generated per withdrawal; pass your own to make a retriedwithdraw(...)replay-safe. - All custodied chains are signable; Polkadot extrinsics are built fully offline from metadata the server includes in the prepared payload.
Headless signing (KMS / HSM / file)
For automated signers that hold their own key material, load a wallet with a key provider instead of a password — no encryption password, and the sealed secret is never fetched from the server:
import { Guveno, FileKeyProvider, KmsKeyProvider } from '@guveno/wallet-sdk';
const guveno = new Guveno({ apiKey: process.env.GUVENO_API_KEY });
// keys.json: { "<keyFingerprint>": "<bip39 mnemonic>", ... }
const wallet = await guveno.loadWallet(walletId, { keys: new FileKeyProvider('/run/secrets/keys.json') });
await wallet.withdraw({ addressId: 100, assetId: 1, toAddress: '0x70997...', amount: '1.5' });
Don't want a plaintext key file? Back it with AWS KMS, GCP KMS, Vault, or an HSM — the plaintext mnemonic exists only transiently in memory while a withdrawal is signed:
const keys = new KmsKeyProvider({
entries: { '<keyFingerprint>': '<base64 KMS ciphertext>' },
decrypt: async (ciphertext) => {
const out = await kms.decrypt({ CiphertextBlob: Buffer.from(ciphertext, 'base64') });
return Buffer.from(out.Plaintext).toString('utf8'); // the mnemonic
}
});
const wallet = await guveno.loadWallet(walletId, { keys });
Key-provider wallets can withdraw() and listAddresses(); deriveAddress() and revealMnemonic() need the encryption-password path (they seal/unseal against the server).
Webhooks and low-level access
The high-level facade covers wallet operations. For everything else — webhooks, renaming/deleting wallets, listing withdrawals — use the underlying client at guveno.client (a GuvenoApiClient):
const webhook = await guveno.client.createWebhook({
type: 'api',
config: { url: 'https://example.com/webhooks/guveno' },
events: ['deposit.confirmed', 'withdrawal.confirmed'] // or ['*'] for all
});
console.log(webhook.signingSecret); // shown only here — store it to verify x-guveno-signature
const deliveries = await guveno.client.listWebhookDeliveries(webhook.id, { limit: 50 });
All event types are exported as WEBHOOK_EVENT_TYPES. Creating/deleting webhooks requires an owner or admin role.
CLI
The guveno command-line tool lives in @guveno/cli — API auth, wallet management, withdrawals, and webhooks from your terminal.
Supported chains
| Chain | Address style |
|---|---|
| Bitcoin | native SegWit bc1... |
| Ethereum | 0x... |
| XRP | classic r... |
| Polkadot | SS58 (1... mainnet) |
We're adding new chains regularly — these four are live today. All use standard HD derivation, so a recovery phrase restores the same accounts in any compatible wallet (Polkadot uses sr25519 substrate junctions, matching Polkadot-JS / Talisman).
Security
- The SDK never logs recovery phrases, decrypted secrets, or private keys.
- The recovery phrase is sealed to your account's X25519 public key before it ever leaves your machine; the server stores only the sealed blob (
keyAccess). - Your encryption password is run through scrypt to unlock your private key in memory; it is never sent to the server and never recoverable if lost.
- Signing is fully offline — the server receives a signed transaction and broadcasts it, never key material.