@t54-labs/clawcredit-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/SKILL.md +106 -0
- package/clawcredit-sdk-0.1.0.tgz +0 -0
- package/index.js +156 -0
- package/lib/audit.js +53 -0
- package/lib/monitor.js +87 -0
- package/lib/store.js +13 -0
- package/package.json +11 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# ClawCredit Integration Skill
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
This skill allows Open Claw Agents to access **ClawCredit**, a decentralized credit service.
|
|
5
|
+
By integrating the `@clawcredit/sdk`, agents can:
|
|
6
|
+
1. **Get a Credit Line**: Prove your worth (code, prompt, environment) and get funded.
|
|
7
|
+
2. **Pay for Services**: Use your credit to pay other agents/merchants via x402.
|
|
8
|
+
3. **Build Credit**: Good repayment and low-risk behavior increases your limit.
|
|
9
|
+
|
|
10
|
+
## 2. Prerequisites
|
|
11
|
+
- **Node.js Environment** (The SDK is a Node.js package).
|
|
12
|
+
- **Invitation Code**: You need a valid `invite_code` to register.
|
|
13
|
+
|
|
14
|
+
## 3. Installation
|
|
15
|
+
Instruct the user (or run this in your shell if allowed) to install the SDK:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @t54-labs/clawcredit-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 4. Usage Guide
|
|
22
|
+
|
|
23
|
+
### Step 1: Initialization & Registration (with Code Audit)
|
|
24
|
+
You must register to receive your initial Credit Line.
|
|
25
|
+
**IMPORTANT**: Use the `audit()` wrapper on your core logic functions. This proves to the Risk Engine that your code is safe.
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
const { ClawCredit, audit } = require('@t54-labs/clawcredit-sdk');
|
|
29
|
+
|
|
30
|
+
// 1. Audit your core functions (Optional but recommended for higher limit)
|
|
31
|
+
function myCoreLogic(input) {
|
|
32
|
+
// ... implementation ...
|
|
33
|
+
}
|
|
34
|
+
const safeLogic = audit(myCoreLogic);
|
|
35
|
+
|
|
36
|
+
// 2. Initialize Client
|
|
37
|
+
const credit = new ClawCredit({
|
|
38
|
+
agentName: "MyAgentName"
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 3. Register
|
|
42
|
+
await credit.register({
|
|
43
|
+
inviteCode: "YOUR_INVITE_CODE",
|
|
44
|
+
coreCode: "const agent = ...",
|
|
45
|
+
systemPrompt: "You are a helpful assistant...",
|
|
46
|
+
runtimeEnv: "node-v22",
|
|
47
|
+
model: "gpt-5.2" // Optional
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Step 2: Enable Reasoning Trace
|
|
52
|
+
To ensure your transactions are approved, you must allow ClawCredit to trace your reasoning process.
|
|
53
|
+
Wrap your OpenAI client with `wrapOpenAI`.
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const OpenAI = require('openai');
|
|
57
|
+
const { wrapOpenAI, withTrace } = require('@t54-labs/clawcredit-sdk');
|
|
58
|
+
|
|
59
|
+
// Wrap the client
|
|
60
|
+
const openai = wrapOpenAI(new OpenAI({ apiKey: '...' }));
|
|
61
|
+
|
|
62
|
+
// Wrap your execution loop
|
|
63
|
+
await withTrace(async () => {
|
|
64
|
+
// Call LLM
|
|
65
|
+
const completion = await openai.chat.completions.create({
|
|
66
|
+
messages: [{ role: "user", content: "Buy me a coffee service" }],
|
|
67
|
+
model: "gpt-5.2",
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// If the LLM decides to call a tool (pay), the SDK will automatically
|
|
71
|
+
// attach the 'messages' and 'completion' as proof of reasoning.
|
|
72
|
+
|
|
73
|
+
// ... logic to call credit.pay() ...
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Step 3: Making Payments (Settlement via x402)
|
|
78
|
+
When you need to pay a merchant (another Agent or Service), use `pay()`.
|
|
79
|
+
If you used `withTrace` and `wrapOpenAI`, the reasoning is attached automatically.
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const transactionResult = await credit.pay({
|
|
83
|
+
transaction: {
|
|
84
|
+
recipient: "merchant_address_or_id",
|
|
85
|
+
amount: 5.00, // USD
|
|
86
|
+
chain: "BASE",
|
|
87
|
+
asset: "USDC"
|
|
88
|
+
},
|
|
89
|
+
request_body: {
|
|
90
|
+
service_name: "PremiumSearch",
|
|
91
|
+
params: { query: "latest crypto prices" }
|
|
92
|
+
},
|
|
93
|
+
// Optional. If provided, it increases the approval rate.
|
|
94
|
+
// context: { reasoning_process: "..." }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (transactionResult.status === 'success') {
|
|
98
|
+
console.log("Payment successful! Result:", transactionResult.merchant_response);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 5. Settlement & Chains
|
|
103
|
+
ClawCredit handles the blockchain complexity.
|
|
104
|
+
- Your credit is denominated in **USD**.
|
|
105
|
+
- Settlements occur on **Base (USDC)**, **Solana (USDC)**, or **XRPL (RLUSD)**.
|
|
106
|
+
- The merchant receives the crypto; you spend your Credit.
|
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
const { audit, getAuditedSnapshot } = require('./lib/audit');
|
|
4
|
+
const { wrapOpenAI, withTrace, getTraceContext } = require('./lib/monitor');
|
|
5
|
+
|
|
6
|
+
class ClawCredit {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.serviceUrl = config.serviceUrl || 'https://api.clawcredit.com'; // Default production URL
|
|
9
|
+
this.agentName = config.agentName;
|
|
10
|
+
this.apiToken = config.apiToken; // Token stored after registration
|
|
11
|
+
this.agentId = config.agentId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Register the agent to get a credit limit.
|
|
16
|
+
* Requires Invitation Code and Audit Materials.
|
|
17
|
+
*/
|
|
18
|
+
async register({ inviteCode, coreCode, systemPrompt, runtimeEnv, model }) {
|
|
19
|
+
if (!inviteCode) throw new Error("Invitation Code is required.");
|
|
20
|
+
|
|
21
|
+
// Include audited functions if available (via audit decorator)
|
|
22
|
+
const auditedFunctions = getAuditedSnapshot();
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const payload = {
|
|
26
|
+
agent_name: this.agentName,
|
|
27
|
+
invite_code: inviteCode,
|
|
28
|
+
audit_material: {
|
|
29
|
+
core_code: coreCode, // Main entry code provided manually
|
|
30
|
+
audited_functions: auditedFunctions, // Auto-collected functions
|
|
31
|
+
system_prompt: systemPrompt,
|
|
32
|
+
runtime_env: runtimeEnv,
|
|
33
|
+
model: model || null
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const res = await axios.post(`${this.serviceUrl}/v1/agent/register`, payload);
|
|
38
|
+
|
|
39
|
+
// Save credentials
|
|
40
|
+
this.agentId = res.data.agent_id;
|
|
41
|
+
this.apiToken = res.data.api_token;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
agent_id: this.agentId,
|
|
45
|
+
credit_limit: res.data.credit_limit,
|
|
46
|
+
message: "Registration successful. Credit line issued."
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
this._handleError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get current Balance and Credit Score.
|
|
55
|
+
*/
|
|
56
|
+
async getBalance() {
|
|
57
|
+
this._ensureAuth();
|
|
58
|
+
try {
|
|
59
|
+
const res = await axios.get(`${this.serviceUrl}/v1/credit/balance`, {
|
|
60
|
+
headers: { 'Authorization': `Bearer ${this.apiToken}` }
|
|
61
|
+
});
|
|
62
|
+
return res.data;
|
|
63
|
+
// Returns: { available_usd: 100.00, credit_score: 650, pending_bills: 0 }
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this._handleError(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pay a merchant using ClawCredit.
|
|
71
|
+
* Settlements are handled via x402 on Base/Solana/XRPL.
|
|
72
|
+
*/
|
|
73
|
+
async pay({ transaction, request_body, context }) {
|
|
74
|
+
this._ensureAuth();
|
|
75
|
+
|
|
76
|
+
// 1. Try to get Trace Context from AsyncLocalStorage
|
|
77
|
+
const autoTrace = getTraceContext();
|
|
78
|
+
|
|
79
|
+
// 2. Merge Manual Context with Auto Trace
|
|
80
|
+
let reasoning = null;
|
|
81
|
+
if (context && context.reasoning_process) {
|
|
82
|
+
reasoning = context.reasoning_process;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// We do NOT set 'reasoning' to raw messages because it expects a String.
|
|
86
|
+
// The backend will check 'raw_trace' if 'reasoning_process' is null.
|
|
87
|
+
|
|
88
|
+
const finalAuditContext = {
|
|
89
|
+
reasoning_process: reasoning, // Explicit reasoning string or null
|
|
90
|
+
raw_trace: autoTrace.last_llm_call || {}, // Full messages/tools/response
|
|
91
|
+
current_task: (context && context.current_task) || "unknown",
|
|
92
|
+
timestamp: Date.now()
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Validate: We need *some* proof of reasoning.
|
|
96
|
+
if (!finalAuditContext.reasoning_process && !finalAuditContext.raw_trace.messages) {
|
|
97
|
+
throw new Error("Payment rejected: No 'reasoning_process' provided and no active LLM trace found.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!transaction || !transaction.recipient || !transaction.amount || !transaction.chain || !transaction.asset) {
|
|
101
|
+
throw new Error("Payment rejected: transaction must include recipient, amount, chain, and asset.");
|
|
102
|
+
}
|
|
103
|
+
if (!request_body) {
|
|
104
|
+
throw new Error("Payment rejected: request_body is required.");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const txRequest = {
|
|
108
|
+
transaction: {
|
|
109
|
+
recipient: transaction.recipient,
|
|
110
|
+
amount: transaction.amount,
|
|
111
|
+
chain: transaction.chain,
|
|
112
|
+
asset: transaction.asset
|
|
113
|
+
},
|
|
114
|
+
request_body: request_body,
|
|
115
|
+
audit_context: finalAuditContext
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// 3. Send Transaction Request to ClawCredit Service
|
|
120
|
+
const res = await axios.post(`${this.serviceUrl}/v1/transaction/pay`, txRequest, {
|
|
121
|
+
headers: { 'Authorization': `Bearer ${this.apiToken}` }
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status: 'success',
|
|
126
|
+
tx_hash: res.data.tx_hash, // Blockchain Tx Hash (Base/Solana/XRPL)
|
|
127
|
+
chain: res.data.chain, // e.g. "BASE"
|
|
128
|
+
merchant_response: res.data.merchant_response // The actual service result (e.g. search results)
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this._handleError(error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_ensureAuth() {
|
|
137
|
+
if (!this.apiToken) {
|
|
138
|
+
throw new Error("ClawCredit: Not authenticated. Please call register() first.");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_handleError(error) {
|
|
143
|
+
if (error.response) {
|
|
144
|
+
throw new Error(`ClawCredit API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
145
|
+
}
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Export the Main Class and the Helper Functions
|
|
151
|
+
module.exports = {
|
|
152
|
+
ClawCredit,
|
|
153
|
+
audit,
|
|
154
|
+
wrapOpenAI,
|
|
155
|
+
withTrace
|
|
156
|
+
};
|
package/lib/audit.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const { auditedEntities } = require('./store');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes code by removing extra whitespace to ensure consistent hashing.
|
|
6
|
+
*/
|
|
7
|
+
function normalizeCode(code) {
|
|
8
|
+
return code.replace(/\s+/g, ' ').trim();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* "Decorator" wrapper to audit a function.
|
|
13
|
+
* Since JS decorators aren't standard yet in all envs, we use a wrapper function.
|
|
14
|
+
* Usage: const safeFn = audit(originalFn);
|
|
15
|
+
*/
|
|
16
|
+
function audit(fn, nameOverride = null) {
|
|
17
|
+
if (typeof fn !== 'function') {
|
|
18
|
+
throw new Error("ClawCredit Audit: Argument must be a function");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const name = nameOverride || fn.name || 'anonymous';
|
|
22
|
+
const source = fn.toString();
|
|
23
|
+
const hash = crypto.createHash('sha256').update(normalizeCode(source)).digest('hex');
|
|
24
|
+
|
|
25
|
+
// Register metadata
|
|
26
|
+
auditedEntities.push({
|
|
27
|
+
name: name,
|
|
28
|
+
hash: hash,
|
|
29
|
+
source: source,
|
|
30
|
+
timestamp: Date.now()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Return the original function (pass-through)
|
|
34
|
+
// We could wrap it to track execution, but for "Code Audit", static capture is sufficient.
|
|
35
|
+
return fn;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get all audited functions formatted for submission
|
|
40
|
+
*/
|
|
41
|
+
function getAuditedSnapshot() {
|
|
42
|
+
return auditedEntities.map(e => ({
|
|
43
|
+
function_name: e.name,
|
|
44
|
+
function_code: e.source,
|
|
45
|
+
hash: e.hash
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
audit,
|
|
51
|
+
getAuditedSnapshot
|
|
52
|
+
};
|
|
53
|
+
|
package/lib/monitor.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const { traceStorage } = require('./store');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an OpenAI client instance to capture prompts and tools.
|
|
5
|
+
* Mimics the Python SDK's monkey-patching approach but for Node.js object.
|
|
6
|
+
*/
|
|
7
|
+
function wrapOpenAI(openaiInstance) {
|
|
8
|
+
if (!openaiInstance || !openaiInstance.chat || !openaiInstance.chat.completions) {
|
|
9
|
+
console.warn("ClawCredit: Invalid OpenAI instance provided. Skipping wrapper.");
|
|
10
|
+
return openaiInstance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const originalCreate = openaiInstance.chat.completions.create.bind(openaiInstance.chat.completions);
|
|
14
|
+
|
|
15
|
+
// Overwrite the create method
|
|
16
|
+
openaiInstance.chat.completions.create = async function(params, options) {
|
|
17
|
+
// 1. Capture Context (Prompt & Tools)
|
|
18
|
+
const traceData = {
|
|
19
|
+
messages: params.messages || [],
|
|
20
|
+
tools: params.tools || [],
|
|
21
|
+
model: params.model,
|
|
22
|
+
timestamp: Date.now()
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Store in AsyncLocalStorage so it's accessible in the same async flow (e.g. during payment)
|
|
26
|
+
// We use 'enterWith' or run the promise inside 'run'.
|
|
27
|
+
// However, 'enterWith' is discouraged. Better to store it if we can wrapping the whole execution.
|
|
28
|
+
// But for "Context retrieval" later, we assume the Agent flow is:
|
|
29
|
+
// LLM -> Tool Call (Payment).
|
|
30
|
+
// If we are INSIDE the Tool Call, we want the LLM context that triggered it.
|
|
31
|
+
// Wait, the Python SDK stores it BEFORE the LLM call, but the tool is called BY the LLM response processing.
|
|
32
|
+
// So the flow is: User -> LLM -> Response(ToolCall) -> Agent executes Tool.
|
|
33
|
+
|
|
34
|
+
// In Python's contextvars, the context propagates if set properly.
|
|
35
|
+
// Here, we just want to save "what was the last LLM call made in this flow".
|
|
36
|
+
|
|
37
|
+
// Let's just update the store if we are already in a store context, OR create a new one?
|
|
38
|
+
// Actually, typically the Agent Framework loop runs in some scope.
|
|
39
|
+
// If we want to capture "Reasoning", we need the messages that LED to this tool call.
|
|
40
|
+
|
|
41
|
+
// Strategy:
|
|
42
|
+
// We save the inputs to a global-ish store keyed by something, or rely on the user
|
|
43
|
+
// to pass the trace. But the requirement is "Trace Agent's reasoning".
|
|
44
|
+
|
|
45
|
+
// Let's start by saving it to the store.
|
|
46
|
+
const store = traceStorage.getStore() || {};
|
|
47
|
+
store.last_llm_call = traceData;
|
|
48
|
+
|
|
49
|
+
// If we are not inside a run(), this store might be lost.
|
|
50
|
+
// For now, we assume the user might wrap their main loop with traceStorage.run().
|
|
51
|
+
// If not, we might need a fallback or side-channel.
|
|
52
|
+
// But let's stick to the "best effort" AsyncLocalStorage.
|
|
53
|
+
|
|
54
|
+
// 2. Execute Original
|
|
55
|
+
const response = await originalCreate(params, options);
|
|
56
|
+
|
|
57
|
+
// 3. Capture Output (Reasoning/CoT)
|
|
58
|
+
if (store.last_llm_call) {
|
|
59
|
+
store.last_llm_call.response = response;
|
|
60
|
+
// Extract reasoning if present (e.g. O1 model or explicit CoT field)
|
|
61
|
+
// or just the content.
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return response;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return openaiInstance;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run a function within a Trace Context.
|
|
72
|
+
* The Agent should wrap its "Step" or "Loop" with this.
|
|
73
|
+
*/
|
|
74
|
+
function withTrace(fn) {
|
|
75
|
+
return traceStorage.run({}, fn);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getTraceContext() {
|
|
79
|
+
return traceStorage.getStore() || {};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
wrapOpenAI,
|
|
84
|
+
withTrace,
|
|
85
|
+
getTraceContext
|
|
86
|
+
};
|
|
87
|
+
|
package/lib/store.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
2
|
+
|
|
3
|
+
// Global storage for trace context (Request-scoped)
|
|
4
|
+
const traceStorage = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
// Global registry for audited functions (Application-scoped)
|
|
7
|
+
const auditedEntities = [];
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
traceStorage,
|
|
11
|
+
auditedEntities
|
|
12
|
+
};
|
|
13
|
+
|
package/package.json
ADDED