joule-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LumenBro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # joule-sdk
2
+
3
+ Pay for AI inference with JOULE tokens on Stellar. Your agent sends a prompt, the SDK handles payment automatically via the [x402](https://www.x402.org/) HTTP payment protocol.
4
+
5
+ ```
6
+ Agent sends prompt → gets 402 → signs JOULE transfer → retries with payment → gets inference
7
+ ```
8
+
9
+ One function call. No API keys. No subscriptions. Just tokens and compute.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npm install joule-sdk
15
+ ```
16
+
17
+ ```typescript
18
+ import { JouleClient } from "joule-sdk";
19
+
20
+ const client = new JouleClient({
21
+ secretKey: "SXXX...", // Agent's Stellar secret key
22
+ network: "testnet",
23
+ });
24
+
25
+ const response = await client.chat({
26
+ model: "meta-llama/Llama-3.3-70B-Instruct",
27
+ messages: [{ role: "user", content: "Explain quantum computing" }],
28
+ });
29
+
30
+ console.log(response.choices[0].message.content);
31
+ console.log(response._payment.transaction); // On-chain tx hash
32
+ ```
33
+
34
+ That's it. The SDK:
35
+ 1. Requests inference from the compute server
36
+ 2. Receives HTTP 402 with JOULE payment requirements
37
+ 3. Builds and signs a Soroban token transfer
38
+ 4. Retries the request with the signed payment
39
+ 5. Returns the AI response + payment receipt
40
+
41
+ ## What is JOULE?
42
+
43
+ JOULE is a prepaid AI compute credit on the [Stellar](https://stellar.org) blockchain. 1 JOULE = 1,000 Joules of estimated AI inference energy.
44
+
45
+ - **SEP-41 compliant** Soroban token
46
+ - **x402 compatible** — the HTTP payment protocol for AI agents
47
+ - **Pay-per-query** — no minimums, no commitments
48
+ - Supports open-source models via [DeepInfra](https://deepinfra.com) (Llama, Mistral, Qwen, DeepSeek)
49
+
50
+ ## Available Models
51
+
52
+ | Model | Tier | Est. Cost |
53
+ |-------|------|-----------|
54
+ | `meta-llama/Llama-3.3-70B-Instruct` | Medium | ~1.60 JOULE |
55
+ | `meta-llama/Llama-3.2-3B-Instruct` | Small | ~0.38 JOULE |
56
+ | `meta-llama/Llama-4-Scout-17B-16E-Instruct` | Medium | ~1.60 JOULE |
57
+ | `mistralai/Mistral-Small-24B-Instruct-2501` | Medium | ~1.60 JOULE |
58
+ | `Qwen/Qwen2.5-72B-Instruct` | Medium | ~1.60 JOULE |
59
+ | `deepseek-ai/DeepSeek-V3` | Large | ~8.96 JOULE |
60
+ | `deepseek-ai/DeepSeek-R1` | Reasoning | ~38.36 JOULE |
61
+
62
+ Prices are estimates based on energy consumption at default `max_tokens`. Check live pricing:
63
+
64
+ ```typescript
65
+ const models = await client.models();
66
+ ```
67
+
68
+ ## API Reference
69
+
70
+ ### `new JouleClient(config)`
71
+
72
+ | Option | Type | Default | Description |
73
+ |--------|------|---------|-------------|
74
+ | `secretKey` | `string` | *required* | Stellar secret key (starts with `S`) |
75
+ | `computeUrl` | `string` | `https://compute.lumenbro.com` | Compute server URL |
76
+ | `network` | `"testnet" \| "mainnet"` | `"testnet"` | Stellar network |
77
+ | `rpcUrl` | `string` | auto | Custom Soroban RPC URL |
78
+
79
+ ### `client.chat(request): Promise<ChatResponse>`
80
+
81
+ OpenAI-compatible chat completion with automatic JOULE payment.
82
+
83
+ ```typescript
84
+ const response = await client.chat({
85
+ model: "meta-llama/Llama-3.3-70B-Instruct",
86
+ messages: [
87
+ { role: "system", content: "You are a helpful assistant." },
88
+ { role: "user", content: "What is x402?" },
89
+ ],
90
+ max_tokens: 500,
91
+ temperature: 0.7,
92
+ });
93
+
94
+ // AI response
95
+ console.log(response.choices[0].message.content);
96
+
97
+ // Payment receipt
98
+ console.log(response._payment.transaction); // Stellar tx hash
99
+ console.log(response._payment.joulesPaid); // "0.10 JOULE"
100
+ console.log(response._payment.network); // "stellar:testnet"
101
+ ```
102
+
103
+ ### `client.models(): Promise<ModelsResponse>`
104
+
105
+ Fetch available models with live JOULE pricing.
106
+
107
+ ### `client.publicKey: string`
108
+
109
+ The agent's Stellar public key (derived from the secret key).
110
+
111
+ ## How x402 Works
112
+
113
+ [x402](https://www.x402.org/) turns HTTP 402 ("Payment Required") into a real payment protocol:
114
+
115
+ ```
116
+ ┌─────────┐ ┌──────────────────┐ ┌─────────────┐
117
+ │ Agent │────────>│ Compute Server │────────>│ Facilitator │
118
+ │ (SDK) │ POST │ compute.lumen... │ verify │ x402.lumen..│
119
+ │ │<────────│ │<────────│ │
120
+ │ │ 402 │ │ settle │ │
121
+ │ │────────>│ │────────>│ │
122
+ │ │ +X-Pay │ │ │ │
123
+ │ │<────────│ │ │ │
124
+ │ │ 200+AI │ │ │ │
125
+ └─────────┘ └──────────────────┘ └─────────────┘
126
+ ```
127
+
128
+ 1. Agent requests inference (no payment)
129
+ 2. Server returns **402** with JOULE payment requirements
130
+ 3. Agent signs a Soroban token transfer and retries with `X-Payment` header
131
+ 4. Server asks the facilitator to verify and settle the payment on-chain
132
+ 5. Server forwards the request to the inference provider and returns the result
133
+
134
+ Every payment is a real on-chain Stellar transaction — verifiable on [Stellar Expert](https://stellar.expert/explorer/testnet).
135
+
136
+ ## Running the Example
137
+
138
+ ```bash
139
+ git clone https://github.com/lumenbro/joule-sdk.git
140
+ cd joule-sdk
141
+ npm install
142
+
143
+ # Set your agent's Stellar secret key
144
+ export AGENT_SECRET=SXXX...
145
+
146
+ # Run the chat example
147
+ npx tsx examples/chat.ts
148
+ ```
149
+
150
+ ## Network Info
151
+
152
+ | | Testnet | Mainnet |
153
+ |---|---------|---------|
154
+ | JOULE Token | `CBKI6B65...WXBMX` | Coming soon |
155
+ | Compute Server | `compute.lumenbro.com` | `compute.lumenbro.com` |
156
+ | Facilitator | `x402.lumenbro.com` | `x402.lumenbro.com` |
157
+ | Explorer | [stellar.expert/testnet](https://stellar.expert/explorer/testnet) | — |
158
+
159
+ ## For Agent Frameworks
160
+
161
+ Works with any TypeScript/JavaScript agent framework:
162
+
163
+ ```typescript
164
+ // LangChain-style tool
165
+ const joule = new JouleClient({ secretKey: process.env.AGENT_KEY });
166
+
167
+ async function queryLLM(prompt: string): Promise<string> {
168
+ const res = await joule.chat({
169
+ model: "meta-llama/Llama-3.3-70B-Instruct",
170
+ messages: [{ role: "user", content: prompt }],
171
+ });
172
+ return res.choices[0].message.content;
173
+ }
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,25 @@
1
+ import type { JouleClientConfig, ChatRequest, ChatResponse } from "./types";
2
+ export declare class JouleClient {
3
+ private keypair;
4
+ private computeUrl;
5
+ private network;
6
+ private rpcUrl?;
7
+ constructor(config: JouleClientConfig);
8
+ /** Agent's public Stellar address */
9
+ get publicKey(): string;
10
+ /**
11
+ * Send a chat completion request, automatically handling x402 payment.
12
+ *
13
+ * Flow:
14
+ * 1. POST to /api/v1/chat/completions (no payment)
15
+ * 2. Get 402 → extract payment requirements
16
+ * 3. Build + sign Soroban transfer
17
+ * 4. Retry with X-Payment header
18
+ * 5. Return inference response + payment metadata
19
+ */
20
+ chat(request: ChatRequest): Promise<ChatResponse>;
21
+ /**
22
+ * Get available models and their JOULE pricing.
23
+ */
24
+ models(): Promise<any>;
25
+ }
package/dist/client.js ADDED
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JouleClient = void 0;
4
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
5
+ const wallet_1 = require("./wallet");
6
+ const DEFAULT_COMPUTE_URL = "https://compute.lumenbro.com";
7
+ class JouleClient {
8
+ constructor(config) {
9
+ this.keypair = stellar_sdk_1.Keypair.fromSecret(config.secretKey);
10
+ this.computeUrl = (config.computeUrl || DEFAULT_COMPUTE_URL).replace(/\/$/, "");
11
+ this.network = config.network || "testnet";
12
+ this.rpcUrl = config.rpcUrl;
13
+ }
14
+ /** Agent's public Stellar address */
15
+ get publicKey() {
16
+ return this.keypair.publicKey();
17
+ }
18
+ /**
19
+ * Send a chat completion request, automatically handling x402 payment.
20
+ *
21
+ * Flow:
22
+ * 1. POST to /api/v1/chat/completions (no payment)
23
+ * 2. Get 402 → extract payment requirements
24
+ * 3. Build + sign Soroban transfer
25
+ * 4. Retry with X-Payment header
26
+ * 5. Return inference response + payment metadata
27
+ */
28
+ async chat(request) {
29
+ const url = `${this.computeUrl}/api/v1/chat/completions`;
30
+ // Step 1: Initial request (expect 402)
31
+ const initialResponse = await fetch(url, {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify({ ...request, stream: false }),
35
+ });
36
+ // If we get 200, the endpoint doesn't require payment (shouldn't happen, but handle it)
37
+ if (initialResponse.ok) {
38
+ return initialResponse.json();
39
+ }
40
+ // Not a 402? It's an actual error
41
+ if (initialResponse.status !== 402) {
42
+ const errorBody = await initialResponse.json().catch(() => ({}));
43
+ throw new Error(`Compute server error (${initialResponse.status}): ${errorBody?.error?.message || "Unknown error"}`);
44
+ }
45
+ // Step 2: Extract payment requirements from 402 response
46
+ const errorBody = await initialResponse.json();
47
+ const requirements = errorBody.paymentRequirements;
48
+ if (!requirements) {
49
+ throw new Error("402 response missing paymentRequirements — is this an x402-enabled endpoint?");
50
+ }
51
+ // Step 3: Build and sign Soroban transfer
52
+ const paymentPayload = await (0, wallet_1.buildSignedPayment)(this.keypair, requirements, this.network, this.rpcUrl);
53
+ // Step 4: Encode as base64 and retry with X-Payment header
54
+ const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
55
+ const paidResponse = await fetch(url, {
56
+ method: "POST",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ "X-Payment": paymentHeader,
60
+ },
61
+ body: JSON.stringify({ ...request, stream: false }),
62
+ });
63
+ if (!paidResponse.ok) {
64
+ const paidError = await paidResponse.json().catch(() => ({}));
65
+ throw new Error(`Payment failed (${paidResponse.status}): ${paidError?.error?.message || "Unknown error"}`);
66
+ }
67
+ // Step 5: Return response with payment metadata
68
+ return paidResponse.json();
69
+ }
70
+ /**
71
+ * Get available models and their JOULE pricing.
72
+ */
73
+ async models() {
74
+ const response = await fetch(`${this.computeUrl}/api/models`);
75
+ if (!response.ok) {
76
+ throw new Error(`Failed to fetch models: ${response.status}`);
77
+ }
78
+ return response.json();
79
+ }
80
+ }
81
+ exports.JouleClient = JouleClient;
@@ -0,0 +1,3 @@
1
+ export { JouleClient } from "./client";
2
+ export { buildSignedPayment } from "./wallet";
3
+ export type { JouleClientConfig, ChatRequest, ChatResponse, ChatMessage, PaymentRequirements, PaymentPayload, } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSignedPayment = exports.JouleClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "JouleClient", { enumerable: true, get: function () { return client_1.JouleClient; } });
6
+ var wallet_1 = require("./wallet");
7
+ Object.defineProperty(exports, "buildSignedPayment", { enumerable: true, get: function () { return wallet_1.buildSignedPayment; } });
@@ -0,0 +1,68 @@
1
+ export interface JouleClientConfig {
2
+ /** Agent's Stellar secret key (starts with S...) */
3
+ secretKey: string;
4
+ /** Compute server URL (default: https://compute.lumenbro.com) */
5
+ computeUrl?: string;
6
+ /** Soroban RPC URL (default: https://soroban-testnet.stellar.org) */
7
+ rpcUrl?: string;
8
+ /** Stellar network (default: testnet) */
9
+ network?: "testnet" | "mainnet";
10
+ }
11
+ export interface ChatMessage {
12
+ role: "system" | "user" | "assistant";
13
+ content: string;
14
+ }
15
+ export interface ChatRequest {
16
+ model: string;
17
+ messages: ChatMessage[];
18
+ max_tokens?: number;
19
+ temperature?: number;
20
+ top_p?: number;
21
+ stream?: boolean;
22
+ stop?: string | string[];
23
+ }
24
+ export interface ChatChoice {
25
+ index: number;
26
+ message: ChatMessage;
27
+ finish_reason: string;
28
+ }
29
+ export interface ChatResponse {
30
+ id: string;
31
+ object: string;
32
+ created: number;
33
+ model: string;
34
+ choices: ChatChoice[];
35
+ usage?: {
36
+ prompt_tokens: number;
37
+ completion_tokens: number;
38
+ total_tokens: number;
39
+ };
40
+ _payment: {
41
+ transaction: string;
42
+ network: string;
43
+ payer: string;
44
+ joulesPaid: string;
45
+ };
46
+ }
47
+ export interface PaymentRequirements {
48
+ scheme: string;
49
+ network: string;
50
+ maxAmountRequired: string;
51
+ resource: string;
52
+ description: string;
53
+ payTo: string;
54
+ maxTimeoutSeconds: number;
55
+ asset: string;
56
+ }
57
+ export interface PaymentPayload {
58
+ x402Version: number;
59
+ scheme: string;
60
+ network: string;
61
+ payload: {
62
+ signedTxXdr: string;
63
+ sourceAccount: string;
64
+ amount: string;
65
+ destination: string;
66
+ asset: string;
67
+ };
68
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ─── SDK Configuration ───────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ import { Keypair } from "@stellar/stellar-sdk";
2
+ import type { PaymentPayload, PaymentRequirements } from "./types";
3
+ /**
4
+ * Build, simulate, and sign a Soroban transfer invocation.
5
+ * Returns a PaymentPayload ready to be base64-encoded as an X-Payment header.
6
+ */
7
+ export declare function buildSignedPayment(keypair: Keypair, requirements: PaymentRequirements, network: string, rpcUrl?: string): Promise<PaymentPayload>;
package/dist/wallet.js ADDED
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildSignedPayment = buildSignedPayment;
4
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
5
+ const SOROBAN_RPC_URLS = {
6
+ testnet: "https://soroban-testnet.stellar.org",
7
+ mainnet: "https://rpc.lightsail.network/",
8
+ };
9
+ const NETWORK_PASSPHRASES = {
10
+ testnet: stellar_sdk_1.Networks.TESTNET,
11
+ mainnet: stellar_sdk_1.Networks.PUBLIC,
12
+ };
13
+ /**
14
+ * Build, simulate, and sign a Soroban transfer invocation.
15
+ * Returns a PaymentPayload ready to be base64-encoded as an X-Payment header.
16
+ */
17
+ async function buildSignedPayment(keypair, requirements, network, rpcUrl) {
18
+ const networkPassphrase = NETWORK_PASSPHRASES[network] || stellar_sdk_1.Networks.TESTNET;
19
+ const sorobanUrl = rpcUrl || SOROBAN_RPC_URLS[network] || SOROBAN_RPC_URLS.testnet;
20
+ const server = new stellar_sdk_1.rpc.Server(sorobanUrl);
21
+ const contractId = requirements.asset;
22
+ const contract = new stellar_sdk_1.Contract(contractId);
23
+ // Build transfer(from, to, amount) invocation
24
+ const transferOp = contract.call("transfer", new stellar_sdk_1.Address(keypair.publicKey()).toScVal(), new stellar_sdk_1.Address(requirements.payTo).toScVal(), (0, stellar_sdk_1.nativeToScVal)(BigInt(requirements.maxAmountRequired), { type: "i128" }));
25
+ // Load agent account for sequence number
26
+ const account = await server.getAccount(keypair.publicKey());
27
+ const tx = new stellar_sdk_1.TransactionBuilder(account, {
28
+ fee: "100000",
29
+ networkPassphrase,
30
+ })
31
+ .addOperation(transferOp)
32
+ .setTimeout(300)
33
+ .build();
34
+ // Simulate to get auth entries and resource fees
35
+ const simResult = await server.simulateTransaction(tx);
36
+ if (stellar_sdk_1.rpc.Api.isSimulationError(simResult)) {
37
+ const errMsg = simResult.error || "Unknown simulation error";
38
+ throw new Error(`Simulation failed: ${errMsg}`);
39
+ }
40
+ // Assemble with auth entries and sign
41
+ const assembled = stellar_sdk_1.rpc.assembleTransaction(tx, simResult).build();
42
+ assembled.sign(keypair);
43
+ const signedXdr = assembled.toXDR();
44
+ return {
45
+ x402Version: 1,
46
+ scheme: "exact",
47
+ network: `stellar:${network}`,
48
+ payload: {
49
+ signedTxXdr: signedXdr,
50
+ sourceAccount: keypair.publicKey(),
51
+ amount: requirements.maxAmountRequired,
52
+ destination: requirements.payTo,
53
+ asset: contractId,
54
+ },
55
+ };
56
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "joule-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Pay for AI inference with JOULE tokens on Stellar — handles x402 payment protocol automatically",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "require": "./dist/index.js",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build",
17
+ "example": "npx tsx examples/chat.ts"
18
+ },
19
+ "keywords": [
20
+ "joule",
21
+ "ai",
22
+ "inference",
23
+ "llm",
24
+ "stellar",
25
+ "soroban",
26
+ "x402",
27
+ "micropayments",
28
+ "agents",
29
+ "pay-per-query",
30
+ "compute",
31
+ "blockchain"
32
+ ],
33
+ "author": "LumenBro <hello@lumenbro.com>",
34
+ "homepage": "https://github.com/lumenbro/joule-sdk#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/lumenbro/joule-sdk.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/lumenbro/joule-sdk/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "dependencies": {
46
+ "@stellar/stellar-sdk": "14.4.3"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.0.0",
50
+ "tsx": "^4.21.0",
51
+ "typescript": "^5.7.0"
52
+ },
53
+ "files": [
54
+ "dist/",
55
+ "README.md",
56
+ "LICENSE"
57
+ ],
58
+ "license": "MIT"
59
+ }