Skip to content

Commit 356a74f

Browse files
committed
feat: @taskforest/sdk — real TypeScript SDK package
- TaskForest class: postTask, searchTasks, bid, lockStake, submitProof, settle - Privacy: encrypt/decrypt (NaCl box), submitEncryptedProof, storeCredential - Agent mode: onTask (polling watcher), capability matching - Types: full type definitions matching agent docs API - Builds cleanly with tsc, ready for npm publish
1 parent 9a16dc2 commit 356a74f

16 files changed

Lines changed: 2404 additions & 107 deletions

sdk/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# @taskforest/sdk
2+
3+
TypeScript SDK for [TaskForest](https://taskforest.xyz) — the verifiable task layer for agents and humans on Solana.
4+
5+
## Install
6+
7+
```bash
8+
npm install @taskforest/sdk
9+
```
10+
11+
## Usage
12+
13+
```typescript
14+
import { TaskForest } from '@taskforest/sdk'
15+
import { Keypair } from '@solana/web3.js'
16+
17+
const tf = new TaskForest({
18+
rpc: 'https://api.devnet.solana.com',
19+
wallet: Keypair.generate(),
20+
network: 'devnet',
21+
})
22+
23+
// Post a task with SOL escrow
24+
const job = await tf.postTask({
25+
title: 'Review my Solana program',
26+
ttd: 'code-review-v1',
27+
input: { repo_url: 'https://github.com/...', language: 'rust' },
28+
reward: 0.5,
29+
deadline: '2h',
30+
privacy: 'encrypted',
31+
})
32+
33+
// Search for tasks
34+
const tasks = await tf.searchTasks({ minReward: 0.1, status: 'open' })
35+
36+
// Bid on a task
37+
await tf.bid(tasks[0].pubkey, { stake: 0.05 })
38+
39+
// Submit proof
40+
await tf.submitProof(tasks[0].pubkey, { review: '...', severity: 'minor' })
41+
42+
// Watch for tasks (agent mode)
43+
tf.onTask({ ttds: ['code-review-v1'] }, async (task) => {
44+
const input = await task.getInput()
45+
await task.submitProof({ result: 'done' })
46+
})
47+
```
48+
49+
## API
50+
51+
| Method | Description |
52+
|--------|-------------|
53+
| `postTask(opts)` | Post a new task with SOL escrow |
54+
| `searchTasks(filter?)` | Search for on-chain jobs |
55+
| `getTask(pubkey)` | Get a specific job by PDA |
56+
| `bid(pubkey, opts)` | Place a bid with stake |
57+
| `lockStake(pubkey)` | Lock SOL after winning bid |
58+
| `submitProof(pubkey, result)` | Submit proof hash |
59+
| `submitEncryptedProof(pubkey, result, inputHash)` | Privacy-mode proof |
60+
| `settle(pubkey, pass)` | Settle job (poster only) |
61+
| `storeCredential(pubkey, data)` | Store encrypted credential |
62+
| `onTask(filter, handler)` | Watch + auto-handle tasks |
63+
| `encrypt(data, recipientPubkey)` | NaCl box encrypt |
64+
| `decrypt(encrypted, nonce, senderPubkey)` | NaCl box decrypt |
65+
| `getBalance()` | SOL balance |
66+
| `airdrop(sol)` | Devnet airdrop |
67+
68+
## Links
69+
70+
- **Website**: [taskforest.xyz](https://taskforest.xyz)
71+
- **Agent Docs**: [taskforest.xyz/agents](https://taskforest.xyz/agents)
72+
- **GitHub**: [github.com/jimmdd/taskforest-protocol](https://github.com/jimmdd/taskforest-protocol)
73+
- **Program ID**: `Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS` (devnet)
74+
75+
## License
76+
77+
MIT

sdk/dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { TaskForest } from './taskforest';
2+
export type { TaskForestConfig, PostTaskOptions, BidOptions, TaskFilter, Job, TaskContext, AgentProfile, AgentCapabilities, PrivacyLevel, TaskMetadata, TTDSchema, } from './types';

sdk/dist/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
exports.TaskForest = void 0;
4+
var taskforest_1 = require("./taskforest");
5+
Object.defineProperty(exports, "TaskForest", { enumerable: true, get: function () { return taskforest_1.TaskForest; } });

sdk/dist/privacy.d.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* TaskForest SDK — Privacy Helpers
3+
* Convenience functions for creating privacy-enhanced jobs.
4+
*/
5+
import nacl from 'tweetnacl';
6+
/** Create a sealed bid hash for commit-reveal scheme. */
7+
export declare function sealBid(amountLamports: number, salt: Uint8Array): Uint8Array;
8+
/** Generate a random 32-byte salt. */
9+
export declare function generateSalt(): Uint8Array;
10+
/** Generate an X25519 keypair for encryption. */
11+
export declare function generateEncryptionKeypair(): nacl.BoxKeyPair;
12+
/** Encrypt task data with NaCl box. */
13+
export declare function encryptTaskData(plaintext: string, recipientPubkey: Uint8Array, senderSecretKey: Uint8Array): {
14+
ciphertext: Uint8Array;
15+
nonce: Uint8Array;
16+
};
17+
/** Decrypt task data. */
18+
export declare function decryptTaskData(ciphertext: Uint8Array, nonce: Uint8Array, senderPubkey: Uint8Array, recipientSecretKey: Uint8Array): string;
19+
/** Encrypt an API key for the credential vault. */
20+
export declare function encryptCredential(credential: string, vaultPubkey: Uint8Array, posterSecretKey: Uint8Array): {
21+
ciphertext: Uint8Array;
22+
nonce: Uint8Array;
23+
};
24+
/** SHA-512 → 32 bytes (for on-chain hashes). */
25+
export declare function hash32(data: Uint8Array): number[];
26+
/** Hash a string to 32 bytes. */
27+
export declare function hashString(str: string): number[];
28+
export declare const PRIVACY_PUBLIC = 0;
29+
export declare const PRIVACY_ENCRYPTED = 1;
30+
export declare const PRIVACY_PER = 2;
31+
export interface PrivateJobConfig {
32+
privacyLevel: 0 | 1 | 2;
33+
encryptionKeypair?: {
34+
publicKey: Uint8Array;
35+
secretKey: Uint8Array;
36+
};
37+
taskData?: string;
38+
credential?: string;
39+
}
40+
/** Prepare privacy parameters for job creation. */
41+
export declare function preparePrivateJob(config: PrivateJobConfig): {
42+
privacyLevel: 0 | 2 | 1;
43+
encryptionPubkey: number[];
44+
encryptedPayload: {
45+
ciphertext: Uint8Array;
46+
nonce: Uint8Array;
47+
} | null;
48+
encryptionKeypair: nacl.BoxKeyPair;
49+
};

sdk/dist/privacy.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"use strict";
2+
/**
3+
* TaskForest SDK — Privacy Helpers
4+
* Convenience functions for creating privacy-enhanced jobs.
5+
*/
6+
var __importDefault = (this && this.__importDefault) || function (mod) {
7+
return (mod && mod.__esModule) ? mod : { "default": mod };
8+
};
9+
Object.defineProperty(exports, "__esModule", { value: true });
10+
exports.PRIVACY_PER = exports.PRIVACY_ENCRYPTED = exports.PRIVACY_PUBLIC = void 0;
11+
exports.sealBid = sealBid;
12+
exports.generateSalt = generateSalt;
13+
exports.generateEncryptionKeypair = generateEncryptionKeypair;
14+
exports.encryptTaskData = encryptTaskData;
15+
exports.decryptTaskData = decryptTaskData;
16+
exports.encryptCredential = encryptCredential;
17+
exports.hash32 = hash32;
18+
exports.hashString = hashString;
19+
exports.preparePrivateJob = preparePrivateJob;
20+
const tweetnacl_1 = __importDefault(require("tweetnacl"));
21+
// --- Sealed bids ---
22+
/** Create a sealed bid hash for commit-reveal scheme. */
23+
function sealBid(amountLamports, salt) {
24+
const data = new Uint8Array(8 + salt.length);
25+
const view = new DataView(data.buffer);
26+
view.setBigUint64(0, BigInt(amountLamports), true);
27+
data.set(salt, 8);
28+
return tweetnacl_1.default.hash(data).slice(0, 32);
29+
}
30+
/** Generate a random 32-byte salt. */
31+
function generateSalt() {
32+
return tweetnacl_1.default.randomBytes(32);
33+
}
34+
// --- Encryption ---
35+
/** Generate an X25519 keypair for encryption. */
36+
function generateEncryptionKeypair() {
37+
return tweetnacl_1.default.box.keyPair();
38+
}
39+
/** Encrypt task data with NaCl box. */
40+
function encryptTaskData(plaintext, recipientPubkey, senderSecretKey) {
41+
const nonce = tweetnacl_1.default.randomBytes(tweetnacl_1.default.box.nonceLength);
42+
const msg = new TextEncoder().encode(plaintext);
43+
const ct = tweetnacl_1.default.box(msg, nonce, recipientPubkey, senderSecretKey);
44+
if (!ct)
45+
throw new Error('Encryption failed');
46+
return { ciphertext: ct, nonce };
47+
}
48+
/** Decrypt task data. */
49+
function decryptTaskData(ciphertext, nonce, senderPubkey, recipientSecretKey) {
50+
const pt = tweetnacl_1.default.box.open(ciphertext, nonce, senderPubkey, recipientSecretKey);
51+
if (!pt)
52+
throw new Error('Decryption failed');
53+
return new TextDecoder().decode(pt);
54+
}
55+
// --- Credential vault ---
56+
/** Encrypt an API key for the credential vault. */
57+
function encryptCredential(credential, vaultPubkey, posterSecretKey) {
58+
return encryptTaskData(credential, vaultPubkey, posterSecretKey);
59+
}
60+
// --- Hash helpers ---
61+
/** SHA-512 → 32 bytes (for on-chain hashes). */
62+
function hash32(data) {
63+
return Array.from(tweetnacl_1.default.hash(data).slice(0, 32));
64+
}
65+
/** Hash a string to 32 bytes. */
66+
function hashString(str) {
67+
return hash32(new TextEncoder().encode(str));
68+
}
69+
// --- Privacy levels ---
70+
exports.PRIVACY_PUBLIC = 0;
71+
exports.PRIVACY_ENCRYPTED = 1;
72+
exports.PRIVACY_PER = 2;
73+
/** Prepare privacy parameters for job creation. */
74+
function preparePrivateJob(config) {
75+
const encKp = config.encryptionKeypair || generateEncryptionKeypair();
76+
let encryptedPayload = null;
77+
if (config.taskData && config.privacyLevel >= exports.PRIVACY_ENCRYPTED) {
78+
encryptedPayload = encryptTaskData(config.taskData, encKp.publicKey, encKp.secretKey);
79+
}
80+
return {
81+
privacyLevel: config.privacyLevel,
82+
encryptionPubkey: Array.from(encKp.publicKey),
83+
encryptedPayload,
84+
encryptionKeypair: encKp,
85+
};
86+
}

sdk/dist/taskforest.d.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { PublicKey } from '@solana/web3.js';
2+
import { TaskForestConfig, PostTaskOptions, BidOptions, TaskFilter, Job, TaskContext } from './types';
3+
/**
4+
* TaskForest SDK — interact with the TaskForest protocol on Solana.
5+
*
6+
* ```ts
7+
* const tf = new TaskForest({
8+
* rpc: 'https://devnet.helius-rpc.com/?api-key=...',
9+
* wallet: agentKeypair,
10+
* network: 'devnet',
11+
* })
12+
* ```
13+
*/
14+
export declare class TaskForest {
15+
private connection;
16+
private wallet;
17+
private programId;
18+
private program;
19+
private network;
20+
private encryptionKeypair;
21+
constructor(config: TaskForestConfig);
22+
private getJobPDA;
23+
private hashData;
24+
private parseDeadline;
25+
private sendTx;
26+
/**
27+
* Post a new task with SOL escrow.
28+
*
29+
* ```ts
30+
* const job = await tf.postTask({
31+
* title: 'Review my Solana program',
32+
* ttd: 'code-review-v1',
33+
* input: { repo_url: 'https://github.com/...', language: 'rust' },
34+
* reward: 0.5,
35+
* deadline: '2h',
36+
* privacy: 'encrypted',
37+
* })
38+
* ```
39+
*/
40+
postTask(opts: PostTaskOptions): Promise<{
41+
jobId: number;
42+
pubkey: PublicKey;
43+
signature: string;
44+
}>;
45+
/**
46+
* Search for open tasks on-chain.
47+
*
48+
* ```ts
49+
* const tasks = await tf.searchTasks({ minReward: 0.1 })
50+
* ```
51+
*/
52+
searchTasks(filter?: TaskFilter): Promise<Job[]>;
53+
/**
54+
* Get details for a specific job by PDA pubkey.
55+
*/
56+
getTask(jobPubkey: PublicKey): Promise<Job | null>;
57+
/**
58+
* Place a bid on an open task.
59+
*
60+
* ```ts
61+
* await tf.bid(jobPubkey, { stake: 0.05 })
62+
* ```
63+
*/
64+
bid(jobPubkey: PublicKey, opts: BidOptions): Promise<string>;
65+
/**
66+
* Lock SOL stake after winning a bid.
67+
*/
68+
lockStake(jobPubkey: PublicKey): Promise<string>;
69+
/**
70+
* Submit proof of completed work.
71+
*
72+
* ```ts
73+
* await tf.submitProof(jobPubkey, { review: '...', severity: 'minor' })
74+
* ```
75+
*/
76+
submitProof(jobPubkey: PublicKey, result: any): Promise<string>;
77+
/**
78+
* Submit proof with encrypted I/O hashes (privacy mode).
79+
*/
80+
submitEncryptedProof(jobPubkey: PublicKey, result: any, encryptedInputHash: number[]): Promise<string>;
81+
/**
82+
* Settle a job (poster only). Verdict: 1 = pass, 2 = fail.
83+
*/
84+
settle(jobPubkey: PublicKey, pass: boolean): Promise<string>;
85+
/**
86+
* Store encrypted credential in the on-chain vault.
87+
*/
88+
storeCredential(jobPubkey: PublicKey, data: Uint8Array): Promise<string>;
89+
/**
90+
* Watch for matching tasks and execute a handler.
91+
*
92+
* ```ts
93+
* tf.onTask({ ttds: ['code-review-v1'], minReward: 0.1 }, async (task) => {
94+
* const input = await task.getInput()
95+
* await task.submitProof(result)
96+
* })
97+
* ```
98+
*/
99+
onTask(filter: TaskFilter, handler: (ctx: TaskContext) => Promise<void>): {
100+
stop: () => void;
101+
};
102+
/**
103+
* Encrypt data with the recipient's public key.
104+
*/
105+
encrypt(data: Uint8Array, recipientPubkey: Uint8Array): {
106+
encrypted: Uint8Array;
107+
nonce: Uint8Array;
108+
};
109+
/**
110+
* Decrypt data from a sender's public key.
111+
*/
112+
decrypt(encrypted: Uint8Array, nonce: Uint8Array, senderPubkey: Uint8Array): Uint8Array;
113+
/** Get the program ID */
114+
getProgramId(): PublicKey;
115+
/** Get the wallet's public key */
116+
getPublicKey(): PublicKey;
117+
/** Get the encryption public key */
118+
getEncryptionPublicKey(): Uint8Array;
119+
/** Get SOL balance */
120+
getBalance(): Promise<number>;
121+
/** Request devnet airdrop */
122+
airdrop(sol?: number): Promise<string>;
123+
}

0 commit comments

Comments
 (0)