fangorn-sdk 0.0.5 → 2026.2.24

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,7 +4,7 @@ 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
 
@@ -12,7 +12,7 @@ Fangorn can be deployed on any EVM chain that has a brige to Lit protocol. Curre
12
12
 
13
13
  ## Build
14
14
 
15
- `pnpm i`.
15
+ `pnpm i
16
16
 
17
17
  ### Usage
18
18
 
package/lib/cli.js CHANGED
@@ -10,34 +10,49 @@ import { Command } from "commander";
10
10
  import { confirm, intro, isCancel, multiselect, note, outro, select, spinner, text } from "@clack/prompts";
11
11
  import { createWalletClient, http } from "viem";
12
12
  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
13
  import "dotenv/config";
18
14
  import { PinataSDK } from "pinata";
19
15
  import { SDK } from "agent0-sdk";
16
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
17
+ import { basename, extname, join } from "path";
18
+ import { homedir } from "os";
20
19
 
21
20
  //#region src/cli.ts
21
+ const CONFIG_DIR = join(homedir(), ".fangorn");
22
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
22
23
  let _config = null;
23
24
  let _account = null;
24
25
  let _fangorn = null;
25
26
  function loadConfig() {
26
27
  if (_config) return _config;
28
+ const privateKey = process.env.DELEGATOR_ETH_PRIVATE_KEY;
27
29
  const jwt = process.env.PINATA_JWT;
28
30
  const gateway = process.env.PINATA_GATEWAY;
29
- const privateKey = process.env.DELEGATOR_ETH_PRIVATE_KEY;
30
31
  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 = {
32
+ if (privateKey && jwt && gateway && chainName) {
33
+ _config = buildConfig({
34
+ privateKey,
35
+ jwt,
36
+ gateway,
37
+ chainName
38
+ });
39
+ return _config;
40
+ }
41
+ if (existsSync(CONFIG_PATH)) {
42
+ _config = buildConfig(JSON.parse(readFileSync(CONFIG_PATH, "utf-8")));
43
+ return _config;
44
+ }
45
+ 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.");
46
+ }
47
+ function buildConfig({ privateKey, jwt, gateway, chainName }) {
48
+ let cfg = FangornConfig.BaseSepolia;
49
+ if (chainName === SupportedNetworks.ArbitrumSepolia.name) cfg = FangornConfig.ArbitrumSepolia;
50
+ return {
51
+ privateKey,
35
52
  jwt,
36
53
  gateway,
37
- privateKey,
38
- cfg: config
54
+ cfg
39
55
  };
40
- return _config;
41
56
  }
42
57
  function getAccount() {
43
58
  if (_account) return _account;
@@ -52,7 +67,6 @@ async function getFangorn(chain) {
52
67
  transport: http(cfg.cfg.rpcUrl),
53
68
  chain
54
69
  });
55
- const litClient = await createLitClient({ network: nagaDev });
56
70
  const appConfig = cfg.cfg;
57
71
  const domain = process.env.DOMAIN || "localhost:3000";
58
72
  const storage = new PinataStorage(new PinataSDK({
@@ -60,7 +74,7 @@ async function getFangorn(chain) {
60
74
  pinataGateway: cfg.gateway
61
75
  }));
62
76
  const chainName = process.env.CHAIN_NAME;
63
- const encryptionService = new LitEncryptionService(litClient, { chainName });
77
+ const encryptionService = await LitEncryptionService.init(chainName);
64
78
  _fangorn = await Fangorn.init(walletClient, storage, encryptionService, domain, appConfig);
65
79
  return _fangorn;
66
80
  }
@@ -85,7 +99,54 @@ const selectChain = async () => {
85
99
  return getNetwork(chainChoice.toString());
86
100
  };
87
101
  const program = new Command();
88
- program.name("Fangorn").description("CLI for Fangorn").version("9129129");
102
+ program.name("Fangorn").description("CLI for Fangorn").version("0.0.1");
103
+ program.command("init").description("Configure your Fangorn credentials").action(async () => {
104
+ intro("Fangorn Setup");
105
+ const privateKey = await text({
106
+ message: "Your wallet private key (stored locally, never transmitted):",
107
+ placeholder: "0x...",
108
+ validate: (v) => {
109
+ if (!v.startsWith("0x") || v.length !== 66) return "Must be a valid 0x-prefixed 32-byte hex key";
110
+ }
111
+ });
112
+ handleCancel(privateKey);
113
+ const jwt = await text({
114
+ message: "Pinata JWT:",
115
+ validate: (v) => {
116
+ if (!v) return "Required";
117
+ }
118
+ });
119
+ handleCancel(jwt);
120
+ const gateway = await text({
121
+ message: "Pinata Gateway URL:",
122
+ placeholder: "https://your-gateway.mypinata.cloud",
123
+ validate: (v) => {
124
+ if (!v) return "Required";
125
+ }
126
+ });
127
+ handleCancel(gateway);
128
+ const chainName = await select({
129
+ message: "Default chain:",
130
+ options: [{
131
+ value: SupportedNetworks.ArbitrumSepolia.name,
132
+ label: "Arbitrum Sepolia"
133
+ }, {
134
+ value: SupportedNetworks.BaseSepolia.name,
135
+ label: "Base Sepolia"
136
+ }]
137
+ });
138
+ handleCancel(chainName);
139
+ const stored = {
140
+ privateKey,
141
+ jwt,
142
+ gateway,
143
+ chainName
144
+ };
145
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
146
+ writeFileSync(CONFIG_PATH, JSON.stringify(stored, null, 2), "utf-8");
147
+ chmodSync(CONFIG_PATH, 384);
148
+ outro(`Config saved to ${CONFIG_PATH}`);
149
+ });
89
150
  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
151
  try {
91
152
  intro("Chain selection");
@@ -354,7 +415,7 @@ program.command("upload").description("Upload file(s) to a data source").argumen
354
415
  return new PaymentGadget({
355
416
  commitment: fieldToHex(await computeTagCommitment(owner, name, file.tag, options.price)),
356
417
  chainName: "arbitrumSepolia",
357
- settlementTrackerContractAddress: "0xb32ed201896ba765e6aa118a5c18c263f559474e",
418
+ settlementTrackerContractAddress: "0x7c6ae9eb3398234eb69b2f3acfae69065505ff69",
358
419
  usdcPrice: options.price
359
420
  });
360
421
  }, options.overwrite);
@@ -365,6 +426,50 @@ program.command("upload").description("Upload file(s) to a data source").argumen
365
426
  process.exit(1);
366
427
  }
367
428
  });
429
+ 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) => {
430
+ try {
431
+ const owner = getAccount().address;
432
+ const manifest = await (await getFangorn(getChain(options.chain))).getManifest(owner, name);
433
+ if (!manifest) {
434
+ console.log("The data source is empty. \n");
435
+ console.log("Upload data with `fangorn upload <dataSourceName> <file> --price <set-price>");
436
+ process.exit(0);
437
+ }
438
+ console.log(`Datasource: ${name} (${owner})`);
439
+ console.log(`Entries (${manifest.entries.length}):`);
440
+ for (const entry of manifest.entries) console.log(` - ${entry.tag} | gadget descriptor: ${JSON.stringify(entry.gadgetDescriptor)} | cid: ${entry.cid}`);
441
+ process.exit(0);
442
+ } catch (err) {
443
+ console.error("Failed to list vault:", err.message);
444
+ process.exit(1);
445
+ }
446
+ });
447
+ 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) => {
448
+ try {
449
+ const owner = getAccount().address;
450
+ const vault = await (await getFangorn(getChain(options.chain))).getDataSource(owner, name);
451
+ console.log(`Datasource: ${name} (${owner})`);
452
+ console.log(`Owner: ${vault.owner}`);
453
+ console.log(`Manifest CID: ${vault.manifestCid == "" ? "Upload data with `fangorn upload <vaultName> <file> --price <set-price>`" : vault.manifestCid}`);
454
+ process.exit(0);
455
+ } catch (err) {
456
+ console.error("Failed to get vault info:", err.message);
457
+ process.exit(1);
458
+ }
459
+ });
460
+ 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) => {
461
+ try {
462
+ const owner = getAccount().address;
463
+ const entry = await (await getFangorn(getChain(options.chain))).getDataSourceData(owner, name, tag);
464
+ console.log(`Entry: ${tag}`);
465
+ console.log(` CID: ${entry.cid}`);
466
+ console.log(` Gadget Descriptor: ${JSON.stringify(entry.gadgetDescriptor)}`);
467
+ process.exit(0);
468
+ } catch (err) {
469
+ console.error("Failed to get entry:", err.message);
470
+ process.exit(1);
471
+ }
472
+ });
368
473
  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
