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 +21 -0
- package/README.md +178 -0
- package/dist/client.d.ts +25 -0
- package/dist/client.js +81 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +3 -0
- package/dist/wallet.d.ts +7 -0
- package/dist/wallet.js +56 -0
- package/package.json +59 -0
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
|
package/dist/client.d.ts
ADDED
|
@@ -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;
|
package/dist/index.d.ts
ADDED
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; } });
|
package/dist/types.d.ts
ADDED
|
@@ -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
package/dist/wallet.d.ts
ADDED
|
@@ -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
|
+
}
|