fangorn-sdk 0.0.5 → 2026.2.2-4.1
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/README.md +44 -14
- package/lib/{cli.js → cli/cli.js} +146 -48
- package/lib/cli/index.d.ts +736 -0
- package/lib/cli/index.js +36 -0
- package/lib/cli/registry.d.ts +29 -0
- package/lib/cli/registry.js +53 -0
- package/lib/config.d.ts +192 -156
- package/lib/deployContract.js +2 -2
- package/lib/fangorn.d.ts +2 -1
- package/lib/fangorn.js +4 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +4 -4
- package/lib/interface/datasource-registry/dataSourceRegistry.d.ts +2 -2
- package/lib/interface/settlement-tracker/settlementTracker.d.ts +2 -2
- package/lib/modules/encryption/lit.d.ts +15 -2
- package/lib/modules/encryption/lit.js +28 -7
- package/lib/test/testbed.js +4 -8
- package/lib/utils/index.d.ts +1 -2
- package/lib/utils/index.js +3 -10
- package/lib/{circuits/poseiden2_hash/target/poseiden2_hash.js → utils/poseidon2_hash.js} +3 -3
- package/package.json +5 -4
- package/lib/circuits/poseiden1_hash/target/poseiden1_hash.js +0 -45
- /package/lib/{cli.d.ts → cli/cli.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -4,35 +4,61 @@ Programmable data.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Fangorn is a programmable data framework that lets you **register datasources** and **publish encrypted data** under public conditions, or predicates, such that it can only be decrypted if you meet the condition.
|
|
7
|
+
Fangorn is a programmable data framework that lets you **register datasources** and **publish encrypted data** under public conditions, or predicates, such that it can only be decrypted if you meet the condition(s).
|
|
8
8
|
|
|
9
9
|
## Supported Networks
|
|
10
10
|
|
|
11
11
|
Fangorn can be deployed on any EVM chain that has a brige to Lit protocol. Currently, contracts are deployed to both Arbitrum Sepolia and Base Sepolia. See the [contracts](#contracts) section for more info.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Usage
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Fangorn is a programmable data framework. It provides tools to register data sources that can be accessed based on owner-defined conditions, like payment.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
## CLI/Quickstart
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
To install the fangorn-sdk from NPM, run:
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
```shell
|
|
22
|
+
npm i -g fangorn-sdk
|
|
23
|
+
```
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
### Register a Datasource
|
|
26
|
+
|
|
27
|
+
First register a datasource
|
|
28
|
+
|
|
29
|
+
``` sh
|
|
30
|
+
fangorn register name-of-your-datasource-agent
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
### Upload Data
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
On upload, data is encrypted under a user-specified **gadget**. For now, the CLI only supports the payment gadget, which is used by specifying the argument `-g "Payment(amount)"`. The minimum amount is `0.000001`.
|
|
36
|
+
|
|
37
|
+
For `-c`, the CLI supports both arbitrumSepolia and baseSepolia.
|
|
38
|
+
|
|
39
|
+
``` sh
|
|
40
|
+
fangorn upload name-of-your-datasource-agent file-to-upload.ext -c arbitrumSepolia -g "Payment(0.0001)"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Decrypt and Download
|
|
44
|
+
|
|
45
|
+
``` sh
|
|
46
|
+
fangorn decrypt [owner] [datasourceName] [tag] -c arbitrumSepolia
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Full Guide
|
|
50
|
+
|
|
51
|
+
### Build
|
|
52
|
+
|
|
53
|
+
`pnpm i`
|
|
54
|
+
|
|
55
|
+
### Setup
|
|
30
56
|
|
|
31
57
|
```js
|
|
32
58
|
// provide the account, rpcurl, and chain externally
|
|
33
59
|
// Initalize a wallet client
|
|
34
60
|
const walletClient = createWalletClient({
|
|
35
|
-
account
|
|
61
|
+
account,
|
|
36
62
|
transport: http(rpcUrl),
|
|
37
63
|
chain,
|
|
38
64
|
});
|
|
@@ -69,7 +95,7 @@ const fangorn = await Fangorn.init(
|
|
|
69
95
|
);
|
|
70
96
|
```
|
|
71
97
|
|
|
72
|
-
|
|
98
|
+
### Datasource Registration
|
|
73
99
|
|
|
74
100
|
Now that you have a Fangorn client, you can create a _datasource_. A datasource is an on-chain asset that stores a commitment to its storage root along with an optional `agentId` field for associating the datasource with an ERC-8004 identity.
|
|
75
101
|
|
|
@@ -79,7 +105,7 @@ const name = "demo";
|
|
|
79
105
|
const id = await this.delegatorFangorn.registerDataSource(name, "");
|
|
80
106
|
```
|
|
81
107
|
|
|
82
|
-
|
|
108
|
+
### Encryption
|
|
83
109
|
|
|
84
110
|
Once a datasource exists, the owner can update its storage root to point it to data. Fangorn leverages Lit protocol for encryption and access control.
|
|
85
111
|
|
|
@@ -229,7 +255,7 @@ The tests will:
|
|
|
229
255
|
To install locally:
|
|
230
256
|
|
|
231
257
|
```sh
|
|
232
|
-
chmod +x
|
|
258
|
+
chmod +x update_cli.sh
|
|
233
259
|
./update_cli.sh
|
|
234
260
|
```
|
|
235
261
|
|
|
@@ -243,8 +269,12 @@ Options:
|
|
|
243
269
|
-h, --help display help for command
|
|
244
270
|
|
|
245
271
|
Commands:
|
|
272
|
+
init Configure your Fangorn credentials
|
|
246
273
|
register [options] <name> Register a new datasource as an agent.
|
|
247
274
|
upload [options] <name> <files...> Upload file(s) to a data source
|
|
275
|
+
list [options] <name> List contents (index) of a data source
|
|
276
|
+
info [options] <name> Get data source info from contract
|
|
277
|
+
entry [options] <name> <tag> Get info about a specific entry
|
|
248
278
|
decrypt [options] <owner> <name> <tag> Decrypt a file from a vault
|
|
249
279
|
help [command] display help for command
|
|
250
280
|
```
|
|
@@ -1,43 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { Fangorn } from "
|
|
4
|
-
import { PinataStorage } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
2
|
+
import { FangornConfig, SupportedNetworks } from "../config.js";
|
|
3
|
+
import { Fangorn } from "../fangorn.js";
|
|
4
|
+
import { PinataStorage } from "../providers/storage/pinata/index.js";
|
|
5
|
+
import { computeTagCommitment, fieldToHex } from "../utils/index.js";
|
|
6
|
+
import { LitEncryptionService } from "../modules/encryption/lit.js";
|
|
7
|
+
import { agentCardBuilder } from "../builders/a2aCardBuilder.js";
|
|
8
|
+
import { getChain, handleCancel, parseGadgetArg, selectChain } from "./index.js";
|
|
9
|
+
import { GADGET_REGISTRY, selectGadget } from "./registry.js";
|
|
10
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
11
|
+
import { basename, extname, join } from "path";
|
|
11
12
|
import { createWalletClient, http } from "viem";
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
import { confirm, intro, multiselect, note, outro, select, spinner, text } from "@clack/prompts";
|
|
12
15
|
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
-
import { createLitClient } from "@lit-protocol/lit-client";
|
|
14
|
-
import { nagaDev } from "@lit-protocol/networks";
|
|
15
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
16
|
-
import { basename, extname } from "path";
|
|
17
16
|
import "dotenv/config";
|
|
18
17
|
import { PinataSDK } from "pinata";
|
|
19
18
|
import { SDK } from "agent0-sdk";
|
|
19
|
+
import { homedir } from "os";
|
|
20
20
|
|
|
21
|
-
//#region src/cli.ts
|
|
21
|
+
//#region src/cli/cli.ts
|
|
22
|
+
const CONFIG_DIR = join(homedir(), ".fangorn");
|
|
23
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
22
24
|
let _config = null;
|
|
23
25
|
let _account = null;
|
|
24
26
|
let _fangorn = null;
|
|
25
27
|
function loadConfig() {
|
|
26
28
|
if (_config) return _config;
|
|
29
|
+
const privateKey = process.env.DELEGATOR_ETH_PRIVATE_KEY;
|
|
27
30
|
const jwt = process.env.PINATA_JWT;
|
|
28
31
|
const gateway = process.env.PINATA_GATEWAY;
|
|
29
|
-
const privateKey = process.env.DELEGATOR_ETH_PRIVATE_KEY;
|
|
30
32
|
const chainName = process.env.CHAIN_NAME;
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if (privateKey && jwt && gateway && chainName) {
|
|
34
|
+
_config = buildConfig({
|
|
35
|
+
privateKey,
|
|
36
|
+
jwt,
|
|
37
|
+
gateway,
|
|
38
|
+
chainName
|
|
39
|
+
});
|
|
40
|
+
return _config;
|
|
41
|
+
}
|
|
42
|
+
if (existsSync(CONFIG_PATH)) {
|
|
43
|
+
_config = buildConfig(JSON.parse(readFileSync(CONFIG_PATH, "utf-8")));
|
|
44
|
+
return _config;
|
|
45
|
+
}
|
|
46
|
+
throw new Error("No configuration found. Run `fangorn init` to set up your credentials, or set DELEGATOR_ETH_PRIVATE_KEY, PINATA_JWT, PINATA_GATEWAY, and CHAIN_NAME env vars.");
|
|
47
|
+
}
|
|
48
|
+
function buildConfig({ privateKey, jwt, gateway, chainName }) {
|
|
49
|
+
let cfg = FangornConfig.BaseSepolia;
|
|
50
|
+
if (chainName === SupportedNetworks.ArbitrumSepolia.name) cfg = FangornConfig.ArbitrumSepolia;
|
|
51
|
+
return {
|
|
52
|
+
privateKey,
|
|
35
53
|
jwt,
|
|
36
54
|
gateway,
|
|
37
|
-
|
|
38
|
-
cfg: config
|
|
55
|
+
cfg
|
|
39
56
|
};
|
|
40
|
-
return _config;
|
|
41
57
|
}
|
|
42
58
|
function getAccount() {
|
|
43
59
|
if (_account) return _account;
|
|
@@ -52,7 +68,6 @@ async function getFangorn(chain) {
|
|
|
52
68
|
transport: http(cfg.cfg.rpcUrl),
|
|
53
69
|
chain
|
|
54
70
|
});
|
|
55
|
-
const litClient = await createLitClient({ network: nagaDev });
|
|
56
71
|
const appConfig = cfg.cfg;
|
|
57
72
|
const domain = process.env.DOMAIN || "localhost:3000";
|
|
58
73
|
const storage = new PinataStorage(new PinataSDK({
|
|
@@ -60,32 +75,59 @@ async function getFangorn(chain) {
|
|
|
60
75
|
pinataGateway: cfg.gateway
|
|
61
76
|
}));
|
|
62
77
|
const chainName = process.env.CHAIN_NAME;
|
|
63
|
-
const encryptionService =
|
|
78
|
+
const encryptionService = await LitEncryptionService.init(chainName);
|
|
64
79
|
_fangorn = await Fangorn.init(walletClient, storage, encryptionService, domain, appConfig);
|
|
65
80
|
return _fangorn;
|
|
66
81
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
const program = new Command();
|
|
83
|
+
program.name("Fangorn").description("CLI for Fangorn").version("0.0.1");
|
|
84
|
+
program.command("init").description("Configure your Fangorn credentials").action(async () => {
|
|
85
|
+
intro("Fangorn Setup");
|
|
86
|
+
const privateKey = await text({
|
|
87
|
+
message: "Your wallet private key (stored locally, never transmitted):",
|
|
88
|
+
placeholder: "0x...",
|
|
89
|
+
validate: (v) => {
|
|
90
|
+
if (!v.startsWith("0x") || v.length !== 66) return "Must be a valid 0x-prefixed 32-byte hex key";
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
handleCancel(privateKey);
|
|
94
|
+
const jwt = await text({
|
|
95
|
+
message: "Pinata JWT:",
|
|
96
|
+
validate: (v) => {
|
|
97
|
+
if (!v) return "Required";
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
handleCancel(jwt);
|
|
101
|
+
const gateway = await text({
|
|
102
|
+
message: "Pinata Gateway URL:",
|
|
103
|
+
placeholder: "https://your-gateway.mypinata.cloud",
|
|
104
|
+
validate: (v) => {
|
|
105
|
+
if (!v) return "Required";
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
handleCancel(gateway);
|
|
109
|
+
const chainName = await select({
|
|
110
|
+
message: "Default chain:",
|
|
76
111
|
options: [{
|
|
77
|
-
value:
|
|
112
|
+
value: SupportedNetworks.ArbitrumSepolia.name,
|
|
78
113
|
label: "Arbitrum Sepolia"
|
|
79
114
|
}, {
|
|
80
|
-
value:
|
|
115
|
+
value: SupportedNetworks.BaseSepolia.name,
|
|
81
116
|
label: "Base Sepolia"
|
|
82
117
|
}]
|
|
83
118
|
});
|
|
84
|
-
handleCancel(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
handleCancel(chainName);
|
|
120
|
+
const stored = {
|
|
121
|
+
privateKey,
|
|
122
|
+
jwt,
|
|
123
|
+
gateway,
|
|
124
|
+
chainName
|
|
125
|
+
};
|
|
126
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
127
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(stored, null, 2), "utf-8");
|
|
128
|
+
chmodSync(CONFIG_PATH, 384);
|
|
129
|
+
outro(`Config saved to ${CONFIG_PATH}`);
|
|
130
|
+
});
|
|
89
131
|
program.command("register").description("Register a new datasource as an agent.").argument("<name>", "Name of the datasource").option("-s, --skip-card", "Skip agent card creation").option("-e, --skip-erc", "Skip ERC-8007 registrion").option("-d, --skip-ds", "Skip datasource registrion").action(async (name, options) => {
|
|
90
132
|
try {
|
|
91
133
|
intro("Chain selection");
|
|
@@ -334,7 +376,7 @@ program.command("register").description("Register a new datasource as an agent."
|
|
|
334
376
|
process.exit(1);
|
|
335
377
|
}
|
|
336
378
|
});
|
|
337
|
-
program.command("upload").description("Upload file(s) to a data source").argument("<name>", "Data source name").argument("<files...>", "File path(s) to upload").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia)").option("-
|
|
379
|
+
program.command("upload").description("Upload file(s) to a data source").argument("<name>", "Data source name").argument("<files...>", "File path(s) to upload").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia)").option("-g, --gadget <type(args)>", "Gadget to use (e.g. Payment(0.000001))").option("-o --overwrite", "Overwrite existing data source contents").action(async (name, files, options) => {
|
|
338
380
|
try {
|
|
339
381
|
const owner = getAccount().address;
|
|
340
382
|
const fangorn = await getFangorn(getChain(options.chain));
|
|
@@ -351,12 +393,20 @@ program.command("upload").description("Upload file(s) to a data source").argumen
|
|
|
351
393
|
};
|
|
352
394
|
});
|
|
353
395
|
const cid = await fangorn.upload(name, filedata, async (file) => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
396
|
+
if (options.gadget) {
|
|
397
|
+
const { type, args } = parseGadgetArg(options.gadget);
|
|
398
|
+
const def = GADGET_REGISTRY[type];
|
|
399
|
+
if (!def) throw new Error(`Unknown gadget type: ${type}`);
|
|
400
|
+
const params = {};
|
|
401
|
+
def.argSchema.forEach((key, i) => {
|
|
402
|
+
params[key] = args[i];
|
|
403
|
+
});
|
|
404
|
+
params.commitment = fieldToHex(await computeTagCommitment(owner, name, file.tag, options.price));
|
|
405
|
+
params.chainName = options.chain;
|
|
406
|
+
params.settlementTrackerContractAddress = options.chain === "arbitrumSepolia" ? "0x7c6ae9eb3398234eb69b2f3acfae69065505ff69" : "0x708751829f5f5f584da4142b62cd5cc9235c8a18";
|
|
407
|
+
return def.build(params);
|
|
408
|
+
}
|
|
409
|
+
return selectGadget(owner, name, file.tag, options.price);
|
|
360
410
|
}, options.overwrite);
|
|
361
411
|
console.log(`Upload complete! Manifest CID: ${cid}`);
|
|
362
412
|
process.exit(0);
|
|
@@ -365,13 +415,61 @@ program.command("upload").description("Upload file(s) to a data source").argumen
|
|
|
365
415
|
process.exit(1);
|
|
366
416
|
}
|
|
367
417
|
});
|
|
418
|
+
program.command("list").description("List contents (index) of a data source").argument("<name>", "Data source name").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia)").action(async (name, options) => {
|
|
419
|
+
try {
|
|
420
|
+
const owner = getAccount().address;
|
|
421
|
+
const manifest = await (await getFangorn(getChain(options.chain))).getManifest(owner, name);
|
|
422
|
+
if (!manifest) {
|
|
423
|
+
console.log("The data source is empty. \n");
|
|
424
|
+
console.log("Upload data with `fangorn upload <dataSourceName> <file> --price <set-price>");
|
|
425
|
+
process.exit(0);
|
|
426
|
+
}
|
|
427
|
+
console.log(`Datasource: ${name} (${owner})`);
|
|
428
|
+
console.log(`Entries (${manifest.entries.length}):`);
|
|
429
|
+
for (const entry of manifest.entries) console.log(` - ${entry.tag} | gadget descriptor: ${JSON.stringify(entry.gadgetDescriptor)} | cid: ${entry.cid}`);
|
|
430
|
+
process.exit(0);
|
|
431
|
+
} catch (err) {
|
|
432
|
+
console.error("Failed to list vault:", err.message);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
program.command("info").description("Get data source info from contract").argument("<name>", "Data source name").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia)").action(async (name, options) => {
|
|
437
|
+
try {
|
|
438
|
+
const owner = getAccount().address;
|
|
439
|
+
const vault = await (await getFangorn(getChain(options.chain))).getDataSource(owner, name);
|
|
440
|
+
console.log(`Datasource: ${name} (${owner})`);
|
|
441
|
+
console.log(`Owner: ${vault.owner}`);
|
|
442
|
+
console.log(`Manifest CID: ${vault.manifestCid == "" ? "Upload data with `fangorn upload <vaultName> <file> --price <set-price>`" : vault.manifestCid}`);
|
|
443
|
+
process.exit(0);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error("Failed to get vault info:", err.message);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
program.command("entry").description("Get info about a specific entry").argument("<name>", "Vault name").argument("<tag>", "File tag").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia").action(async (name, tag, options) => {
|
|
450
|
+
try {
|
|
451
|
+
const owner = getAccount().address;
|
|
452
|
+
const entry = await (await getFangorn(getChain(options.chain))).getDataSourceData(owner, name, tag);
|
|
453
|
+
console.log(`Entry: ${tag}`);
|
|
454
|
+
console.log(` CID: ${entry.cid}`);
|
|
455
|
+
console.log(` Gadget Descriptor: ${JSON.stringify(entry.gadgetDescriptor)}`);
|
|
456
|
+
process.exit(0);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
console.error("Failed to get entry:", err.message);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
368
462
|
program.command("decrypt").description("Decrypt a file from a vault").argument("<owner>", "The owner of the datasource").argument("<name>", "The name of the datasource").argument("<tag>", "File tag").option("-c, --chain <chain>", "The chain to use as the backend (arbitrumSepolia or baseSepolia").option("-o, --output <path>", "Output file path").action(async (owner, name, tag, options) => {
|
|
369
463
|
try {
|
|
370
464
|
const decrypted = await (await getFangorn(getChain(options.chain))).decryptFile(owner, name, tag);
|
|
371
465
|
if (options.output) {
|
|
372
466
|
writeFileSync(options.output, Buffer.from(decrypted));
|
|
373
467
|
console.log(`Decrypted file saved to: ${options.output}`);
|
|
374
|
-
} else
|
|
468
|
+
} else {
|
|
469
|
+
const buf = Buffer.from(decrypted);
|
|
470
|
+
process.stdout.write(atob(buf.toString()));
|
|
471
|
+
process.stdout.write("\n");
|
|
472
|
+
}
|
|
375
473
|
process.exit(0);
|
|
376
474
|
} catch (err) {
|
|
377
475
|
console.error("Failed to decrypt:", err.message);
|