### Run an example (bash) Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/README.md Commands to set up and run an example: copy the .env.example and follow the per-language README. ```bash cd examples/ cp .env.example .env # set X_ACCESS_TOKEN + CHAT_CONVERSATION_ID # follow the per-language README to build and run ``` -------------------------------- ### Setup from source with maturin develop Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/pyo3/README.md Editable install from source. Requires Python 3.10+, Rust (stable), and maturin. ```bash cd crates/pyo3 pip install maturin maturin develop ``` -------------------------------- ### Get an access token with xurl (bash) Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/README.md Steps to install xurl, register app credentials, run the OAuth2 flow, and verify the token. The token is stored in ~/.xurl. ```bash # 1. Install (macOS) brew install --cask xdevplatform/tap/xurl # 2. Register your app's credentials (from the X Developer Portal) xurl auth apps add my-bot --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET # 3. Run the OAuth2 user-context flow (opens a browser, asks for DM scopes) xurl auth oauth2 --app my-bot # 4. Verify it works xurl /2/users/me ``` -------------------------------- ### JavaScript/WASM: First boot with createChat and setup Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md First boot flow for a brand-new user: create the chat without a juicebox_config, generate keypairs, POST them to /2/users/:id/public_keys, then update the config and call setup with the passcode. ```typescript import { createChat } from '@xdevplatform/chat-xdk'; const chat = await createChat({ getAuthToken: async (realmId) => await backend.getToken(realmId), }); const payload = chat.generateKeypairs(); // … POST payload to /2/users/:id/public_keys, GET juicebox_config back … chat.updateConfig(configJson); await chat.setup("2580"); ``` -------------------------------- ### setup Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Initializes the key storage with a PIN and returns the public keys. ```APIDOC ## setup ### Description Initializes the key storage with a PIN and returns the public keys. ### Method setup ### Parameters - **pin** (byte array/string) - Required - The PIN to set up the key storage. - **configJSON** (string) - Optional - Configuration in JSON format (Go and JVM only). ### Returns - **PublicKeys** - The public keys generated during setup. ### Language Signatures - **Rust**: `async setup(pin: &[u8]) → PublicKeys` - **JavaScript**: `setup(pin: string | Uint8Array) → Promise` - **Python**: `setup(pin: str) → PublicKeys` - **Go**: `Setup(pin []byte, configJSON string) (*PublicKeys, error)` - **JVM**: `setup(String pin, String configJson)` - **.NET**: `Setup(string pin, string configJson)` ``` -------------------------------- ### Install chatxdk with pip Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/pyo3/README.md Install the PyPI distribution `chatxdk`; import it as `chat_xdk`. ```bash pip install chatxdk ``` -------------------------------- ### Quick start: initial setup and later session with createChat Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/wasm/js/README.md First boot creates a chat without juicebox_config (a new user has none yet), generates keypairs, POSTs them, then updates config and sets up key storage. Later sessions supply the config up front and unlock. Both paths use the same createChat API; later sessions also set identity, cache, signing keys, and perform decryption/encryption operations. Requires the juicebox-sdk peer dependency for setup/unlock. ```typescript import { createChat } from "@xdevplatform/chat-xdk"; const chat = await createChat({ getAuthToken: async (realmId) => await myBackend.getToken(realmId), }); const payload = chat.generateKeypairs(); // … POST payload to /2/users/:id/public_keys, GET juicebox_config back … chat.updateConfig(configJson); await chat.setup("2580"); ``` ```typescript import { createChat } from "@xdevplatform/chat-xdk"; const chat = await createChat({ juiceboxConfig: configJson, getAuthToken: async (realmId) => await myBackend.getToken(realmId), }); await chat.unlock("2580"); // Set the session once: your user id + registered signing-key version, // the participants' signing keys, and the opt-in conversation-key cache. chat.setIdentity("111", "v1"); chat.setCacheKeys(true); // Each signing-key entry carries all five fields from the X API // public keys response. chat.setSigningKeys([ { userId: "111", publicKeyVersion: "v1", publicKey: "BASE64...", identityPublicKey: "BASE64...", identityPublicKeySignature: "BASE64...", }, { userId: "222", publicKeyVersion: "v1", publicKey: "BASE64...", identityPublicKey: "BASE64...", identityPublicKeySignature: "BASE64...", }, ]); // Initial load — batch decrypt with automatic key extraction. Signing keys // come from the store; the verified conversation keys populate the cache. const result = chat.decryptEvents(rawEvents); for (const dm of result.messages) { if (dm.event.type === "message") { console.log(dm.event.senderId, dm.event.content.text); } } // Individual events after the initial load: conversation keys resolve from // the cache and signing keys from the store (pass either explicitly to // override). const event = chat.decryptEvent(webhookEventB64); // Sending resolves the identity and conversation key from the session too. const payload = chat.encryptMessage({ conversationId: event.conversationId, text: "hi!" }); // Reply / react by handing back the raw event being answered. const reply = chat.encryptReply({ conversationId: event.conversationId, text: "pong", replyToEvent: webhookEventB64, }); const reaction = chat.encryptAddReaction({ emoji: "👍", targetEvent: webhookEventB64 }); ``` -------------------------------- ### setup() Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Initializes the SDK and returns the public keys for the user. This method must be called before other operations. ```APIDOC ## setup() ### Description Initializes the SDK and returns the public keys for the user. This method must be called before other operations. ### Method setup ### Returns - **PublicKeys** - An object containing the identity and signing public keys. ### Response Example { "identity": "Base64-encoded identity public key (P-256 SEC1)", "signing": "Base64-encoded signing public key (P-256 SEC1)", "version": "Key version identifier (may be empty)" } ``` -------------------------------- ### Build native library and install JVM binding Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/jvm/README.md Commands to build the native chat_xdk_dotnet library and install the JVM binding into the local Maven repository. Run from the repo root. ```bash cargo build -p chat-xdk-dotnet --release cd crates/jvm/java/chatxdk && mvn install ``` -------------------------------- ### Run the browser app Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/js/README.md Set up environment, build the browser-target WASM package, and start the dev server on http://localhost:8787. ```bash cp .env.example .env # set X_ACCESS_TOKEN npm run build:wasm # build a browser (web-target) WASM package npm start # serve on http://localhost:8787 ``` -------------------------------- ### Install chat-xdk and optional juicebox-sdk Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/wasm/js/README.md Install the chat-xdk package. Juicebox SDK is an optional peer dependency required only for PIN-based key storage operations (setup, unlock, changePin). The compiled WASM engine is included in the package, so no build step is needed. ```bash npm install @xdevplatform/chat-xdk ``` ```bash npm install juicebox-sdk # only needed for setup()/unlock()/changePin() ``` -------------------------------- ### Run the bot (first run and subsequent runs) Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/dotnet/README.md First run with no keys prints a registration payload and private blob. After configuring .env, re-run to start the bot. ```bash cp .env.example .env # First run with no keys prints a registration payload + a private blob: dotnet run --project ChatBot # Paste CHAT_PRIVATE_KEYS_B64 into .env, register the public key with the X API, # set X_ACCESS_TOKEN + CHAT_CONVERSATION_ID, then re-run. dotnet run --project ChatBot ``` -------------------------------- ### Chat (Java) - Session Setup and Event Decryption Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Demonstrates creating a Chat instance, unlocking, setting identity and keys, and decrypting events. ```APIDOC ## Chat (Java) - Session Setup and Event Decryption ### Description Creates a Chat instance, unlocks with a PIN, sets identity and keys, and decrypts events. ### Method Constructor and methods ### Endpoint N/A (SDK) ### Parameters - `pin` (string) - Required - PIN for unlocking. - `juiceboxConfigJson` (string) - Required - Juicebox configuration JSON. - `myUserId` (string) - Required - User ID for identity. - `mySigningKeyVersion` (string) - Required - Signing key version. - `signingKeys` (Map) - Required - Signing keys for all participants. - `rawEvents` (object) - Required - Raw events to decrypt. ### Request Example ```java try (Chat chat = new Chat()) { chat.unlock("2580", juiceboxConfigJson); chat.setIdentity(myUserId, mySigningKeyVersion); chat.setCacheKeys(true); chat.setSigningKeys(signingKeys); DecryptEventsResult batch = chat.decryptEvents(rawEvents, null); } ``` ### Response #### Success Response - `batch.messages` (array) - List of decrypted messages. - `dm.event` (JsonNode) - Decrypted event. #### Response Example ```java for (DecryptedMessage dm : batch.messages) { JsonNode evt = dm.event; if (evt != null && "Message".equals(evt.path("type").asText())) { String text = evt.path("content").path("text").asText(); } } ``` ``` -------------------------------- ### Build release wheel with maturin Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/pyo3/README.md Build a release wheel and install it with pip. ```bash maturin build --release pip install target/wheels/*.whl ``` -------------------------------- ### Run offline crypto tests for all bindings (make test-all) Source: https://github.com/xdevplatform/chat-xdk/blob/main/README.md Command to run offline crypto tests for every binding from the examples directory. No credentials are required. ```bash cd examples && make test-all # offline crypto tests for every binding (no creds) ``` -------------------------------- ### Chat (C#) - Session Setup and Event Decryption Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Demonstrates creating a Chat instance, configuring it, unlocking, setting identity and keys, and decrypting events. ```APIDOC ## Chat (C#) - Session Setup and Event Decryption ### Description Creates a Chat instance, configures it with a juicebox config, unlocks with a PIN, sets identity and keys, and decrypts events. ### Method Constructor and methods ### Endpoint N/A (SDK) ### Parameters - `juiceboxConfigJson` (string) - Required - Juicebox configuration JSON. - `pin` (string) - Required - PIN for unlocking. - `myUserId` (string) - Required - User ID for identity. - `mySigningKeyVersion` (string) - Required - Signing key version. - `signingKeys` (object) - Required - Signing keys for all participants. - `rawEvents` (object) - Required - Raw events to decrypt. ### Request Example ```csharp using var chat = new Chat(); chat.UpdateConfig(juiceboxConfigJson); chat.Unlock(pin, juiceboxConfigJson); chat.SetIdentity(myUserId, mySigningKeyVersion); chat.SetCacheKeys(true); chat.SetSigningKeys(signingKeys); var result = chat.DecryptEvents(rawEvents); ``` ### Response #### Success Response - `result.Messages` (array) - List of decrypted messages. - `dm.Event` (JsonElement) - Decrypted event. #### Response Example ```csharp foreach (var dm in result.Messages) { if (dm.Event.GetProperty("type").GetString() == "Message") Console.WriteLine(dm.Event.GetProperty("content").GetProperty("text").GetString()); } ``` ``` -------------------------------- ### DecryptEvent Go example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Go example of DecryptEvent. Returns an event and error; panics on error and prints the event type. ```go event, err := chat.DecryptEvent(eventB64, cachedKeys, senderSigningKeys) if err != nil { panic(err) } fmt.Println(event.Type) ``` -------------------------------- ### get_public_keys() Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Retrieves the current public keys for the user. Returns the same structure as setup(). ```APIDOC ## get_public_keys() ### Description Retrieves the current public keys for the user. Returns the same structure as setup(). ### Method get_public_keys ### Returns - **PublicKeys** - An object containing the identity and signing public keys. ### Response Example { "identity": "Base64-encoded identity public key (P-256 SEC1)", "signing": "Base64-encoded signing public key (P-256 SEC1)", "version": "Key version identifier (may be empty)" } ``` -------------------------------- ### Example architecture flowchart (Mermaid) Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/README.md Shows the separation between the crypto core, I/O layer, and bot loop, and how the offline test exercises the crypto core directly. ```mermaid flowchart LR subgraph Example["each example (per language)"] bot["bot loop
receive → reply → send"] core["crypto core
(wraps the binding)"] io["I/O layer
(env config + HTTP)"] bot --> core bot --> io end core --> binding["chat-xdk binding"] io --> api["X Chat API"] test["offline test"] --> core ``` -------------------------------- ### Quick Start: ChatXdk usage in C# Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/dotnet/README.md Shows the main workflow: configure, unlock, set identity, decrypt events, encrypt messages, and use stateless helpers. Includes comments for alternative key import and individual event decryption. ```csharp using ChatXdk; using var chat = new Chat(); chat.UpdateConfig(juiceboxConfigJson); chat.Unlock(pin, juiceboxConfigJson); // Or load a local key blob + its registered version instead of Juicebox: // chat.ImportKeys(privateKeyBytes, registeredKeyVersion); // Set the session once: your user id + registered signing-key version, // the opt-in conversation-key cache, and the participants' signing keys. chat.SetIdentity(myUserId, registeredKeyVersion); chat.SetCacheKeys(true); chat.SetSigningKeys(signingKeys); // Batch load — signing keys come from the store; the verified conversation // keys populate the cache. var batch = chat.DecryptEvents(rawEventB64List); foreach (var dm in batch.Messages) { if (dm.Event.GetProperty("type").GetString() == "Message") Console.WriteLine(dm.Event.GetProperty("sender_id").GetString()); } // Individual events: omitted arguments resolve from the session stores // (pass conversation/signing keys explicitly to override). var evt = chat.DecryptEvent(eventB64); var conversationId = evt.GetProperty("conversation_id").GetString()!; // Sending resolves the identity and conversation key from the session too. var payload = chat.EncryptMessage(new EncryptMessageParams(conversationId, "hi!")); // Reply / react by handing back the raw event being answered. var reply = chat.EncryptReply(new EncryptReplyParams(conversationId, "pong", eventB64)); var reaction = chat.EncryptAddReaction(new EncryptReactionParams(eventB64, "👍")); var plaintext = chat.Decrypt(encryptedGroupNameB64, convKeyBytes); ``` -------------------------------- ### DecryptEvent .NET example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md .NET example of DecryptEvent. Checks if the event type is "Message" and prints the text content. ```csharp var eventJson = chat.DecryptEvent(eventB64, cachedKeys, senderSigningKeys); if (eventJson.GetProperty("type").GetString() == "Message") Console.WriteLine(eventJson.GetProperty("content").GetProperty("text").GetString()); ``` -------------------------------- ### Add the chatxdk Go module with go get Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/go/README.md Use this command to add the chatxdk module to your Go project. The static library for your platform is already committed under libs/. ```bash go get github.com/xdevplatform/chat-xdk/go/chatxdk ``` -------------------------------- ### Project directory structure Source: https://github.com/xdevplatform/chat-xdk/blob/main/README.md Shows the top-level layout of the chat-xdk repository, including crates for each binding, examples, docs, and tests. ```text chat-xdk/ ├── crates/ │ ├── core/ # chat-xdk-core: ChatCore engine, crypto, keys, Thrift types │ ├── pyo3/ # Python binding (chat_xdk) │ ├── wasm/ # JS/WASM binding (+ js/ wrapper and tests) │ ├── go/ # Go binding (cgo + prebuilt staticlib) │ └── dotnet/ # .NET + JVM native cdylib (chat_xdk_dotnet) ├── examples/ # one clone-and-run bot per binding (+ Makefile, CI) ├── docs/ # API.md, ARCHITECTURE.md, CRYPTO.md └── tests/ # cross-binding fixtures (sdk_vectors.json) ``` -------------------------------- ### Quick Start: encrypt and decrypt Direct Messages with chatxdk Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/go/README.md Shows the main workflow: create a chat instance, import keys, set identity and signing keys, batch decrypt events, decrypt a single webhook, and encrypt a message. Requires Go 1.21+ and a C compiler. The SDK generates the message id; read it from payload.MessageID. ```go package main import ( "fmt" "github.com/xdevplatform/chat-xdk/go/chatxdk" ) func main() { chat := chatxdk.New() defer chat.Close() // Import keys + registered version, then set the session stores once: // the identity used to sign, the signing keys used to verify, and the // opt-in conversation-key cache filled by the batch decrypt path. _ = chat.ImportKeysWithVersion(privateKeyBytes, registeredKeyVersion) chat.SetIdentity(myUserID, registeredKeyVersion) chat.SetCacheKeys(true) _ = chat.SetSigningKeys(signingKeyEntries) // Batch load: decrypt many events at once (nil = stored signing keys) batch, err := chat.DecryptEvents(rawEventB64List, nil) if err != nil { panic(err) } for _, row := range batch.Messages { if row.Event.Type == "Message" { fmt.Println(row.Event.AsMessage().Text()) } } // Single webhook: nil maps resolve from the session stores event, err := chat.DecryptEvent(eventB64, nil, nil) if err != nil { panic(err) } _ = event // Sender and conversation key resolve from the session; explicit // SenderID/SigningKeyVersion and ConversationKey/ConversationKeyVersion // fields remain as overrides. payload, err := chat.EncryptMessage(chatxdk.EncryptMessageParams{ ConversationID: "…", Text: "Hello from Go!", }) if err != nil { panic(err) } // The SDK generates the message id; read it back from payload.MessageID. _ = payload } ``` -------------------------------- ### Quick Start: Chat encryption workflow Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/jvm/README.md Demonstrates the main Chat API: import keys, set identity, configure cache and signing keys, decrypt events, and encrypt messages/replies/reactions. Null arguments resolve from session stores. Note: no forward secrecy or post-compromise security. ```java import com.fasterxml.jackson.databind.JsonNode; import com.x.chatxdk.*; import com.x.chatxdk.Types.*; import java.util.Map; try (Chat chat = new Chat()) { // Load a key blob + its registered version, then set the session once: // identity, the opt-in conversation-key cache, and the signing-key store. chat.importKeys(exportedBytes, registeredKeyVersion); chat.setIdentity(myUserId, registeredKeyVersion); chat.setCacheKeys(true); chat.setSigningKeys(signingKeys); // Batch load — null signing keys fall back to the store; the verified // conversation keys populate the cache. DecryptEventsResult batch = chat.decryptEvents(rawEventB64List, null); // Individual events: null arguments resolve from the session stores. JsonNode event = chat.decryptEvent(eventB64, (Map) null, null); String conversationId = event.path("conversation_id").asText(); // Sending resolves the identity and conversation key from the session too. SendPayload payload = chat.encryptMessage(new EncryptMessageParams(conversationId, "hi!")); // Reply / react by handing back the raw event being answered. SendPayload reply = chat.encryptReply(new EncryptReplyParams(conversationId, "pong", eventB64)); SendPayload reaction = chat.encryptAddReaction(new EncryptReactionParams(eventB64, "👍")); } ``` -------------------------------- ### One-to-one creation / key rotation with prepareConversationKeyChange Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md End-to-end example of creating a one-to-one conversation or rotating a key. Fetches public keys, calls prepareConversationKeyChange (deriving the id for a 1:1), POSTs the result to the X API, and stores the conversation key locally. For a group rotation, set conversationId to the existing group's id. ```typescript // 1. App fetches public keys (self + recipient) from X API const publicKeys = await api.getPublicKeys([myId, recipientId]); // 2. SDK generates, encrypts, and signs the change (id derived for a 1:1; // the sender identity resolves from setIdentity — set senderId / // signingKeyVersion on the params to override) const result = chat.prepareConversationKeyChange({ publicKeys }); // 3. App POSTs to X API await fetch(`/api/conversations/${result.conversationId}/keys`, { body: JSON.stringify({ conversationKeyVersion: result.conversationKeyVersion, conversationParticipantKeys: result.participantKeys, actionSignatures: result.actionSignatures, }) }); // 4. App stores the conversation key locally cache.set(result.conversationId, result.conversationKey, result.conversationKeyVersion); ``` -------------------------------- ### Go: DecryptEvents example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Calls chat.DecryptEvents with raw events and signing keys, handles errors, iterates decrypted messages, and caches conversation keys for later use. ```go result, err := chat.DecryptEvents(rawEvents, signingKeys) if err != nil { panic(err) } for _, dm := range result.Messages { if dm.Event.Type == "Message" { fmt.Println(dm.Event.AsMessage().Text()) } } // Cache for later cachedKeys := result.ConversationKeys.Keys ``` -------------------------------- ### Python: decrypt_events example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Calls chat.decrypt_events with raw events and signing keys, iterates decrypted messages, and caches conversation keys for later use. ```python result = chat.decrypt_events(raw_events, signing_keys) for dm in result["messages"]: evt = dm["event"] if evt["type"] == "Message": print(evt["sender_id"], evt["content"]["text"]) # Cache for later cached_keys = result["conversation_keys"] ``` -------------------------------- ### PublicKeys interface (TypeScript) and Python equivalent Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md PublicKeys is returned by setup() and get_public_keys(). The TypeScript interface shows the fields; the Python comment notes the same fields are available as attributes. ```typescript interface PublicKeys { identity: string; // Base64-encoded identity public key (P-256 SEC1) signing: string; // Base64-encoded signing public key (P-256 SEC1) version?: string; // Key version identifier (may be empty) } ``` ```python # Python — same fields: .identity, .signing, .version (str; may be "") ``` -------------------------------- ### Python quick start: import keys, decrypt events, encrypt message Source: https://github.com/xdevplatform/chat-xdk/blob/main/README.md Shows the core flow in Python: import keys, set identity, cache keys, decrypt a batch of events, decrypt a single event, and encrypt a message. The result of encrypt_message is a payload to POST to the X Chat API. ```python from chat_xdk import Chat chat = Chat() chat.import_keys(private_key_blob, version=key_version) # or Chat(config).unlock(pin) chat.set_identity(my_user_id, key_version) chat.set_cache_keys(True) chat.set_signing_keys(signing_keys) result = chat.decrypt_events(raw_events) for dm in result["messages"]: if dm["event"]["type"] == "Message": print(dm["event"]["sender_id"], dm["event"]["content"]["text"]) event = chat.decrypt_event(new_event_b64) payload = chat.encrypt_message(conversation_id, "hello") # POST payload to the X API ``` -------------------------------- ### Go chat-xdk usage: unlock, decrypt, encrypt, and media Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Complete Go example for the chat-xdk Go SDK. It unlocks with the juicebox config, sets the session identity and signing keys, decrypts stored and new events, encrypts messages/replies/reactions, and decrypts metadata and media using a raw conversation key. Note that nil arguments in DecryptEvent resolve from the key cache and signing-key store, and AsMessage returns nil unless the event type is "Message". ```go import ( "fmt" "github.com/xdevplatform/chat-xdk/go/chatxdk" ) func main() { chat := chatxdk.New() defer chat.Close() _ = chat.Unlock([]byte("2580"), juiceboxConfigJSON) // ── Set the session once ── chat.SetIdentity(myUserID, mySigningKeyVersion) chat.SetCacheKeys(true) _ = chat.SetSigningKeys(signingKeys) // all participants // ── Initial load: use DecryptEvents (nil = stored signing keys) ── result, err := chat.DecryptEvents(rawEvents, nil) if err != nil { panic(err) } for _, dm := range result.Messages { if dm.Event.Type == "Message" { fmt.Println(dm.Event.AsMessage().Text()) } } // ── New event arrives: use DecryptEvent ── // nil arguments resolve from the key cache and signing-key store. event, err := chat.DecryptEvent(eventB64, nil, nil) if err != nil { panic(err) } msg := event.AsMessage() // nil unless event.Type == "Message" if msg != nil { fmt.Println(msg.Text()) } // ── Send: identity and conversation key resolve from the session ── payload, err := chat.EncryptMessage(chatxdk.EncryptMessageParams{ ConversationID: *msg.ConversationID, Text: "hi!", }) if err != nil { panic(err) } _ = payload reply, _ := chat.EncryptReply(chatxdk.EncryptReplyParams{ ConversationID: *msg.ConversationID, Text: "pong", ReplyToEvent: eventB64, // preview derived + embedded for validation }) _ = reply reaction, _ := chat.EncryptAddReaction(chatxdk.EncryptReactionParams{ Emoji: "👍", TargetEvent: eventB64, }) _ = reaction // Raw 32-byte conversation key for the metadata/media calls below, // e.g. from DecryptConversationKey or result.ConversationKeys.Keys. convKey := result.ConversationKeys.Keys[keyVersion] // ── Metadata (group name, avatar, description) ── groupName, err := chat.Decrypt(conversationGroupNameB64, convKey) _ = groupName // ── Media (raw bytes in, raw bytes out) ── decrypted, err := chat.DecryptStream(encryptedMedia, convKey) _ = decrypted } ``` -------------------------------- ### JVM: decryptEvents example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Calls chat.decryptEvents with raw events and signing keys, iterates decrypted messages, and caches conversation keys for later use. ```java DecryptEventsResult result = chat.decryptEvents(rawEvents, signingKeys); for (DecryptedMessage dm : result.messages) { JsonNode event = dm.event; if ("Message".equals(event.path("type").asText())) { System.out.println(event.path("content").path("text").asText()); } } // Cache for later Map cachedKeys = result.conversationKeys.keys; ``` -------------------------------- ### JavaScript/WASM quick start: create chat, unlock, decrypt, encrypt Source: https://github.com/xdevplatform/chat-xdk/blob/main/README.md Shows the core flow in JavaScript/WASM: create a chat instance, unlock with a PIN, set identity, cache keys, decrypt events, and encrypt a message. Uses camelCase method names. ```typescript import { createChat } from "@xdevplatform/chat-xdk"; const chat = await createChat({ juiceboxConfig, getAuthToken }); await chat.unlock("2580"); chat.setIdentity(myUserId, signingKeyVersion); chat.setCacheKeys(true); chat.setSigningKeys(signingKeys); const result = chat.decryptEvents(rawEvents); for (const dm of result.messages) { if (dm.event.type === "message") console.log(dm.event.senderId, dm.event.content.text); } const event = chat.decryptEvent(newEventB64); const payload = chat.encryptMessage({ conversationId, text: "hello" }); ``` -------------------------------- ### Quick start: encrypt and decrypt X Direct Messages Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/pyo3/README.md Initializes a Chat instance, unlocks it, sets identity and signing keys, decrypts events, and encrypts messages, replies, and reactions. Note: no forward secrecy or post-compromise security. ```python from chat_xdk import Chat chat = Chat(config_json) chat.unlock("2580") # Or load a local key blob + its registered version instead of Juicebox: # chat.import_keys(private_key_bytes, version=registered_key_version) # Set the session once: your user id + registered signing-key version, # the opt-in conversation-key cache, and the participants' signing keys. # Each signing-key entry must include the identity public key and its binding # signature so the SDK can verify the signing key is authentic. chat.set_identity("111", "v1") chat.set_cache_keys(True) chat.set_signing_keys([ { "user_id": "111", "public_key_version": "v1", "public_key": "BASE64...", "identity_public_key": "BASE64...", "identity_public_key_signature": "BASE64...", }, { "user_id": "222", "public_key_version": "v1", "public_key": "BASE64...", "identity_public_key": "BASE64...", "identity_public_key_signature": "BASE64...", }, ]) # Initial load — batch decrypt with automatic key extraction. Signing keys # come from the store; the verified conversation keys populate the cache. result = chat.decrypt_events(raw_events) for dm in result["messages"]: if dm["event"]["type"] == "Message": print(dm["event"]["sender_id"], dm["event"]["content"]["text"]) # Individual events after the initial load: conversation keys resolve from # the cache and signing keys from the store (pass either explicitly to # override). event = chat.decrypt_event(new_event_b64) # Sending resolves the identity and conversation key from the session too. payload = chat.encrypt_message(event["conversation_id"], "hi!") # Reply / react by handing back the raw event being answered. reply = chat.encrypt_reply(event["conversation_id"], "pong", new_event_b64) reaction = chat.encrypt_add_reaction(new_event_b64, "👍") ``` -------------------------------- ### prepareConversationKeyChange Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Prepares a conversation key change for starting a one-to-one conversation or rotating an existing conversation's key. Returns a PreparedConversationChange with a fresh conversation key and action signatures. ```APIDOC ## prepareConversationKeyChange ### Description Prepares a conversation key change for starting a one-to-one conversation (first contact) or rotating an existing conversation's key. Returns a `PreparedConversationChange` with a fresh conversation key and action signatures. ### Usage - **Start a one-to-one**: Omit `conversationId`. - **Rotate an existing conversation's key**: Pass the conversation id. ### Returns - `PreparedConversationChange` containing: - Fresh conversation key - Encrypted participant keys - Action signatures (one for the key change) ### After Preparation POST to the appropriate endpoint: - For first contact: `/2/chat/conversations/{recipientId}/keys` - For rotation: `/2/chat/conversations/{id}/keys` ### Notes - The signed payload of the key-change signature embeds the plaintext conversation key, so the SDK withholds it; the `signaturePayload` field is empty/omitted and must never be reconstructed and sent. - Verify recipient keys before wrapping using `verifyKeyBinding` on fetched keys. ``` -------------------------------- ### Rust chat-xdk usage: unlock, decrypt, encrypt, and media Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Rust equivalent of the chat-xdk usage example. It creates a Chat from a JuiceboxConfig, unlocks with the passphrase "2580", sets identity and signing keys, decrypts events (empty slice for stored signing keys), encrypts messages/replies/reactions, and decrypts metadata and media. Empty arguments in decrypt_event resolve from the key cache and signing-key store. ```rust use chat_xdk_core::{Chat, EncryptMessageParams, EncryptReactionParams, EncryptReplyParams, Event}; use chat_xdk_core::keys::juicebox::JuiceboxConfig; let config = JuiceboxConfig::from_json(config_json); let chat = Chat::new(config); chat.unlock(b"2580").await?; // ── Set the session once ── chat.set_identity(my_user_id, my_signing_key_version); chat.set_cache_keys(true); chat.set_signing_keys(signing_keys); // all participants // ── Initial load: use decrypt_events (empty slice = stored signing keys) ── let result = chat.decrypt_events(&raw_events, &[]); for dm in &result.messages { if let Event::Message(msg) = &dm.event { println!("{}: {}", msg.meta.sender_id.as_deref().unwrap_or("?"), msg.text().unwrap_or("")); } } // ── New event arrives: use decrypt_event ── // Empty arguments resolve from the key cache and signing-key store. let event = chat.decrypt_event(event_b64, &Default::default(), &[])?; // ── Send: identity and conversation key resolve from the session ── let payload = chat.encrypt_message(EncryptMessageParams::new(conversation_id, "hi!"))?; let reply = chat.encrypt_reply(EncryptReplyParams::new(conversation_id, "pong", event_b64))?; let reaction = chat.encrypt_add_reaction(&EncryptReactionParams::new(event_b64, "👍"))?; // ── Metadata (group name, avatar, description) ── let group_name = chat.decrypt(&conversation.group_name, &conv_key)?; // ── Media ── let decrypted = chat.decrypt_stream(&encrypted_bytes, &conv_key)?; ``` -------------------------------- ### decryptEvent JVM example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md JVM example of decryptEvent. Checks if the event type is "Message" and prints the text content. ```java JsonNode event = chat.decryptEvent(eventB64, cachedKeys, senderSigningKeys); if ("Message".equals(event.path("type").asText())) { System.out.println(event.path("content").path("text").asText()); } ``` -------------------------------- ### Run the bot: copy .env and run cargo Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/rust/README.md Copy the .env.example file, then run the bot with cargo run. On first run, it prints a registration payload and a private blob. After configuring .env with the required keys, re-run cargo run. ```bash cp .env.example .env # First run with no keys prints a registration payload + a private blob: cargo run # Paste CHAT_PRIVATE_KEYS_B64 into .env, register the public key with the X API, # set X_ACCESS_TOKEN + CHAT_CONVERSATION_ID, then re-run. cargo run ``` -------------------------------- ### decrypt_event Python example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Python example of decrypt_event. Checks if the event type is "Message" and prints the text content. ```python event = chat.decrypt_event(event_b64, cached_keys, sender_signing_keys) if event["type"] == "Message": print(event["content"]["text"]) ``` -------------------------------- ### Register your key (one-time) Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/js/README.md Build the WASM package and run the registration script with a confirmation flag. This is rate-limited and should be run only once. ```bash npm run build:wasm # once per Rust change X_ACCESS_TOKEN=... CHAT_PIN=... npm run register -- --confirm ``` -------------------------------- ### decryptEvent JavaScript example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md JavaScript example of decryptEvent using cached keys from a previous decryptEvents call. Passes a signing key entry with userId, publicKeyVersion, publicKey, identityPublicKey, and identityPublicKeySignature. Logs event.type, senderId, content.text, and verified. ```typescript // You cached keys from a previous decryptEvents call const cachedKeys = previousResult.conversationKeys.keys; // A new event arrives via webhook const event = chat.decryptEvent( webhookEventB64, cachedKeys, [{ userId: "111", publicKeyVersion: "v2", publicKey: "BASE64...", identityPublicKey: "BASE64...", identityPublicKeySignature: "BASE64..." }] ); console.log(event.type); // "message" console.log(event.senderId); // "111" console.log(event.content.text); // "hello" console.log(event.verified); // true ``` -------------------------------- ### Go: chatxdk.New Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Creates a new chat client instance. Must be closed with Close() when done. ```APIDOC ## Go: chatxdk.New ### Description Creates a new chat client instance. Must be closed with Close() when done. ### Method New ### Endpoint chatxdk.New() ### Parameters None ### Request Example ```go chat := chatxdk.New() defer chat.Close() ``` ### Response #### Success Response - **chat** (*chatxdk.Chat) - The chat client instance #### Response Example ```go chat := chatxdk.New() ``` ``` -------------------------------- ### Tuple (Python) Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Python tuple representation of a rich-text entity as (start, end, entity_type). ```APIDOC ## Tuple (Python) ### Description Python representation of a rich-text entity as a tuple `(start, end, entity_type)`. The `start` and `end` are byte offsets, and `entity_type` is one of the supported entity types. ### Fields - **start** (int) - Required - Start byte offset in the message text. - **end** (int) - Required - End byte offset in the message text. - **entity_type** (str) - Required - One of the supported entity types, e.g. "url", "mention". ### Example ```python # Example: [(0, 23, "url"), (25, 30, "mention")] ``` ``` -------------------------------- ### Go: Use BytesToBase64, DetectMimeType, DetectImageDimensions Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Import the chatxdk package and call the utility functions. BytesToBase64 returns the base64 string and an error; DetectMimeType returns an empty string with nil error for unknown types; DetectImageDimensions returns a non-nil *ImageDimensions or nil. ```go import "github.com/xdevplatform/chat-xdk/go/chatxdk" b64, _ := chatxdk.BytesToBase64([]byte{1, 2, 3}) // "AQID" mime, _ := chatxdk.DetectMimeType(imageBytes) // "image/png" or "" if unknown dims, _ := chatxdk.DetectImageDimensions(imageBytes) // non-nil *ImageDimensions or nil ``` -------------------------------- ### EntityTuple (JS) Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md JavaScript tuple representation of a rich-text entity as an array [start, end, type]. ```APIDOC ## EntityTuple (JS) ### Description JavaScript representation of a rich-text entity as a tuple `[start, end, type]`. The `start` and `end` are byte offsets, and `type` is one of the supported entity types. ### Fields - **start** (number) - Required - Start byte offset in the message text. - **end** (number) - Required - End byte offset in the message text. - **type** (string) - Required - One of the supported entity types, e.g. "url", "mention". ### Example ```typescript const entities: EntityTuple[] = [[0, 23, 'url'], [25, 30, 'mention']]; ``` ``` -------------------------------- ### Run the headless bot Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/js/README.md Set up environment variables and run the bot. The bot echoes messages back (pong for ping, otherwise 'You said: …'). ```bash cp .env.example .env # set X_ACCESS_TOKEN, CHAT_CONVERSATION_ID, CHAT_PRIVATE_KEYS_B64 npm run bot ``` -------------------------------- ### EntityDescriptor (Rust) Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Rust struct representing a rich-text entity with start and end byte offsets and an entity type. ```APIDOC ## EntityDescriptor (Rust) ### Description Rust struct representing a rich-text entity to embed in a message. The `start` and `end` fields are byte offsets within the message text, and `entity_type` is one of the supported entity types. ### Fields - **start** (i32) - Required - Start byte offset in the message text. - **end** (i32) - Required - End byte offset in the message text. - **entity_type** (String) - Required - One of the supported entity types, e.g. "url", "mention". ### Example ```rust pub struct EntityDescriptor { pub start: i32, pub end: i32, pub entity_type: String, } ``` ``` -------------------------------- ### Build managed wrapper with dotnet Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/dotnet/README.md Builds the .NET wrapper from the crates/dotnet/dotnet/ChatXdk directory. Requires the native library to be built first. ```bash cd crates/dotnet/dotnet/ChatXdk dotnet build -c Release ``` -------------------------------- ### Rebuild prebuilt libraries and run Go tests Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/go-prebuilts.md Commands to rebuild the static library for the current platform and to run Go tests, which first rebuilds the prebuilt. Requires Rust stable via rustup. ```bash make prebuilt # build + copy the .a for your current platform, then commit it make test-go # runs prebuilt first, then go test ``` -------------------------------- ### .NET: DecryptEvents example Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Calls chat.DecryptEvents with raw events and signing keys, iterates decrypted messages, and caches conversation keys for later use. ```csharp var result = chat.DecryptEvents(rawEvents, signingKeys); foreach (var dm in result.Messages) { if (dm.Event.GetProperty("type").GetString() == "Message") Console.WriteLine(dm.Event.GetProperty("content").GetProperty("text").GetString()); } // Cache for later var cachedKeys = result.ConversationKeys.Keys; ``` -------------------------------- ### new Source: https://github.com/xdevplatform/chat-xdk/blob/main/docs/API.md Creates a new Chat instance. The constructor accepts a configuration object or JSON string depending on the language binding. ```APIDOC ## new ### Description Creates a new Chat instance. The constructor accepts a configuration object or JSON string depending on the language binding. ### Method Constructor ### Parameters - **config** (JuiceboxConfig / string / optional) - Optional - Configuration for the SDK. In JS, `juiceboxConfig` is optional to support first boot. ### Language Signatures - **Rust**: `Chat::new(config: JuiceboxConfig)` - **JS**: `new Chat()` (config via `createChat(opts)`; `juiceboxConfig` optional) - **Python**: `Chat(config_json?: str)` - **Go**: `New() *Chat` (free with `Close()`) - **JVM**: `new Chat()` (free with `close()` / `try-with-resources`) - **.NET**: `new Chat()` then `UpdateConfig(string)` (free with `Dispose()` / `using`) ### Notes - In JS, realm auth tokens always come from the `getAuthToken` callback instead of the config. - Created without a config, the instance runs every crypto method but builds no Juicebox client; `setup`/`unlock`/`changePin`/`delete` throw a clear error until `updateConfig` supplies a real config. ``` -------------------------------- ### Run JVM tests with make jvm-test Source: https://github.com/xdevplatform/chat-xdk/blob/main/crates/jvm/README.md Builds the cdylib and runs Maven tests with jna.library.path pointing at target/release. Requires JDK 17+ and Maven. ```bash make jvm-test ``` -------------------------------- ### Run offline tests Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/dotnet/README.md Builds the native lib first, then runs the test suite. Tests import fixture private keys, check binding reproduces committed public-key/signature vectors, and run encrypt/decrypt and ECIES round-trips. ```bash cargo build -p chat-xdk-dotnet --release # build the native lib first dotnet test ``` -------------------------------- ### Register public key with X_ACCESS_TOKEN and --confirm Source: https://github.com/xdevplatform/chat-xdk/blob/main/examples/rust/README.md One-time registration of the bot's public key. It is rate-limited, so run only once. Generates identity, saves private-key blob under state/ before network call, then POSTs public key. On HTTP 429, stops and tells when the window resets. Use --force to mint a new identity. ```bash X_ACCESS_TOKEN=... cargo run --bin register -- --confirm ```