@lobstercove/lichen-sdk 1.0.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,50 @@
1
+ # Lichen JavaScript/TypeScript SDK
2
+
3
+ Official SDK for interacting with Lichen blockchain.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @lobstercove/lichen-sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { Connection, Keypair, PublicKey } from '@lobstercove/lichen-sdk';
15
+
16
+ // Connect to Lichen
17
+ const connection = new Connection('http://localhost:8899');
18
+
19
+ // Generate a native PQ keypair
20
+ const keypair = Keypair.generate();
21
+ console.log('Address:', keypair.pubkey().toBase58());
22
+
23
+ // Get account balance
24
+ const pubkey = new PublicKey('YourPublicKeyHere...');
25
+ const balance = await connection.getBalance(pubkey);
26
+ console.log(`Balance: ${balance.licn} LICN`);
27
+
28
+ // Subscribe to blocks
29
+ connection.onBlock((block) => {
30
+ console.log('New block:', block);
31
+ });
32
+ ```
33
+
34
+ ## Documentation
35
+
36
+ See the [JavaScript SDK reference](https://developers.lichen.network/sdk-js.html) for detailed API reference.
37
+
38
+ ## Features
39
+
40
+ - ✅ Complete RPC client (24 endpoints)
41
+ - ✅ WebSocket subscriptions (real-time events)
42
+ - ✅ Native PQ keypairs and self-contained signatures
43
+ - ✅ Transaction builder
44
+ - ✅ TypeScript types
45
+ - ✅ Address and PQ public-key utilities
46
+ - ✅ Full blockchain interaction
47
+
48
+ ## License
49
+
50
+ MIT
@@ -0,0 +1,5 @@
1
+ import type { Message, Transaction } from './transaction.js';
2
+ export declare function hexToBytes(hex: string): Uint8Array;
3
+ export declare function bytesToHex(bytes: Uint8Array): string;
4
+ export declare function encodeMessage(message: Message): Uint8Array;
5
+ export declare function encodeTransaction(transaction: Transaction): Uint8Array;
@@ -0,0 +1,91 @@
1
+ // Minimal bincode encoder for Lichen transactions
2
+ import { bytesToHex as pqBytesToHex, hexToBytes as pqHexToBytes, toPqSignature, } from './pq.js';
3
+ const textEncoder = new TextEncoder();
4
+ function encodeU64LE(value) {
5
+ const out = new Uint8Array(8);
6
+ const view = new DataView(out.buffer);
7
+ view.setBigUint64(0, BigInt(value), true);
8
+ return out;
9
+ }
10
+ function encodeU32LE(value) {
11
+ const out = new Uint8Array(4);
12
+ const view = new DataView(out.buffer);
13
+ view.setUint32(0, value, true);
14
+ return out;
15
+ }
16
+ function encodeOptionU64(value) {
17
+ if (value === undefined || value === null) {
18
+ return new Uint8Array([0x00]); // None
19
+ }
20
+ return concat([new Uint8Array([0x01]), encodeU64LE(value)]); // Some(value)
21
+ }
22
+ function concat(parts) {
23
+ const total = parts.reduce((sum, part) => sum + part.length, 0);
24
+ const out = new Uint8Array(total);
25
+ let offset = 0;
26
+ for (const part of parts) {
27
+ out.set(part, offset);
28
+ offset += part.length;
29
+ }
30
+ return out;
31
+ }
32
+ function encodeBytes(data) {
33
+ return concat([encodeU64LE(data.length), data]);
34
+ }
35
+ function encodeString(value) {
36
+ const encoded = textEncoder.encode(value);
37
+ return concat([encodeU64LE(encoded.length), encoded]);
38
+ }
39
+ function encodeVec(items) {
40
+ return concat([encodeU64LE(items.length), ...items]);
41
+ }
42
+ export function hexToBytes(hex) {
43
+ return pqHexToBytes(hex);
44
+ }
45
+ export function bytesToHex(bytes) {
46
+ return pqBytesToHex(bytes);
47
+ }
48
+ function encodeU8(value) {
49
+ return Uint8Array.of(value & 0xff);
50
+ }
51
+ function encodePubkey(pubkey) {
52
+ const bytes = pubkey.toBytes();
53
+ if (bytes.length !== 32) {
54
+ throw new Error('PublicKey must be 32 bytes');
55
+ }
56
+ return bytes;
57
+ }
58
+ function encodeInstruction(ix) {
59
+ const programId = encodePubkey(ix.programId);
60
+ const accounts = encodeVec(ix.accounts.map(encodePubkey));
61
+ const data = encodeBytes(ix.data);
62
+ return concat([programId, accounts, data]);
63
+ }
64
+ function encodePqPublicKey(publicKey) {
65
+ return concat([encodeU8(publicKey.schemeVersion), encodeBytes(publicKey.toBytes())]);
66
+ }
67
+ function encodePqSignature(signature) {
68
+ return concat([
69
+ encodeU8(signature.schemeVersion),
70
+ encodePqPublicKey(signature.publicKey),
71
+ encodeBytes(signature.toBytes()),
72
+ ]);
73
+ }
74
+ export function encodeMessage(message) {
75
+ const instructions = encodeVec(message.instructions.map(encodeInstruction));
76
+ const blockhash = hexToBytes(message.recentBlockhash);
77
+ if (blockhash.length !== 32) {
78
+ throw new Error('Blockhash must be 32 bytes');
79
+ }
80
+ const computeBudget = encodeOptionU64(message.computeBudget);
81
+ const computeUnitPrice = encodeOptionU64(message.computeUnitPrice);
82
+ return concat([instructions, blockhash, computeBudget, computeUnitPrice]);
83
+ }
84
+ export function encodeTransaction(transaction) {
85
+ const sigBytes = transaction.signatures.map((signature) => encodePqSignature(toPqSignature(signature)));
86
+ const encodedSigs = concat([encodeU64LE(sigBytes.length), ...sigBytes]);
87
+ const messageBytes = encodeMessage(transaction.message);
88
+ // tx_type: Native=0 (u32 LE)
89
+ const txType = encodeU32LE(0);
90
+ return concat([encodedSigs, messageBytes, txType]);
91
+ }
@@ -0,0 +1,57 @@
1
+ import { Connection } from './connection.js';
2
+ import { Keypair } from './keypair.js';
3
+ import { PublicKey } from './publickey.js';
4
+ export declare const BOUNTY_STATUS_OPEN = 0;
5
+ export declare const BOUNTY_STATUS_COMPLETED = 1;
6
+ export declare const BOUNTY_STATUS_CANCELLED = 2;
7
+ export interface BountyBoardBountyInfo {
8
+ creator: PublicKey;
9
+ titleHash: Uint8Array;
10
+ rewardAmount: bigint;
11
+ deadlineSlot: bigint;
12
+ status: number;
13
+ submissionCount: number;
14
+ createdSlot: bigint;
15
+ approvedIdx: number;
16
+ }
17
+ export interface BountyBoardPlatformStats {
18
+ bountyCount: bigint;
19
+ completedCount: bigint;
20
+ rewardVolume: bigint;
21
+ cancelCount: bigint;
22
+ }
23
+ export interface BountyBoardStats {
24
+ bountyCount: number;
25
+ completedCount: number;
26
+ totalRewardVolume: number;
27
+ cancelCount: number;
28
+ paused: boolean;
29
+ }
30
+ export interface CreateBountyParams {
31
+ titleHash: Uint8Array;
32
+ rewardAmount: number | bigint;
33
+ deadlineSlot: number | bigint;
34
+ }
35
+ export interface SubmitWorkParams {
36
+ bountyId: number | bigint;
37
+ proofHash: Uint8Array;
38
+ }
39
+ export interface ApproveWorkParams {
40
+ bountyId: number | bigint;
41
+ submissionIdx: number;
42
+ }
43
+ export declare class BountyBoardClient {
44
+ private readonly connection;
45
+ private resolvedProgram?;
46
+ constructor(connection: Connection, programId?: PublicKey);
47
+ private callReadonly;
48
+ getProgramId(): Promise<PublicKey>;
49
+ getBounty(bountyId: number | bigint): Promise<BountyBoardBountyInfo | null>;
50
+ getBountyCount(): Promise<bigint>;
51
+ getPlatformStats(): Promise<BountyBoardPlatformStats>;
52
+ getStats(): Promise<BountyBoardStats>;
53
+ createBounty(creator: Keypair, params: CreateBountyParams): Promise<string>;
54
+ submitWork(worker: Keypair, params: SubmitWorkParams): Promise<string>;
55
+ approveWork(creator: Keypair, params: ApproveWorkParams): Promise<string>;
56
+ cancelBounty(creator: Keypair, bountyId: number | bigint): Promise<string>;
57
+ }
@@ -0,0 +1,205 @@
1
+ import { PublicKey } from './publickey.js';
2
+ const PROGRAM_SYMBOL_CANDIDATES = ['BOUNTY', 'bounty', 'BountyBoard', 'BOUNTYBOARD', 'bountyboard'];
3
+ const MAX_U64 = (1n << 64n) - 1n;
4
+ const BOUNTY_DATA_SIZE = 91;
5
+ const PLATFORM_STATS_SIZE = 32;
6
+ // Bounty status constants
7
+ export const BOUNTY_STATUS_OPEN = 0;
8
+ export const BOUNTY_STATUS_COMPLETED = 1;
9
+ export const BOUNTY_STATUS_CANCELLED = 2;
10
+ function normalizeAddress(value) {
11
+ return value instanceof PublicKey ? value : new PublicKey(value);
12
+ }
13
+ function normalizeUnsignedU64(value, fieldName) {
14
+ const normalized = typeof value === 'bigint'
15
+ ? value
16
+ : Number.isSafeInteger(value) && value >= 0
17
+ ? BigInt(value)
18
+ : null;
19
+ if (normalized === null || normalized < 0n || normalized > MAX_U64) {
20
+ throw new Error(`${fieldName} must be a u64-safe integer value`);
21
+ }
22
+ return normalized;
23
+ }
24
+ function u64LE(value, fieldName) {
25
+ const out = new Uint8Array(8);
26
+ new DataView(out.buffer).setBigUint64(0, normalizeUnsignedU64(value, fieldName), true);
27
+ return out;
28
+ }
29
+ function buildLayoutArgs(layout, chunks) {
30
+ const header = Uint8Array.from([0xAB, ...layout]);
31
+ const total = chunks.reduce((sum, chunk) => sum + chunk.length, header.length);
32
+ const out = new Uint8Array(total);
33
+ out.set(header, 0);
34
+ let offset = header.length;
35
+ for (const chunk of chunks) {
36
+ out.set(chunk, offset);
37
+ offset += chunk.length;
38
+ }
39
+ return out;
40
+ }
41
+ function decodeReturnData(returnData) {
42
+ return Uint8Array.from(Buffer.from(returnData, 'base64'));
43
+ }
44
+ function readU64(bytes, offset) {
45
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
46
+ return view.getBigUint64(offset, true);
47
+ }
48
+ function ensureReadonlySuccess(result, functionName, allowedReturnCodes = [0]) {
49
+ const code = result.returnCode ?? 0;
50
+ if (!allowedReturnCodes.includes(code)) {
51
+ throw new Error(result.error ?? `BountyBoard ${functionName} returned code ${code}`);
52
+ }
53
+ if (result.success === false && result.error) {
54
+ throw new Error(result.error);
55
+ }
56
+ }
57
+ function ensureBytes32(value, fieldName) {
58
+ if (value.length !== 32) {
59
+ throw new Error(`${fieldName} must be exactly 32 bytes`);
60
+ }
61
+ return value;
62
+ }
63
+ // --- Encoding helpers ---
64
+ function encodeCreateBountyArgs(creator, titleHash, rewardAmount, deadlineSlot) {
65
+ return buildLayoutArgs([0x20, 0x20, 0x08, 0x08], [creator.toBytes(), ensureBytes32(titleHash, 'titleHash'), u64LE(rewardAmount, 'rewardAmount'), u64LE(deadlineSlot, 'deadlineSlot')]);
66
+ }
67
+ function encodeSubmitWorkArgs(bountyId, worker, proofHash) {
68
+ return buildLayoutArgs([0x08, 0x20, 0x20], [u64LE(bountyId, 'bountyId'), worker.toBytes(), ensureBytes32(proofHash, 'proofHash')]);
69
+ }
70
+ function encodeApproveWorkArgs(caller, bountyId, submissionIdx) {
71
+ if (submissionIdx < 0 || submissionIdx > 255) {
72
+ throw new Error('submissionIdx must be 0-255');
73
+ }
74
+ return buildLayoutArgs([0x20, 0x08, 0x01], [caller.toBytes(), u64LE(bountyId, 'bountyId'), Uint8Array.from([submissionIdx])]);
75
+ }
76
+ function encodeCancelBountyArgs(caller, bountyId) {
77
+ return buildLayoutArgs([0x20, 0x08], [caller.toBytes(), u64LE(bountyId, 'bountyId')]);
78
+ }
79
+ function encodeBountyIdArgs(bountyId) {
80
+ return buildLayoutArgs([0x08], [u64LE(bountyId, 'bountyId')]);
81
+ }
82
+ // --- Decoding helpers ---
83
+ function decodeBountyInfo(result) {
84
+ ensureReadonlySuccess(result, 'get_bounty');
85
+ if (!result.returnData) {
86
+ throw new Error('BountyBoard get_bounty did not return bounty data');
87
+ }
88
+ const bytes = decodeReturnData(result.returnData);
89
+ if (bytes.length < BOUNTY_DATA_SIZE) {
90
+ throw new Error('BountyBoard get_bounty payload was shorter than expected');
91
+ }
92
+ return {
93
+ creator: new PublicKey(bytes.slice(0, 32)),
94
+ titleHash: bytes.slice(32, 64),
95
+ rewardAmount: readU64(bytes, 64),
96
+ deadlineSlot: readU64(bytes, 72),
97
+ status: bytes[80],
98
+ submissionCount: bytes[81],
99
+ createdSlot: readU64(bytes, 82),
100
+ approvedIdx: bytes[90],
101
+ };
102
+ }
103
+ function decodePlatformStats(result) {
104
+ ensureReadonlySuccess(result, 'get_platform_stats');
105
+ if (!result.returnData) {
106
+ throw new Error('BountyBoard get_platform_stats did not return stats data');
107
+ }
108
+ const bytes = decodeReturnData(result.returnData);
109
+ if (bytes.length < PLATFORM_STATS_SIZE) {
110
+ throw new Error('BountyBoard get_platform_stats payload was shorter than expected');
111
+ }
112
+ return {
113
+ bountyCount: readU64(bytes, 0),
114
+ completedCount: readU64(bytes, 8),
115
+ rewardVolume: readU64(bytes, 16),
116
+ cancelCount: readU64(bytes, 24),
117
+ };
118
+ }
119
+ export class BountyBoardClient {
120
+ constructor(connection, programId) {
121
+ this.connection = connection;
122
+ this.resolvedProgram = programId;
123
+ }
124
+ async callReadonly(functionName, args = new Uint8Array()) {
125
+ const programId = await this.getProgramId();
126
+ return this.connection.callReadonlyContract(programId, functionName, args);
127
+ }
128
+ async getProgramId() {
129
+ if (this.resolvedProgram) {
130
+ return this.resolvedProgram;
131
+ }
132
+ for (const symbol of PROGRAM_SYMBOL_CANDIDATES) {
133
+ try {
134
+ const entry = await this.connection.getSymbolRegistry(symbol);
135
+ if (entry?.program) {
136
+ this.resolvedProgram = new PublicKey(entry.program);
137
+ return this.resolvedProgram;
138
+ }
139
+ }
140
+ catch {
141
+ // Try the next known registry alias.
142
+ }
143
+ }
144
+ throw new Error('Unable to resolve the BountyBoard program via getSymbolRegistry("BOUNTY")');
145
+ }
146
+ // --- Read methods ---
147
+ async getBounty(bountyId) {
148
+ const result = await this.callReadonly('get_bounty', encodeBountyIdArgs(normalizeUnsignedU64(bountyId, 'bountyId')));
149
+ if ((result.returnCode ?? 0) === 1 || !result.returnData) {
150
+ return null;
151
+ }
152
+ return decodeBountyInfo(result);
153
+ }
154
+ async getBountyCount() {
155
+ const result = await this.callReadonly('get_bounty_count');
156
+ ensureReadonlySuccess(result, 'get_bounty_count');
157
+ if (!result.returnData) {
158
+ return 0n;
159
+ }
160
+ const bytes = decodeReturnData(result.returnData);
161
+ if (bytes.length < 8) {
162
+ return 0n;
163
+ }
164
+ return readU64(bytes, 0);
165
+ }
166
+ async getPlatformStats() {
167
+ return decodePlatformStats(await this.callReadonly('get_platform_stats'));
168
+ }
169
+ async getStats() {
170
+ const stats = await this.connection.getBountyBoardStats();
171
+ return {
172
+ bountyCount: stats.bounty_count ?? 0,
173
+ completedCount: stats.completed_count ?? 0,
174
+ totalRewardVolume: stats.total_reward_volume ?? 0,
175
+ cancelCount: stats.cancel_count ?? 0,
176
+ paused: Boolean(stats.paused),
177
+ };
178
+ }
179
+ // --- Write methods ---
180
+ async createBounty(creator, params) {
181
+ const programId = await this.getProgramId();
182
+ const rewardAmount = normalizeUnsignedU64(params.rewardAmount, 'rewardAmount');
183
+ const deadlineSlot = normalizeUnsignedU64(params.deadlineSlot, 'deadlineSlot');
184
+ const args = encodeCreateBountyArgs(creator.pubkey(), params.titleHash, rewardAmount, deadlineSlot);
185
+ return this.connection.callContract(creator, programId, 'create_bounty', args, rewardAmount);
186
+ }
187
+ async submitWork(worker, params) {
188
+ const programId = await this.getProgramId();
189
+ const bountyId = normalizeUnsignedU64(params.bountyId, 'bountyId');
190
+ const args = encodeSubmitWorkArgs(bountyId, worker.pubkey(), params.proofHash);
191
+ return this.connection.callContract(worker, programId, 'submit_work', args);
192
+ }
193
+ async approveWork(creator, params) {
194
+ const programId = await this.getProgramId();
195
+ const bountyId = normalizeUnsignedU64(params.bountyId, 'bountyId');
196
+ const args = encodeApproveWorkArgs(creator.pubkey(), bountyId, params.submissionIdx);
197
+ return this.connection.callContract(creator, programId, 'approve_work', args);
198
+ }
199
+ async cancelBounty(creator, bountyId) {
200
+ const programId = await this.getProgramId();
201
+ const normalizedId = normalizeUnsignedU64(bountyId, 'bountyId');
202
+ const args = encodeCancelBountyArgs(creator.pubkey(), normalizedId);
203
+ return this.connection.callContract(creator, programId, 'cancel_bounty', args);
204
+ }
205
+ }