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/README.md +134 -0
- package/dist/index.d.mts +250 -0
- package/dist/index.d.ts +250 -0
- package/dist/index.js +257 -0
- package/dist/index.mjs +231 -0
- package/package.json +36 -0
- package/src/index.ts +413 -0
- package/tsconfig.json +16 -0
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
|
+
}
|