@openzeppelin/guardian-client 0.15.2 → 0.16.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 +16 -0
- package/dist/conversion.d.ts.map +1 -1
- package/dist/conversion.js +4 -2
- package/dist/conversion.js.map +1 -1
- package/dist/error-codes.d.ts +30 -0
- package/dist/error-codes.d.ts.map +1 -0
- package/dist/error-codes.js +81 -0
- package/dist/error-codes.js.map +1 -0
- package/dist/http.d.ts +62 -10
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +146 -29
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server-types.d.ts +13 -0
- package/dist/server-types.d.ts.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/conversion.test.ts +16 -0
- package/src/conversion.ts +4 -2
- package/src/error-codes.test.ts +69 -0
- package/src/error-codes.ts +91 -0
- package/src/http.test.ts +216 -29
- package/src/http.ts +180 -29
- package/src/index.ts +9 -0
- package/src/server-types.ts +15 -1
- package/src/types.ts +23 -1
- package/dist/auth-request.test.d.ts +0 -2
- package/dist/auth-request.test.d.ts.map +0 -1
- package/dist/auth-request.test.js +0 -10
- package/dist/auth-request.test.js.map +0 -1
- package/dist/conversion.test.d.ts +0 -2
- package/dist/conversion.test.d.ts.map +0 -1
- package/dist/conversion.test.js +0 -332
- package/dist/conversion.test.js.map +0 -1
- package/dist/http.test.d.ts +0 -2
- package/dist/http.test.d.ts.map +0 -1
- package/dist/http.test.js +0 -750
- package/dist/http.test.js.map +0 -1
package/dist/server-types.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type ServerDeltaStatus = {
|
|
|
27
27
|
} | {
|
|
28
28
|
status: 'discarded';
|
|
29
29
|
timestamp: string;
|
|
30
|
+
reason?: string;
|
|
30
31
|
};
|
|
31
32
|
export type ServerProposalType = 'add_signer' | 'remove_signer' | 'change_threshold' | 'update_procedure_threshold' | 'switch_guardian' | 'consume_notes' | 'p2id' | 'custom' | (string & {});
|
|
32
33
|
export interface ServerProposalMetadata {
|
|
@@ -47,6 +48,8 @@ export interface ServerProposalMetadata {
|
|
|
47
48
|
recipient_id?: string;
|
|
48
49
|
faucet_id?: string;
|
|
49
50
|
amount?: string;
|
|
51
|
+
/** P2ID note visibility, "public" or "private" (issue #322). Absent => public. */
|
|
52
|
+
note_type?: string;
|
|
50
53
|
}
|
|
51
54
|
export interface ServerDeltaObject {
|
|
52
55
|
account_id: string;
|
|
@@ -142,6 +145,16 @@ export interface ServerDeltaProposalResponse {
|
|
|
142
145
|
export interface ServerProposalsResponse {
|
|
143
146
|
proposals: ServerDeltaObject[];
|
|
144
147
|
}
|
|
148
|
+
export interface ServerAbandonCandidateRequest {
|
|
149
|
+
account_id: string;
|
|
150
|
+
nonce: number;
|
|
151
|
+
}
|
|
152
|
+
export interface ServerAbandonCandidateResponse {
|
|
153
|
+
account_id: string;
|
|
154
|
+
nonce: number;
|
|
155
|
+
state: 'pending' | 'abandoned';
|
|
156
|
+
abandon_requested_at?: string;
|
|
157
|
+
}
|
|
145
158
|
export interface ServerSignProposalRequest {
|
|
146
159
|
account_id: string;
|
|
147
160
|
commitment: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-types.d.ts","sourceRoot":"","sources":["../src/server-types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,GAAG,oBAAoB,CAAC;AAEnF,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,uBAAuB,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,uBAAuB,EAAE,CAAA;CAAE,GACvG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"server-types.d.ts","sourceRoot":"","sources":["../src/server-types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export type DeltaStatus = {
|
|
|
57
57
|
} | {
|
|
58
58
|
status: 'discarded';
|
|
59
59
|
timestamp: string;
|
|
60
|
+
reason?: string;
|
|
60
61
|
};
|
|
61
62
|
export type ProposalType = 'add_signer' | 'remove_signer' | 'change_threshold' | 'update_procedure_threshold' | 'switch_guardian' | 'consume_notes' | 'p2id' | 'custom' | (string & {});
|
|
62
63
|
export interface ProposalMetadata {
|
|
@@ -77,6 +78,8 @@ export interface ProposalMetadata {
|
|
|
77
78
|
recipientId?: string;
|
|
78
79
|
faucetId?: string;
|
|
79
80
|
amount?: string;
|
|
81
|
+
/** P2ID note visibility, "public" or "private" (issue #322). Absent => public. */
|
|
82
|
+
noteType?: string;
|
|
80
83
|
}
|
|
81
84
|
export interface DeltaObject {
|
|
82
85
|
accountId: string;
|
|
@@ -171,6 +174,24 @@ export interface SignProposalRequest {
|
|
|
171
174
|
commitment: string;
|
|
172
175
|
signature: ProposalSignature;
|
|
173
176
|
}
|
|
177
|
+
export interface AbandonCandidateResponse {
|
|
178
|
+
accountId: string;
|
|
179
|
+
nonce: number;
|
|
180
|
+
/**
|
|
181
|
+
* `'pending'` while the guardian's worker still has to resolve the
|
|
182
|
+
* abandon intent (the account stays locked until then); `'abandoned'`
|
|
183
|
+
* once the delta is discarded as client-abandoned and the account
|
|
184
|
+
* released.
|
|
185
|
+
*/
|
|
186
|
+
state: 'pending' | 'abandoned';
|
|
187
|
+
/**
|
|
188
|
+
* RFC 3339 UTC timestamp of the recorded abandon request. Retries
|
|
189
|
+
* return the original timestamp; absent once resolved.
|
|
190
|
+
*/
|
|
191
|
+
abandonRequestedAt?: string;
|
|
192
|
+
}
|
|
193
|
+
/** Resolution of an abandon request, as observed via the delta feed. */
|
|
194
|
+
export type AbandonStatus = 'waiting' | 'landed' | 'abandoned' | 'unexpected';
|
|
174
195
|
export interface PushDeltaResponse {
|
|
175
196
|
accountId: string;
|
|
176
197
|
nonce: number;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC3F,WAAW,CAAC,CACV,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,kBAAkB,GACjC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC5B,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAEhE;;;;;;;;;OASG;IACH,iBAAiB,CAAC,CAChB,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,QAAQ,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG,cAAc,CAAC;AAEjE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEjD,MAAM,MAAM,UAAU,GAClB;IACE,cAAc,EAAE;QACd,oBAAoB,EAAE,MAAM,EAAE,CAAC;KAChC,CAAC;CACH,GACD;IACE,UAAU,EAAE;QACV,oBAAoB,EAAE,MAAM,EAAE,CAAC;KAChC,CAAC;CACH,CAAC;AAEN,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,iBAAiB,EAAE,CAAA;CAAE,GAC/F;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/conversion.test.ts
CHANGED
|
@@ -416,5 +416,21 @@ describe('conversion', () => {
|
|
|
416
416
|
expect(result.amount).toBe(original.amount);
|
|
417
417
|
expect(result.salt).toBe(original.salt);
|
|
418
418
|
});
|
|
419
|
+
|
|
420
|
+
it('p2id noteType survives roundtrip as note_type on the wire (issue #322)', () => {
|
|
421
|
+
const original: ProposalMetadata = {
|
|
422
|
+
proposalType: 'p2id',
|
|
423
|
+
recipientId: '0xrecipient',
|
|
424
|
+
faucetId: '0xfaucet',
|
|
425
|
+
amount: '1000',
|
|
426
|
+
noteType: 'private',
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const server = toServerProposalMetadata(original);
|
|
430
|
+
expect(server.note_type).toBe('private');
|
|
431
|
+
|
|
432
|
+
const result = fromServerProposalMetadata(server);
|
|
433
|
+
expect(result.noteType).toBe('private');
|
|
434
|
+
});
|
|
419
435
|
});
|
|
420
436
|
});
|
package/src/conversion.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function fromServerDeltaStatus(server: ServerDeltaStatus): DeltaStatus {
|
|
|
85
85
|
case 'canonical':
|
|
86
86
|
return { status: 'canonical', timestamp: server.timestamp };
|
|
87
87
|
case 'discarded':
|
|
88
|
-
return { status: 'discarded', timestamp: server.timestamp };
|
|
88
|
+
return { status: 'discarded', timestamp: server.timestamp, reason: server.reason };
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
@@ -106,6 +106,7 @@ export function fromServerProposalMetadata(server: ServerProposalMetadata): Prop
|
|
|
106
106
|
recipientId: server.recipient_id,
|
|
107
107
|
faucetId: server.faucet_id,
|
|
108
108
|
amount: server.amount,
|
|
109
|
+
noteType: server.note_type,
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -194,7 +195,7 @@ export function toServerDeltaStatus(status: DeltaStatus): ServerDeltaStatus {
|
|
|
194
195
|
case 'canonical':
|
|
195
196
|
return { status: 'canonical', timestamp: status.timestamp };
|
|
196
197
|
case 'discarded':
|
|
197
|
-
return { status: 'discarded', timestamp: status.timestamp };
|
|
198
|
+
return { status: 'discarded', timestamp: status.timestamp, reason: status.reason };
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
201
|
|
|
@@ -215,6 +216,7 @@ export function toServerProposalMetadata(meta: ProposalMetadata): ServerProposal
|
|
|
215
216
|
recipient_id: meta.recipientId,
|
|
216
217
|
faucet_id: meta.faucetId,
|
|
217
218
|
amount: meta.amount,
|
|
219
|
+
note_type: meta.noteType,
|
|
218
220
|
};
|
|
219
221
|
}
|
|
220
222
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
GUARDIAN_ERROR_CODES,
|
|
7
|
+
isGuardianErrorCode,
|
|
8
|
+
normalizeGuardianErrorCode,
|
|
9
|
+
} from './error-codes.js';
|
|
10
|
+
|
|
11
|
+
describe('normalizeGuardianErrorCode', () => {
|
|
12
|
+
it('maps the SCREAMING_SNAKE wire forms to their snake_case union members', () => {
|
|
13
|
+
expect(normalizeGuardianErrorCode('GUARDIAN_ACCOUNT_PAUSED')).toBe('account_paused');
|
|
14
|
+
expect(normalizeGuardianErrorCode('GUARDIAN_ACCOUNT_RELEASED')).toBe('account_released');
|
|
15
|
+
expect(normalizeGuardianErrorCode('GUARDIAN_INSUFFICIENT_OPERATOR_PERMISSION')).toBe(
|
|
16
|
+
'insufficient_operator_permission'
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes recognized snake_case codes through', () => {
|
|
21
|
+
expect(normalizeGuardianErrorCode('commitment_mismatch')).toBe('commitment_mismatch');
|
|
22
|
+
expect(normalizeGuardianErrorCode('rate_limit_exceeded')).toBe('rate_limit_exceeded');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns null for unknown codes instead of widening to string', () => {
|
|
26
|
+
expect(normalizeGuardianErrorCode('some_future_code')).toBeNull();
|
|
27
|
+
expect(normalizeGuardianErrorCode('')).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('isGuardianErrorCode', () => {
|
|
32
|
+
it('narrows members and rejects non-members', () => {
|
|
33
|
+
expect(isGuardianErrorCode('account_paused')).toBe(true);
|
|
34
|
+
expect(isGuardianErrorCode('GUARDIAN_ACCOUNT_PAUSED')).toBe(false);
|
|
35
|
+
expect(isGuardianErrorCode('acount_paused')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('drift guard against GuardianError::code()', () => {
|
|
40
|
+
it('the TS union matches the server vocabulary exactly', () => {
|
|
41
|
+
// Source of truth: the match arms of `pub fn code(&self)` in the server's
|
|
42
|
+
// error module. The OpenAPI spec is not usable here — it declares `code`
|
|
43
|
+
// as a bare string with no enum (see issue #318).
|
|
44
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
const errorRs = readFileSync(
|
|
46
|
+
join(here, '../../../crates/server/src/error.rs'),
|
|
47
|
+
'utf8'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const codeFnStart = errorRs.indexOf('pub fn code(&self)');
|
|
51
|
+
expect(codeFnStart).toBeGreaterThan(-1);
|
|
52
|
+
// The function body ends at the first line containing only ` }` after
|
|
53
|
+
// the match block; slicing to the next `pub fn` is a robust over-bound.
|
|
54
|
+
const nextFn = errorRs.indexOf('pub fn', codeFnStart + 1);
|
|
55
|
+
const codeFn = errorRs.slice(codeFnStart, nextFn === -1 ? undefined : nextFn);
|
|
56
|
+
|
|
57
|
+
const serverWireCodes = [...codeFn.matchAll(/"([A-Za-z_]+)"/g)].map((m) => m[1]);
|
|
58
|
+
expect(serverWireCodes.length).toBeGreaterThan(0);
|
|
59
|
+
|
|
60
|
+
const normalizedServerCodes = new Set(
|
|
61
|
+
serverWireCodes.map((wire) => normalizeGuardianErrorCode(wire) ?? `UNKNOWN:${wire}`)
|
|
62
|
+
);
|
|
63
|
+
const unionCodes = new Set<string>(GUARDIAN_ERROR_CODES);
|
|
64
|
+
|
|
65
|
+
// Every server code must normalize into the union (no UNKNOWN: entries),
|
|
66
|
+
// and the union must not carry stale members the server no longer emits.
|
|
67
|
+
expect([...normalizedServerCodes].sort()).toEqual([...unionCodes].sort());
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed vocabulary of Guardian's stable, machine-readable error codes
|
|
3
|
+
* (issue #318). Mirrors `GuardianError::code()` in
|
|
4
|
+
* `crates/server/src/error.rs` — a drift-guard test asserts the two stay in
|
|
5
|
+
* sync. The four codes the server emits in SCREAMING_SNAKE wire form
|
|
6
|
+
* (`GUARDIAN_ACCOUNT_PAUSED`, `GUARDIAN_ACCOUNT_RELEASED`,
|
|
7
|
+
* `GUARDIAN_INSUFFICIENT_OPERATOR_PERMISSION`, `GUARDIAN_CANDIDATE_LANDED`)
|
|
8
|
+
* are normalized to snake_case here so the union has one consistent shape; use
|
|
9
|
+
* {@link normalizeGuardianErrorCode} at the wire boundary.
|
|
10
|
+
*/
|
|
11
|
+
export const GUARDIAN_ERROR_CODES = [
|
|
12
|
+
'account_already_exists',
|
|
13
|
+
'account_data_unavailable',
|
|
14
|
+
'account_not_found',
|
|
15
|
+
'account_paused',
|
|
16
|
+
'account_released',
|
|
17
|
+
'authentication_failed',
|
|
18
|
+
'authorization_failed',
|
|
19
|
+
'candidate_landed',
|
|
20
|
+
'commitment_mismatch',
|
|
21
|
+
'configuration_error',
|
|
22
|
+
'conflict_pending_delta',
|
|
23
|
+
'conflict_pending_proposal',
|
|
24
|
+
'data_unavailable',
|
|
25
|
+
'delta_not_found',
|
|
26
|
+
'insufficient_operator_permission',
|
|
27
|
+
'insufficient_signatures',
|
|
28
|
+
'invalid_account_id',
|
|
29
|
+
'invalid_commitment',
|
|
30
|
+
'invalid_cursor',
|
|
31
|
+
'invalid_delta',
|
|
32
|
+
'invalid_evm_proposal',
|
|
33
|
+
'invalid_input',
|
|
34
|
+
'invalid_limit',
|
|
35
|
+
'invalid_network_config',
|
|
36
|
+
'invalid_proposal_signature',
|
|
37
|
+
'invalid_status_filter',
|
|
38
|
+
'network_error',
|
|
39
|
+
'pending_proposals_limit',
|
|
40
|
+
'proposal_already_signed',
|
|
41
|
+
'proposal_not_found',
|
|
42
|
+
'rate_limit_exceeded',
|
|
43
|
+
'rpc_unavailable',
|
|
44
|
+
'rpc_validation_failed',
|
|
45
|
+
'signer_not_authorized',
|
|
46
|
+
'signing_error',
|
|
47
|
+
'state_not_found',
|
|
48
|
+
'storage_error',
|
|
49
|
+
'unsupported_evm_chain',
|
|
50
|
+
'unsupported_for_network',
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A stable Guardian error code, compiler-checked: comparing against a
|
|
55
|
+
* non-member literal is a type error, and `switch` statements can be
|
|
56
|
+
* exhaustiveness-checked. Branch and localize on this — never on message
|
|
57
|
+
* text or the HTTP status alone.
|
|
58
|
+
*/
|
|
59
|
+
export type GuardianErrorCode = (typeof GUARDIAN_ERROR_CODES)[number];
|
|
60
|
+
|
|
61
|
+
const CODE_SET: ReadonlySet<string> = new Set(GUARDIAN_ERROR_CODES);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wire form → union form for the codes the server emits in SCREAMING_SNAKE.
|
|
65
|
+
* Every other code is already snake_case on the wire.
|
|
66
|
+
*/
|
|
67
|
+
const WIRE_ALIASES: Readonly<Record<string, GuardianErrorCode>> = {
|
|
68
|
+
GUARDIAN_ACCOUNT_PAUSED: 'account_paused',
|
|
69
|
+
GUARDIAN_ACCOUNT_RELEASED: 'account_released',
|
|
70
|
+
GUARDIAN_INSUFFICIENT_OPERATOR_PERMISSION: 'insufficient_operator_permission',
|
|
71
|
+
GUARDIAN_CANDIDATE_LANDED: 'candidate_landed',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Type guard narrowing an arbitrary string to {@link GuardianErrorCode}. */
|
|
75
|
+
export function isGuardianErrorCode(value: string): value is GuardianErrorCode {
|
|
76
|
+
return CODE_SET.has(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Normalize a server-emitted wire code to the typed union: SCREAMING_SNAKE
|
|
81
|
+
* aliases map to their snake_case form; recognized snake_case codes pass
|
|
82
|
+
* through. Returns `null` for codes outside the known vocabulary (e.g. a
|
|
83
|
+
* newer server) — callers keep the verbatim wire string separately (see
|
|
84
|
+
* `GuardianHttpError.rawCode`) rather than letting an unknown code silently
|
|
85
|
+
* satisfy a string branch.
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeGuardianErrorCode(raw: string): GuardianErrorCode | null {
|
|
88
|
+
const aliased = WIRE_ALIASES[raw];
|
|
89
|
+
if (aliased !== undefined) return aliased;
|
|
90
|
+
return isGuardianErrorCode(raw) ? raw : null;
|
|
91
|
+
}
|
package/src/http.test.ts
CHANGED
|
@@ -81,18 +81,17 @@ describe('GuardianHttpClient', () => {
|
|
|
81
81
|
statusText: 'Conflict',
|
|
82
82
|
text: async () =>
|
|
83
83
|
JSON.stringify({
|
|
84
|
-
success: false,
|
|
85
84
|
code: 'GUARDIAN_ACCOUNT_RELEASED',
|
|
86
|
-
|
|
87
|
-
retryable: false,
|
|
88
|
-
released_at: '2026-07-06T10:00:00Z',
|
|
85
|
+
message: 'This account has moved to a different guardian. Reconnect it to continue.',
|
|
86
|
+
meta: { retryable: false, released_at: '2026-07-06T10:00:00Z' },
|
|
89
87
|
}),
|
|
90
88
|
});
|
|
91
89
|
|
|
92
90
|
const error = await client.getPubkey().catch((e) => e);
|
|
93
91
|
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
94
92
|
expect(error.status).toBe(409);
|
|
95
|
-
expect(error.code).toBe('
|
|
93
|
+
expect(error.code).toBe('account_released');
|
|
94
|
+
expect(error.rawCode).toBe('GUARDIAN_ACCOUNT_RELEASED');
|
|
96
95
|
expect(error.releasedAt).toBe('2026-07-06T10:00:00Z');
|
|
97
96
|
});
|
|
98
97
|
|
|
@@ -103,9 +102,9 @@ describe('GuardianHttpClient', () => {
|
|
|
103
102
|
statusText: 'Not Found',
|
|
104
103
|
text: async () =>
|
|
105
104
|
JSON.stringify({
|
|
106
|
-
success: false,
|
|
107
105
|
code: 'account_not_found',
|
|
108
|
-
|
|
106
|
+
message: "We couldn't find that. It may have been completed or removed.",
|
|
107
|
+
meta: { retryable: false },
|
|
109
108
|
}),
|
|
110
109
|
});
|
|
111
110
|
|
|
@@ -457,6 +456,157 @@ describe('GuardianHttpClient', () => {
|
|
|
457
456
|
});
|
|
458
457
|
});
|
|
459
458
|
|
|
459
|
+
describe('abandonCandidate', () => {
|
|
460
|
+
it('should record an abandon intent and map the response to camelCase', async () => {
|
|
461
|
+
client.setSigner(mockSigner);
|
|
462
|
+
|
|
463
|
+
mockFetch.mockResolvedValueOnce({
|
|
464
|
+
ok: true,
|
|
465
|
+
json: async () => ({
|
|
466
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
467
|
+
nonce: 7,
|
|
468
|
+
state: 'pending',
|
|
469
|
+
abandon_requested_at: '2026-07-14T12:00:00Z',
|
|
470
|
+
}),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const result = await client.abandonCandidate('0x' + 'a'.repeat(30), 7);
|
|
474
|
+
|
|
475
|
+
expect(result).toEqual({
|
|
476
|
+
accountId: '0x' + 'a'.repeat(30),
|
|
477
|
+
nonce: 7,
|
|
478
|
+
state: 'pending',
|
|
479
|
+
abandonRequestedAt: '2026-07-14T12:00:00Z',
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Wire format is snake_case
|
|
483
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
484
|
+
'http://localhost:3000/delta/candidate/abandon',
|
|
485
|
+
expect.objectContaining({
|
|
486
|
+
method: 'POST',
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
489
|
+
nonce: 7,
|
|
490
|
+
}),
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it('surfaces 409 GUARDIAN_CANDIDATE_LANDED with a parseable error envelope', async () => {
|
|
496
|
+
client.setSigner(mockSigner);
|
|
497
|
+
|
|
498
|
+
mockFetch.mockResolvedValueOnce({
|
|
499
|
+
ok: false,
|
|
500
|
+
status: 409,
|
|
501
|
+
statusText: 'Conflict',
|
|
502
|
+
text: async () =>
|
|
503
|
+
JSON.stringify({
|
|
504
|
+
code: 'GUARDIAN_CANDIDATE_LANDED',
|
|
505
|
+
message: "This transaction already went through, so it can't be abandoned.",
|
|
506
|
+
meta: { retryable: false },
|
|
507
|
+
}),
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const error = await client
|
|
511
|
+
.abandonCandidate('0x' + 'a'.repeat(30), 7)
|
|
512
|
+
.catch((e) => e);
|
|
513
|
+
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
514
|
+
expect(error.status).toBe(409);
|
|
515
|
+
expect(error.code).toBe('candidate_landed');
|
|
516
|
+
expect(error.rawCode).toBe('GUARDIAN_CANDIDATE_LANDED');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('surfaces 404 delta_not_found when no candidate exists at the nonce', async () => {
|
|
520
|
+
client.setSigner(mockSigner);
|
|
521
|
+
|
|
522
|
+
mockFetch.mockResolvedValueOnce({
|
|
523
|
+
ok: false,
|
|
524
|
+
status: 404,
|
|
525
|
+
statusText: 'Not Found',
|
|
526
|
+
text: async () =>
|
|
527
|
+
JSON.stringify({
|
|
528
|
+
code: 'delta_not_found',
|
|
529
|
+
message: "We couldn't find that. It may have been completed or removed.",
|
|
530
|
+
meta: { retryable: false },
|
|
531
|
+
}),
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
const error = await client
|
|
535
|
+
.abandonCandidate('0x' + 'a'.repeat(30), 7)
|
|
536
|
+
.catch((e) => e);
|
|
537
|
+
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
538
|
+
expect(error.status).toBe(404);
|
|
539
|
+
expect(error.code).toBe('delta_not_found');
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
describe('abandonStatus', () => {
|
|
544
|
+
const serverDelta = (status: object) => ({
|
|
545
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
546
|
+
nonce: 7,
|
|
547
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
548
|
+
delta_payload: { tx_summary: { data: 'base64summary' }, signatures: [] },
|
|
549
|
+
status,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('classifies a still-pending candidate as waiting', async () => {
|
|
553
|
+
client.setSigner(mockSigner);
|
|
554
|
+
mockFetch.mockResolvedValueOnce({
|
|
555
|
+
ok: true,
|
|
556
|
+
json: async () =>
|
|
557
|
+
serverDelta({ status: 'candidate', timestamp: '2026-07-14T12:00:00Z' }),
|
|
558
|
+
});
|
|
559
|
+
expect(await client.abandonStatus('0x' + 'a'.repeat(30), 7)).toBe('waiting');
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('classifies a canonicalized delta as landed', async () => {
|
|
563
|
+
client.setSigner(mockSigner);
|
|
564
|
+
mockFetch.mockResolvedValueOnce({
|
|
565
|
+
ok: true,
|
|
566
|
+
json: async () =>
|
|
567
|
+
serverDelta({ status: 'canonical', timestamp: '2026-07-14T12:00:00Z' }),
|
|
568
|
+
});
|
|
569
|
+
expect(await client.abandonStatus('0x' + 'a'.repeat(30), 7)).toBe('landed');
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('classifies a client-abandoned discard as abandoned', async () => {
|
|
573
|
+
client.setSigner(mockSigner);
|
|
574
|
+
mockFetch.mockResolvedValueOnce({
|
|
575
|
+
ok: true,
|
|
576
|
+
json: async () =>
|
|
577
|
+
serverDelta({
|
|
578
|
+
status: 'discarded',
|
|
579
|
+
timestamp: '2026-07-14T12:00:00Z',
|
|
580
|
+
reason: 'client_abandoned',
|
|
581
|
+
}),
|
|
582
|
+
});
|
|
583
|
+
expect(await client.abandonStatus('0x' + 'a'.repeat(30), 7)).toBe('abandoned');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('classifies a reasonless discard and a missing delta as unexpected', async () => {
|
|
587
|
+
client.setSigner(mockSigner);
|
|
588
|
+
mockFetch.mockResolvedValueOnce({
|
|
589
|
+
ok: true,
|
|
590
|
+
json: async () =>
|
|
591
|
+
serverDelta({ status: 'discarded', timestamp: '2026-07-14T12:00:00Z' }),
|
|
592
|
+
});
|
|
593
|
+
expect(await client.abandonStatus('0x' + 'a'.repeat(30), 7)).toBe('unexpected');
|
|
594
|
+
|
|
595
|
+
mockFetch.mockResolvedValueOnce({
|
|
596
|
+
ok: false,
|
|
597
|
+
status: 404,
|
|
598
|
+
statusText: 'Not Found',
|
|
599
|
+
text: async () =>
|
|
600
|
+
JSON.stringify({
|
|
601
|
+
code: 'delta_not_found',
|
|
602
|
+
message: "We couldn't find that. It may have been completed or removed.",
|
|
603
|
+
meta: { retryable: false },
|
|
604
|
+
}),
|
|
605
|
+
});
|
|
606
|
+
expect(await client.abandonStatus('0x' + 'a'.repeat(30), 7)).toBe('unexpected');
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
460
610
|
describe('signDeltaProposal', () => {
|
|
461
611
|
it('should sign a delta proposal', async () => {
|
|
462
612
|
client.setSigner(mockSigner);
|
|
@@ -825,6 +975,27 @@ describe('GuardianHttpError', () => {
|
|
|
825
975
|
expect(error.name).toBe('GuardianHttpError');
|
|
826
976
|
});
|
|
827
977
|
|
|
978
|
+
it('parses a { code, message, meta } body into structured accessors (feature 009)', () => {
|
|
979
|
+
const body = JSON.stringify({
|
|
980
|
+
code: 'rate_limit_exceeded',
|
|
981
|
+
message: 'Too many requests — please try again shortly.',
|
|
982
|
+
meta: { retryable: true, retry_after_secs: 30 },
|
|
983
|
+
});
|
|
984
|
+
const error = new GuardianHttpError(429, 'Too Many Requests', body);
|
|
985
|
+
expect(error.code).toBe('rate_limit_exceeded');
|
|
986
|
+
expect(error.userMessage).toBe('Too many requests — please try again shortly.');
|
|
987
|
+
expect(error.meta?.retryable).toBe(true);
|
|
988
|
+
expect(error.meta?.retryAfterSecs).toBe(30); // snake_case → camelCase
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it('leaves accessors undefined for a non-JSON / non-conforming body', () => {
|
|
992
|
+
const plain = new GuardianHttpError(502, 'Bad Gateway', 'upstream exploded');
|
|
993
|
+
expect(plain.code).toBeNull();
|
|
994
|
+
expect(plain.releasedAt).toBeNull();
|
|
995
|
+
expect(plain.userMessage).toBeUndefined();
|
|
996
|
+
expect(plain.meta).toBeUndefined();
|
|
997
|
+
});
|
|
998
|
+
|
|
828
999
|
describe('error envelope contract (account-paused path)', () => {
|
|
829
1000
|
let client: GuardianHttpClient;
|
|
830
1001
|
beforeEach(() => {
|
|
@@ -835,15 +1006,18 @@ describe('GuardianHttpError', () => {
|
|
|
835
1006
|
it('surfaces 409 GUARDIAN_ACCOUNT_PAUSED with a parseable error envelope on pushDeltaProposal', async () => {
|
|
836
1007
|
client.setSigner(mockSigner);
|
|
837
1008
|
|
|
838
|
-
// The server's GuardianError::AccountPaused → IntoResponse contract
|
|
839
|
-
//
|
|
1009
|
+
// The server's GuardianError::AccountPaused → IntoResponse contract,
|
|
1010
|
+
// reshaped to { code, message, meta } (feature 009). Locks client/server
|
|
1011
|
+
// in lockstep: a regression to the legacy { success, error, paused_* } or
|
|
840
1012
|
// "(400, {delta: {account_id: 'error text'}})" shape would break this.
|
|
841
1013
|
const envelope = {
|
|
842
|
-
success: false,
|
|
843
1014
|
code: 'GUARDIAN_ACCOUNT_PAUSED',
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1015
|
+
message: "This account is paused and can't approve transactions right now.",
|
|
1016
|
+
meta: {
|
|
1017
|
+
retryable: false,
|
|
1018
|
+
paused_at: '2026-05-20T10:00:00Z',
|
|
1019
|
+
paused_reason: 'compliance review',
|
|
1020
|
+
},
|
|
847
1021
|
};
|
|
848
1022
|
|
|
849
1023
|
mockFetch.mockResolvedValueOnce({
|
|
@@ -864,26 +1038,37 @@ describe('GuardianHttpError', () => {
|
|
|
864
1038
|
.catch((e) => e as GuardianHttpError);
|
|
865
1039
|
|
|
866
1040
|
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
expect(
|
|
872
|
-
expect(
|
|
873
|
-
expect(
|
|
874
|
-
expect(
|
|
875
|
-
|
|
1041
|
+
const e = error as GuardianHttpError;
|
|
1042
|
+
expect(e.status).toBe(409);
|
|
1043
|
+
|
|
1044
|
+
// Structured accessors parsed from { code, message, meta } (feature 009).
|
|
1045
|
+
expect(e.code).toBe('account_paused');
|
|
1046
|
+
expect(e.rawCode).toBe('GUARDIAN_ACCOUNT_PAUSED');
|
|
1047
|
+
expect(typeof e.userMessage).toBe('string');
|
|
1048
|
+
expect(e.userMessage).not.toContain('compliance review'); // sanitized
|
|
1049
|
+
expect(e.meta?.retryable).toBe(false);
|
|
1050
|
+
expect(e.meta?.pausedAt).toBe('2026-05-20T10:00:00Z');
|
|
1051
|
+
expect(e.meta?.pausedReason).toBe('compliance review');
|
|
1052
|
+
|
|
1053
|
+
const parsed = JSON.parse(e.body);
|
|
1054
|
+
// Legacy fields gone; not a domain object.
|
|
1055
|
+
expect(parsed.success).toBeUndefined();
|
|
1056
|
+
expect(parsed.error).toBeUndefined();
|
|
876
1057
|
expect(parsed.delta).toBeUndefined();
|
|
877
1058
|
});
|
|
878
1059
|
|
|
879
1060
|
it('surfaces 401 AUTHENTICATION_FAILED with a parseable error envelope', async () => {
|
|
880
1061
|
client.setSigner(mockSigner);
|
|
881
1062
|
const envelope = {
|
|
882
|
-
success: false,
|
|
883
1063
|
code: 'authentication_failed',
|
|
884
|
-
|
|
1064
|
+
message: 'Your session has expired. Please sign in again.',
|
|
1065
|
+
meta: { retryable: false },
|
|
885
1066
|
};
|
|
886
|
-
|
|
1067
|
+
// `authentication_failed` triggers the replay-retry path (the specific
|
|
1068
|
+
// "Replay attack" detail is sanitized off the wire in feature 009, so we
|
|
1069
|
+
// retry on the auth code). Use a persistent mock so the retries resolve
|
|
1070
|
+
// and the final attempt throws the typed error.
|
|
1071
|
+
mockFetch.mockResolvedValue({
|
|
887
1072
|
ok: false,
|
|
888
1073
|
status: 401,
|
|
889
1074
|
statusText: 'Unauthorized',
|
|
@@ -899,10 +1084,12 @@ describe('GuardianHttpError', () => {
|
|
|
899
1084
|
.catch((e) => e as GuardianHttpError);
|
|
900
1085
|
|
|
901
1086
|
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
expect(
|
|
905
|
-
expect(
|
|
1087
|
+
const e = error as GuardianHttpError;
|
|
1088
|
+
expect(e.status).toBe(401);
|
|
1089
|
+
expect(e.code).toBe('authentication_failed');
|
|
1090
|
+
expect(typeof e.userMessage).toBe('string');
|
|
1091
|
+
const parsed = JSON.parse(e.body);
|
|
1092
|
+
expect(parsed.success).toBeUndefined();
|
|
906
1093
|
expect(parsed.delta).toBeUndefined();
|
|
907
1094
|
});
|
|
908
1095
|
});
|