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 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
- ## Build
13
+ ## Usage
14
14
 
15
- `pnpm i`.
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
- ### Usage
17
+ ## CLI/Quickstart
18
18
 
19
- Fangorn is a programmable data framework. It provides tools to register data sources that can be accessed based on owner-defined conditions, like payment.
19
+ To install the fangorn-sdk from NPM, run:
20
20
 
21
- #### Quickstart
21
+ ```shell
22
+ npm i -g fangorn-sdk
23
+ ```
22
24
 
23
- ```js
24
- # Coming soon
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
- #### Full Guide
33
+ ### Upload Data
28
34
 
29
- #### Setup
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
- ##### Datasource Registration
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
- ##### Encryption
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 update_clci.sh
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 getNetwork, { FangornConfig, SupportedNetworks } from "./config.js";
3
- import { Fangorn } from "./fangorn.js";
4
- import { PinataStorage } from "./providers/storage/pinata/index.js";
5
- import { LitEncryptionService } from "./modules/encryption/lit.js";
6
- import { computeTagCommitment, fieldToHex } from "./utils/index.js";
7
- import { PaymentGadget } from "./modules/gadgets/payment.js";
8
- import { agentCardBuilder } from "./builders/a2aCardBuilder.js";
9
- import { Command } from "commander";
10
- import { confirm, intro, isCancel, multiselect, note, outro, select, spinner, text } from "@clack/prompts";
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 (!chainName || !jwt || !gateway || !privateKey) throw new Error("Missing required env vars: CHAIN_NAME, PINATA_JWT, PINATA_GATEWAY, DELEGATOR_ETH_PRIVATE_KEY");
32
- let config = FangornConfig.BaseSepolia;
33
- if (chainName === SupportedNetworks.ArbitrumSepolia.name) config = FangornConfig.ArbitrumSepolia;
34
- _config = {
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
- privateKey,
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 = new LitEncryptionService(litClient, { chainName });
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 getChain = (chainStr) => {
68
- return getNetwork(chainStr);
69
- };
70
- const handleCancel = (value) => {
71
- if (isCancel(value)) process.exit(0);
72
- };
73
- const selectChain = async () => {
74
- const chainChoice = await select({
75
- message: "Pick your chain.",
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: "arbitrumSepolia",
112
+ value: SupportedNetworks.ArbitrumSepolia.name,
78
113
  label: "Arbitrum Sepolia"
79
114
  }, {
80
- value: "baseSepolia",
115
+ value: SupportedNetworks.BaseSepolia.name,
81
116
  label: "Base Sepolia"
82
117
  }]
83
118
  });
84
- handleCancel(chainChoice);
85
- return getNetwork(chainChoice.toString());
86
- };
87
- const program = new Command();
88
- program.name("Fangorn").description("CLI for Fangorn").version("9129129");
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("-p, --price <price>", "price to access the file").option("--overwrite", "Overwrite existing data source contents").action(async (name, files, options) => {
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
- return new PaymentGadget({
355
- commitment: fieldToHex(await computeTagCommitment(owner, name, file.tag, options.price)),
356
- chainName: "arbitrumSepolia",
357
- settlementTrackerContractAddress: "0xb32ed201896ba765e6aa118a5c18c263f559474e",
358
- usdcPrice: options.price
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 process.stdout.write(Buffer.from(decrypted));
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);