@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/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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openzeppelin/guardian-client",
3
- "version": "0.16.1",
3
+ "version": "0.17.0-rc.1",
4
4
  "description": "TypeScript HTTP client for Guardian server",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
+ }
@@ -15,6 +15,7 @@ export const GUARDIAN_ERROR_CODES = [
15
15
  'account_paused',
16
16
  'account_released',
17
17
  'authentication_failed',
18
+ 'authentication_replay',
18
19
  'authorization_failed',
19
20
  'candidate_landed',
20
21
  'commitment_mismatch',
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('surfaces 401 AUTHENTICATION_FAILED with a parseable error envelope', async () => {
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: 'Your session has expired. Please sign in again.',
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
  });