474
  try {
370
475
  const decrypted = await (await getFangorn(getChain(options.chain))).decryptFile(owner, name, tag);
package/lib/fangorn.d.ts CHANGED
@@ -48,6 +48,7 @@ declare class Fangorn {
48
48
  */
49
49
  decryptFile(owner: Address, name: string, tag: string, authContext?: any): Promise<Uint8Array>;
50
50
  getDataSource(owner: Address, name: string): Promise<Vault>;
51
+ registry(): DataSourceRegistry;
51
52
  getManifest(owner: Address, name: string): Promise<VaultManifest | undefined>;
52
53
  getDataSourceData(owner: Address, name: string, tag: string): Promise<VaultEntry>;
53
54
  getAddress(): Hex;
package/lib/fangorn.js CHANGED
@@ -107,6 +107,9 @@ var Fangorn = class Fangorn {
107
107
  async getDataSource(owner, name) {
108
108
  return await this.dataSourceRegistry.getDataSource(owner, name);
109
109
  }
110
+ registry() {
111
+ return this.dataSourceRegistry;
112
+ }
110
113
  async getManifest(owner, name) {
111
114
  const vault = await this.getDataSource(owner, name);
112
115
  if (!vault.manifestCid || vault.manifestCid === "") return;
@@ -11,9 +11,22 @@ interface LitEncryptionServiceConfig {
11
11
  }
12
12
  declare class LitEncryptionService implements EncryptionService {
13
13
  private litClient;
14
- private config;
15
- constructor(litClient: LitClient, config: LitEncryptionServiceConfig);
14
+ private chainName;
15
+ constructor(litClient: LitClient, chainName: string);
16
+ static init(chain: string): Promise<LitEncryptionService>;
17
+ /**
18
+ * Encrypt filedata under the given gadget
19
+ * @param file The filedata to encrypt
20
+ * @param gadget The gadget to use
21
+ * @returns The ciphertext bundle
22
+ */
16
23
  encrypt(file: Filedata, gadget: Gadget): Promise<EncryptedPayload>;
24
+ /**
25
+ * Attempt to decrypt some encrypted data
26
+ * @param payload The encrytped bundle to recover
27
+ * @param authContext The authorization context
28
+ * @returns The decrytped output (on success), else empty
29
+ */
17
30
  decrypt(payload: EncryptedPayload, authContext: AuthContextWrapper): Promise<DecryptedPayload>;
18
31
  createAuthContext(walletClient: WalletClient, domain: string): Promise<AuthContextWrapper>;
19
32
  private createAuthSig;
@@ -1,6 +1,8 @@
1
1
  import { decryptData, encryptData } from "./aes.js";
2
2
  import { LitAccessControlConditionResource, LitActionResource, LitPKPResource, createSiweMessage } from "@lit-protocol/auth-helpers";
3
+ import { createLitClient } from "@lit-protocol/lit-client";
3
4
  import { createAuthManager, storagePlugins } from "@lit-protocol/auth";
5
+ import { nagaDev } from "@lit-protocol/networks";
4
6
 
5
7
  //#region src/modules/encryption/lit.ts
6
8
  const createDecryptLitActionCode = (chainName) => `(async () => {
@@ -23,18 +25,27 @@ const createDecryptLitActionCode = (chainName) => `(async () => {
23
25
  });
24
26
  }
25
27
  })();`;
26
- var LitEncryptionService = class {
27
- constructor(litClient, config) {
28
+ var LitEncryptionService = class LitEncryptionService {
29
+ constructor(litClient, chainName) {
28
30
  this.litClient = litClient;
29
- this.config = config;
31
+ this.chainName = chainName;
30
32
  }
33
+ static async init(chain) {
34
+ return new LitEncryptionService(await createLitClient({ network: nagaDev }), chain);
35
+ }
36
+ /**
37
+ * Encrypt filedata under the given gadget
38
+ * @param file The filedata to encrypt
39
+ * @param gadget The gadget to use
40
+ * @returns The ciphertext bundle
41
+ */
31
42
  async encrypt(file, gadget) {
32
43
  const { encryptedData, keyMaterial } = await encryptData(file.data);
33
44
  const acc = await gadget.toAccessCondition();
34
45
  const litEncryptedKey = await this.litClient.encrypt({
35
46
  dataToEncrypt: keyMaterial.toString(),
36
47
  unifiedAccessControlConditions: acc,
37
- chain: this.config.chainName
48
+ chain: this.chainName
38
49
  });
39
50
  return {
40
51
  data: encryptedData,
@@ -46,6 +57,12 @@ var LitEncryptionService = class {
46
57
  litAction: gadget.toLitAction()
47
58
  };
48
59
  }
60
+ /**
61
+ * Attempt to decrypt some encrypted data
62
+ * @param payload The encrytped bundle to recover
63
+ * @param authContext The authorization context
64
+ * @returns The decrytped output (on success), else empty
65
+ */
49
66
  async decrypt(payload, authContext) {
50
67
  const result = await this.litClient.executeJs({
51
68
  code: createDecryptLitActionCode(authContext.chainName),
@@ -61,12 +78,16 @@ var LitEncryptionService = class {
61
78
  return { data: await decryptData(payload.data, key) };
62
79
  }
63
80
  async createAuthContext(walletClient, domain) {
64
- const account = walletClient.account;
65
- const sessionContext = await createAuthManager({ storage: storagePlugins.localStorageNode({
81
+ const isWindowUndefined = typeof window === "undefined";
82
+ const account = isWindowUndefined ? walletClient.account : walletClient;
83
+ const sessionContext = await (isWindowUndefined ? createAuthManager({ storage: storagePlugins.localStorageNode({
66
84
  appName: "fangorn",
67
85
  networkName: "naga-dev",
68
86
  storagePath: "./lit-auth-storage"
69
- }) }).createEoaAuthContext({
87
+ }) }) : createAuthManager({ storage: storagePlugins.localStorage({
88
+ appName: "fangorn",
89
+ networkName: "naga-dev"
90
+ }) })).createEoaAuthContext({
70
91
  litClient: this.litClient,
71
92
  config: { account },
72
93
  authConfig: {
@@ -6,8 +6,6 @@ import { PaymentGadget } from "../modules/gadgets/payment.js";
6
6
  import { emptyWallet } from "./test-gadget.js";
7
7
  import { SettlementTracker } from "../interface/settlement-tracker/settlementTracker.js";
8
8
  import { createPublicClient, http, keccak256, parseUnits, toHex } from "viem";
9
- import { createLitClient } from "@lit-protocol/lit-client";
10
- import { nagaDev } from "@lit-protocol/networks";
11
9
  import { arbitrumSepolia, baseSepolia } from "viem/chains";
12
10
  import { PinataSDK } from "pinata";
13
11
 
@@ -41,10 +39,8 @@ var TestBed = class TestBed {
41
39
  rpcUrl,
42
40
  caip2
43
41
  };
44
- const delegatorLitClient = await createLitClient({ network: nagaDev });
45
- const delegateeLitClient = await createLitClient({ network: nagaDev });
46
- const delegatorEncryption = new LitEncryptionService(delegatorLitClient, { chainName: chain });
47
- const delegateeEncryption = new LitEncryptionService(delegateeLitClient, { chainName: chain });
42
+ const delegatorEncryption = await LitEncryptionService.init(chain);
43
+ const delegateeEncryption = await LitEncryptionService.init(chain);
48
44
  const pinata = new PinataSDK({
49
45
  pinataJwt: jwt,
50
46
  pinataGateway: gateway
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "fangorn-sdk",
3
- "version": "0.0.5",
3
+ "version": "2026.02.24",
4
4
  "description": "A zero-knowledge conditional access control framework",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/fangorn-network/fangorn.git"
8
8
  },
9
+ "bin": {
10
+ "fangorn": "lib/cli.js"
11
+ },
9
12
  "license": "MIT",
10
13
  "type": "module",
11
14
  "main": "lib/index.js",