@openzeppelin/guardian-client 0.18.2 → 0.19.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.
Files changed (49) hide show
  1. package/README.md +66 -5
  2. package/dist/auth-request.d.ts +13 -0
  3. package/dist/auth-request.d.ts.map +1 -1
  4. package/dist/auth-request.js +26 -5
  5. package/dist/auth-request.js.map +1 -1
  6. package/dist/conversion.d.ts.map +1 -1
  7. package/dist/conversion.js +12 -2
  8. package/dist/conversion.js.map +1 -1
  9. package/dist/error-codes.d.ts +3 -5
  10. package/dist/error-codes.d.ts.map +1 -1
  11. package/dist/error-codes.js +21 -4
  12. package/dist/error-codes.js.map +1 -1
  13. package/dist/execution.d.ts +99 -0
  14. package/dist/execution.d.ts.map +1 -0
  15. package/dist/execution.js +124 -0
  16. package/dist/execution.js.map +1 -0
  17. package/dist/http.d.ts +57 -4
  18. package/dist/http.d.ts.map +1 -1
  19. package/dist/http.js +111 -13
  20. package/dist/http.js.map +1 -1
  21. package/dist/index.d.ts +7 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/request-envelope.d.ts +22 -0
  26. package/dist/request-envelope.d.ts.map +1 -0
  27. package/dist/request-envelope.js +51 -0
  28. package/dist/request-envelope.js.map +1 -0
  29. package/dist/server-types.d.ts +20 -0
  30. package/dist/server-types.d.ts.map +1 -1
  31. package/dist/types.d.ts +48 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +2 -2
  34. package/src/auth-request.test.ts +12 -0
  35. package/src/auth-request.ts +37 -5
  36. package/src/conversion.test.ts +81 -0
  37. package/src/conversion.ts +17 -2
  38. package/src/dependencies.test.ts +25 -0
  39. package/src/error-codes.test.ts +14 -1
  40. package/src/error-codes.ts +21 -4
  41. package/src/execution.test.ts +145 -0
  42. package/src/execution.ts +206 -0
  43. package/src/http.test.ts +430 -14
  44. package/src/http.ts +153 -13
  45. package/src/index.ts +34 -1
  46. package/src/request-envelope.test.ts +30 -0
  47. package/src/request-envelope.ts +68 -0
  48. package/src/server-types.ts +20 -1
  49. package/src/types.ts +50 -1
@@ -2,10 +2,8 @@
2
2
  * Typed vocabulary of Guardian's stable, machine-readable error codes
