darqlet
v2.0.2
Published
DARQlet - Secure self-custody XRPL wallet for DARQ Labs, with complete (71-type) transaction coverage
Maintainers
Readme
DARQlet
Secure self-custody XRPL wallet for DARQ Labs — complete transaction coverage
A self-custody wallet for the XRP Ledger. Keys are encrypted client-side using AES-256-GCM and never leave the wallet — the seed is decrypted only transiently in memory, signing is client-side, and no code path persists it in plaintext, logs it, or transmits it.
Built on xrpl.js v5, DARQlet's TransactionService can construct, sign, submit, and validate every one of the 71 user-submittable XRPL transaction types (the full SubmittableTransaction union) — type-safe via discriminated unions, with the seed-never-leaves invariant held for all of them. The signing core ships as a platform-agnostic darqlet/core entry point (no React/IndexedDB), so web apps and a future React Native shell share one canonical signing contract. This is the wallet DARQ Records and BlockHB converge on.
- Library = exhaustive (all 71 types). UI = curated (a product-relevant subset; the rest are programmatic).
- Coverage matrix, per-type status, and the live testnet smoke results live in
.SYSTEMX/.
Architecture
%%{init: {'theme': 'dark', 'themeVariables': { 'lineColor': '#7c3aed', 'primaryColor': '#0f0f12', 'primaryBorderColor': '#7c3aed'}}}%%
flowchart TB
subgraph USER["👤 User"]
direction TB
PWD[/"Password"/]
SEED[/"Seed Phrase"/]
end
subgraph BROWSER["🌐 Browser app — darqlet (+ darqlet/components)"]
direction TB
subgraph UI["React Components — curated UX"]
WM[WalletModal]
UM[UnlockModal]
TC[TransactionConfirm]
CW[CreateWallet]
IW[ImportWallet]
end
subgraph HOOKS["React Hooks"]
UD[useDARQlet]
UT[useTransaction]
end
subgraph BROWSERBOUND["Browser-bound core"]
KV[KeyVault<br/>AES-256-GCM]
SM[SessionManager<br/>15min · transient seed]
WDB[WalletService<br/>+ WalletDB]
IDB[(IndexedDB<br/>Encrypted only)]
end
end
subgraph AGN["⬡ darqlet/core — platform-agnostic (web + mobile · no React/IndexedDB)"]
direction TB
TS[TransactionService<br/>prepare · sign · submit · validate]
SG[Signer<br/>xrpl.js v5 · client-side]
REG[TX_REGISTRY<br/>all 71 tx types]
end
subgraph XRPL["⛓️ XRP Ledger"]
NET[XRPL Network<br/>Testnet/Mainnet]
end
PWD -.->|"PBKDF2 100k"| KV
SEED -.->|"one-time backup"| CW
WM --> UD
UM --> UD
TC --> UT
UD --> WDB
UT --> TS
WDB --> KV
KV --> IDB
SM --> KV
SM -.->|"transient seed (memory only)"| SG
TS --> SG
TS --> REG
TS --> NET
SG --> NET
style USER fill:#1a1a2e,stroke:#7c3aed,stroke-width:2px
style BROWSER fill:#0f0f12,stroke:#06b6d4,stroke-width:2px
style AGN fill:#0f0f12,stroke:#7c3aed,stroke-width:3px
style XRPL fill:#0f0f12,stroke:#22c55e,stroke-width:2px
style KV fill:#7c3aed,stroke:#fff,stroke-width:2px,color:#fff
style IDB fill:#18181b,stroke:#f59e0b,stroke-width:2px
style REG fill:#18181b,stroke:#06b6d4,stroke-width:2pxSecurity Model
%%{init: {'theme': 'dark'}}%%
flowchart LR
subgraph ENCRYPTION["🔐 Encryption Flow"]
direction TB
P1[Password] --> PBKDF2
PBKDF2 -->|"100,000 iterations<br/>SHA-256"| DK[Derived Key]
DK --> AES[AES-256-GCM]
SEED1[Seed] --> AES
AES --> CT[Ciphertext]
SALT[Random Salt] --> PBKDF2
IV[Random IV] --> AES
end
subgraph STORAGE["💾 Storage"]
direction TB
CT --> IDB2[(IndexedDB)]
SALT --> IDB2
IV --> IDB2
ADDR[Address<br/>Public] --> IDB2
end
subgraph NEVER["❌ Never Stored"]
direction TB
P2[Password]
DK2[Derived Key]
SEED2[Plaintext Seed]
end
style ENCRYPTION fill:#0f0f12,stroke:#7c3aed,stroke-width:2px
style STORAGE fill:#0f0f12,stroke:#22c55e,stroke-width:2px
style NEVER fill:#0f0f12,stroke:#dc2626,stroke-width:2px
style AES fill:#7c3aed,stroke:#fff,color:#fffTransaction Flow
%%{init: {'theme': 'dark'}}%%
sequenceDiagram
participant U as User
participant UI as DARQlet UI
participant TS as TransactionService
participant KV as KeyVault
participant SM as SessionManager
participant XRPL as XRP Ledger
U->>UI: Request NFT Mint
UI->>SM: Check Session
alt Session Locked
SM-->>UI: Locked
UI->>U: Show Unlock Modal
U->>UI: Enter Password
UI->>KV: Decrypt Seed
KV-->>SM: Store in Memory
end
SM-->>UI: Unlocked
UI->>TS: Prepare Transaction
TS->>XRPL: autofill (fee, sequence, LastLedgerSequence)
XRPL-->>TS: account_info / fee
TS->>TS: validate() — client-side, fail fast
TS-->>UI: Show Confirmation
U->>UI: Approve
UI->>SM: Get Seed
SM-->>UI: Decrypted Seed
UI->>TS: Sign Transaction
TS->>XRPL: Submit tx_blob
XRPL-->>TS: tesSUCCESS
TS-->>UI: Transaction Hash
UI->>U: Success!Revenue Architecture
%%{init: {'theme': 'dark'}}%%
flowchart TB
subgraph ARTIST["🎵 Artist"]
AM[Mint Music NFT]
end
subgraph DARQLET["DARQlet"]
TS2[TransactionService]
FC[FeeConfig<br/>0.5 XRP per mint]
end
subgraph TRANSACTIONS["📝 Transactions"]
TX1[NFTokenMint<br/>Network Fee: 0.012 XRP]
TX2[Payment<br/>Platform Fee: 0.5 XRP]
end
subgraph RECIPIENTS["💰 Recipients"]
XRPL2[XRPL Network<br/>Validators]
DARQ[DARQ Labs<br/>Platform Address]
end
AM --> TS2
TS2 --> FC
FC --> TX1
FC --> TX2
TX1 -->|"0.012 XRP"| XRPL2
TX2 -->|"0.5 XRP"| DARQ
style ARTIST fill:#0f0f12,stroke:#7c3aed
style DARQLET fill:#0f0f12,stroke:#06b6d4
style TRANSACTIONS fill:#0f0f12,stroke:#f59e0b
style RECIPIENTS fill:#0f0f12,stroke:#22c55eInstallation
npm install darqlet
# or
yarn add darqlet
# or
pnpm add darqletRequirements: React ≥18 (peer — only needed for the UI/hooks). Ships xrpl.js v5 as a dependency.
Entry points:
| Import | What it gives you | Browser-bound? |
|---|---|---|
| darqlet | Full wallet: hooks, services, security core, registry | yes (IndexedDB + WebCrypto) |
| darqlet/components | React components (WalletModal, UnlockModal, …) | yes (React) |
| darqlet/core | Platform-agnostic signing core — Signer, TransactionService, TX_REGISTRY — no React/IndexedDB | no |
Quick Start
import { useDARQlet, useTransaction } from 'darqlet';
import { WalletModal } from 'darqlet/components';
function App() {
const { activeWallet, isUnlocked } = useDARQlet();
const { requestTransaction, state } = useTransaction();
const handleMintNFT = async () => {
await requestTransaction({
type: 'NFTokenMint',
payload: {
NFTokenTaxon: 0, // required (enforced at compile time)
URI: '697066733A2F2F516D...', // IPFS hash (hex encoded)
Flags: 8, // Transferable
TransferFee: 5000, // 5% royalty
},
});
};
return (
<div>
{activeWallet ? (
<div>
<p>Connected: {activeWallet.address}</p>
<button onClick={handleMintNFT}>Mint NFT</button>
</div>
) : (
<WalletModal
isOpen={true}
onClose={() => {}}
onConnect={async (address) => console.log('Connected:', address)}
/>
)}
</div>
);
}Features
Security
- AES-256-GCM encryption for seed storage
- PBKDF2 key derivation (100,000 iterations)
- 15-minute session timeout with auto-lock
- PIN quick-unlock within 2-minute window
- Keys never leave the browser
Wallet Operations
- Create new XRPL wallet
- Import via seed phrase (12/24 words) or family seed
- Export encrypted backup
- Multiple wallet support
Transaction Support — complete (71 types)
TransactionService covers every user-submittable XRPL transaction type (xrpl.js v5's full SubmittableTransaction union), each type-safe (discriminated unions) and holding the seed-never-leaves invariant:
- Accounts: Payment · AccountSet · AccountDelete · SetRegularKey · SignerListSet · DepositPreauth · TicketCreate · DelegateSet
- Escrow / Checks / Payment channels: EscrowCreate/Finish/Cancel · CheckCreate/Cash/Cancel · PaymentChannelCreate/Fund/Claim
- Tokens & NFTs: TrustSet · Clawback · NFTokenMint/Burn/CreateOffer/CancelOffer/AcceptOffer · NFTokenModify
- Multi-Purpose Tokens (MPT): MPTokenIssuanceCreate/Destroy/Set · MPTokenAuthorize
- DEX & AMM: OfferCreate/Cancel · AMMCreate/Deposit/Withdraw/Vote/Bid/Delete · AMMClawback
- Identity & data: CredentialCreate/Accept/Delete · DIDSet/DIDDelete · OracleSet/OracleDelete · PermissionedDomainSet/Delete
- Vaults & lending: Vault* (6) · Loan*/LoanBroker* (9)
- Cross-chain & batch: XChain* (8) · Batch
The library is exhaustive; the UI is curated (UI_CURATED_TYPES). See Transaction Coverage for live testnet vs. hermetic status.
Revenue Model
- Configurable platform fees
- 0.5 XRP per NFT mint (default)
- White-label ready
API Reference
useDARQlet Hook
const {
wallets, // WalletInfo[]
activeWallet, // WalletInfo | null
isUnlocked, // boolean
canQuickUnlock, // boolean
createWallet, // (options: CreateWalletOptions) => Promise<WalletInfo>
importWallet, // (options: ImportWalletOptions) => Promise<WalletInfo>
deleteWallet, // (id: string, password: string) => Promise<void>
switchWallet, // (id: string) => void
exportWallet, // (password: string) => Promise<WalletBackup>
unlock, // (password: string) => Promise<UnlockResult>
unlockWithPin, // (pin: string) => Promise<UnlockResult>
lock, // () => void
loading, // boolean
error, // string | null
} = useDARQlet();useTransaction Hook
const {
state, // TransactionConfirmState
requestTransaction, // (request: TransactionRequest) => Promise<void>
approve, // () => Promise<TransactionResult>
reject, // () => void
reset, // () => void
} = useTransaction();White-Label Configuration
import { getTransactionService } from 'darqlet';
const transactionService = getTransactionService({
nftMintFee: '1.0', // 1 XRP per mint
platformAddress: 'rYourAddress...',
enabled: true,
}, 'mainnet');Platform-Agnostic Core (darqlet/core)
The construction + signing core is importable without React, IndexedDB, or WebCrypto, so web apps and a future React Native shell share one canonical signing contract. This is the entry point downstream wallets (e.g. BlockHB's adapter) build against.
import {
Signer, // walletFromSeed / sign / deriveAddress / signPaymentChannelClaim …
TransactionService, // prepare → sign → submit → validate (full lifecycle)
getTransactionService, // configured singleton
TX_REGISTRY, // Record<SupportedTransactionType, TxTypeInfo> — all 71 types
ALL_TX_TYPES, // string[] — every type name
TESTNET_TYPES, // submittable on testnet today (46)
HERMETIC_ONLY_TYPES, // constructible but amendment off/absent on testnet (25)
UI_CURATED_TYPES, // the product-relevant subset (17)
} from 'darqlet/core';
import type {
TxTypeInfo, TxCategory, TestnetStatus,
AnyTransactionRequest, TransactionRequest, SupportedTransactionType,
PreparedTransaction, SignedTransaction, TransactionResult, FeeConfig,
} from 'darqlet/core';Keystone: Signer receives a seed only as a function argument at call time and returns only the signed tx_blob/hash — nothing in this entry point stores, logs, transmits, or persists a seed.
TransactionRequest is a discriminated union derived from xrpl.js's SubmittableTransaction, so narrowing on type enforces that transaction type's required fields at compile time:
const svc = getTransactionService({ enabled: false }, 'testnet');
const result = await svc.execute(
{ type: 'EscrowCreate', payload: { Destination: 'r…', Amount: '1000000', FinishAfter: 800000000 } },
seed, // decrypted transiently by the host; never stored by the core
);Transaction Coverage
| Tier | Count | Meaning |
|---|---:|---|
| COVERED-testnet | 46 | constructible in xrpl 5 and amendment live on testnet → build/sign/submit |
| COVERED-hermetic | 25 | constructible+signable, but amendment off/absent on testnet → no submit there |
| Total (library scope) | 71 | every SubmittableTransaction member |
Status is computed from the installed xrpl version intersected with the live testnet amendment set. The full per-type matrix, the verified testnet smoke results, and the assumption ledger live in .SYSTEMX/. Two-tier verification: a hermetic suite proves every type constructs, signs, and keeps the seed contained; a testnet smoke driver proves real round-trips.
CLI
A ground-truth-driven operator CLI ships as the darqlet bin (and node dist/cli.js):
darqlet amendments # live testnet `feature` → enabled amendments
darqlet types --testnet # list types (filters: --testnet | --hermetic | --ui)
darqlet matrix # full coverage matrix from the registry
darqlet hermetic # construct + sign + seed-boundary, all 71 types (offline)
darqlet smoke # hermetic tier + faucet-funded real testnet round-tripsLicense
MIT - DARQ Labs 2024
Links
- DARQ Records - Decentralized Music Platform
- XRPL Documentation
