@openzeppelin/guardian-client 0.13.2

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.
Files changed (47) hide show
  1. package/README.md +140 -0
  2. package/dist/auth-request.d.ts +10 -0
  3. package/dist/auth-request.d.ts.map +1 -0
  4. package/dist/auth-request.js +38 -0
  5. package/dist/auth-request.js.map +1 -0
  6. package/dist/auth-request.test.d.ts +2 -0
  7. package/dist/auth-request.test.d.ts.map +1 -0
  8. package/dist/auth-request.test.js +10 -0
  9. package/dist/auth-request.test.js.map +1 -0
  10. package/dist/conversion.d.ts +18 -0
  11. package/dist/conversion.d.ts.map +1 -0
  12. package/dist/conversion.js +187 -0
  13. package/dist/conversion.js.map +1 -0
  14. package/dist/conversion.test.d.ts +2 -0
  15. package/dist/conversion.test.d.ts.map +1 -0
  16. package/dist/conversion.test.js +317 -0
  17. package/dist/conversion.test.js.map +1 -0
  18. package/dist/http.d.ts +33 -0
  19. package/dist/http.d.ts.map +1 -0
  20. package/dist/http.js +191 -0
  21. package/dist/http.js.map +1 -0
  22. package/dist/http.test.d.ts +2 -0
  23. package/dist/http.test.d.ts.map +1 -0
  24. package/dist/http.test.js +493 -0
  25. package/dist/http.test.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +3 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/server-types.d.ts +150 -0
  31. package/dist/server-types.d.ts.map +1 -0
  32. package/dist/server-types.js +2 -0
  33. package/dist/server-types.js.map +1 -0
  34. package/dist/types.d.ts +159 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +2 -0
  37. package/dist/types.js.map +1 -0
  38. package/package.json +36 -0
  39. package/src/auth-request.test.ts +11 -0
  40. package/src/auth-request.ts +49 -0
  41. package/src/conversion.test.ts +400 -0
  42. package/src/conversion.ts +247 -0
  43. package/src/http.test.ts +597 -0
  44. package/src/http.ts +244 -0
  45. package/src/index.ts +25 -0
  46. package/src/server-types.ts +142 -0
  47. package/src/types.ts +167 -0
