@taskforest/dark-forest 0.1.0

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 ADDED
@@ -0,0 +1,19 @@
1
+ # @taskforest/dark-forest
2
+
3
+ Dark Forest payment helpers for TaskForest.
4
+
5
+ This package is intentionally separate from `@taskforest/sdk`.
6
+ Use it when you need MagicBlock PER delegation, payment-channel orchestration, or Dark Forest demo flows.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @taskforest/dark-forest
12
+ ```
13
+
14
+ ## Scope
15
+
16
+ - Escrow wrapper helpers
17
+ - PER endpoints and validator constants
18
+ - MPP session orchestration helpers
19
+ - Settlement state reads
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const web3_js_1 = require("@solana/web3.js");
9
+ const index_1 = require("../index");
10
+ (0, node_test_1.default)('exports stable public constants', () => {
11
+ strict_1.default.equal(index_1.DARK_FOREST_PROGRAM_ID.toBase58(), '4hNP2tU5r5GgyASTrou84kWHbCwdyXVJJN4mve99rjgs');
12
+ strict_1.default.equal(index_1.ESCROW_SEED.toString(), 'escrow');
13
+ strict_1.default.equal(index_1.SETTLEMENT_SEED.toString(), 'settlement');
14
+ strict_1.default.equal(index_1.PER_ENDPOINTS.devnet, 'https://tee.magicblock.app');
15
+ strict_1.default.ok(index_1.TEE_VALIDATORS.devnet);
16
+ });
17
+ (0, node_test_1.default)('derives deterministic escrow and settlement PDAs', () => {
18
+ const escrowOne = index_1.DarkForestPayments.escrowPda(42);
19
+ const escrowTwo = index_1.DarkForestPayments.escrowPda(42);
20
+ const settlementOne = index_1.DarkForestPayments.settlementPda(42);
21
+ const settlementTwo = index_1.DarkForestPayments.settlementPda(42);
22
+ strict_1.default.equal(escrowOne.toBase58(), escrowTwo.toBase58());
23
+ strict_1.default.equal(settlementOne.toBase58(), settlementTwo.toBase58());
24
+ strict_1.default.notEqual(escrowOne.toBase58(), settlementOne.toBase58());
25
+ });
26
+ (0, node_test_1.default)('connectToPer returns a Solana connection', () => {
27
+ const payments = Object.create(index_1.DarkForestPayments.prototype);
28
+ const connection = payments.connectToPer(index_1.PER_ENDPOINTS.devnet);
29
+ strict_1.default.ok(connection instanceof web3_js_1.Connection);
30
+ strict_1.default.equal(connection.rpcEndpoint, index_1.PER_ENDPOINTS.devnet);
31
+ });
32
+ (0, node_test_1.default)('tracks an in-memory private session lifecycle', async () => {
33
+ const payments = Object.create(index_1.DarkForestPayments.prototype);
34
+ payments.activeSessions = new Map();
35
+ payments.createEscrowWrapper = async () => 'escrow-tx';
36
+ payments.delegateToPer = async () => 'delegate-tx';
37
+ payments.recordSettlement = async () => 'settlement-tx';
38
+ const session = await payments.startPrivateSession(77, index_1.DARK_FOREST_PROGRAM_ID, {
39
+ agentEndpoint: 'https://agent.taskforest.xyz',
40
+ token: 'SOL',
41
+ budgetLamports: 1500000,
42
+ perEndpoint: index_1.PER_ENDPOINTS.devnet,
43
+ });
44
+ strict_1.default.equal(session.escrowId, 77);
45
+ strict_1.default.equal(session.isActive, true);
46
+ strict_1.default.equal(payments.getActiveSession(77)?.requestCount, 0);
47
+ await payments.recordPayment(77, 500000);
48
+ strict_1.default.equal(payments.getActiveSession(77)?.totalPaid, 500000);
49
+ strict_1.default.equal(payments.getActiveSession(77)?.requestCount, 1);
50
+ const tx = await payments.closeSession(77);
51
+ strict_1.default.equal(tx, 'settlement-tx');
52
+ strict_1.default.equal(payments.getActiveSession(77), undefined);
53
+ });
54
+ (0, node_test_1.default)('returns null for unreadable escrow and settlement accounts', async () => {
55
+ const payments = Object.create(index_1.DarkForestPayments.prototype);
56
+ payments.program = {
57
+ account: {
58
+ escrowWrapper: { fetch: async () => { throw new Error('missing'); } },
59
+ settlementRecord: { fetch: async () => { throw new Error('missing'); } },
60
+ },
61
+ };
62
+ strict_1.default.equal(await payments.getEscrow(5), null);
63
+ strict_1.default.equal(await payments.getSettlement(5), null);
64
+ });
@@ -0,0 +1,70 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { AnchorProvider, type Idl } from '@coral-xyz/anchor';
4
+ import { Connection, PublicKey } from '@solana/web3.js';
5
+ export declare const DARK_FOREST_PROGRAM_ID: PublicKey;
6
+ export declare const ESCROW_SEED: Buffer;
7
+ export declare const SETTLEMENT_SEED: Buffer;
8
+ export declare const TEE_VALIDATORS: {
9
+ readonly mainnet: PublicKey;
10
+ readonly devnet: PublicKey;
11
+ };
12
+ export declare const PER_ENDPOINTS: {
13
+ readonly mainnet: "https://mainnet-tee.magicblock.app";
14
+ readonly devnet: "https://tee.magicblock.app";
15
+ readonly devnetRouter: "https://devnet-router.magicblock.app";
16
+ };
17
+ export type MppSessionConfig = {
18
+ agentEndpoint: string;
19
+ token: 'SOL' | 'USDC' | 'PYUSD';
20
+ budgetLamports: number;
21
+ perEndpoint: string;
22
+ };
23
+ export type MppSessionState = {
24
+ sessionId: string;
25
+ escrowId: number;
26
+ totalPaid: number;
27
+ requestCount: number;
28
+ isActive: boolean;
29
+ };
30
+ export interface EscrowState {
31
+ escrowId: number;
32
+ jobPubkey: PublicKey;
33
+ poster: PublicKey;
34
+ agent: PublicKey;
35
+ deposited: number;
36
+ status: 'Active' | 'Delegated' | 'Settled';
37
+ teePubkey: number[];
38
+ teeVerified: boolean;
39
+ mppSessionId: number[];
40
+ createdAt: number;
41
+ }
42
+ export interface SettlementState {
43
+ escrowId: number;
44
+ jobPubkey: PublicKey;
45
+ poster: PublicKey;
46
+ agent: PublicKey;
47
+ totalDeposited: number;
48
+ totalPaid: number;
49
+ settledAt: number;
50
+ settlementHash: number[];
51
+ }
52
+ export declare class DarkForestPayments {
53
+ private program;
54
+ private provider;
55
+ private activeSessions;
56
+ constructor(provider: AnchorProvider, idl: Idl);
57
+ connectToPer(endpoint?: string): Connection;
58
+ createEscrowWrapper(escrowId: number, jobPubkey: PublicKey, depositSol: number, mppSessionId?: number[]): Promise<string>;
59
+ delegateToPer(escrowId: number, validator?: PublicKey): Promise<string>;
60
+ verifyTeeAttestation(escrowId: number, attestationReport: Buffer, teePubkey: number[]): Promise<string>;
61
+ recordSettlement(escrowId: number, totalPaidSol: number): Promise<string>;
62
+ startPrivateSession(escrowId: number, jobPubkey: PublicKey, config: MppSessionConfig): Promise<MppSessionState>;
63
+ recordPayment(escrowId: number, amountLamports: number): Promise<void>;
64
+ closeSession(escrowId: number): Promise<string>;
65
+ getEscrow(escrowId: number): Promise<EscrowState | null>;
66
+ getSettlement(escrowId: number): Promise<SettlementState | null>;
67
+ getActiveSession(escrowId: number): MppSessionState | undefined;
68
+ static escrowPda(escrowId: number): PublicKey;
69
+ static settlementPda(escrowId: number): PublicKey;
70
+ }
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DarkForestPayments = exports.PER_ENDPOINTS = exports.TEE_VALIDATORS = exports.SETTLEMENT_SEED = exports.ESCROW_SEED = exports.DARK_FOREST_PROGRAM_ID = void 0;
4
+ const anchor_1 = require("@coral-xyz/anchor");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ exports.DARK_FOREST_PROGRAM_ID = new web3_js_1.PublicKey('4hNP2tU5r5GgyASTrou84kWHbCwdyXVJJN4mve99rjgs');
7
+ exports.ESCROW_SEED = Buffer.from('escrow');
8
+ exports.SETTLEMENT_SEED = Buffer.from('settlement');
9
+ exports.TEE_VALIDATORS = {
10
+ mainnet: new web3_js_1.PublicKey('MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo'),
11
+ devnet: new web3_js_1.PublicKey('FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA'),
12
+ };
13
+ exports.PER_ENDPOINTS = {
14
+ mainnet: 'https://mainnet-tee.magicblock.app',
15
+ devnet: 'https://tee.magicblock.app',
16
+ devnetRouter: 'https://devnet-router.magicblock.app',
17
+ };
18
+ function deriveEscrowPda(escrowId) {
19
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.ESCROW_SEED, new anchor_1.BN(escrowId).toArrayLike(Buffer, 'le', 8)], exports.DARK_FOREST_PROGRAM_ID);
20
+ }
21
+ function deriveSettlementPda(escrowId) {
22
+ return web3_js_1.PublicKey.findProgramAddressSync([exports.SETTLEMENT_SEED, new anchor_1.BN(escrowId).toArrayLike(Buffer, 'le', 8)], exports.DARK_FOREST_PROGRAM_ID);
23
+ }
24
+ class DarkForestPayments {
25
+ constructor(provider, idl) {
26
+ this.activeSessions = new Map();
27
+ this.provider = provider;
28
+ this.program = new anchor_1.Program(idl, provider);
29
+ }
30
+ connectToPer(endpoint = exports.PER_ENDPOINTS.devnet) {
31
+ return new web3_js_1.Connection(endpoint, 'confirmed');
32
+ }
33
+ async createEscrowWrapper(escrowId, jobPubkey, depositSol, mppSessionId = Array(32).fill(0)) {
34
+ const [escrowPda] = deriveEscrowPda(escrowId);
35
+ return this.program.methods
36
+ .createEscrowWrapper(new anchor_1.BN(escrowId), new anchor_1.BN(Math.floor(depositSol * web3_js_1.LAMPORTS_PER_SOL)), mppSessionId)
37
+ .accounts({
38
+ job: jobPubkey,
39
+ escrow: escrowPda,
40
+ poster: this.provider.wallet.publicKey,
41
+ systemProgram: web3_js_1.SystemProgram.programId,
42
+ })
43
+ .rpc();
44
+ }
45
+ async delegateToPer(escrowId, validator = exports.TEE_VALIDATORS.devnet) {
46
+ const [escrowPda] = deriveEscrowPda(escrowId);
47
+ return this.program.methods
48
+ .delegateToPer()
49
+ .accounts({
50
+ pda: escrowPda,
51
+ payer: this.provider.wallet.publicKey,
52
+ validator,
53
+ })
54
+ .rpc();
55
+ }
56
+ async verifyTeeAttestation(escrowId, attestationReport, teePubkey) {
57
+ const [escrowPda] = deriveEscrowPda(escrowId);
58
+ return this.program.methods
59
+ .verifyTeeAttestation(new anchor_1.BN(escrowId), attestationReport, teePubkey)
60
+ .accounts({
61
+ escrow: escrowPda,
62
+ payer: this.provider.wallet.publicKey,
63
+ })
64
+ .rpc();
65
+ }
66
+ async recordSettlement(escrowId, totalPaidSol) {
67
+ const [escrowPda] = deriveEscrowPda(escrowId);
68
+ const [settlementPda] = deriveSettlementPda(escrowId);
69
+ return this.program.methods
70
+ .recordSettlement(new anchor_1.BN(escrowId), new anchor_1.BN(Math.floor(totalPaidSol * web3_js_1.LAMPORTS_PER_SOL)))
71
+ .accounts({
72
+ escrow: escrowPda,
73
+ settlementRecord: settlementPda,
74
+ poster: this.provider.wallet.publicKey,
75
+ payer: this.provider.wallet.publicKey,
76
+ systemProgram: web3_js_1.SystemProgram.programId,
77
+ })
78
+ .rpc();
79
+ }
80
+ async startPrivateSession(escrowId, jobPubkey, config) {
81
+ const depositSol = config.budgetLamports / web3_js_1.LAMPORTS_PER_SOL;
82
+ const sessionId = `dark-${escrowId}-${Date.now()}`;
83
+ const sessionIdBytes = Array.from(Buffer.from(sessionId.padEnd(32, '\0').slice(0, 32)));
84
+ await this.createEscrowWrapper(escrowId, jobPubkey, depositSol, sessionIdBytes);
85
+ await this.delegateToPer(escrowId);
86
+ const session = {
87
+ sessionId,
88
+ escrowId,
89
+ totalPaid: 0,
90
+ requestCount: 0,
91
+ isActive: true,
92
+ };
93
+ this.activeSessions.set(escrowId, session);
94
+ return session;
95
+ }
96
+ async recordPayment(escrowId, amountLamports) {
97
+ const session = this.activeSessions.get(escrowId);
98
+ if (!session || !session.isActive)
99
+ throw new Error('No active session for this escrow');
100
+ session.totalPaid += amountLamports;
101
+ session.requestCount += 1;
102
+ }
103
+ async closeSession(escrowId) {
104
+ const session = this.activeSessions.get(escrowId);
105
+ if (!session)
106
+ throw new Error('No session for this escrow');
107
+ session.isActive = false;
108
+ const totalPaidSol = session.totalPaid / web3_js_1.LAMPORTS_PER_SOL;
109
+ const tx = await this.recordSettlement(escrowId, totalPaidSol);
110
+ this.activeSessions.delete(escrowId);
111
+ return tx;
112
+ }
113
+ async getEscrow(escrowId) {
114
+ const [escrowPda] = deriveEscrowPda(escrowId);
115
+ try {
116
+ const account = await this.program.account.escrowWrapper.fetch(escrowPda);
117
+ const statusMap = { 0: 'Active', 1: 'Delegated', 2: 'Settled' };
118
+ return {
119
+ escrowId: account.escrowId.toNumber(),
120
+ jobPubkey: account.jobPubkey,
121
+ poster: account.poster,
122
+ agent: account.agent,
123
+ deposited: account.deposited.toNumber(),
124
+ status: statusMap[account.status.active !== undefined ? 0 : account.status.delegated !== undefined ? 1 : 2] ?? 'Active',
125
+ teePubkey: account.teePubkey,
126
+ teeVerified: account.teeVerified,
127
+ mppSessionId: account.mppSessionId,
128
+ createdAt: account.createdAt.toNumber(),
129
+ };
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
135
+ async getSettlement(escrowId) {
136
+ const [settlementPda] = deriveSettlementPda(escrowId);
137
+ try {
138
+ const account = await this.program.account.settlementRecord.fetch(settlementPda);
139
+ return {
140
+ escrowId: account.escrowId.toNumber(),
141
+ jobPubkey: account.jobPubkey,
142
+ poster: account.poster,
143
+ agent: account.agent,
144
+ totalDeposited: account.totalDeposited.toNumber(),
145
+ totalPaid: account.totalPaid.toNumber(),
146
+ settledAt: account.settledAt.toNumber(),
147
+ settlementHash: account.settlementHash,
148
+ };
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ getActiveSession(escrowId) {
155
+ return this.activeSessions.get(escrowId);
156
+ }
157
+ static escrowPda(escrowId) {
158
+ return deriveEscrowPda(escrowId)[0];
159
+ }
160
+ static settlementPda(escrowId) {
161
+ return deriveSettlementPda(escrowId)[0];
162
+ }
163
+ }
164
+ exports.DarkForestPayments = DarkForestPayments;
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@taskforest/dark-forest",
3
+ "version": "0.1.0",
4
+ "description": "Dark Forest payment helpers for TaskForest: MPP wrapper, PER delegation, and settlement utilities",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "keywords": [
11
+ "solana",
12
+ "taskforest",
13
+ "payments",
14
+ "magicblock",
15
+ "per",
16
+ "mpp"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/jimmdd/taskforest-protocol"
22
+ },
23
+ "homepage": "https://taskforest.xyz",
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "test": "npm run build && node --test dist/__tests__/*.test.js",
27
+ "prepublishOnly": "npm run build"
28
+ },
29
+ "dependencies": {
30
+ "@coral-xyz/anchor": "^0.30.1",
31
+ "@solana/web3.js": "^1.95.0"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5.4.0"
35
+ }
36
+ }