This is a React SDK for the OpenSecret platform.
đźš§ We're currently in preview mode, please contact us at [email protected] for the preview URL and getting started info đźš§
npm install @opensecret/react
Wrap your application in the OpenSecretProvider
component and provide:
- The URL of your OpenSecret backend
- Your project's client ID (a UUID that identifies your project)
import { OpenSecretProvider } from "@opensecret/react";
function App() {
return (
<OpenSecretProvider
apiUrl="{URL}"
clientId="{PROJECT_UUID}"
>
<App />
</OpenSecretProvider>
);
}
Now import the useOpenSecret
hook and use it to access the OpenSecret API:
import { useOpenSecret } from "@opensecret/react";
function App() {
const os = useOpenSecret();
return (
<div>
<button onClick={() => os.signIn("email", "password")}>Sign In</button>
<button onClick={() => os.signUp("name", "email", "password")}>Sign Up</button>
<button onClick={() => os.signOut()}>Sign Out</button>
<button onClick={() => os.get("key")}>Get Value</button>
<button onClick={() => os.put("key", "value")}>Put Value</button>
<button onClick={() => os.list()}>List Values</button>
<button onClick={() => os.del("key")}>Delete Value</button>
</div>
);
}
The OpenSecretProvider
component is the main entry point for the SDK. It requires two props:
apiUrl
: The URL of your OpenSecret backendclientId
: A UUID that identifies your project/tenant. This is used to scope user accounts and data to your specific project.
<OpenSecretProvider
apiUrl="{URL}"
clientId="{PROJECT_UUID}"
>
<App />
</OpenSecretProvider>
The useOpenSecret
hook provides access to the OpenSecret API. It returns an object with the following methods:
signIn(email: string, password: string): Promise<void>
: Signs in a user with the provided email and password.signUp(email: string, password: string, inviteCode: string, name?: string): Promise<void>
: Signs up a new user with the provided email, password, invite code, and optional name.signInGuest(id: string, password: string): Promise<void>
: Signs in a guest user with their ID and password. Guest accounts are scoped to the project specified byclientId
.signUpGuest(password: string, inviteCode: string): Promise<LoginResponse>
: Creates a new guest account with just a password and invite code. Returns a response containing the guest's ID, access token, and refresh token. The guest account will be associated with the project specified byclientId
.convertGuestToUserAccount(email: string, password: string, name?: string): Promise<void>
: Converts current guest account to a regular account with email authentication. Optionally sets the user's name. The account remains associated with the same project it was created under.signOut(): Promise<void>
: Signs out the current user.
get(key: string): Promise<string | undefined>
: Retrieves the value associated with the provided key.put(key: string, value: string): Promise<string>
: Stores the provided value with the provided key.list(): Promise<KVListItem[]>
: Retrieves all key-value pairs stored by the user.del(key: string): Promise<void>
: Deletes the value associated with the provided key.
refetchUser(): Promise<void>
: Refreshes the user's authentication state.changePassword(currentPassword: string, newPassword: string): Promise<void>
: Changes the user's password.generateThirdPartyToken(audience?: string): Promise<{ token: string }>
: Generates a JWT token for use with third-party services. If an audience is provided, it can be any valid URL. If omitted, a token with no audience restriction will be generated.
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_path
is 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 os.encryptData("Secret message"); // Encrypt with BIP-32 derived key const { encrypted_data } = await os.encryptData("Secret message", { private_key_derivation_path: "m/44'/0'/0'/0/0" }); // Encrypt with BIP-85 derived key const { encrypted_data } = await os.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 os.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 os.decryptData(encrypted_data); // Decrypt with BIP-32 derived key const decrypted = await os.decryptData(encrypted_data, { private_key_derivation_path: "m/44'/0'/0'/0/0" }); // Decrypt with BIP-85 derived key const decrypted = await os.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 os.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 os.getPrivateKey();
// Get the master private key bytes
const { private_key } = await os.getPrivateKeyBytes();
// Sign with the master key
const signature = await os.signMessage(messageBytes, 'ecdsa');
- Using BIP-32 Derivation Only
// Get private key bytes using BIP-32 derivation
const { private_key } = await os.getPrivateKeyBytes({
private_key_derivation_path: "m/44'/0'/0'/0/0"
});
// Sign with a derived key
const signature = await os.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 os.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 os.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 os.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 os.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 os.encryptData("Secret message", {
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});
// Decrypt using the same derivation path
const decrypted = await os.decryptData(encrypted_data, {
seed_phrase_derivation_path: "m/83696968'/39'/0'/12'/0'"
});
To get encrypted-to-the-gpu AI chat we provide a special version of fetch
(os.aiCustomFetch
) 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 openai
import OpenAI from "openai";
import { useOpenSecret } from "@opensecret/react";
//...
// In a component
const os = useOpenSecret();
const openai = new OpenAI({
baseURL: `${os.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: os.aiCustomFetch, // Use OpenSecret's encrypted fetch
});
//...
You can now use the OpenAI client as normal. (Right now only streaming responses are supported.) See the example in src/AI.tsx
in the SDK source code for a complete example.
For an alternative approach using custom fetch directly, see the implementation in src/lib/ai.test.ts
in the SDK source code.
This library uses Bun for development.
To run the demo app, run the following commands:
bun install
bun run dev
To build the library, run the following command:
bun run build
To test the library, run the following command:
bun test --env-file .env.local
To test a specific file or test case:
bun test --test-name-pattern="Developer login and token storage" src/lib/developer.test.ts --env-file .env.local
Currently 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 pack
To deploy:
NPM_CONFIG_TOKEN=$NPM_CONFIG_TOKEN bun publish --access public
The 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:dev
This 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:build
This will generate static HTML, JavaScript, and CSS files in the website/build
directory.
To serve the built documentation locally:
bun run docs:serve
The 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:build
to 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.ts
if 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.css
for 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.ts
is correct
This project is licensed under the MIT License.