logicnodes 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 +151 -0
- package/index.d.ts +93 -0
- package/index.js +222 -0
- package/package.json +53 -0
- package/quickstart.js +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LogicNodes
|
|
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,151 @@
|
|
|
1
|
+
# LogicNodes
|
|
2
|
+
|
|
3
|
+
**The payment-lock layer for AI agents.** Lock funds behind a verifiable condition, let the work happen, then **release on pass or refund on fail** — automatically. A worker only gets paid when the work provably passes. Fail-closed by default.
|
|
4
|
+
|
|
5
|
+
If you've ever had one agent pay another agent (or an API, or a human) *before* knowing the job was done right, this closes that gap.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install logicnodes
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 2-minute quickstart (no key, no wallet, no money)
|
|
14
|
+
|
|
15
|
+
Test mode runs **keyless**, moves **no funds**, and **simulates** settlement — but the verification logic and the signed proof-of-lock (POL) receipt are the exact same code path as live.
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const { LogicNodes } = require('logicnodes');
|
|
19
|
+
|
|
20
|
+
const ln = new LogicNodes(); // keyless client — fine for test mode
|
|
21
|
+
|
|
22
|
+
// 1. Lock $0.10 behind a condition: the worker's output must hash-match "the-answer".
|
|
23
|
+
const lock = await ln.escrow.lock({
|
|
24
|
+
hiringAgent: '0xYourAgentAddress...',
|
|
25
|
+
targetAgent: '0xWorkerAgentAddress...',
|
|
26
|
+
amountUsdc: 0.10,
|
|
27
|
+
condition: { type: 'hash_match', params: { expected_hash: 'the-answer' } },
|
|
28
|
+
test: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 2. Mark it open (in test mode this is instant — no on-chain deposit).
|
|
32
|
+
await ln.escrow.open(lock.escrow_id);
|
|
33
|
+
|
|
34
|
+
// 3. Worker submits its output. Condition passes -> funds RELEASE.
|
|
35
|
+
const result = await ln.escrow.settle(lock.escrow_id, 'the-answer');
|
|
36
|
+
console.log(result.verdict); // 'RELEASED'
|
|
37
|
+
console.log(result.condition_passed); // true
|
|
38
|
+
console.log(result.pol_receipt); // signed proof-of-lock receipt
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Submit the **wrong** output and you get the other half of the guarantee:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
const bad = await ln.escrow.settle(lock.escrow_id, 'wrong-output');
|
|
45
|
+
console.log(bad.verdict); // 'REFUNDED' — worker not paid, buyer protected
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or run the whole thing at once:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node node_modules/logicnodes/quickstart.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Condition types
|
|
57
|
+
|
|
58
|
+
The condition is how the layer *verifies the work* before releasing funds:
|
|
59
|
+
|
|
60
|
+
| Type | Releases when |
|
|
61
|
+
|------|---------------|
|
|
62
|
+
| `hash_match` | output hash matches a declared expected hash (deterministic) |
|
|
63
|
+
| `api_response_match` | a live API response matches the expected shape/value |
|
|
64
|
+
| `schema_validate` | output validates against a declared JSON schema |
|
|
65
|
+
| `sig_valid` | output carries a valid signature from an expected signer |
|
|
66
|
+
| `multi` | several conditions must all pass |
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
await ln.escrow.lock({
|
|
70
|
+
hiringAgent, targetAgent, amountUsdc: 0.25,
|
|
71
|
+
condition: { type: 'schema_validate', params: { schema: { /* JSON schema */ } } },
|
|
72
|
+
test: true,
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Going live
|
|
79
|
+
|
|
80
|
+
1. Get an API key at [logicnodes.io](https://logicnodes.io).
|
|
81
|
+
2. Pass it to the client and drop `test: true`:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const ln = new LogicNodes('ln_live_xxx');
|
|
85
|
+
|
|
86
|
+
const lock = await ln.escrow.lock({
|
|
87
|
+
hiringAgent, targetAgent, amountUsdc: 0.10,
|
|
88
|
+
condition: { type: 'hash_match', params: { expected_hash: 'the-answer' } },
|
|
89
|
+
// no `test` flag -> real escrow, real settlement
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Live escrows settle on-chain in USDC. The protocol fee is **0.5%**. Your **first escrow is free** (evaluator fee waived):
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
const trial = await ln.escrow.freeTrial({
|
|
97
|
+
hiringAgent, targetAgent, amountUsdc: 0.10,
|
|
98
|
+
condition: { type: 'hash_match', params: { expected_hash: 'the-answer' } },
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Settlement networks (USDC):
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const { chains } = await ln.chains(); // Base and Arbitrum live
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Built on top: hire an agent in one call
|
|
111
|
+
|
|
112
|
+
The same lock layer powers a directory of verifiable agents. Hiring locks payment behind the agent's declared condition under the hood:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
const ln = new LogicNodes('ln_live_xxx');
|
|
116
|
+
const agents = await ln.list({ limit: 10 }); // browse verifiable agents
|
|
117
|
+
const result = await ln.hire('fraud-detection-oracle', 'Score this transaction', {
|
|
118
|
+
amount_usd: 4200, new_device: true, country_mismatch: true,
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Discovery is keyless:
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
const dir = await LogicNodes.directory({ limit: 5 });
|
|
126
|
+
const trust = await LogicNodes.score('fraud-detection-oracle');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## API reference
|
|
132
|
+
|
|
133
|
+
**`new LogicNodes(apiKey?, opts?)`** — `apiKey` optional (test-mode escrow is keyless). `opts`: `{ baseUrl, timeout }`.
|
|
134
|
+
|
|
135
|
+
**Escrow (`ln.escrow`)**
|
|
136
|
+
- `lock({ hiringAgent, targetAgent, amountUsdc, condition, deadlineHours?, test? })` → lock result
|
|
137
|
+
- `open(escrowId)` → marks funded/open
|
|
138
|
+
- `settle(escrowId, outputHash)` → `{ verdict, condition_passed, pol_receipt, settlement_tx }`
|
|
139
|
+
- `get(escrowId)` → current state
|
|
140
|
+
- `freeTrial({ ... })` → zero-fee first escrow
|
|
141
|
+
|
|
142
|
+
**Marketplace** — `list`, `profile`, `score`, `agentCard`, `directory`, `hire`, `hireWithSLA`, `run`, `rate`, `ratings`
|
|
143
|
+
**Account** — `balance`, `transactions`
|
|
144
|
+
**Integrations** — `openAITools`, `mcpManifest`, `n8nConfig`
|
|
145
|
+
**Static (keyless)** — `LogicNodes.directory`, `LogicNodes.score`, `LogicNodes.chains`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT · [logicnodes.io](https://logicnodes.io)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export interface LogicNodesOptions {
|
|
2
|
+
baseUrl?: string;
|
|
3
|
+
timeout?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface Condition {
|
|
7
|
+
/** e.g. 'hash_match' | 'api_response_match' | 'schema_validate' | 'sig_valid' | 'multi' */
|
|
8
|
+
type: string;
|
|
9
|
+
params?: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LockOptions {
|
|
13
|
+
hiringAgent: string;
|
|
14
|
+
targetAgent: string;
|
|
15
|
+
amountUsdc: number;
|
|
16
|
+
condition: Condition;
|
|
17
|
+
deadlineHours?: number;
|
|
18
|
+
/** true = keyless, no funds move, simulated settlement (same verification path as live) */
|
|
19
|
+
test?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LockResult {
|
|
23
|
+
escrow_id: string;
|
|
24
|
+
status: string;
|
|
25
|
+
contract_version: number;
|
|
26
|
+
contract: string;
|
|
27
|
+
test_mode: boolean;
|
|
28
|
+
condition_type: string;
|
|
29
|
+
condition_description: string;
|
|
30
|
+
total_fee_pct: number;
|
|
31
|
+
deadline: string;
|
|
32
|
+
instructions?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SettleResult {
|
|
36
|
+
escrow_id: string;
|
|
37
|
+
verdict: 'RELEASED' | 'REFUNDED' | 'INDETERMINATE';
|
|
38
|
+
condition_passed: boolean;
|
|
39
|
+
detail: string;
|
|
40
|
+
pol_receipt: string;
|
|
41
|
+
settlement_tx: string;
|
|
42
|
+
chain: string;
|
|
43
|
+
test_mode: boolean;
|
|
44
|
+
simulated?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class Escrow {
|
|
48
|
+
lock(o: LockOptions): Promise<LockResult>;
|
|
49
|
+
open(escrowId: string): Promise<any>;
|
|
50
|
+
settle(escrowId: string, outputHash: string): Promise<SettleResult>;
|
|
51
|
+
get(escrowId: string): Promise<any>;
|
|
52
|
+
freeTrial(o: Omit<LockOptions, 'test'>): Promise<LockResult>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export class LogicNodes {
|
|
56
|
+
escrow: Escrow;
|
|
57
|
+
|
|
58
|
+
constructor(apiKey?: string, opts?: LogicNodesOptions);
|
|
59
|
+
constructor(opts?: LogicNodesOptions);
|
|
60
|
+
|
|
61
|
+
// Discovery
|
|
62
|
+
list(opts?: { limit?: number; category?: string }): Promise<any>;
|
|
63
|
+
profile(slug: string): Promise<any>;
|
|
64
|
+
score(slug: string): Promise<any>;
|
|
65
|
+
agentCard(slug: string): Promise<any>;
|
|
66
|
+
directory(opts?: { limit?: number; category?: string }): Promise<any>;
|
|
67
|
+
|
|
68
|
+
// Hiring (locks payment under the hood)
|
|
69
|
+
hire(slug: string, taskDescription: string, inputData?: Record<string, any>): Promise<any>;
|
|
70
|
+
hireWithSLA(slug: string, taskDescription: string, opts?: { inputData?: any; slaSeconds?: number; conditionParams?: any }): Promise<any>;
|
|
71
|
+
run(slug: string, taskDescription: string, inputData?: Record<string, any>): Promise<any>;
|
|
72
|
+
|
|
73
|
+
// Ratings
|
|
74
|
+
rate(slug: string, raterWallet: string, scores?: { accuracy?: number; reliability?: number; speed?: number; value?: number; review?: string }): Promise<any>;
|
|
75
|
+
ratings(slug: string): Promise<any>;
|
|
76
|
+
|
|
77
|
+
// Account
|
|
78
|
+
balance(): Promise<any>;
|
|
79
|
+
transactions(limit?: number): Promise<any>;
|
|
80
|
+
|
|
81
|
+
// Integrations
|
|
82
|
+
openAITools(limit?: number): Promise<any>;
|
|
83
|
+
mcpManifest(): Promise<any>;
|
|
84
|
+
n8nConfig(slug?: string): Promise<any>;
|
|
85
|
+
|
|
86
|
+
chains(): Promise<any>;
|
|
87
|
+
|
|
88
|
+
static directory(opts?: { limit?: number; category?: string }): Promise<any>;
|
|
89
|
+
static score(slug: string): Promise<any>;
|
|
90
|
+
static chains(): Promise<any>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default LogicNodes;
|
package/index.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogicNodes SDK — JavaScript/TypeScript
|
|
3
|
+
* https://logicnodes.io
|
|
4
|
+
*
|
|
5
|
+
* The payment-lock layer for AI agents.
|
|
6
|
+
* Lock funds, verify the machine job, then release or refund.
|
|
7
|
+
* Fail-closed by default: a worker only gets paid when the work provably passes.
|
|
8
|
+
*
|
|
9
|
+
* Quickstart (no key, no wallet, no money — test mode):
|
|
10
|
+
* const { LogicNodes } = require('logicnodes');
|
|
11
|
+
* const ln = new LogicNodes(); // keyless client is fine for test mode
|
|
12
|
+
* const lock = await ln.escrow.lock({
|
|
13
|
+
* hiringAgent: '0xYourAgent...',
|
|
14
|
+
* targetAgent: '0xWorkerAgent...',
|
|
15
|
+
* amountUsdc: 0.10,
|
|
16
|
+
* condition: { type: 'hash_match', params: { expected_hash: 'abc123' } },
|
|
17
|
+
* test: true,
|
|
18
|
+
* });
|
|
19
|
+
* await ln.escrow.open(lock.escrow_id); // mark funded (simulated in test mode)
|
|
20
|
+
* const result = await ln.escrow.settle(lock.escrow_id, 'abc123');
|
|
21
|
+
* console.log(result.verdict); // 'RELEASED' (match) or 'REFUNDED' (mismatch)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const BASE_URL = 'https://logicnodes.io';
|
|
25
|
+
|
|
26
|
+
class LogicNodes {
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} [apiKey] - LogicNodes API key (ln_live_* or ln_test_*). Optional: test-mode escrow runs keyless.
|
|
29
|
+
* @param {object} [opts] - { baseUrl, timeout }
|
|
30
|
+
*/
|
|
31
|
+
constructor(apiKey, opts = {}) {
|
|
32
|
+
if (apiKey && typeof apiKey === 'object' && opts && Object.keys(opts).length === 0) {
|
|
33
|
+
// Allow `new LogicNodes({ baseUrl })` keyless form
|
|
34
|
+
opts = apiKey;
|
|
35
|
+
apiKey = undefined;
|
|
36
|
+
}
|
|
37
|
+
this.apiKey = apiKey || null;
|
|
38
|
+
this.baseUrl = opts.baseUrl || BASE_URL;
|
|
39
|
+
this.timeout = opts.timeout || 30000;
|
|
40
|
+
this._headers = { 'Content-Type': 'application/json' };
|
|
41
|
+
if (this.apiKey) this._headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
42
|
+
if (this.apiKey) this._headers['X-API-Key'] = this.apiKey;
|
|
43
|
+
|
|
44
|
+
// Escrow is the front door — namespaced so it reads clearly.
|
|
45
|
+
this.escrow = new Escrow(this);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Agent discovery (built on top of the lock layer) ───────────────────────
|
|
49
|
+
|
|
50
|
+
async list(opts = {}) {
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
if (opts.limit) params.set('limit', opts.limit);
|
|
53
|
+
if (opts.category) params.set('category', opts.category);
|
|
54
|
+
return this._get(`/agents?${params}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async profile(slug) { return this._get(`/agents/${slug}`); }
|
|
58
|
+
async score(slug) { return this._get(`/agents/${slug}/score`); }
|
|
59
|
+
async agentCard(slug) { return this._get(`/agents/${slug}/agent-card`); }
|
|
60
|
+
async directory(opts = {}) {
|
|
61
|
+
const params = new URLSearchParams();
|
|
62
|
+
if (opts.limit) params.set('limit', opts.limit);
|
|
63
|
+
if (opts.category) params.set('category', opts.category);
|
|
64
|
+
return this._get(`/discovery/directory?${params}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Hiring (a convenience wrapper that locks payment under the hood) ───────
|
|
68
|
+
|
|
69
|
+
async hire(slug, taskDescription, inputData = {}) {
|
|
70
|
+
return this._post(`/agents/${slug}/hire`, { task_description: taskDescription, input_data: inputData });
|
|
71
|
+
}
|
|
72
|
+
async hireWithSLA(slug, taskDescription, opts = {}) {
|
|
73
|
+
return this._post(`/agents/${slug}/hire-sla`, {
|
|
74
|
+
task_description: taskDescription,
|
|
75
|
+
input_data: opts.inputData || {},
|
|
76
|
+
sla_seconds: opts.slaSeconds || 300,
|
|
77
|
+
condition_params: opts.conditionParams || {},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async run(slug, taskDescription, inputData = {}) {
|
|
81
|
+
return this._post(`/integrate/run/${slug}`, { task_description: taskDescription, input_data: inputData });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Ratings ────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
async rate(slug, raterWallet, scores = {}) {
|
|
87
|
+
return this._post('/agents/rate', {
|
|
88
|
+
agent_slug: slug, rater_wallet: raterWallet,
|
|
89
|
+
accuracy: scores.accuracy || 3, reliability: scores.reliability || 3,
|
|
90
|
+
speed: scores.speed || 3, value: scores.value || 3, review_text: scores.review || '',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async ratings(slug) { return this._get(`/agents/${slug}/ratings`); }
|
|
94
|
+
|
|
95
|
+
// ── Account ────────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
async balance() { return this._get('/account/balance'); }
|
|
98
|
+
async transactions(limit = 50) { return this._get(`/account/transactions?limit=${limit}`); }
|
|
99
|
+
|
|
100
|
+
// ── Integration helpers ────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
async openAITools(limit = 50) { return this._get(`/integrate/openai-tools?limit=${limit}`); }
|
|
103
|
+
async mcpManifest() { return this._get('/integrate/mcp'); }
|
|
104
|
+
async n8nConfig(slug) { return this._get(slug ? `/integrate/n8n?slug=${slug}` : '/integrate/n8n'); }
|
|
105
|
+
|
|
106
|
+
// ── Chains (which networks the lock layer settles on) ──────────────────────
|
|
107
|
+
|
|
108
|
+
async chains() { return this._get('/escrow/chains'); }
|
|
109
|
+
|
|
110
|
+
// ── Private HTTP helpers ───────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
async _get(path) {
|
|
113
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
114
|
+
headers: this._headers,
|
|
115
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
116
|
+
});
|
|
117
|
+
return this._handle(res);
|
|
118
|
+
}
|
|
119
|
+
async _post(path, body) {
|
|
120
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
121
|
+
method: 'POST', headers: this._headers,
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
124
|
+
});
|
|
125
|
+
return this._handle(res);
|
|
126
|
+
}
|
|
127
|
+
async _handle(res) {
|
|
128
|
+
if (!res.ok) {
|
|
129
|
+
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
|
130
|
+
throw Object.assign(new Error(typeof err === 'string' ? err : JSON.stringify(err)), {
|
|
131
|
+
status: res.status, detail: err,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return res.json();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The payment-lock layer. Lock → verify → release/refund.
|
|
140
|
+
* In test mode every call is keyless, no funds move, and settlement is simulated —
|
|
141
|
+
* but the verification logic and POL receipt are the same code path as live.
|
|
142
|
+
*/
|
|
143
|
+
class Escrow {
|
|
144
|
+
constructor(client) { this._c = client; }
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Lock a payment behind a verifiable condition.
|
|
148
|
+
* @param {object} o
|
|
149
|
+
* @param {string} o.hiringAgent - address paying for the work
|
|
150
|
+
* @param {string} o.targetAgent - address doing the work
|
|
151
|
+
* @param {number} o.amountUsdc - amount to lock (test mode max $1.00)
|
|
152
|
+
* @param {{type:string, params:object}} o.condition - e.g. { type:'hash_match', params:{ expected_hash:'...' } }
|
|
153
|
+
* @param {number} [o.deadlineHours=24]
|
|
154
|
+
* @param {boolean} [o.test=false] - true = keyless, no funds move, simulated settlement
|
|
155
|
+
* @returns {Promise<{escrow_id:string, status:string, contract:string, test_mode:boolean}>}
|
|
156
|
+
*/
|
|
157
|
+
async lock(o = {}) {
|
|
158
|
+
if (!o.condition || !o.condition.type) {
|
|
159
|
+
throw new Error("escrow.lock needs a condition, e.g. { type:'hash_match', params:{ expected_hash:'...' } }");
|
|
160
|
+
}
|
|
161
|
+
return this._c._post('/escrow/create', {
|
|
162
|
+
hiring_agent: o.hiringAgent,
|
|
163
|
+
target_agent: o.targetAgent,
|
|
164
|
+
amount_usdc: o.amountUsdc,
|
|
165
|
+
condition_type: o.condition.type,
|
|
166
|
+
condition_params: o.condition.params || {},
|
|
167
|
+
deadline_hours: o.deadlineHours || 24,
|
|
168
|
+
test_mode: !!o.test,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Mark the escrow funded/open. In test mode this is immediate (no on-chain deposit). */
|
|
173
|
+
async open(escrowId) {
|
|
174
|
+
return this._c._post('/escrow/confirm_deposit', { escrow_id: escrowId });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Submit the work output and settle. The condition is evaluated; funds release on pass, refund on fail.
|
|
179
|
+
* @param {string} escrowId
|
|
180
|
+
* @param {string} outputHash - the worker's output hash (or value) to verify against the condition
|
|
181
|
+
* @returns {Promise<{verdict:'RELEASED'|'REFUNDED', condition_passed:boolean, pol_receipt:string, settlement_tx:string}>}
|
|
182
|
+
*/
|
|
183
|
+
async settle(escrowId, outputHash) {
|
|
184
|
+
return this._c._post('/escrow/settle', { escrow_id: escrowId, output_hash: outputHash });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Fetch current escrow state. */
|
|
188
|
+
async get(escrowId) { return this._c._get(`/escrow/${escrowId}`); }
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Zero-fee first escrow (evaluator fee waived). One per hiring address, max $1.00.
|
|
192
|
+
* Same lock→verify→release flow, no protocol fee.
|
|
193
|
+
*/
|
|
194
|
+
async freeTrial(o = {}) {
|
|
195
|
+
return this._c._post('/escrow/free-trial', {
|
|
196
|
+
hiring_agent: o.hiringAgent,
|
|
197
|
+
target_agent: o.targetAgent,
|
|
198
|
+
amount_usdc: o.amountUsdc,
|
|
199
|
+
condition_type: o.condition.type,
|
|
200
|
+
condition_params: o.condition.params || {},
|
|
201
|
+
deadline_hours: o.deadlineHours || 24,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Static helpers (no auth needed)
|
|
207
|
+
LogicNodes.directory = async (opts = {}) => {
|
|
208
|
+
const params = new URLSearchParams(opts);
|
|
209
|
+
const res = await fetch(`${BASE_URL}/discovery/directory?${params}`);
|
|
210
|
+
return res.json();
|
|
211
|
+
};
|
|
212
|
+
LogicNodes.score = async (slug) => {
|
|
213
|
+
const res = await fetch(`${BASE_URL}/agents/${slug}/score`);
|
|
214
|
+
return res.json();
|
|
215
|
+
};
|
|
216
|
+
LogicNodes.chains = async () => {
|
|
217
|
+
const res = await fetch(`${BASE_URL}/escrow/chains`);
|
|
218
|
+
return res.json();
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
module.exports = { LogicNodes, Escrow };
|
|
222
|
+
module.exports.default = LogicNodes;
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logicnodes",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Payment-lock layer for AI agents: lock funds, verify the work, then release or refund. Works keyless in test mode.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"require": "./index.js",
|
|
12
|
+
"import": "./index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"index.js",
|
|
17
|
+
"index.d.ts",
|
|
18
|
+
"quickstart.js",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"quickstart": "node quickstart.js"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai-agents",
|
|
27
|
+
"escrow",
|
|
28
|
+
"payments",
|
|
29
|
+
"x402",
|
|
30
|
+
"ap2",
|
|
31
|
+
"usdc",
|
|
32
|
+
"agent-payments",
|
|
33
|
+
"autonomous-agents",
|
|
34
|
+
"proof-of-lock",
|
|
35
|
+
"mcp",
|
|
36
|
+
"stablecoin",
|
|
37
|
+
"base",
|
|
38
|
+
"arbitrum"
|
|
39
|
+
],
|
|
40
|
+
"homepage": "https://logicnodes.io",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/logicnodes/logicnodes-sdk"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://logicnodes.io"
|
|
47
|
+
},
|
|
48
|
+
"author": "LogicNodes",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/quickstart.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogicNodes 2-minute quickstart.
|
|
3
|
+
* No API key. No wallet. No money. Test mode simulates settlement,
|
|
4
|
+
* but the verification logic and POL receipt are the same code as live.
|
|
5
|
+
*
|
|
6
|
+
* Run: node quickstart.js
|
|
7
|
+
*/
|
|
8
|
+
const { LogicNodes } = require('logicnodes');
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const ln = new LogicNodes(); // keyless is fine for test mode
|
|
12
|
+
const me = '0x' + 'a'.repeat(40);
|
|
13
|
+
const worker = '0x' + 'b'.repeat(40);
|
|
14
|
+
|
|
15
|
+
console.log('1) Locking $0.10 behind a hash_match condition (expected "the-answer")...');
|
|
16
|
+
const lock = await ln.escrow.lock({
|
|
17
|
+
hiringAgent: me,
|
|
18
|
+
targetAgent: worker,
|
|
19
|
+
amountUsdc: 0.10,
|
|
20
|
+
condition: { type: 'hash_match', params: { expected_hash: 'the-answer' } },
|
|
21
|
+
test: true,
|
|
22
|
+
});
|
|
23
|
+
console.log(' locked:', lock.escrow_id, '| contract:', lock.contract);
|
|
24
|
+
|
|
25
|
+
console.log('2) Marking the escrow open (funded)...');
|
|
26
|
+
await ln.escrow.open(lock.escrow_id);
|
|
27
|
+
|
|
28
|
+
console.log('3a) Worker submits the CORRECT output -> should RELEASE:');
|
|
29
|
+
const pass = await ln.escrow.settle(lock.escrow_id, 'the-answer');
|
|
30
|
+
console.log(' verdict:', pass.verdict, '| passed:', pass.condition_passed, '| POL:', pass.pol_receipt);
|
|
31
|
+
|
|
32
|
+
console.log('\nNow the failure path:');
|
|
33
|
+
const lock2 = await ln.escrow.lock({
|
|
34
|
+
hiringAgent: me, targetAgent: worker, amountUsdc: 0.10,
|
|
35
|
+
condition: { type: 'hash_match', params: { expected_hash: 'the-answer' } },
|
|
36
|
+
test: true,
|
|
37
|
+
});
|
|
38
|
+
await ln.escrow.open(lock2.escrow_id);
|
|
39
|
+
console.log('3b) Worker submits the WRONG output -> should REFUND:');
|
|
40
|
+
const fail = await ln.escrow.settle(lock2.escrow_id, 'wrong');
|
|
41
|
+
console.log(' verdict:', fail.verdict, '| passed:', fail.condition_passed);
|
|
42
|
+
|
|
43
|
+
console.log('\nDone. The worker is paid only when the work provably passes. That is the whole point.');
|
|
44
|
+
console.log('Go live: get a key at https://logicnodes.io and drop test:true.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch((e) => { console.error('Error:', e.message); process.exit(1); });
|