3
3
  * (issue #318). Mirrors `GuardianError::code()` in
4
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
5
+ * sync. The codes the server emits in SCREAMING_SNAKE wire form (see
6
+ * `WIRE_ALIASES`) are normalized to snake_case here so the union has one consistent shape; use
9
7
  * {@link normalizeGuardianErrorCode} at the wire boundary.
10
8
  */
11
9
  export const GUARDIAN_ERROR_CODES = [
@@ -14,6 +12,7 @@ export const GUARDIAN_ERROR_CODES = [
14
12
  'account_not_found',
15
13
  'account_paused',
16
14
  'account_released',
15
+ 'account_request_capacity_exceeded',
17
16
  'authentication_failed',
18
17
  'authentication_replay',
19
18
  'authorization_failed',
@@ -24,6 +23,9 @@ export const GUARDIAN_ERROR_CODES = [
24
23
  'conflict_pending_proposal',
25
24
  'data_unavailable',
26
25
  'delta_not_found',
26
+ 'execution_busy',
27
+ 'execution_conflict',
28
+ 'execution_not_found',
27
29
  'insufficient_operator_permission',
28
30
  'insufficient_signatures',
29
31
  'invalid_account_id',
@@ -40,7 +42,12 @@ export const GUARDIAN_ERROR_CODES = [
40
42
  'network_error',
41
43
  'pending_proposals_limit',
42
44
  'proposal_already_signed',
45
+ 'proposal_executes_locally',
46
+ 'proposal_missing_transaction_request',
43
47
  'proposal_not_found',
48
+ 'proposal_not_ready',
49
+ 'proposal_request_too_large',
50
+ 'proving_unavailable',
44
51
  'rate_limit_exceeded',
45
52
  'rpc_unavailable',
46
53
  'rpc_validation_failed',
@@ -50,6 +57,7 @@ export const GUARDIAN_ERROR_CODES = [
50
57
  'signer_not_authorized',
51
58
  'signing_error',
52
59
  'state_not_found',
60
+ 'state_not_on_chain',
53
61
  'storage_error',
54
62
  'unsupported_evm_chain',
55
63
  'unsupported_for_network',
@@ -75,6 +83,15 @@ const WIRE_ALIASES: Readonly<Record<string, GuardianErrorCode>> = {
75
83
  GUARDIAN_ACCOUNT_RELEASED: 'account_released',
76
84
  GUARDIAN_INSUFFICIENT_OPERATOR_PERMISSION: 'insufficient_operator_permission',
77
85
  GUARDIAN_CANDIDATE_LANDED: 'candidate_landed',
86
+ GUARDIAN_PROVING_UNAVAILABLE: 'proving_unavailable',
87
+ GUARDIAN_PROPOSAL_MISSING_TRANSACTION_REQUEST: 'proposal_missing_transaction_request',
88
+ GUARDIAN_PROPOSAL_EXECUTES_LOCALLY: 'proposal_executes_locally',
89
+ GUARDIAN_PROPOSAL_NOT_READY: 'proposal_not_ready',
90
+ GUARDIAN_EXECUTION_CONFLICT: 'execution_conflict',
91
+ GUARDIAN_EXECUTION_BUSY: 'execution_busy',
92
+ GUARDIAN_EXECUTION_NOT_FOUND: 'execution_not_found',
93
+ GUARDIAN_PROPOSAL_REQUEST_TOO_LARGE: 'proposal_request_too_large',
94
+ GUARDIAN_ACCOUNT_REQUEST_CAPACITY_EXCEEDED: 'account_request_capacity_exceeded',
78
95
  };
79
96
 
80
97
  /** Type guard narrowing an arbitrary string to {@link GuardianErrorCode}. */
@@ -0,0 +1,145 @@
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
+ EXECUTION_FAILURE_CODES,
7
+ EXECUTION_STATES,
8
+ EXECUTION_UNAVAILABLE_REASONS,
9
+ EXPIRATION_BOUNDS,
10
+ FOREIGN_ACCOUNT_UNAVAILABLE_REASONS,
11
+ REQUEST_INVALID_REASONS,
12
+ fromServerExecution,
13
+ fromServerExecutionCapability,
14
+ isTerminalExecutionState,
15
+ type ServerProposalExecution,
16
+ } from './execution.js';
17
+
18
+ const executionRs = readFileSync(
19
+ join(dirname(fileURLToPath(import.meta.url)), '../../../crates/shared/src/execution.rs'),
20
+ 'utf8'
21
+ );
22
+
23
+ function snake(variant: string): string {
24
+ return variant.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
25
+ }
26
+
27
+ function rustVariants(enumName: string): string[] {
28
+ const start = executionRs.indexOf(`pub enum ${enumName} {`);
29
+ expect(start, `${enumName} is defined in guardian-shared`).toBeGreaterThan(-1);
30
+ const body = executionRs.slice(start, executionRs.indexOf('}', start));
31
+ return [...body.matchAll(/^\s+([A-Z][A-Za-z]+)[,(]/gm)].map((m) => m[1]);
32
+ }
33
+
34
+ function envelope(overrides: Partial<ServerProposalExecution>): ServerProposalExecution {
35
+ return {
36
+ account_id: '0xacc',
37
+ proposal_id: '0xprop',
38
+ state: 'pending',
39
+ newly_accepted: false,
40
+ proposal_exists: true,
41
+ ignored_signatures: 0,
42
+ updated_at: '2026-09-30T12:00:00Z',
43
+ ...overrides,
44
+ };
45
+ }
46
+
47
+ describe('drift guard against guardian_shared::execution', () => {
48
+ it('the states and meta value sets match the Rust enums', () => {
49
+ expect([...EXECUTION_STATES]).toEqual(rustVariants('ExecutionState').map(snake));
50
+ expect([...REQUEST_INVALID_REASONS]).toEqual(rustVariants('RequestInvalidReason').map(snake));
51
+ expect([...EXPIRATION_BOUNDS]).toEqual(rustVariants('ExpirationBound').map(snake));
52
+ expect([...FOREIGN_ACCOUNT_UNAVAILABLE_REASONS]).toEqual(
53
+ rustVariants('ForeignAccountUnavailableReason').map(snake)
54
+ );
55
+ });
56
+
57
+ it('the execution-unavailable reasons match the server enum', () => {
58
+ const configRs = readFileSync(
59
+ join(dirname(fileURLToPath(import.meta.url)), '../../../crates/server/src/config/execution.rs'),
60
+ 'utf8'
61
+ );
62
+ const body = configRs.slice(configRs.indexOf('pub enum ExecutionUnavailable {'));
63
+ const variants = [...body.slice(0, body.indexOf('\n}')).matchAll(/^\s{4}([A-Z][A-Za-z]+),$/gm)].map((m) => m[1]);
64
+ expect([...EXECUTION_UNAVAILABLE_REASONS]).toEqual(variants.map(snake));
65
+ });
66
+
67
+ it('the failure codes match ExecutionFailureCode::as_str', () => {
68
+ const start = executionRs.indexOf('pub fn as_str(&self) -> &\'static str {', executionRs.indexOf('impl ExecutionFailureCode'));
69
+ const body = executionRs.slice(start, executionRs.indexOf('pub fn meta', start));
70
+ const rustCodes = [...body.matchAll(/"(GUARDIAN_EXECUTION_[A-Z_]+)"/g)].map((m) => m[1]);
71
+ expect([...EXECUTION_FAILURE_CODES].sort()).toEqual(rustCodes.sort());
72
+ });
73
+
74
+ it('never carries the codes the design withdrew', () => {
75
+ for (const withdrawn of ['ANCHOR_EXPIRED', 'NO_FINITE_EXPIRATION', 'FOREIGN_INPUTS_UNSUPPORTED']) {
76
+ expect(EXECUTION_FAILURE_CODES.some((code) => code.endsWith(withdrawn))).toBe(false);
77
+ }
78
+ });
79
+ });
80
+
81
+ describe('fromServerExecution', () => {
82
+ it('decodes a failure with its typed meta', () => {
83
+ const execution = fromServerExecution(
84
+ envelope({
85
+ state: 'failed',
86
+ proposal_exists: false,
87
+ error: {
88
+ code: 'GUARDIAN_EXECUTION_EXPIRATION_REACHED',
89
+ message: 'expired',
90
+ meta: { bound: 'transaction' },
91
+ },
92
+ })
93
+ );
94
+ expect(execution.error).toEqual({
95
+ code: 'GUARDIAN_EXECUTION_EXPIRATION_REACHED',
96
+ message: 'expired',
97
+ bound: 'transaction',
98
+ });
99
+ expect(execution.proposalExists).toBe(false);
100
+ });
101
+
102
+ it('refuses an unknown state, an unknown reason and a failure without its error', () => {
103
+ expect(() => fromServerExecution(envelope({ state: 'queued' }))).toThrow();
104
+ expect(() =>
105
+ fromServerExecution(
106
+ envelope({
107
+ state: 'failed',
108
+ error: { code: 'GUARDIAN_EXECUTION_REQUEST_INVALID', message: 'x', meta: { reason: 'other' } },
109
+ })
110
+ )
111
+ ).toThrow();
112
+ expect(() => fromServerExecution(envelope({ state: 'failed' }))).toThrow();
113
+ });
114
+
115
+ it('maps nothing in flight and an absent nonce to null', () => {
116
+ expect(fromServerExecution(envelope({})).deltaNonce).toBeNull();
117
+ expect(fromServerExecution(envelope({ delta_nonce: 4 })).deltaNonce).toBe(4);
118
+ });
119
+ });
120
+
121
+ describe('isTerminalExecutionState', () => {
122
+ it('matches ExecutionState::is_terminal', () => {
123
+ expect(EXECUTION_STATES.filter(isTerminalExecutionState)).toEqual(['committed', 'failed']);
124
+ });
125
+ });
126
+
127
+ describe('fromServerExecutionCapability', () => {
128
+ it('decodes both shapes', () => {
129
+ expect(fromServerExecutionCapability({ enabled: true })).toEqual({ enabled: true });
130
+ expect(fromServerExecutionCapability({ enabled: false, reason: 'disabled' })).toEqual({
131
+ enabled: false,
132
+ reason: 'disabled',
133
+ });
134
+ });
135
+
136
+ it('refuses an unknown reason, a missing field and a reason on an enabled capability', () => {
137
+ expect(() => fromServerExecutionCapability({ enabled: false, reason: 'gone_fishing' })).toThrow(
138
+ /unknown execution unavailable reason/
139
+ );
140
+ expect(() => fromServerExecutionCapability({ enabled: false })).toThrow(/unknown execution unavailable reason/);
141
+ expect(() => fromServerExecutionCapability({ enabled: 'yes' })).toThrow(/boolean enabled/);
142
+ expect(() => fromServerExecutionCapability('yes' as never)).toThrow(/malformed execution capability/);
143
+ expect(() => fromServerExecutionCapability({ enabled: true, reason: 'disabled' })).toThrow(/with a reason/);
144
+ });
145
+ });
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Guardian execution of threshold-met proposals, as the base client reports it. Mirrors the
3
+ * `guardian_shared::execution` vocabulary; drift-guard tests keep the two in step.
4
+ */
5
+
6
+ /** The five reported states. Closed: adding one is a breaking change. */
7
+ export const EXECUTION_STATES = ['pending', 'proving', 'submitted', 'committed', 'failed'] as const;
8
+ export type ExecutionState = (typeof EXECUTION_STATES)[number];
9
+
10
+ /** Whether the execution has finished, one way or the other. Mirrors `ExecutionState::is_terminal`. */
11
+ export function isTerminalExecutionState(state: ExecutionState): boolean {
12
+ switch (state) {
13
+ case 'committed':
14
+ case 'failed':
15
+ return true;
16
+ case 'pending':
17
+ case 'proving':
18
+ case 'submitted':
19
+ return false;
20
+ default: {
21
+ const unreachable: never = state;
22
+ throw new Error(`Unknown execution state: ${String(unreachable)}`);
23
+ }
24
+ }
25
+ }
26
+
27
+ export const REQUEST_INVALID_REASONS = [
28
+ 'bound_block_not_declared',
29
+ 'auth_args_missing',
30
+ 'approval_expiration_missing',
31
+ 'input_notes_not_pinned',
32
+ 'too_many_declared_blocks',
33
+ ] as const;
34
+ export type RequestInvalidReason = (typeof REQUEST_INVALID_REASONS)[number];
35
+
36
+ export const EXPIRATION_BOUNDS = ['approval', 'transaction'] as const;
37
+ export type ExpirationBound = (typeof EXPIRATION_BOUNDS)[number];
38
+
39
+ export const FOREIGN_ACCOUNT_UNAVAILABLE_REASONS = ['private', 'unavailable'] as const;
40
+ export type ForeignAccountUnavailableReason = (typeof FOREIGN_ACCOUNT_UNAVAILABLE_REASONS)[number];
41
+
42
+ /** Why a server does not offer Guardian execution, as `GET /status` reports it. */
43
+ export const EXECUTION_UNAVAILABLE_REASONS = ['prover_not_configured', 'disabled', 'canonicalization_disabled'] as const;
44
+ export type ExecutionUnavailableReason = (typeof EXECUTION_UNAVAILABLE_REASONS)[number];
45
+
46
+ /**
47
+ * Whether the server accepts Guardian execution requests. It reflects the server's configuration,
48
+ * not whether its prover is reachable right now.
49
+ */
50
+ export type ServerExecutionCapability = { enabled: true } | { enabled: false; reason: ExecutionUnavailableReason };
51
+
52
+ /** The capability as `GET /status` sends it, before validation. */
53
+ export interface ServerExecutionCapabilityWire {
54
+ enabled: unknown;
55
+ reason?: unknown;
56
+ }
57
+
58
+ /** Failure codes that carry no structured meta. */
59
+ export const PLAIN_EXECUTION_FAILURE_CODES = [
60
+ 'GUARDIAN_EXECUTION_BINDING_MISMATCH',
61
+ 'GUARDIAN_EXECUTION_STATE_MISMATCH',
62
+ 'GUARDIAN_EXECUTION_REQUEST_CODEC',
63
+ 'GUARDIAN_EXECUTION_PROTOCOL_MISMATCH',
64
+ 'GUARDIAN_EXECUTION_CHAIN_BEHIND',
65
+ 'GUARDIAN_EXECUTION_CHAIN_INCONSISTENT',
66
+ 'GUARDIAN_EXECUTION_NODE_UNAVAILABLE',
67
+ 'GUARDIAN_EXECUTION_INSUFFICIENT_FEE',
68
+ 'GUARDIAN_EXECUTION_INSUFFICIENT_SIGNATURES',
69
+ 'GUARDIAN_EXECUTION_PROVING_FAILED',
70
+ 'GUARDIAN_EXECUTION_SEALING_FAILED',
71
+ 'GUARDIAN_EXECUTION_ACKNOWLEDGEMENT_FAILED',
72
+ 'GUARDIAN_EXECUTION_EXPIRATION_BEYOND_HORIZON',
73
+ 'GUARDIAN_EXECUTION_ACCOUNT_INADMISSIBLE',
74
+ 'GUARDIAN_EXECUTION_SUBMISSION_REJECTED',
75
+ 'GUARDIAN_EXECUTION_SUBMISSION_NOT_PROCESSED',
76
+ 'GUARDIAN_EXECUTION_CANDIDATE_DISCARDED',
77
+ 'GUARDIAN_EXECUTION_EXPIRED',
78
+ 'GUARDIAN_EXECUTION_LEASE_EXPIRED',
79
+ 'GUARDIAN_EXECUTION_ABANDONED',
80
+ ] as const;
81
+ export type PlainExecutionFailureCode = (typeof PLAIN_EXECUTION_FAILURE_CODES)[number];
82
+
83
+ /** Why an execution failed, discriminated by `code`; codes with meta carry it typed. */
84
+ export type ExecutionFailure =
85
+ | { code: 'GUARDIAN_EXECUTION_REQUEST_INVALID'; message: string; reason: RequestInvalidReason }
86
+ | { code: 'GUARDIAN_EXECUTION_EXPIRATION_REACHED'; message: string; bound: ExpirationBound }
87
+ | {
88
+ code: 'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE';
89
+ message: string;
90
+ reason: ForeignAccountUnavailableReason;
91
+ }
92
+ | { code: PlainExecutionFailureCode; message: string };
93
+
94
+ export type ExecutionFailureCode = ExecutionFailure['code'];
95
+
96
+ export const EXECUTION_FAILURE_CODES: readonly ExecutionFailureCode[] = [
97
+ ...PLAIN_EXECUTION_FAILURE_CODES,
98
+ 'GUARDIAN_EXECUTION_REQUEST_INVALID',
99
+ 'GUARDIAN_EXECUTION_EXPIRATION_REACHED',
100
+ 'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE',
101
+ ];
102
+
103
+ /** One execution of a proposal, identified by `(accountId, proposalId)`. */
104
+ export interface ProposalExecution {
105
+ accountId: string;
106
+ proposalId: string;
107
+ state: ExecutionState;
108
+ /** Present exactly when `state` is `'failed'`. */
109
+ error: ExecutionFailure | null;
110
+ /** The delta nonce the execution committed to, once it crossed the no-retry boundary. */
111
+ deltaNonce: number | null;
112
+ /** Whether this request started the execution, rather than finding it already running. */
113
+ newlyAccepted: boolean;
114
+ /** Whether the proposal is still stored. A fact, not retry advice. */
115
+ proposalExists: boolean;
116
+ /** Stored signatures the server ignored as invalid, duplicate or not from a cosigner. */
117
+ ignoredSignatures: number;
118
+ updatedAt: string;
119
+ }
120
+
121
+ export interface ServerExecutionError {
122
+ code: string;
123
+ message: string;
124
+ meta?: Record<string, unknown>;
125
+ }
126
+
127
+ export interface ServerProposalExecution {
128
+ account_id: string;
129
+ proposal_id: string;
130
+ state: string;
131
+ error?: ServerExecutionError;
132
+ delta_nonce?: number;
133
+ newly_accepted: boolean;
134
+ proposal_exists: boolean;
135
+ ignored_signatures: number;
136
+ updated_at: string;
137
+ }
138
+
139
+ export interface ServerCurrentExecution {
140
+ execution: ServerProposalExecution | null;
141
+ }
142
+
143
+ function member<T extends string>(values: readonly T[], value: unknown, what: string): T {
144
+ if (typeof value === 'string' && (values as readonly string[]).includes(value)) {
145
+ return value as T;
146
+ }
147
+ throw new Error(`Guardian returned an unknown ${what}: ${JSON.stringify(value)}`);
148
+ }
149
+
150
+ /**
151
+ * Decodes a `GET /status` capability the server sent, refusing a missing flag or an unknown
152
+ * reason. A status without one does not offer Guardian execution.
153
+ */
154
+ export function fromServerExecutionCapability(server: ServerExecutionCapabilityWire): ServerExecutionCapability {
155
+ if (typeof server !== 'object' || server === null) {
156
+ throw new Error(`Guardian returned a malformed execution capability: ${JSON.stringify(server)}`);
157
+ }
158
+ if (server.enabled === true) {
159
+ if (server.reason !== undefined) {
160
+ throw new Error(`Guardian returned an enabled execution capability with a reason: ${JSON.stringify(server.reason)}`);
161
+ }
162
+ return { enabled: true };
163
+ }
164
+ if (server.enabled === false) {
165
+ return { enabled: false, reason: member(EXECUTION_UNAVAILABLE_REASONS, server.reason, 'execution unavailable reason') };
166
+ }
167
+ throw new Error(`Guardian returned an execution capability without a boolean enabled: ${JSON.stringify(server.enabled)}`);
168
+ }
169
+
170
+ function fromServerFailure(error: ServerExecutionError): ExecutionFailure {
171
+ const { code, message, meta } = error;
172
+ switch (code) {
173
+ case 'GUARDIAN_EXECUTION_REQUEST_INVALID':
174
+ return { code, message, reason: member(REQUEST_INVALID_REASONS, meta?.reason, 'request reason') };
175
+ case 'GUARDIAN_EXECUTION_EXPIRATION_REACHED':
176
+ return { code, message, bound: member(EXPIRATION_BOUNDS, meta?.bound, 'expiration bound') };
177
+ case 'GUARDIAN_EXECUTION_FOREIGN_ACCOUNT_UNAVAILABLE':
178
+ return {
179
+ code,
180
+ message,
181
+ reason: member(FOREIGN_ACCOUNT_UNAVAILABLE_REASONS, meta?.reason, 'foreign account reason'),
182
+ };
183
+ default:
184
+ return { code: member(PLAIN_EXECUTION_FAILURE_CODES, code, 'execution failure code'), message };
185
+ }
186
+ }
187
+
188
+ /** Decodes the wire envelope, refusing any state, code or meta outside the vocabulary. */
189
+ export function fromServerExecution(server: ServerProposalExecution): ProposalExecution {
190
+ const state = member(EXECUTION_STATES, server.state, 'execution state');
191
+ const error = server.error ? fromServerFailure(server.error) : null;
192
+ if ((state === 'failed') !== (error !== null)) {
193
+ throw new Error(`Guardian returned an execution in state '${state}' ${error ? 'with' : 'without'} an error`);
194
+ }
195
+ return {
196
+ accountId: server.account_id,
197
+ proposalId: server.proposal_id,
198
+ state,
199
+ error,
200
+ deltaNonce: server.delta_nonce ?? null,
201
+ newlyAccepted: server.newly_accepted,
202
+ proposalExists: server.proposal_exists,
203
+ ignoredSignatures: server.ignored_signatures,
204
+ updatedAt: server.updated_at,
205
+ };
206
+ }