package/src/http.ts ADDED
@@ -0,0 +1,244 @@
1
+ import type {
2
+ ConfigureRequest,
3
+ ConfigureResponse,
4
+ DeltaObject,
5
+ DeltaProposalRequest,
6
+ DeltaProposalResponse,
7
+ ExecutionDelta,
8
+ PubkeyResponse,
9
+ PushDeltaResponse,
10
+ SignProposalRequest,
11
+ SignatureScheme,
12
+ Signer,
13
+ StateObject,
14
+ } from './types.js';
15
+ import { RequestAuthPayload } from './auth-request.js';
16
+ import type {
17
+ ServerDeltaObject,
18
+ ServerDeltaProposalResponse,
19
+ ServerProposalsResponse,
20
+ ServerPubkeyResponse,
21
+ ServerStateObject,
22
+ ServerConfigureResponse,
23
+ ServerPushDeltaResponse,
24
+ } from './server-types.js';
25
+ import {
26
+ fromServerConfigureResponse,
27
+ fromServerDeltaObject,
28
+ fromServerStateObject,
29
+ toServerConfigureRequest,
30
+ toServerDeltaProposalRequest,
31
+ toServerExecutionDelta,
32
+ toServerSignProposalRequest,
33
+ } from './conversion.js';
34
+
35
+ /**
36
+ * Error thrown by the GUARDIAN HTTP client.
37
+ */
38
+ export class GuardianHttpError extends Error {
39
+ constructor(
40
+ public readonly status: number,
41
+ public readonly statusText: string,
42
+ public readonly body: string
43
+ ) {
44
+ super(`GUARDIAN HTTP error ${status}: ${statusText} - ${body}`);
45
+ this.name = 'GuardianHttpError';
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Minimal HTTP client for GUARDIAN server.
51
+ */
52
+ export class GuardianHttpClient {
53
+ private signer: Signer | null = null;
54
+ private readonly baseUrl: string;
55
+ private lastTimestamp = 0;
56
+
57
+ constructor(baseUrl: string) {
58
+ this.baseUrl = baseUrl;
59
+ }
60
+
61
+ setSigner(signer: Signer): void {
62
+ this.signer = signer;
63
+ }
64
+
65
+ async getPubkey(scheme?: SignatureScheme): Promise<PubkeyResponse> {
66
+ const query = scheme ? `?scheme=${scheme}` : '';
67
+ const response = await this.fetch(`/pubkey${query}`, { method: 'GET' });
68
+ const data = (await response.json()) as ServerPubkeyResponse;
69
+ return {
70
+ commitment: data.commitment,
71
+ pubkey: data.pubkey,
72
+ };
73
+ }
74
+
75
+ async configure(request: ConfigureRequest): Promise<ConfigureResponse> {
76
+ const serverRequest = toServerConfigureRequest(request);
77
+ const response = await this.fetchAuthenticated('/configure', {
78
+ method: 'POST',
79
+ body: JSON.stringify(serverRequest),
80
+ }, request.accountId, serverRequest);
81
+ const server = (await response.json()) as ServerConfigureResponse;
82
+ return fromServerConfigureResponse(server);
83
+ }
84
+
85
+ async getState(accountId: string): Promise<StateObject> {
86
+ const requestQuery = { account_id: accountId };
87
+ const params = new URLSearchParams(requestQuery);
88
+ const response = await this.fetchAuthenticated(`/state?${params}`, {
89
+ method: 'GET',
90
+ }, accountId, requestQuery);
91
+ const server = (await response.json()) as ServerStateObject;
92
+ return fromServerStateObject(server);
93
+ }
94
+
95
+ async getDeltaProposals(accountId: string): Promise<DeltaObject[]> {
96
+ const requestQuery = { account_id: accountId };
97
+ const params = new URLSearchParams(requestQuery);
98
+ const response = await this.fetchAuthenticated(`/delta/proposal?${params}`, {
99
+ method: 'GET',
100
+ }, accountId, requestQuery);
101
+ const data = (await response.json()) as ServerProposalsResponse;
102
+ return data.proposals.map(fromServerDeltaObject);
103
+ }
104
+
105
+ async getDeltaProposal(accountId: string, commitment: string): Promise<DeltaObject> {
106
+ const requestQuery = { account_id: accountId, commitment };
107
+ const params = new URLSearchParams(requestQuery);
108
+ const response = await this.fetchAuthenticated(`/delta/proposal/single?${params}`, {
109
+ method: 'GET',
110
+ }, accountId, requestQuery);
111
+ const data = (await response.json()) as ServerDeltaObject;
112
+ return fromServerDeltaObject(data);
113
+ }
114
+
115
+ async pushDeltaProposal(request: DeltaProposalRequest): Promise<DeltaProposalResponse> {
116
+ const serverRequest = toServerDeltaProposalRequest(request);
117
+ const response = await this.fetchAuthenticated('/delta/proposal', {
118
+ method: 'POST',
119
+ body: JSON.stringify(serverRequest),
120
+ }, request.accountId, serverRequest);
121
+ const server = (await response.json()) as ServerDeltaProposalResponse;
122
+ return {
123
+ delta: fromServerDeltaObject(server.delta),
124
+ commitment: server.commitment,
125
+ };
126
+ }
127
+
128
+ async signDeltaProposal(request: SignProposalRequest): Promise<DeltaObject> {
129
+ const serverRequest = toServerSignProposalRequest(request);
130
+ const response = await this.fetchAuthenticated('/delta/proposal', {
131
+ method: 'PUT',
132
+ body: JSON.stringify(serverRequest),
133
+ }, request.accountId, serverRequest);
134
+ const server = (await response.json()) as ServerDeltaObject;
135
+ return fromServerDeltaObject(server);
136
+ }
137
+
138
+ async pushDelta(delta: ExecutionDelta): Promise<PushDeltaResponse> {
139
+ const serverDelta = toServerExecutionDelta(delta);
140
+ const response = await this.fetchAuthenticated('/delta', {
141
+ method: 'POST',
142
+ body: JSON.stringify(serverDelta),
143
+ }, delta.accountId, serverDelta);
144
+ const server = (await response.json()) as ServerPushDeltaResponse;
145
+ return {
146
+ accountId: server.account_id,
147
+ nonce: server.nonce,
148
+ newCommitment: server.new_commitment,
149
+ ackSig: server.ack_sig,
150
+ ackPubkey: server.ack_pubkey,
151
+ ackScheme: server.ack_scheme,
152
+ };
153
+ }
154
+
155
+ async getDelta(accountId: string, nonce: number): Promise<DeltaObject> {
156
+ const requestPayload = {
157
+ account_id: accountId,
158
+ nonce,
159
+ };
160
+ const requestQuery = {
161
+ account_id: accountId,
162
+ nonce: nonce.toString(),
163
+ };
164
+ const params = new URLSearchParams(requestQuery);
165
+ const response = await this.fetchAuthenticated(`/delta?${params}`, {
166
+ method: 'GET',
167
+ }, accountId, requestPayload);
168
+ const server = (await response.json()) as ServerDeltaObject;
169
+ return fromServerDeltaObject(server);
170
+ }
171
+
172
+ async getDeltaSince(accountId: string, fromNonce: number): Promise<DeltaObject> {
173
+ const requestPayload = {
174
+ account_id: accountId,
175
+ nonce: fromNonce,
176
+ };
177
+ const requestQuery = {
178
+ account_id: accountId,
179
+ nonce: fromNonce.toString(),
180
+ };
181
+ const params = new URLSearchParams(requestQuery);
182
+ const response = await this.fetchAuthenticated(`/delta/since?${params}`, {
183
+ method: 'GET',
184
+ }, accountId, requestPayload);
185
+ const server = (await response.json()) as ServerDeltaObject;
186
+ return fromServerDeltaObject(server);
187
+ }
188
+
189
+ private async fetch(path: string, init: RequestInit): Promise<Response> {
190
+ const url = `${this.baseUrl}${path}`;
191
+ const response = await fetch(url, {
192
+ ...init,
193
+ headers: {
194
+ 'Content-Type': 'application/json',
195
+ ...init.headers,
196
+ },
197
+ });
198
+
199
+ if (!response.ok) {
200
+ const body = await response.text();
201
+ throw new GuardianHttpError(response.status, response.statusText, body);
202
+ }
203
+
204
+ return response;
205
+ }
206
+
207
+ private async fetchAuthenticated(
208
+ path: string,
209
+ init: RequestInit,
210
+ accountId: string,
211
+ requestPayload: unknown,
212
+ retries = 2
213
+ ): Promise<Response> {
214
+ if (!this.signer) {
215
+ throw new Error('No signer configured. Call setSigner() first.');
216
+ }
217
+
218
+ const now = Date.now();
219
+ const timestamp = now > this.lastTimestamp ? now : this.lastTimestamp + 1;
220
+ this.lastTimestamp = timestamp;
221
+ const authPayload = RequestAuthPayload.fromRequest(requestPayload);
222
+ const signature = this.signer.signRequest
223
+ ? await this.signer.signRequest(accountId, timestamp, authPayload)
224
+ : await this.signer.signAccountIdWithTimestamp(accountId, timestamp);
225
+
226
+ try {
227
+ return await this.fetch(path, {
228
+ ...init,
229
+ headers: {
230
+ ...init.headers,
231
+ 'x-pubkey': this.signer.publicKey,
232
+ 'x-signature': signature,
233
+ 'x-timestamp': timestamp.toString(),
234
+ },
235
+ });
236
+ } catch (err) {
237
+ if (retries > 0 && err instanceof GuardianHttpError && err.body.includes('Replay attack')) {
238
+ await new Promise((resolve) => setTimeout(resolve, 50));
239
+ return this.fetchAuthenticated(path, init, accountId, requestPayload, retries - 1);
240
+ }
241
+ throw err;
242
+ }
243
+ }
244
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ export { GuardianHttpClient, GuardianHttpError } from './http.js';
2
+ export { RequestAuthPayload } from './auth-request.js';
3
+
4
+ export type {
5
+ Signer,
6
+ FalconSignature,
7
+ EcdsaSignature,
8
+ ProposalSignature,
9
+ SignatureScheme,
10
+ CosignerSignature,
11
+ AuthConfig,
12
+ DeltaStatus,
13
+ DeltaObject,
14
+ ExecutionDelta,
15
+ StateObject,
16
+ ProposalType,
17
+ ProposalMetadata,
18
+ ConfigureRequest,
19
+ ConfigureResponse,
20
+ PubkeyResponse,
21
+ DeltaProposalRequest,
22
+ DeltaProposalResponse,
23
+ ProposalsResponse,
24
+ SignProposalRequest,
25
+ } from './types.js';
@@ -0,0 +1,142 @@
1
+ export interface ServerFalconSignature {
2
+ scheme: 'falcon';
3
+ signature: string;
4
+ }
5
+
6
+ export interface ServerEcdsaSignature {
7
+ scheme: 'ecdsa';
8
+ signature: string;
9
+ public_key?: string;
10
+ }
11
+
12
+ export type ServerProposalSignature = ServerFalconSignature | ServerEcdsaSignature;
13
+
14
+ export interface ServerCosignerSignature {
15
+ signer_id: string;
16
+ signature: ServerProposalSignature;
17
+ timestamp: string;
18
+ }
19
+
20
+ export type ServerDeltaStatus =
21
+ | { status: 'pending'; timestamp: string; proposer_id: string; cosigner_sigs: ServerCosignerSignature[] }
22
+ | { status: 'candidate'; timestamp: string }
23
+ | { status: 'canonical'; timestamp: string }
24
+ | { status: 'discarded'; timestamp: string };
25
+
26
+ export type ServerProposalType =
27
+ | 'add_signer'
28
+ | 'remove_signer'
29
+ | 'change_threshold'
30
+ | 'update_procedure_threshold'
31
+ | 'switch_guardian'
32
+ | 'consume_notes'
33
+ | 'p2id'
34
+ | 'custom';
35
+
36
+ export interface ServerProposalMetadata {
37
+ proposal_type?: ServerProposalType;
38
+ target_threshold?: number;
39
+ required_signatures?: number;
40
+ signer_commitments?: string[];
41
+ target_procedure?: string;
42
+ salt?: string;
43
+ description?: string;
44
+ new_guardian_pubkey?: string;
45
+ new_guardian_endpoint?: string;
46
+ note_ids?: string[];
47
+ recipient_id?: string;
48
+ faucet_id?: string;
49
+ amount?: string;
50
+ }
51
+
52
+ export interface ServerDeltaObject {
53
+ account_id: string;
54
+ nonce: number;
55
+ prev_commitment: string;
56
+ new_commitment?: string;
57
+ delta_payload: {
58
+ tx_summary?: { data: string };
59
+ data?: string;
60
+ signatures?: Array<{ signer_id: string; signature: ServerProposalSignature }>;
61
+ metadata?: ServerProposalMetadata;
62
+ };
63
+ ack_sig?: string;
64
+ ack_pubkey?: string;
65
+ ack_scheme?: string;
66
+ status: ServerDeltaStatus;
67
+ }
68
+
69
+ export interface ServerPushDeltaResponse {
70
+ account_id: string;
71
+ nonce: number;
72
+ new_commitment?: string;
73
+ ack_sig?: string;
74
+ ack_pubkey?: string;
75
+ ack_scheme?: string;
76
+ }
77
+
78
+ export interface ServerExecutionDelta {
79
+ account_id: string;
80
+ nonce: number;
81
+ prev_commitment: string;
82
+ new_commitment?: string;
83
+ delta_payload: { data: string };
84
+ ack_sig?: string;
85
+ status: ServerDeltaStatus;
86
+ }
87
+
88
+ export interface ServerStateObject {
89
+ account_id: string;
90
+ commitment: string;
91
+ state_json: { data: string };
92
+ created_at: string;
93
+ updated_at: string;
94
+ auth_scheme?: string;
95
+ }
96
+
97
+ export type ServerAuthConfig =
98
+ | { MidenFalconRpo: { cosigner_commitments: string[] } }
99
+ | { MidenEcdsa: { cosigner_commitments: string[] } };
100
+
101
+ export interface ServerConfigureRequest {
102
+ account_id: string;
103
+ auth: ServerAuthConfig;
104
+ initial_state: { data: string; account_id: string };
105
+ }
106
+
107
+ export interface ServerConfigureResponse {
108
+ success: boolean;
109
+ message: string;
110
+ ack_pubkey?: string;
111
+ ack_commitment?: string;
112
+ }
113
+
114
+ export interface ServerDeltaProposalRequest {
115
+ account_id: string;
116
+ nonce: number;
117
+ delta_payload: {
118
+ tx_summary: { data: string };
119
+ signatures: Array<{ signer_id: string; signature: ServerProposalSignature }>;
120
+ metadata?: ServerProposalMetadata;
121
+ };
122
+ }
123
+
124
+ export interface ServerDeltaProposalResponse {
125
+ delta: ServerDeltaObject;
126
+ commitment: string;
127
+ }
128
+
129
+ export interface ServerProposalsResponse {
130
+ proposals: ServerDeltaObject[];
131
+ }
132
+
133
+ export interface ServerSignProposalRequest {
134
+ account_id: string;
135
+ commitment: string;
136
+ signature: ServerProposalSignature;
137
+ }
138
+
139
+ export interface ServerPubkeyResponse {
140
+ commitment: string;
141
+ pubkey?: string;
142
+ }
package/src/types.ts ADDED
@@ -0,0 +1,167 @@
1
+ import type { RequestAuthPayload } from './auth-request.js';
2
+
3
+ export interface Signer {
4
+ readonly commitment: string;
5
+ readonly publicKey: string;
6
+ readonly scheme: SignatureScheme;
7
+ signAccountIdWithTimestamp(accountId: string, timestamp: number): Promise<string> | string;
8
+ signRequest?(
9
+ accountId: string,
10
+ timestamp: number,
11
+ requestPayload: RequestAuthPayload
12
+ ): Promise<string> | string;
13
+ signCommitment(commitmentHex: string): Promise<string> | string;
14
+ }
15
+
16
+ export interface FalconSignature {
17
+ scheme: 'falcon';
18
+ signature: string;
19
+ }
20
+
21
+ export interface EcdsaSignature {
22
+ scheme: 'ecdsa';
23
+ signature: string;
24
+ publicKey?: string;
25
+ }
26
+
27
+ export type ProposalSignature = FalconSignature | EcdsaSignature;
28
+
29
+ export type SignatureScheme = 'falcon' | 'ecdsa';
30
+
31
+ export type AuthConfig =
32
+ | {
33
+ MidenFalconRpo: {
34
+ cosigner_commitments: string[];
35
+ };
36
+ }
37
+ | {
38
+ MidenEcdsa: {
39
+ cosigner_commitments: string[];
40
+ };
41
+ };
42
+
43
+ export interface CosignerSignature {
44
+ signerId: string;
45
+ signature: ProposalSignature;
46
+ timestamp: string;
47
+ }
48
+
49
+ export type DeltaStatus =
50
+ | { status: 'pending'; timestamp: string; proposerId: string; cosignerSigs: CosignerSignature[] }
51
+ | { status: 'candidate'; timestamp: string }
52
+ | { status: 'canonical'; timestamp: string }
53
+ | { status: 'discarded'; timestamp: string };
54
+
55
+ export type ProposalType =
56
+ | 'add_signer'
57
+ | 'remove_signer'
58
+ | 'change_threshold'
59
+ | 'update_procedure_threshold'
60
+ | 'switch_guardian'
61
+ | 'consume_notes'
62
+ | 'p2id'
63
+ | 'custom'
64
+ | 'unknown';
65
+
66
+ export interface ProposalMetadata {
67
+ proposalType?: ProposalType;
68
+ targetThreshold?: number;
69
+ requiredSignatures?: number;
70
+ signerCommitments?: string[];
71
+ targetProcedure?: string;
72
+ salt?: string;
73
+ description?: string;
74
+ newGuardianPubkey?: string;
75
+ newGuardianEndpoint?: string;
76
+ noteIds?: string[];
77
+ recipientId?: string;
78
+ faucetId?: string;
79
+ amount?: string;
80
+ }
81
+
82
+ export interface DeltaObject {
83
+ accountId: string;
84
+ nonce: number;
85
+ prevCommitment: string;
86
+ newCommitment?: string;
87
+ deltaPayload: {
88
+ txSummary: { data: string };
89
+ signatures: Array<{ signerId: string; signature: ProposalSignature }>;
90
+ metadata?: ProposalMetadata;
91
+ };
92
+ ackSig?: string;
93
+ ackPubkey?: string;
94
+ ackScheme?: string;
95
+ status: DeltaStatus;
96
+ }
97
+
98
+ export interface ExecutionDelta {
99
+ accountId: string;
100
+ nonce: number;
101
+ prevCommitment: string;
102
+ newCommitment?: string;
103
+ deltaPayload: { data: string };
104
+ ackSig?: string;
105
+ status: DeltaStatus;
106
+ }
107
+
108
+ export interface StateObject {
109
+ accountId: string;
110
+ commitment: string;
111
+ stateJson: { data: string };
112
+ createdAt: string;
113
+ updatedAt: string;
114
+ authScheme?: string;
115
+ }
116
+
117
+ export interface ConfigureRequest {
118
+ accountId: string;
119
+ auth: AuthConfig;
120
+ initialState: { data: string; accountId: string };
121
+ }
122
+
123
+ export interface ConfigureResponse {
124
+ success: boolean;
125
+ message: string;
126
+ ackPubkey?: string;
127
+ ackCommitment?: string;
128
+ }
129
+
130
+ export interface PubkeyResponse {
131
+ commitment: string;
132
+ pubkey?: string;
133
+ }
134
+
135
+ export interface DeltaProposalRequest {
136
+ accountId: string;
137
+ nonce: number;
138
+ deltaPayload: {
139
+ txSummary: { data: string };
140
+ signatures: Array<{ signerId: string; signature: ProposalSignature }>;
141
+ metadata?: ProposalMetadata;
142
+ };
143
+ }
144
+
145
+ export interface DeltaProposalResponse {
146
+ delta: DeltaObject;
147
+ commitment: string;
148
+ }
149
+
150
+ export interface ProposalsResponse {
151
+ proposals: DeltaObject[];
152
+ }
153
+
154
+ export interface SignProposalRequest {
155
+ accountId: string;
156
+ commitment: string;
157
+ signature: ProposalSignature;
158
+ }
159
+
160
+ export interface PushDeltaResponse {
161
+ accountId: string;
162
+ nonce: number;
163
+ newCommitment?: string;
164
+ ackSig?: string;
165
+ ackPubkey?: string;
166
+ ackScheme?: string;
167
+ }