@openzeppelin/guardian-client 0.16.1 → 0.17.0-rc.1
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 +84 -0
- package/dist/conversion.d.ts +4 -2
- package/dist/conversion.d.ts.map +1 -1
- package/dist/conversion.js +37 -0
- package/dist/conversion.js.map +1 -1
- package/dist/error-codes.d.ts +1 -1
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +1 -0
- package/dist/error-codes.js.map +1 -1
- package/dist/http.d.ts +25 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +66 -9
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/server-types.d.ts +36 -0
- package/dist/server-types.d.ts.map +1 -1
- package/dist/types.d.ts +82 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/conversion.test.ts +34 -0
- package/src/conversion.ts +46 -0
- package/src/error-codes.ts +1 -0
- package/src/http.test.ts +312 -6
- package/src/http.ts +78 -8
- package/src/index.ts +10 -0
- package/src/server-types.ts +43 -0
- package/src/types.ts +98 -0
package/dist/types.d.ts
CHANGED
|
@@ -89,6 +89,17 @@ export interface ProposalMetadata {
|
|
|
89
89
|
amount?: string;
|
|
90
90
|
/** P2ID note visibility, "public" or "private" (issue #322). Absent => public. */
|
|
91
91
|
noteType?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Base64-serialized Miden `ChainAnchor` pinning the reference block the
|
|
94
|
+
* proposal's transaction summary was built at. Since protocol 0.16 the
|
|
95
|
+
* signed summary binds the reference block commitment, so cosigners and the
|
|
96
|
+
* executor need this anchor to reproduce the summary the proposer signed.
|
|
97
|
+
*/
|
|
98
|
+
chainAnchor?: string;
|
|
99
|
+
/** P2IDE reclaim block height (issue #366). Presence of either height means a P2IDE note. */
|
|
100
|
+
reclaimHeight?: number;
|
|
101
|
+
/** P2IDE timelock block height (issue #366). */
|
|
102
|
+
timelockHeight?: number;
|
|
92
103
|
}
|
|
93
104
|
export interface DeltaObject {
|
|
94
105
|
accountId: string;
|
|
@@ -229,4 +240,75 @@ export interface LookupAccount {
|
|
|
229
240
|
export interface LookupResponse {
|
|
230
241
|
accounts: LookupAccount[];
|
|
231
242
|
}
|
|
243
|
+
/** Note classification decoded from the on-chain note script. */
|
|
244
|
+
export type HistoryNoteTag = 'p2id' | 'p2ide' | 'pswap' | 'mint' | 'burn' | 'custom';
|
|
245
|
+
/** On-chain note visibility from the note metadata. */
|
|
246
|
+
export type HistoryNoteVisibility = 'public' | 'private';
|
|
247
|
+
/** Which section of the persisted payload failed to decode. */
|
|
248
|
+
export type HistoryDecodeSection = 'tx_summary' | 'metadata' | 'input_notes' | 'output_notes' | 'vault' | 'storage';
|
|
249
|
+
/**
|
|
250
|
+
* Delta lifecycle status of a history entry. Only `canonical` is
|
|
251
|
+
* emitted today; the set widens if the feed gains a status filter.
|
|
252
|
+
*/
|
|
253
|
+
export type HistoryEntryStatus = 'canonical';
|
|
254
|
+
/**
|
|
255
|
+
* One decoded asset inside a history note. `amount` is a base-10
|
|
256
|
+
* string for fungible assets, absent for non-fungible ones.
|
|
257
|
+
*/
|
|
258
|
+
export interface HistoryNoteAsset {
|
|
259
|
+
assetId: string;
|
|
260
|
+
kind: 'fungible' | 'non_fungible';
|
|
261
|
+
amount?: string;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* One decoded note attached to a history entry. `sender` / `recipient`
|
|
265
|
+
* are account IDs when the note script exposes them.
|
|
266
|
+
*/
|
|
267
|
+
export interface HistoryNote {
|
|
268
|
+
noteId: string;
|
|
269
|
+
tag: HistoryNoteTag;
|
|
270
|
+
/** On-chain visibility from the note metadata. */
|
|
271
|
+
noteType: HistoryNoteVisibility;
|
|
272
|
+
assets: HistoryNoteAsset[];
|
|
273
|
+
sender?: string;
|
|
274
|
+
recipient?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Why a history entry's note sections are empty: the persisted payload
|
|
278
|
+
* could not be decoded server-side (schema drift). The entry itself is
|
|
279
|
+
* still returned.
|
|
280
|
+
*/
|
|
281
|
+
export interface HistoryDecodeWarning {
|
|
282
|
+
section: HistoryDecodeSection;
|
|
283
|
+
reason: string;
|
|
284
|
+
}
|
|
285
|
+
/** One canonical transaction in an account's history (issue #413). */
|
|
286
|
+
export interface HistoryEntry {
|
|
287
|
+
nonce: number;
|
|
288
|
+
/** Delta lifecycle status; always `canonical` today. */
|
|
289
|
+
status: HistoryEntryStatus;
|
|
290
|
+
/** RFC 3339 UTC timestamp at which the delta became canonical. */
|
|
291
|
+
timestamp: string;
|
|
292
|
+
/**
|
|
293
|
+
* Account commitment after this transaction; `undefined` when the
|
|
294
|
+
* stored row predates commitment recording.
|
|
295
|
+
*/
|
|
296
|
+
newCommitment?: string;
|
|
297
|
+
inputNotes: HistoryNote[];
|
|
298
|
+
outputNotes: HistoryNote[];
|
|
299
|
+
decodeWarnings: HistoryDecodeWarning[];
|
|
300
|
+
}
|
|
301
|
+
/** One page of canonical delta history, newest-first by nonce. */
|
|
302
|
+
export interface HistoryPage {
|
|
303
|
+
entries: HistoryEntry[];
|
|
304
|
+
/** Opaque resume token; `undefined` when the feed is exhausted. */
|
|
305
|
+
nextCursor?: string;
|
|
306
|
+
}
|
|
307
|
+
/** Options for `getDeltaHistory`. */
|
|
308
|
+
export interface HistoryOptions {
|
|
309
|
+
/** Page size in `[1, 500]`; server default 50 when omitted. */
|
|
310
|
+
limit?: number;
|
|
311
|
+
/** Opaque `nextCursor` from a previous page. */
|
|
312
|
+
cursor?: string;
|
|
313
|
+
}
|
|
232
314
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
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
|
@@ -454,6 +454,21 @@ describe('conversion', () => {
|
|
|
454
454
|
expect(result.salt).toBe(original.salt);
|
|
455
455
|
});
|
|
456
456
|
|
|
457
|
+
it('chainAnchor survives roundtrip as chain_anchor on the wire', () => {
|
|
458
|
+
const original: ProposalMetadata = {
|
|
459
|
+
proposalType: 'add_signer',
|
|
460
|
+
targetThreshold: 2,
|
|
461
|
+
signerCommitments: ['0xabc'],
|
|
462
|
+
chainAnchor: 'bW9jay1jaGFpbi1hbmNob3I=',
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const server = toServerProposalMetadata(original);
|
|
466
|
+
expect(server.chain_anchor).toBe('bW9jay1jaGFpbi1hbmNob3I=');
|
|
467
|
+
|
|
468
|
+
const result = fromServerProposalMetadata(server);
|
|
469
|
+
expect(result.chainAnchor).toBe('bW9jay1jaGFpbi1hbmNob3I=');
|
|
470
|
+
});
|
|
471
|
+
|
|
457
472
|
it('p2id noteType survives roundtrip as note_type on the wire (issue #322)', () => {
|
|
458
473
|
const original: ProposalMetadata = {
|
|
459
474
|
proposalType: 'p2id',
|
|
@@ -469,5 +484,24 @@ describe('conversion', () => {
|
|
|
469
484
|
const result = fromServerProposalMetadata(server);
|
|
470
485
|
expect(result.noteType).toBe('private');
|
|
471
486
|
});
|
|
487
|
+
|
|
488
|
+
it('p2ide heights survive roundtrip as reclaim_height/timelock_height on the wire (issue #366)', () => {
|
|
489
|
+
const original: ProposalMetadata = {
|
|
490
|
+
proposalType: 'p2id',
|
|
491
|
+
recipientId: '0xrecipient',
|
|
492
|
+
faucetId: '0xfaucet',
|
|
493
|
+
amount: '1000',
|
|
494
|
+
reclaimHeight: 12345,
|
|
495
|
+
timelockHeight: 700,
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const server = toServerProposalMetadata(original);
|
|
499
|
+
expect(server.reclaim_height).toBe(12345);
|
|
500
|
+
expect(server.timelock_height).toBe(700);
|
|
501
|
+
|
|
502
|
+
const result = fromServerProposalMetadata(server);
|
|
503
|
+
expect(result.reclaimHeight).toBe(12345);
|
|
504
|
+
expect(result.timelockHeight).toBe(700);
|
|
505
|
+
});
|
|
472
506
|
});
|
|
473
507
|
});
|
package/src/conversion.ts
CHANGED
|
@@ -6,6 +6,9 @@ import type {
|
|
|
6
6
|
DeltaProposalRequest,
|
|
7
7
|
DeltaStatus,
|
|
8
8
|
ExecutionDelta,
|
|
9
|
+
HistoryEntry,
|
|
10
|
+
HistoryNote,
|
|
11
|
+
HistoryPage,
|
|
9
12
|
LookupResponse,
|
|
10
13
|
ProposalSignature,
|
|
11
14
|
ProposalMetadata,
|
|
@@ -20,6 +23,9 @@ import type {
|
|
|
20
23
|
ServerDeltaProposalRequest,
|
|
21
24
|
ServerDeltaStatus,
|
|
22
25
|
ServerExecutionDelta,
|
|
26
|
+
ServerHistoryEntry,
|
|
27
|
+
ServerHistoryNote,
|
|
28
|
+
ServerHistoryPage,
|
|
23
29
|
ServerLookupResponse,
|
|
24
30
|
ServerProposalSignature,
|
|
25
31
|
ServerProposalMetadata,
|
|
@@ -109,6 +115,9 @@ export function fromServerProposalMetadata(server: ServerProposalMetadata): Prop
|
|
|
109
115
|
faucetId: server.faucet_id,
|
|
110
116
|
amount: server.amount,
|
|
111
117
|
noteType: server.note_type,
|
|
118
|
+
chainAnchor: server.chain_anchor,
|
|
119
|
+
reclaimHeight: server.reclaim_height,
|
|
120
|
+
timelockHeight: server.timelock_height,
|
|
112
121
|
};
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -221,6 +230,9 @@ export function toServerProposalMetadata(meta: ProposalMetadata): ServerProposal
|
|
|
221
230
|
faucet_id: meta.faucetId,
|
|
222
231
|
amount: meta.amount,
|
|
223
232
|
note_type: meta.noteType,
|
|
233
|
+
chain_anchor: meta.chainAnchor,
|
|
234
|
+
reclaim_height: meta.reclaimHeight,
|
|
235
|
+
timelock_height: meta.timelockHeight,
|
|
224
236
|
};
|
|
225
237
|
}
|
|
226
238
|
|
|
@@ -266,3 +278,37 @@ export function toServerExecutionDelta(delta: ExecutionDelta): ServerExecutionDe
|
|
|
266
278
|
status: toServerDeltaStatus(delta.status),
|
|
267
279
|
};
|
|
268
280
|
}
|
|
281
|
+
|
|
282
|
+
function fromServerHistoryNote(server: ServerHistoryNote): HistoryNote {
|
|
283
|
+
return {
|
|
284
|
+
noteId: server.note_id,
|
|
285
|
+
tag: server.tag,
|
|
286
|
+
noteType: server.note_type,
|
|
287
|
+
assets: server.assets.map((asset) => ({
|
|
288
|
+
assetId: asset.asset_id,
|
|
289
|
+
kind: asset.kind,
|
|
290
|
+
amount: asset.amount,
|
|
291
|
+
})),
|
|
292
|
+
sender: server.sender,
|
|
293
|
+
recipient: server.recipient,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function fromServerHistoryEntry(server: ServerHistoryEntry): HistoryEntry {
|
|
298
|
+
return {
|
|
299
|
+
nonce: server.nonce,
|
|
300
|
+
status: server.status,
|
|
301
|
+
timestamp: server.timestamp,
|
|
302
|
+
newCommitment: server.new_commitment ?? undefined,
|
|
303
|
+
inputNotes: server.input_notes.map(fromServerHistoryNote),
|
|
304
|
+
outputNotes: server.output_notes.map(fromServerHistoryNote),
|
|
305
|
+
decodeWarnings: server.decode_warnings ?? [],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function fromServerHistoryPage(server: ServerHistoryPage): HistoryPage {
|
|
310
|
+
return {
|
|
311
|
+
entries: server.items.map(fromServerHistoryEntry),
|
|
312
|
+
nextCursor: server.next_cursor ?? undefined,
|
|
313
|
+
};
|
|
314
|
+
}
|
package/src/error-codes.ts
CHANGED
package/src/http.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
3
|
import { GuardianHttpClient, GuardianHttpError } from './http.js';
|
|
3
4
|
import type { Signer, ConfigureResponse, StateObject, DeltaObject, DeltaProposalResponse } from './types.js';
|
|
@@ -60,6 +61,7 @@ describe('GuardianHttpClient', () => {
|
|
|
60
61
|
it('should throw GuardianHttpError on non-ok response', async () => {
|
|
61
62
|
mockFetch.mockResolvedValueOnce({
|
|
62
63
|
ok: false,
|
|
64
|
+
headers: new Headers(),
|
|
63
65
|
status: 500,
|
|
64
66
|
statusText: 'Internal Server Error',
|
|
65
67
|
text: async () => 'Server error message',
|
|
@@ -77,6 +79,7 @@ describe('GuardianHttpClient', () => {
|
|
|
77
79
|
it('exposes code and released_at from a GUARDIAN_ACCOUNT_RELEASED envelope', async () => {
|
|
78
80
|
mockFetch.mockResolvedValueOnce({
|
|
79
81
|
ok: false,
|
|
82
|
+
headers: new Headers(),
|
|
80
83
|
status: 409,
|
|
81
84
|
statusText: 'Conflict',
|
|
82
85
|
text: async () =>
|
|
@@ -98,6 +101,7 @@ describe('GuardianHttpClient', () => {
|
|
|
98
101
|
it('exposes code without releasedAt for other envelope errors', async () => {
|
|
99
102
|
mockFetch.mockResolvedValueOnce({
|
|
100
103
|
ok: false,
|
|
104
|
+
headers: new Headers(),
|
|
101
105
|
status: 404,
|
|
102
106
|
statusText: 'Not Found',
|
|
103
107
|
text: async () =>
|
|
@@ -152,6 +156,7 @@ describe('GuardianHttpClient', () => {
|
|
|
152
156
|
it('should throw GuardianHttpError on non-ok response', async () => {
|
|
153
157
|
mockFetch.mockResolvedValueOnce({
|
|
154
158
|
ok: false,
|
|
159
|
+
headers: new Headers(),
|
|
155
160
|
status: 503,
|
|
156
161
|
statusText: 'Service Unavailable',
|
|
157
162
|
text: async () => 'down',
|
|
@@ -227,6 +232,52 @@ describe('GuardianHttpClient', () => {
|
|
|
227
232
|
|
|
228
233
|
await expect(client.configure(request)).rejects.toThrow('No signer configured');
|
|
229
234
|
});
|
|
235
|
+
|
|
236
|
+
it('retries a configure replay rejection with fresh authentication', async () => {
|
|
237
|
+
client.setSigner(mockSigner);
|
|
238
|
+
const signRequest = mockSigner.signRequest;
|
|
239
|
+
if (!signRequest) {
|
|
240
|
+
throw new Error('test signer must implement signRequest');
|
|
241
|
+
}
|
|
242
|
+
const signRequestMock = vi.mocked(signRequest);
|
|
243
|
+
signRequestMock.mockImplementation(
|
|
244
|
+
(_accountId, timestamp) => `0x${timestamp.toString(16).padStart(128, '0')}`
|
|
245
|
+
);
|
|
246
|
+
mockFetch
|
|
247
|
+
.mockResolvedValueOnce({
|
|
248
|
+
ok: false,
|
|
249
|
+
headers: new Headers(),
|
|
250
|
+
status: 401,
|
|
251
|
+
statusText: 'Unauthorized',
|
|
252
|
+
text: async () =>
|
|
253
|
+
JSON.stringify({
|
|
254
|
+
code: 'authentication_replay',
|
|
255
|
+
message: 'Guardian received this request out of order. Please try again.',
|
|
256
|
+
meta: { retryable: true },
|
|
257
|
+
}),
|
|
258
|
+
})
|
|
259
|
+
.mockResolvedValueOnce({
|
|
260
|
+
ok: true,
|
|
261
|
+
json: async () => ({ success: true, message: 'Account configured' }),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
await client.configure({
|
|
265
|
+
accountId: '0x' + 'd'.repeat(30),
|
|
266
|
+
auth: {
|
|
267
|
+
MidenFalconRpo: {
|
|
268
|
+
cosigner_commitments: ['0x' + 'e'.repeat(64)],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
initialState: { data: 'base64data', accountId: '0x' + 'd'.repeat(30) },
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
275
|
+
const timestamps = mockFetch.mock.calls.map((call) =>
|
|
276
|
+
Number((call[1].headers as Record<string, string>)['x-timestamp'])
|
|
277
|
+
);
|
|
278
|
+
expect(timestamps[1]).toBeGreaterThan(timestamps[0]);
|
|
279
|
+
expect(signRequestMock).toHaveBeenCalledTimes(2);
|
|
280
|
+
});
|
|
230
281
|
});
|
|
231
282
|
|
|
232
283
|
describe('getState', () => {
|
|
@@ -497,6 +548,7 @@ describe('GuardianHttpClient', () => {
|
|
|
497
548
|
|
|
498
549
|
mockFetch.mockResolvedValueOnce({
|
|
499
550
|
ok: false,
|
|
551
|
+
headers: new Headers(),
|
|
500
552
|
status: 409,
|
|
501
553
|
statusText: 'Conflict',
|
|
502
554
|
text: async () =>
|
|
@@ -521,6 +573,7 @@ describe('GuardianHttpClient', () => {
|
|
|
521
573
|
|
|
522
574
|
mockFetch.mockResolvedValueOnce({
|
|
523
575
|
ok: false,
|
|
576
|
+
headers: new Headers(),
|
|
524
577
|
status: 404,
|
|
525
578
|
statusText: 'Not Found',
|
|
526
579
|
text: async () =>
|
|
@@ -612,6 +665,7 @@ describe('GuardianHttpClient', () => {
|
|
|
612
665
|
|
|
613
666
|
mockFetch.mockResolvedValueOnce({
|
|
614
667
|
ok: false,
|
|
668
|
+
headers: new Headers(),
|
|
615
669
|
status: 404,
|
|
616
670
|
statusText: 'Not Found',
|
|
617
671
|
text: async () =>
|
|
@@ -840,6 +894,136 @@ describe('GuardianHttpClient', () => {
|
|
|
840
894
|
});
|
|
841
895
|
});
|
|
842
896
|
|
|
897
|
+
// --- getDeltaHistory (issue #413) -----
|
|
898
|
+
|
|
899
|
+
describe('getDeltaHistory', () => {
|
|
900
|
+
const accountId = '0x' + 'a'.repeat(30);
|
|
901
|
+
|
|
902
|
+
const serverPage = {
|
|
903
|
+
items: [
|
|
904
|
+
{
|
|
905
|
+
nonce: 5,
|
|
906
|
+
status: 'canonical',
|
|
907
|
+
timestamp: '2026-08-01T12:00:05Z',
|
|
908
|
+
new_commitment: '0x' + 'c'.repeat(64),
|
|
909
|
+
input_notes: [],
|
|
910
|
+
output_notes: [
|
|
911
|
+
{
|
|
912
|
+
note_id: '0x' + 'd'.repeat(64),
|
|
913
|
+
tag: 'p2id',
|
|
914
|
+
note_type: 'public',
|
|
915
|
+
assets: [
|
|
916
|
+
{ asset_id: '0x' + 'e'.repeat(30), kind: 'fungible', amount: '100' },
|
|
917
|
+
],
|
|
918
|
+
sender: accountId,
|
|
919
|
+
recipient: '0x' + 'f'.repeat(30),
|
|
920
|
+
},
|
|
921
|
+
],
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
nonce: 4,
|
|
925
|
+
status: 'canonical',
|
|
926
|
+
timestamp: '2026-08-01T12:00:04Z',
|
|
927
|
+
new_commitment: null,
|
|
928
|
+
input_notes: [],
|
|
929
|
+
output_notes: [],
|
|
930
|
+
decode_warnings: [{ section: 'tx_summary', reason: 'malformed_tx_summary' }],
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
next_cursor: 'opaque-cursor',
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
it('returns a camelCase page and maps notes and warnings', async () => {
|
|
937
|
+
client.setSigner(mockSigner);
|
|
938
|
+
mockFetch.mockResolvedValueOnce({
|
|
939
|
+
ok: true,
|
|
940
|
+
json: async () => serverPage,
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
const result = await client.getDeltaHistory(accountId, { limit: 2 });
|
|
944
|
+
|
|
945
|
+
expect(result.nextCursor).toBe('opaque-cursor');
|
|
946
|
+
expect(result.entries).toHaveLength(2);
|
|
947
|
+
expect(result.entries[0].nonce).toBe(5);
|
|
948
|
+
expect(result.entries[0].newCommitment).toBe('0x' + 'c'.repeat(64));
|
|
949
|
+
expect(result.entries[0].decodeWarnings).toEqual([]);
|
|
950
|
+
expect(result.entries[0].status).toBe('canonical');
|
|
951
|
+
expect(result.entries[0].outputNotes[0]).toEqual({
|
|
952
|
+
noteId: '0x' + 'd'.repeat(64),
|
|
953
|
+
tag: 'p2id',
|
|
954
|
+
noteType: 'public',
|
|
955
|
+
assets: [{ assetId: '0x' + 'e'.repeat(30), kind: 'fungible', amount: '100' }],
|
|
956
|
+
sender: accountId,
|
|
957
|
+
recipient: '0x' + 'f'.repeat(30),
|
|
958
|
+
});
|
|
959
|
+
expect(result.entries[1].newCommitment).toBeUndefined();
|
|
960
|
+
expect(result.entries[1].decodeWarnings).toEqual([
|
|
961
|
+
{ section: 'tx_summary', reason: 'malformed_tx_summary' },
|
|
962
|
+
]);
|
|
963
|
+
|
|
964
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
965
|
+
`http://localhost:3000/delta/history?account_id=${accountId}&limit=2`,
|
|
966
|
+
expect.objectContaining({
|
|
967
|
+
method: 'GET',
|
|
968
|
+
headers: expect.objectContaining({
|
|
969
|
+
'x-pubkey': mockSigner.publicKey,
|
|
970
|
+
}),
|
|
971
|
+
})
|
|
972
|
+
);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it('signs the same key set it sends: omitted options stay omitted', async () => {
|
|
976
|
+
client.setSigner(mockSigner);
|
|
977
|
+
mockFetch.mockResolvedValueOnce({
|
|
978
|
+
ok: true,
|
|
979
|
+
json: async () => ({ items: [], next_cursor: null }),
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
const result = await client.getDeltaHistory(accountId);
|
|
983
|
+
|
|
984
|
+
expect(result.entries).toEqual([]);
|
|
985
|
+
expect(result.nextCursor).toBeUndefined();
|
|
986
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
987
|
+
`http://localhost:3000/delta/history?account_id=${accountId}`,
|
|
988
|
+
expect.anything()
|
|
989
|
+
);
|
|
990
|
+
// The signed payload must byte-match the server's canonical JSON
|
|
991
|
+
// of its query struct: only account_id when limit/cursor are
|
|
992
|
+
// omitted, and limit as a string when present.
|
|
993
|
+
const signRequest = mockSigner.signRequest as ReturnType<typeof vi.fn>;
|
|
994
|
+
const lastCall = signRequest.mock.calls.at(-1);
|
|
995
|
+
if (lastCall === undefined) {
|
|
996
|
+
throw new Error('Expected signRequest to be called');
|
|
997
|
+
}
|
|
998
|
+
const authPayload = lastCall[2];
|
|
999
|
+
expect(authPayload.toCanonicalJson()).toBe(JSON.stringify({ account_id: accountId }));
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
it('passes the cursor through query and signed payload', async () => {
|
|
1003
|
+
client.setSigner(mockSigner);
|
|
1004
|
+
mockFetch.mockResolvedValueOnce({
|
|
1005
|
+
ok: true,
|
|
1006
|
+
json: async () => ({ items: [], next_cursor: null }),
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
await client.getDeltaHistory(accountId, { limit: 10, cursor: 'abc123' });
|
|
1010
|
+
|
|
1011
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
1012
|
+
`http://localhost:3000/delta/history?account_id=${accountId}&limit=10&cursor=abc123`,
|
|
1013
|
+
expect.anything()
|
|
1014
|
+
);
|
|
1015
|
+
const signRequest = mockSigner.signRequest as ReturnType<typeof vi.fn>;
|
|
1016
|
+
const lastCall = signRequest.mock.calls.at(-1);
|
|
1017
|
+
if (lastCall === undefined) {
|
|
1018
|
+
throw new Error('Expected signRequest to be called');
|
|
1019
|
+
}
|
|
1020
|
+
const authPayload = lastCall[2];
|
|
1021
|
+
expect(authPayload.toCanonicalJson()).toBe(
|
|
1022
|
+
JSON.stringify({ account_id: accountId, cursor: 'abc123', limit: '10' })
|
|
1023
|
+
);
|
|
1024
|
+
});
|
|
1025
|
+
});
|
|
1026
|
+
|
|
843
1027
|
// --- lookupAccountByKeyCommitment -----
|
|
844
1028
|
|
|
845
1029
|
describe('lookupAccountByKeyCommitment', () => {
|
|
@@ -952,6 +1136,7 @@ describe('GuardianHttpClient', () => {
|
|
|
952
1136
|
client.setSigner(signer);
|
|
953
1137
|
mockFetch.mockResolvedValueOnce({
|
|
954
1138
|
ok: false,
|
|
1139
|
+
headers: new Headers(),
|
|
955
1140
|
status: 401,
|
|
956
1141
|
statusText: 'Unauthorized',
|
|
957
1142
|
text: async () => '{"code":"authentication_failed","error":"..."}',
|
|
@@ -1014,6 +1199,35 @@ describe('GuardianHttpError', () => {
|
|
|
1014
1199
|
expect(plain.meta).toBeUndefined();
|
|
1015
1200
|
});
|
|
1016
1201
|
|
|
1202
|
+
describe('retry classification (shared fixture)', () => {
|
|
1203
|
+
interface FixtureCase {
|
|
1204
|
+
name: string;
|
|
1205
|
+
body: Record<string, unknown> | null;
|
|
1206
|
+
http: { status: number; retryAfterHeader?: string };
|
|
1207
|
+
expected: { retryable: boolean; retryAfterSecs: number | null };
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const fixture = JSON.parse(
|
|
1211
|
+
readFileSync(
|
|
1212
|
+
new URL('../../../fixtures/guardian-client/rate-limit-policy.json', import.meta.url),
|
|
1213
|
+
'utf-8'
|
|
1214
|
+
)
|
|
1215
|
+
) as { cases: FixtureCase[] };
|
|
1216
|
+
|
|
1217
|
+
it.each(fixture.cases.map((c) => [c.name, c] as const))('%s', (_name, c) => {
|
|
1218
|
+
const body = c.body === null ? 'plain failure' : JSON.stringify(c.body);
|
|
1219
|
+
const error = new GuardianHttpError(
|
|
1220
|
+
c.http.status,
|
|
1221
|
+
'error',
|
|
1222
|
+
body,
|
|
1223
|
+
c.http.retryAfterHeader ?? null
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1226
|
+
expect(error.isRetryable()).toBe(c.expected.retryable);
|
|
1227
|
+
expect(error.retryAfterSecs()).toBe(c.expected.retryAfterSecs ?? undefined);
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1017
1231
|
describe('error envelope contract (account-paused path)', () => {
|
|
1018
1232
|
let client: GuardianHttpClient;
|
|
1019
1233
|
beforeEach(() => {
|
|
@@ -1040,6 +1254,7 @@ describe('GuardianHttpError', () => {
|
|
|
1040
1254
|
|
|
1041
1255
|
mockFetch.mockResolvedValueOnce({
|
|
1042
1256
|
ok: false,
|
|
1257
|
+
headers: new Headers(),
|
|
1043
1258
|
status: 409,
|
|
1044
1259
|
statusText: 'Conflict',
|
|
1045
1260
|
text: async () => JSON.stringify(envelope),
|
|
@@ -1075,19 +1290,16 @@ describe('GuardianHttpError', () => {
|
|
|
1075
1290
|
expect(parsed.delta).toBeUndefined();
|
|
1076
1291
|
});
|
|
1077
1292
|
|
|
1078
|
-
it('
|
|
1293
|
+
it('does not retry a terminal 401 authentication_failed rejection', async () => {
|
|
1079
1294
|
client.setSigner(mockSigner);
|
|
1080
1295
|
const envelope = {
|
|
1081
1296
|
code: 'authentication_failed',
|
|
1082
|
-
message: '
|
|
1297
|
+
message: 'Guardian could not authenticate this request.',
|
|
1083
1298
|
meta: { retryable: false },
|
|
1084
1299
|
};
|
|
1085
|
-
// `authentication_failed` triggers the replay-retry path (the specific
|
|
1086
|
-
// "Replay attack" detail is sanitized off the wire in feature 009, so we
|
|
1087
|
-
// retry on the auth code). Use a persistent mock so the retries resolve
|
|
1088
|
-
// and the final attempt throws the typed error.
|
|
1089
1300
|
mockFetch.mockResolvedValue({
|
|
1090
1301
|
ok: false,
|
|
1302
|
+
headers: new Headers(),
|
|
1091
1303
|
status: 401,
|
|
1092
1304
|
statusText: 'Unauthorized',
|
|
1093
1305
|
text: async () => JSON.stringify(envelope),
|
|
@@ -1106,9 +1318,103 @@ describe('GuardianHttpError', () => {
|
|
|
1106
1318
|
expect(e.status).toBe(401);
|
|
1107
1319
|
expect(e.code).toBe('authentication_failed');
|
|
1108
1320
|
expect(typeof e.userMessage).toBe('string');
|
|
1321
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
1109
1322
|
const parsed = JSON.parse(e.body);
|
|
1110
1323
|
expect(parsed.success).toBeUndefined();
|
|
1111
1324
|
expect(parsed.delta).toBeUndefined();
|
|
1112
1325
|
});
|
|
1326
|
+
|
|
1327
|
+
it('retries an authentication_replay rejection with a fresh timestamp and signature', async () => {
|
|
1328
|
+
client.setSigner(mockSigner);
|
|
1329
|
+
const signRequest = mockSigner.signRequest;
|
|
1330
|
+
if (!signRequest) {
|
|
1331
|
+
throw new Error('test signer must implement signRequest');
|
|
1332
|
+
}
|
|
1333
|
+
const signRequestMock = vi.mocked(signRequest);
|
|
1334
|
+
signRequestMock.mockClear();
|
|
1335
|
+
const signatureForTimestamp = (_accountId: string, timestamp: number) =>
|
|
1336
|
+
`0x${timestamp.toString(16).padStart(128, '0')}`;
|
|
1337
|
+
signRequestMock
|
|
1338
|
+
.mockImplementationOnce(signatureForTimestamp)
|
|
1339
|
+
.mockImplementationOnce(signatureForTimestamp);
|
|
1340
|
+
const replayResponse = {
|
|
1341
|
+
ok: false,
|
|
1342
|
+
headers: new Headers(),
|
|
1343
|
+
status: 401,
|
|
1344
|
+
statusText: 'Unauthorized',
|
|
1345
|
+
text: async () =>
|
|
1346
|
+
JSON.stringify({
|
|
1347
|
+
code: 'authentication_replay',
|
|
1348
|
+
message: 'Guardian received this request out of order. Please try again.',
|
|
1349
|
+
meta: { retryable: true },
|
|
1350
|
+
}),
|
|
1351
|
+
};
|
|
1352
|
+
const serverResponse = {
|
|
1353
|
+
delta: {
|
|
1354
|
+
account_id: '0x' + 'a'.repeat(30),
|
|
1355
|
+
nonce: 1,
|
|
1356
|
+
prev_commitment: '0x' + 'b'.repeat(64),
|
|
1357
|
+
delta_payload: { tx_summary: { data: '' }, signatures: [] },
|
|
1358
|
+
status: {
|
|
1359
|
+
status: 'pending',
|
|
1360
|
+
timestamp: '2024-01-01T00:00:00Z',
|
|
1361
|
+
proposer_id: '0x' + 'c'.repeat(64),
|
|
1362
|
+
cosigner_sigs: [],
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
commitment: '0x' + 'd'.repeat(64),
|
|
1366
|
+
};
|
|
1367
|
+
mockFetch
|
|
1368
|
+
.mockResolvedValueOnce(replayResponse)
|
|
1369
|
+
.mockResolvedValueOnce({ ok: true, json: async () => serverResponse });
|
|
1370
|
+
|
|
1371
|
+
const result = await client.pushDeltaProposal({
|
|
1372
|
+
accountId: '0x' + 'a'.repeat(30),
|
|
1373
|
+
nonce: 1,
|
|
1374
|
+
deltaPayload: { txSummary: { data: '' }, signatures: [] },
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
expect(result.commitment).toBe('0x' + 'd'.repeat(64));
|
|
1378
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
1379
|
+
const timestamps = mockFetch.mock.calls.map((call) =>
|
|
1380
|
+
Number((call[1].headers as Record<string, string>)['x-timestamp'])
|
|
1381
|
+
);
|
|
1382
|
+
const signatures = mockFetch.mock.calls.map(
|
|
1383
|
+
(call) => (call[1].headers as Record<string, string>)['x-signature']
|
|
1384
|
+
);
|
|
1385
|
+
expect(timestamps[1]).toBeGreaterThan(timestamps[0]);
|
|
1386
|
+
expect(signatures[1]).not.toBe(signatures[0]);
|
|
1387
|
+
expect(signRequestMock).toHaveBeenCalledTimes(2);
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
it('gives up after exhausting the bounded replay retry budget', async () => {
|
|
1391
|
+
client.setSigner(mockSigner);
|
|
1392
|
+
mockFetch.mockResolvedValue({
|
|
1393
|
+
ok: false,
|
|
1394
|
+
headers: new Headers(),
|
|
1395
|
+
status: 401,
|
|
1396
|
+
statusText: 'Unauthorized',
|
|
1397
|
+
text: async () =>
|
|
1398
|
+
JSON.stringify({
|
|
1399
|
+
code: 'authentication_replay',
|
|
1400
|
+
message: 'Guardian received this request out of order. Please try again.',
|
|
1401
|
+
meta: { retryable: true },
|
|
1402
|
+
}),
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
const error = await client
|
|
1406
|
+
.pushDeltaProposal({
|
|
1407
|
+
accountId: '0x' + 'a'.repeat(30),
|
|
1408
|
+
nonce: 1,
|
|
1409
|
+
deltaPayload: { txSummary: { data: '' }, signatures: [] },
|
|
1410
|
+
})
|
|
1411
|
+
.catch((e) => e as GuardianHttpError);
|
|
1412
|
+
|
|
1413
|
+
expect(error).toBeInstanceOf(GuardianHttpError);
|
|
1414
|
+
const e = error as GuardianHttpError;
|
|
1415
|
+
expect(e.code).toBe('authentication_replay');
|
|
1416
|
+
expect(e.meta?.retryable).toBe(true);
|
|
1417
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
1418
|
+
});
|
|
1113
1419
|
});
|
|
1114
1420
|
});
|