This is a JavaScript/TypeScript SDK for the OpenSecret platform. It is framework-agnostic with no React (or other UI-framework) dependency, and runs in the browser, React Native, Node, and Bun.
🚧 We're currently in preview mode, please contact us at team@opensecret.cloud for the preview URL and getting started info 🚧
npm install @agicash/opensecretConfigure the SDK once at startup, then call the API functions directly. The SDK is framework-agnostic — use it from any UI layer (React, Vue, Svelte, vanilla JS) or a non-UI runtime (Node, Bun, CLI, MCP).
import { configure, browserStorage, signIn, signUp, signOut, get, put, list, del } from "@agicash/opensecret";
// Configure once at app initialization
configure({
apiUrl: "{URL}",
clientId: "{PROJECT_UUID}",
storage: browserStorage
});
// Then call the functions directly
await signIn("email", "password"); // tokens are persisted through your StorageProvider
await signUp("email", "password", "inviteCode");
await put("key", "value");
const value = await get("key");
const items = await list();
await del("key");
await signOut();The SDK persists auth tokens and the encrypted-session handshake through a StorageProvider you pass to configure({ storage }). Storage is required.
Browser apps can use the bundled browserStorage helper, which maps to localStorage (persistent) and sessionStorage (session):
import { configure, browserStorage } from "@agicash/opensecret";
configure({ apiUrl: "{URL}", clientId: "{PROJECT_UUID}", storage: browserStorage });For non-browser runtimes (React Native, Node, Bun, CLI, MCP), implement the StorageProvider interface against your own backing store. It has two scopes, each a KeyValueStore (getItem / setItem / removeItem). Methods may be synchronous or return a Promise, so asynchronous stores (React Native AsyncStorage, IndexedDB, async SQLite, remote KV) work too:
import { configure, type StorageProvider } from "@agicash/opensecret";
const storage: StorageProvider = {
// long-lived: auth tokens (access_token, refresh_token); must survive restarts
persistent: {
getItem: (key) => myStore.get(key),
setItem: (key, value) => myStore.set(key, value),
removeItem: (key) => myStore.delete(key)
},
// ephemeral per session: the attestation handshake (sessionKey, sessionId)
session: {
getItem: (key) => mySessionStore.get(key),
setItem: (key, value) => mySessionStore.set(key, value),
removeItem: (key) => mySessionStore.delete(key)
}
};
configure({ apiUrl: "{URL}", clientId: "{PROJECT_UUID}", storage });Configures the OpenSecret SDK with your API URL, client ID, and storage provider. Must be called before using any other SDK functions.
configure({
apiUrl: string, // The URL of your OpenSecret backend
clientId: string, // A UUID that identifies your project/tenant
storage: StorageProvider // Where the SDK persists tokens + session (see Storage)
})Example:
import { configure, browserStorage } from '@agicash/opensecret';
configure({
apiUrl: 'https://api.opensecret.cloud',
clientId: '550e8400-e29b-41d4-a716-446655440000',
storage: browserStorage
});All functions can be imported directly from the package:
import { signIn, signUp, signInGuest, signUpGuest, convertGuestToUserAccount, signOut } from '@agicash/opensecret';
// Sign in with email/password
await signIn(email: string, password: string);
// Sign up new user
await signUp(email: string, password: string, inviteCode: string, name?: string);
// Guest authentication
await signInGuest(id: string, password: string);
const { id, access_token, refresh_token } = await signUpGuest(password: string, inviteCode: string);
// Convert guest to full account
await convertGuestToUserAccount(email: string, password: string, name?: string);
// Sign out
await signOut();import { get, put, list, del } from '@agicash/opensecret';
// Get a value
const value = await get(key: string);
// Store a value
await put(key: string, value: string);
// List all key-value pairs
const items = await list();
// Delete a value
await del(key: string);import { fetchUser, changePassword, generateThirdPartyToken } from '@agicash/opensecret';
// Get current user
const user = await fetchUser();
// Change password
await changePassword(currentPassword: string, newPassword: string);
// Generate third-party JWT token
const { token } = await generateThirdPartyToken(audience?: string);For cryptographic operations, the SDK supports a KeyOptions object with the following structure:
type KeyOptions = {
/**
* BIP-85 derivation path to derive a child mnemonic
* Example: "m/83696968'/39'/0'/12'/0'"
*/
seed_phrase_derivation_path?: string;
/**
* BIP-32 derivation path to derive a child key from the master (or BIP-85 derived) seed
* Example: "m/44'/0'/0'/0/0"
*/
private_key_derivation_path?: string;
};All cryptographic methods accept this KeyOptions object as a parameter to specify derivation options.
-
getPrivateKey(key_options?: KeyOptions): Promise<{ mnemonic: string }>: Retrieves the user's private key mnemonic phrase.- If no key_options are provided, returns the master mnemonic
- If
seed_phrase_derivation_pathis provided, returns a BIP-85 derived child mnemonic - For BIP-85, the path format is typically
m/83696968'/39'/0'/12'/0'where:83696968'is the hardened BIP-85 application number (ASCII for "BIPS")39'is the hardened BIP-39 application (for mnemonic derivation)0'is the hardened coin type (0' for Bitcoin)12'is the hardened entropy in words (12-word mnemonic)0'is the hardened index (can be incremented to generate different phrases)
-
getPrivateKeyBytes(key_options?: KeyOptions): Promise<{ private_key: string }>: Retrieves the private key bytes with flexible derivation options.- Supports multiple derivation approaches:
-
Master key only (no parameters)
- Returns the master private key bytes
-
BIP-32 derivation only
- Uses path format like
m/44'/0'/0'/0/0 - Supports both absolute (starting with "m/") and relative paths
- Supports hardened derivation using either ' or h notation
- Uses path format like
-
BIP-85 derivation only
- Derives a child mnemonic from the master seed using BIP-85
- Then returns the master private key of that derived seed
-
Combined BIP-85 and BIP-32 derivation
- First derives a child mnemonic via BIP-85
- Then applies BIP-32 derivation to that derived seed
Common BIP-32 paths:
- BIP44 (Legacy):
m/44'/0'/0'/0/0 - BIP49 (SegWit):
m/49'/0'/0'/0/0 - BIP84 (Native SegWit):
m/84'/0'/0'/0/0 - BIP86 (Taproot):
m/86'/0'/0'/0/0
-
getPublicKey(algorithm: 'schnorr' | 'ecdsa', key_options?: KeyOptions): Promise<PublicKeyResponse>: Retrieves the user's public key for the specified signing algorithm and derivation options.The derivation paths determine which key is used to generate the public key:
- Master key (no parameters)
- BIP-32 derived key
- BIP-85 derived key
- Combined BIP-85 + BIP-32 derived key
Supports two algorithms:
'schnorr': For Schnorr signatures'ecdsa': For ECDSA signatures
-
signMessage(messageBytes: Uint8Array, algorithm: 'schnorr' | 'ecdsa', key_options?: KeyOptions): Promise<SignatureResponse>: Signs a message using the specified algorithm and derivation options.Example message preparation:
// From string const messageBytes = new TextEncoder().encode("Hello, World!"); // From hex const messageBytes = new Uint8Array(Buffer.from("deadbeef", "hex"));
-
encryptData(data: string, key_options?: KeyOptions): Promise<{ encrypted_data: string }>: Encrypts arbitrary string data using the user's private key with flexible derivation options.Examples:
// Encrypt with master key const { encrypted_data } = await encryptData("Secret message"); // Encrypt with BIP-32 derived key const { encrypted_data } = await encryptData("Secret message", { private_key_derivation_path: "m/44'/0'/0'/0/0" }); // Encrypt with BIP-85 derived key const { encrypted_data } = await encryptData("Secret message", { seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'" }); // Encrypt with combined BIP-85 and BIP-32 derivation const { encrypted_data } = await encryptData("Secret message", { seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'", private_key_derivation_path: "m/44'/0'/0'/0/0" });
-
decryptData(encryptedData: string, key_options?: KeyOptions): Promise<string>: Decrypts data that was previously encrypted with the user's key.IMPORTANT: You must use the exact same derivation options for decryption that were used for encryption.
Examples:
// Decrypt with master key const decrypted = await decryptData(encrypted_data); // Decrypt with BIP-32 derived key const decrypted = await decryptData(encrypted_data, { private_key_derivation_path: "m/44'/0'/0'/0/0" }); // Decrypt with BIP-85 derived key const decrypted = await decryptData(encrypted_data, { seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'" }); // Decrypt with combined BIP-85 and BIP-32 derivation const decrypted = await decryptData(encrypted_data, { seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'", private_key_derivation_path: "m/44'/0'/0'/0/0" });
- Basic Usage with Default Master Key
// Get the master mnemonic
const { mnemonic } = await getPrivateKey();
// Get the master private key bytes
const { private_key } = await getPrivateKeyBytes();
// Sign with the master key
const signature = await signMessage(messageBytes, 'ecdsa');- Using BIP-32 Derivation Only
// Get private key bytes using BIP-32 derivation
const { private_key } = await getPrivateKeyBytes({
private_key_derivation_path: "m/44'/0'/0'/0/0"
});
// Sign with a derived key
const signature = await signMessage(messageBytes, 'ecdsa', {
private_key_derivation_path: "m/44'/0'/0'/0/0"
});- Using BIP-85 Derivation Only
// Get a child mnemonic phrase derived via BIP-85
const { mnemonic } = await getPrivateKey({
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});
// Get master private key of a BIP-85 derived seed
const { private_key } = await getPrivateKeyBytes({
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});- Using Both BIP-85 and BIP-32 Derivation
// Get private key bytes derived through BIP-85 and then BIP-32
const { private_key } = await getPrivateKeyBytes({
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'",
private_key_derivation_path: "m/44'/0'/0'/0/0"
});
// Sign a message with a key derived through both methods
const signature = await signMessage(messageBytes, 'schnorr', {
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'",
private_key_derivation_path: "m/44'/0'/0'/0/0"
});- Encryption/Decryption with Derived Keys
// Encrypt with a BIP-85 derived key
const { encrypted_data } = await encryptData("Secret message", {
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});
// Decrypt using the same derivation path
const decrypted = await decryptData(encrypted_data, {
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});To get encrypted-to-the-gpu AI chat, use the createAiCustomFetch function to create a special version of fetch that handles all the encryption. Because we require the user to be logged in, and do the encryption client-side, this is safe to call from the client.
The easiest way to use this is through the OpenAI client:
npm install openaiimport OpenAI from "openai";
import { configure, createAiCustomFetch, getConfig } from "@agicash/opensecret";
// Configure the SDK
configure({
apiUrl: "https://api.opensecret.cloud",
clientId: "your-project-uuid"
});
// Create the OpenAI client with encrypted fetch
const openai = new OpenAI({
baseURL: `${getConfig().apiUrl}/v1/`,
dangerouslyAllowBrowser: true,
apiKey: "api-key-doesnt-matter", // The actual API key is handled by OpenSecret
defaultHeaders: {
"Accept-Encoding": "identity",
"Content-Type": "application/json",
},
fetch: createAiCustomFetch(), // Use OpenSecret's encrypted fetch
});
// Use the OpenAI client as normal
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: "Hello!" }],
stream: true
});You can now use the OpenAI client as normal. (Right now only streaming responses are supported.)
For an alternative approach using createAiCustomFetch directly, see the integration test at src/lib/test/integration/ai.test.ts in the SDK source code.
This library uses Bun for development.
Install dependencies:
bun installTo build the library, run the following command:
bun run buildTo test the library, run the following command:
bun test --env-file .env.localTo test a specific file or test case:
bun test --test-name-pattern="Developer login and token storage" src/lib/test/integration/developer.test.ts --env-file .env.localCurrently this build step requires npx because of a Bun incompatibility with vite-plugin-dts.
To pack the library (for publishing) run the following command:
bun run packTo deploy:
NPM_CONFIG_TOKEN=$NPM_CONFIG_TOKEN bun publish --access publicThe SDK documentation is built using Docusaurus, a modern documentation framework. The documentation is automatically generated from TypeScript code comments and supplemented with manually written guides.
To start the documentation development server:
bun run docs:devThis will start the Docusaurus development server and open the documentation in your browser at http://localhost:3000/. The server supports hot-reloading, so any changes you make to the documentation will be immediately reflected in the browser.
To build the documentation for production:
bun run docs:buildThis will generate static HTML, JavaScript, and CSS files in the website/build directory.
To serve the built documentation locally:
bun run docs:serveThe documentation is organized into the following directories:
/website/docs/- Contains all manual documentation filesindex.md- The documentation landing page/guides/- Step-by-step guides for using the SDK/api/- API reference documentation (mostly auto-generated)
The API reference documentation is automatically generated from TypeScript code comments using TypeDoc. To update the API documentation:
- Write proper JSDoc comments in the TypeScript source code
- Run
bun run docs:buildto regenerate the documentation
Important notes for API documentation:
- Use standard JSDoc syntax for documenting parameters, return types, and descriptions
- For Markdown in JSDoc comments, be aware that backticks (`) must be properly escaped
- For code examples with apostrophes (e.g., BIP paths like
m/44'/0'/0'/0/0), use backslash escaping:m/44\'/0\'/0\'/0/0
To add a new guide:
- Create a new Markdown file in the
/website/docs/guides/directory - Add frontmatter at the top of the file:
--- title: Your Guide Title sidebar_position: X # Controls the order in the sidebar ---
- Update the sidebar configuration in
/website/sidebars.tsif needed
The main configuration files for Docusaurus are:
/website/docusaurus.config.ts- Main Docusaurus configuration/website/sidebars.ts- Sidebar configuration/website/typedoc.json- TypeDoc configuration for API docs
To customize the appearance:
- Edit
/website/src/css/custom.cssfor global styles - Create or modify components in
/website/src/components/
The documentation can be deployed to various platforms like GitHub Pages, Netlify, or Vercel. For CloudFlare Pages deployment, as mentioned in our guideline:
- In CloudFlare Pages, create a new project connected to your GitHub repo
- Use these build settings:
- Build command:
cd website && bun run build - Build output directory:
website/build
- Build command:
- Set up a custom domain through CloudFlare's dashboard
Common issues:
- If TypeDoc fails to generate documentation, check the JSDoc comments for syntax errors
- If you see "Could not parse expression with acorn" errors, there are likely unescaped characters in code examples
- If links are broken, check that the referenced pages exist and paths are correct
- For sidebar issues, verify that the sidebar configuration in
sidebars.tsis correct
This project is licensed under the MIT License.