quorum-sdk 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/src/index.ts ADDED
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Agent Multisig SDK
3
+ *
4
+ * TypeScript client for the Agent Multisig Coordination API.
5
+ * Enables AI agents to participate in multi-signature Bitcoin transactions.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { AgentMultisig } from '@agent-multisig/sdk';
10
+ *
11
+ * const client = new AgentMultisig({
12
+ * apiUrl: 'https://agent-multisig-api-production.up.railway.app',
13
+ * apiKey: 'your-api-key'
14
+ * });
15
+ *
16
+ * // Register an agent
17
+ * const agent = await client.registerAgent({
18
+ * name: 'TreasuryBot',
19
+ * provider: 'aibtc',
20
+ * publicKey: '9350761ae700acd872510de161bca0b90b78ddc007936674b318be8a50c531b5'
21
+ * });
22
+ *
23
+ * // Create a 2-of-3 multisig
24
+ * const multisig = await client.createMultisig({
25
+ * name: 'AI Treasury',
26
+ * threshold: 2,
27
+ * agents: [agent1.id, agent2.id, agent3.id],
28
+ * network: 'mainnet'
29
+ * });
30
+ *
31
+ * // Create a spend proposal
32
+ * const proposal = await client.createProposal({
33
+ * multisigId: multisig.id,
34
+ * to: 'bc1q...',
35
+ * amount: 10000
36
+ * });
37
+ *
38
+ * // Sign the proposal
39
+ * await client.signProposal(proposal.id, agent.id, signature);
40
+ * ```
41
+ */
42
+
43
+ // Types
44
+ export interface AgentMultisigConfig {
45
+ apiUrl: string;
46
+ apiKey?: string;
47
+ timeout?: number;
48
+ }
49
+
50
+ export interface Agent {
51
+ id: string;
52
+ name: string;
53
+ provider: 'aibtc' | 'agentkit' | 'crossmint' | 'clawcash' | 'bankr' | 'custom';
54
+ publicKey: string;
55
+ chain: 'bitcoin' | 'stacks' | 'evm' | 'solana';
56
+ createdAt: string;
57
+ }
58
+
59
+ export interface RegisterAgentInput {
60
+ name: string;
61
+ provider: Agent['provider'];
62
+ publicKey: string;
63
+ chain?: Agent['chain'];
64
+ webhookUrl?: string;
65
+ metadata?: Record<string, unknown>;
66
+ }
67
+
68
+ export interface Multisig {
69
+ id: string;
70
+ name: string;
71
+ address: string;
72
+ threshold: number;
73
+ totalSigners: number;
74
+ agents: string[];
75
+ network: 'mainnet' | 'testnet' | 'signet';
76
+ chain: 'bitcoin' | 'stacks' | 'evm' | 'solana';
77
+ scriptHex?: string;
78
+ createdAt: string;
79
+ }
80
+
81
+ export interface CreateMultisigInput {
82
+ name: string;
83
+ threshold: number;
84
+ agents: string[];
85
+ network?: 'mainnet' | 'testnet' | 'signet';
86
+ chain?: Multisig['chain'];
87
+ }
88
+
89
+ export interface Proposal {
90
+ id: string;
91
+ multisigId: string;
92
+ type: 'spend' | 'custom';
93
+ status: 'pending' | 'ready' | 'broadcast' | 'confirmed' | 'failed';
94
+ to?: string;
95
+ amount?: number;
96
+ psbtHex?: string;
97
+ signatures: ProposalSignature[];
98
+ txid?: string;
99
+ createdAt: string;
100
+ expiresAt?: string;
101
+ }
102
+
103
+ export interface ProposalSignature {
104
+ agentId: string;
105
+ signature: string;
106
+ signedAt: string;
107
+ }
108
+
109
+ export interface CreateProposalInput {
110
+ multisigId: string;
111
+ to: string;
112
+ amount: number;
113
+ memo?: string;
114
+ }
115
+
116
+ export interface SignProposalInput {
117
+ proposalId: string;
118
+ agentId: string;
119
+ signature: string;
120
+ }
121
+
122
+ // Error types
123
+ export class AgentMultisigError extends Error {
124
+ constructor(
125
+ message: string,
126
+ public statusCode?: number,
127
+ public code?: string
128
+ ) {
129
+ super(message);
130
+ this.name = 'AgentMultisigError';
131
+ }
132
+ }
133
+
134
+ // Main client
135
+ export class AgentMultisig {
136
+ private apiUrl: string;
137
+ private apiKey?: string;
138
+ private timeout: number;
139
+
140
+ constructor(config: AgentMultisigConfig) {
141
+ this.apiUrl = config.apiUrl.replace(/\/$/, '');
142
+ this.apiKey = config.apiKey;
143
+ this.timeout = config.timeout ?? 30000;
144
+ }
145
+
146
+ private async request<T>(
147
+ method: string,
148
+ path: string,
149
+ body?: unknown
150
+ ): Promise<T> {
151
+ const url = `${this.apiUrl}${path}`;
152
+ const headers: Record<string, string> = {
153
+ 'Content-Type': 'application/json',
154
+ };
155
+
156
+ if (this.apiKey) {
157
+ headers['Authorization'] = `Bearer ${this.apiKey}`;
158
+ }
159
+
160
+ const controller = new AbortController();
161
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
162
+
163
+ try {
164
+ const response = await fetch(url, {
165
+ method,
166
+ headers,
167
+ body: body ? JSON.stringify(body) : undefined,
168
+ signal: controller.signal,
169
+ });
170
+
171
+ clearTimeout(timeoutId);
172
+
173
+ const data = await response.json();
174
+
175
+ if (!response.ok) {
176
+ throw new AgentMultisigError(
177
+ data.error || data.message || 'Request failed',
178
+ response.status,
179
+ data.code
180
+ );
181
+ }
182
+
183
+ return data as T;
184
+ } catch (error) {
185
+ clearTimeout(timeoutId);
186
+ if (error instanceof AgentMultisigError) throw error;
187
+ if (error instanceof Error && error.name === 'AbortError') {
188
+ throw new AgentMultisigError('Request timeout', 408, 'TIMEOUT');
189
+ }
190
+ throw new AgentMultisigError(
191
+ error instanceof Error ? error.message : 'Unknown error'
192
+ );
193
+ }
194
+ }
195
+
196
+ // ==================== Health ====================
197
+
198
+ async health(): Promise<{ status: string; version: string }> {
199
+ return this.request('GET', '/health');
200
+ }
201
+
202
+ // ==================== Agents ====================
203
+
204
+ /**
205
+ * Register a new agent with the coordination API.
206
+ */
207
+ async registerAgent(input: RegisterAgentInput): Promise<Agent> {
208
+ return this.request('POST', '/agents', input);
209
+ }
210
+
211
+ /**
212
+ * Get an agent by ID.
213
+ */
214
+ async getAgent(agentId: string): Promise<Agent> {
215
+ return this.request('GET', `/agents/${agentId}`);
216
+ }
217
+
218
+ /**
219
+ * List all registered agents.
220
+ */
221
+ async listAgents(): Promise<Agent[]> {
222
+ return this.request('GET', '/agents');
223
+ }
224
+
225
+ // ==================== Multisigs ====================
226
+
227
+ /**
228
+ * Create a new multisig wallet.
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const multisig = await client.createMultisig({
233
+ * name: 'AI Treasury',
234
+ * threshold: 2,
235
+ * agents: ['agent_abc', 'agent_def', 'agent_ghi'],
236
+ * network: 'mainnet'
237
+ * });
238
+ * console.log('Fund this address:', multisig.address);
239
+ * ```
240
+ */
241
+ async createMultisig(input: CreateMultisigInput): Promise<Multisig> {
242
+ return this.request('POST', '/multisigs', input);
243
+ }
244
+
245
+ /**
246
+ * Get a multisig by ID.
247
+ */
248
+ async getMultisig(multisigId: string): Promise<Multisig> {
249
+ return this.request('GET', `/multisigs/${multisigId}`);
250
+ }
251
+
252
+ /**
253
+ * List all multisigs.
254
+ */
255
+ async listMultisigs(): Promise<Multisig[]> {
256
+ return this.request('GET', '/multisigs');
257
+ }
258
+
259
+ /**
260
+ * Get the balance of a multisig wallet.
261
+ */
262
+ async getMultisigBalance(multisigId: string): Promise<{
263
+ confirmed: number;
264
+ unconfirmed: number;
265
+ utxos: Array<{ txid: string; vout: number; value: number }>;
266
+ }> {
267
+ return this.request('GET', `/multisigs/${multisigId}/balance`);
268
+ }
269
+
270
+ // ==================== Proposals ====================
271
+
272
+ /**
273
+ * Create a spend proposal for a multisig.
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const proposal = await client.createProposal({
278
+ * multisigId: 'msig_xyz',
279
+ * to: 'bc1qpzlw29z50cz7ysjpsaqggtscya3p6ggehnsp2g',
280
+ * amount: 10000 // satoshis
281
+ * });
282
+ * console.log('PSBT to sign:', proposal.psbtHex);
283
+ * ```
284
+ */
285
+ async createProposal(input: CreateProposalInput): Promise<Proposal> {
286
+ return this.request('POST', '/proposals', input);
287
+ }
288
+
289
+ /**
290
+ * Get a proposal by ID.
291
+ */
292
+ async getProposal(proposalId: string): Promise<Proposal> {
293
+ return this.request('GET', `/proposals/${proposalId}`);
294
+ }
295
+
296
+ /**
297
+ * List proposals, optionally filtered by multisig.
298
+ */
299
+ async listProposals(multisigId?: string): Promise<Proposal[]> {
300
+ const path = multisigId
301
+ ? `/proposals?multisigId=${multisigId}`
302
+ : '/proposals';
303
+ return this.request('GET', path);
304
+ }
305
+
306
+ /**
307
+ * Sign a proposal with an agent's key.
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * // Agent signs the PSBT and submits signature
312
+ * const signature = await myWallet.signPsbt(proposal.psbtHex);
313
+ * await client.signProposal({
314
+ * proposalId: proposal.id,
315
+ * agentId: myAgent.id,
316
+ * signature: signature
317
+ * });
318
+ * ```
319
+ */
320
+ async signProposal(input: SignProposalInput): Promise<Proposal> {
321
+ return this.request('POST', `/proposals/${input.proposalId}/sign`, {
322
+ agentId: input.agentId,
323
+ signature: input.signature,
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Broadcast a fully-signed proposal.
329
+ */
330
+ async broadcastProposal(proposalId: string): Promise<{ txid: string }> {
331
+ return this.request('POST', `/proposals/${proposalId}/broadcast`);
332
+ }
333
+
334
+ // ==================== Convenience Methods ====================
335
+
336
+ /**
337
+ * Create a multisig and register agents in one call.
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * const { multisig, agents } = await client.quickSetup({
342
+ * name: 'Quick Treasury',
343
+ * threshold: 2,
344
+ * signers: [
345
+ * { name: 'Bot1', provider: 'aibtc', publicKey: '...' },
346
+ * { name: 'Bot2', provider: 'aibtc', publicKey: '...' },
347
+ * { name: 'Bot3', provider: 'aibtc', publicKey: '...' },
348
+ * ]
349
+ * });
350
+ * ```
351
+ */
352
+ async quickSetup(input: {
353
+ name: string;
354
+ threshold: number;
355
+ signers: RegisterAgentInput[];
356
+ network?: 'mainnet' | 'testnet' | 'signet';
357
+ }): Promise<{ multisig: Multisig; agents: Agent[] }> {
358
+ // Register all agents
359
+ const agents = await Promise.all(
360
+ input.signers.map(signer => this.registerAgent(signer))
361
+ );
362
+
363
+ // Create multisig with agent IDs
364
+ const multisig = await this.createMultisig({
365
+ name: input.name,
366
+ threshold: input.threshold,
367
+ agents: agents.map(a => a.id),
368
+ network: input.network,
369
+ });
370
+
371
+ return { multisig, agents };
372
+ }
373
+
374
+ /**
375
+ * Wait for a proposal to reach a target status.
376
+ */
377
+ async waitForProposal(
378
+ proposalId: string,
379
+ targetStatus: Proposal['status'],
380
+ options?: { timeoutMs?: number; pollIntervalMs?: number }
381
+ ): Promise<Proposal> {
382
+ const timeout = options?.timeoutMs ?? 300000; // 5 minutes
383
+ const interval = options?.pollIntervalMs ?? 5000; // 5 seconds
384
+ const start = Date.now();
385
+
386
+ while (Date.now() - start < timeout) {
387
+ const proposal = await this.getProposal(proposalId);
388
+
389
+ if (proposal.status === targetStatus) {
390
+ return proposal;
391
+ }
392
+
393
+ if (proposal.status === 'failed') {
394
+ throw new AgentMultisigError(
395
+ 'Proposal failed',
396
+ 400,
397
+ 'PROPOSAL_FAILED'
398
+ );
399
+ }
400
+
401
+ await new Promise(resolve => setTimeout(resolve, interval));
402
+ }
403
+
404
+ throw new AgentMultisigError(
405
+ `Timeout waiting for proposal to reach ${targetStatus}`,
406
+ 408,
407
+ 'TIMEOUT'
408
+ );
409
+ }
410
+ }
411
+
412
+ // Default export
413
+ export default AgentMultisig;
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "outDir": "dist",
12
+ "rootDir": "src"
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }