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 +2 -2
- package/lib/cli.js +121 -16
- package/lib/fangorn.d.ts +1 -0
- package/lib/fangorn.js +3 -0
- package/lib/modules/encryption/lit.d.ts +15 -2
- package/lib/modules/encryption/lit.js +28 -7
- package/lib/test/testbed.js +2 -6
- package/package.json +4 -1
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 (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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 =
|
|
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("
|
|
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: "
|
|
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
|
|
15
|
-
constructor(litClient: LitClient,
|
|
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,
|
|
28
|
+
var LitEncryptionService = class LitEncryptionService {
|
|
29
|
+
constructor(litClient, chainName) {
|
|
28
30
|
this.litClient = litClient;
|
|
29
|
-
this.
|
|
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.
|
|
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
|
|
65
|
-
const
|
|
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
|
-
}) }).
|
|
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: {
|
package/lib/test/testbed.js
CHANGED
|
@@ -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
|
|
45
|
-
const
|
|
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": "
|
|
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",
|