@lodestar/api 1.45.0-dev.f535421f29 → 1.45.0-rc.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.
Files changed (39) hide show
  1. package/lib/beacon/routes/beacon/index.d.ts +1 -1
  2. package/lib/beacon/routes/beacon/index.d.ts.map +1 -1
  3. package/lib/beacon/routes/beacon/index.js.map +1 -1
  4. package/lib/beacon/routes/beacon/pool.d.ts +0 -34
  5. package/lib/beacon/routes/beacon/pool.d.ts.map +1 -1
  6. package/lib/beacon/routes/beacon/pool.js +1 -44
  7. package/lib/beacon/routes/beacon/pool.js.map +1 -1
  8. package/lib/beacon/routes/beacon/state.d.ts +54 -2
  9. package/lib/beacon/routes/beacon/state.d.ts.map +1 -1
  10. package/lib/beacon/routes/beacon/state.js +34 -1
  11. package/lib/beacon/routes/beacon/state.js.map +1 -1
  12. package/lib/beacon/routes/events.d.ts +18 -1
  13. package/lib/beacon/routes/events.d.ts.map +1 -1
  14. package/lib/beacon/routes/events.js +4 -0
  15. package/lib/beacon/routes/events.js.map +1 -1
  16. package/lib/beacon/routes/node.d.ts +2 -0
  17. package/lib/beacon/routes/node.d.ts.map +1 -1
  18. package/lib/beacon/routes/node.js +2 -0
  19. package/lib/beacon/routes/node.js.map +1 -1
  20. package/lib/beacon/routes/validator.d.ts +28 -2
  21. package/lib/beacon/routes/validator.d.ts.map +1 -1
  22. package/lib/beacon/routes/validator.js +34 -6
  23. package/lib/beacon/routes/validator.js.map +1 -1
  24. package/lib/utils/serdes.d.ts +2 -0
  25. package/lib/utils/serdes.d.ts.map +1 -1
  26. package/lib/utils/serdes.js +2 -0
  27. package/lib/utils/serdes.js.map +1 -1
  28. package/lib/utils/server/handler.d.ts.map +1 -1
  29. package/lib/utils/server/handler.js +4 -0
  30. package/lib/utils/server/handler.js.map +1 -1
  31. package/package.json +9 -9
  32. package/src/beacon/routes/beacon/index.ts +3 -0
  33. package/src/beacon/routes/beacon/pool.ts +0 -72
  34. package/src/beacon/routes/beacon/state.ts +63 -2
  35. package/src/beacon/routes/events.ts +5 -0
  36. package/src/beacon/routes/node.ts +3 -1
  37. package/src/beacon/routes/validator.ts +61 -7
  38. package/src/utils/serdes.ts +3 -0
  39. package/src/utils/server/handler.ts +5 -0
@@ -6,7 +6,6 @@ import {
6
6
  ForkPreElectra,
7
7
  MAX_PAYLOAD_ATTESTATIONS,
8
8
  PTC_SIZE,
9
- SLOTS_PER_EPOCH,
10
9
  isForkPostElectra,
11
10
  } from "@lodestar/params";
12
11
  import {
@@ -47,12 +46,6 @@ const SignedBLSToExecutionChangeListType = ArrayOf(ssz.capella.SignedBLSToExecut
47
46
  const SyncCommitteeMessageListType = ArrayOf(ssz.altair.SyncCommitteeMessage);
48
47
  const PayloadAttestationListType = ArrayOf(ssz.gloas.PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS);
49
48
  const PayloadAttestationMessageListType = ArrayOf(ssz.gloas.PayloadAttestationMessage, PTC_SIZE);
50
- // 2 * SLOTS_PER_EPOCH max normally; getAll() may return up to 3 * SLOTS_PER_EPOCH during non-finality
51
- const MAX_PROPOSER_PREFERENCES_PER_REQUEST = 3 * SLOTS_PER_EPOCH;
52
- const SignedProposerPreferencesListType = ArrayOf(
53
- ssz.gloas.SignedProposerPreferences,
54
- MAX_PROPOSER_PREFERENCES_PER_REQUEST
55
- );
56
49
 
57
50
  type AttestationListPhase0 = ValueOf<typeof AttestationListTypePhase0>;
58
51
  type AttestationListElectra = ValueOf<typeof AttestationListTypeElectra>;
@@ -68,7 +61,6 @@ type SignedBLSToExecutionChangeList = ValueOf<typeof SignedBLSToExecutionChangeL
68
61
  type SyncCommitteeMessageList = ValueOf<typeof SyncCommitteeMessageListType>;
69
62
  type PayloadAttestationList = ValueOf<typeof PayloadAttestationListType>;
70
63
  type PayloadAttestationMessageList = ValueOf<typeof PayloadAttestationMessageListType>;
71
- type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;
72
64
 
73
65
  export type Endpoints = {
74
66
  /**
@@ -95,18 +87,6 @@ export type Endpoints = {
95
87
  VersionMeta
96
88
  >;
97
89
 
98
- /**
99
- * Get signed proposer preferences from operations pool
100
- * Retrieves proposer preferences known by the node but not necessarily incorporated into any block.
101
- */
102
- getPoolProposerPreferences: Endpoint<
103
- "GET",
104
- {slot?: Slot},
105
- {query: {slot?: number}},
106
- SignedProposerPreferencesList,
107
- VersionMeta
108
- >;
109
-
110
90
  /**
111
91
  * Get AttesterSlashings from operations pool
112
92
  * Retrieves attester slashings known by the node but not necessarily incorporated into any block
@@ -246,18 +226,6 @@ export type Endpoints = {
246
226
  EmptyResponseData,
247
227
  EmptyMeta
248
228
  >;
249
-
250
- /**
251
- * Submit signed proposer preferences
252
- * Submits signed proposer preferences to the beacon node.
253
- */
254
- submitSignedProposerPreferences: Endpoint<
255
- "POST",
256
- {signedProposerPreferences: SignedProposerPreferencesList},
257
- {body: unknown; headers: {[MetaHeader.Version]: string}},
258
- EmptyResponseData,
259
- EmptyMeta
260
- >;
261
229
  };
262
230
 
263
231
  export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
@@ -288,19 +256,6 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
288
256
  meta: VersionCodec,
289
257
  },
290
258
  },
291
- getPoolProposerPreferences: {
292
- url: "/eth/v1/beacon/pool/proposer_preferences",
293
- method: "GET",
294
- req: {
295
- writeReq: ({slot}) => ({query: {slot}}),
296
- parseReq: ({query}) => ({slot: query.slot}),
297
- schema: {query: {slot: Schema.Uint}},
298
- },
299
- resp: {
300
- data: SignedProposerPreferencesListType,
301
- meta: VersionCodec,
302
- },
303
- },
304
259
  getPoolAttesterSlashingsV2: {
305
260
  url: "/eth/v2/beacon/pool/attester_slashings",
306
261
  method: "GET",
@@ -516,32 +471,5 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
516
471
  },
517
472
  resp: EmptyResponseCodec,
518
473
  },
519
- submitSignedProposerPreferences: {
520
- url: "/eth/v1/beacon/pool/proposer_preferences",
521
- method: "POST",
522
- req: {
523
- writeReqJson: ({signedProposerPreferences}) => ({
524
- body: SignedProposerPreferencesListType.toJson(signedProposerPreferences),
525
- headers: {[MetaHeader.Version]: ForkName.gloas},
526
- }),
527
- parseReqJson: ({body, headers}) => {
528
- toForkName(fromHeaders(headers, MetaHeader.Version));
529
- return {signedProposerPreferences: SignedProposerPreferencesListType.fromJson(body)};
530
- },
531
- writeReqSsz: ({signedProposerPreferences}) => ({
532
- body: SignedProposerPreferencesListType.serialize(signedProposerPreferences),
533
- headers: {[MetaHeader.Version]: ForkName.gloas},
534
- }),
535
- parseReqSsz: ({body, headers}) => {
536
- toForkName(fromHeaders(headers, MetaHeader.Version));
537
- return {signedProposerPreferences: SignedProposerPreferencesListType.deserialize(body)};
538
- },
539
- schema: {
540
- body: Schema.ObjectArray,
541
- headers: {[MetaHeader.Version]: Schema.String},
542
- },
543
- },
544
- resp: EmptyResponseCodec,
545
- },
546
474
  };
547
475
  }
@@ -3,6 +3,7 @@ import {ChainForkConfig} from "@lodestar/config";
3
3
  import {MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
4
4
  import {
5
5
  ArrayOf,
6
+ BuilderStatus,
6
7
  CommitteeIndex,
7
8
  Epoch,
8
9
  RootHex,
@@ -22,7 +23,7 @@ import {
22
23
  ExecutionOptimisticFinalizedAndVersionCodec,
23
24
  ExecutionOptimisticFinalizedAndVersionMeta,
24
25
  } from "../../../utils/metadata.js";
25
- import {fromValidatorIdsStr, toValidatorIdsStr} from "../../../utils/serdes.js";
26
+ import {fromBuilderIdsStr, fromValidatorIdsStr, toBuilderIdsStr, toValidatorIdsStr} from "../../../utils/serdes.js";
26
27
  import {WireFormat} from "../../../utils/wireFormat.js";
27
28
  import {RootResponse, RootResponseType} from "./block.js";
28
29
 
@@ -37,8 +38,9 @@ export type StateArgs = {
37
38
  };
38
39
 
39
40
  export type ValidatorId = string | number;
41
+ export type BuilderId = string | number;
40
42
 
41
- export type {ValidatorStatus};
43
+ export type {BuilderStatus, ValidatorStatus};
42
44
 
43
45
  export const RandaoResponseType = new ContainerType({
44
46
  randao: ssz.Root,
@@ -57,6 +59,11 @@ export const ValidatorResponseType = new ContainerType({
57
59
  status: new StringType<ValidatorStatus>(),
58
60
  validator: ssz.phase0.Validator,
59
61
  });
62
+ export const BuilderResponseType = new ContainerType({
63
+ index: ssz.BuilderIndex,
64
+ status: new StringType<BuilderStatus>(),
65
+ builder: ssz.gloas.Builder,
66
+ });
60
67
  export const ValidatorIdentityType = new ContainerType(
61
68
  {
62
69
  index: ssz.ValidatorIndex,
@@ -84,6 +91,7 @@ export const EpochSyncCommitteeResponseType = new ContainerType(
84
91
  {jsonCase: "eth2"}
85
92
  );
86
93
  export const ValidatorResponseListType = ArrayOf(ValidatorResponseType);
94
+ export const BuilderResponseListType = ArrayOf(BuilderResponseType);
87
95
  export const ValidatorIdentitiesType = ArrayOf(ValidatorIdentityType);
88
96
  export const EpochCommitteeResponseListType = ArrayOf(EpochCommitteeResponseType);
89
97
  export const ValidatorBalanceListType = ArrayOf(ValidatorBalanceType);
@@ -91,11 +99,13 @@ export const ValidatorBalanceListType = ArrayOf(ValidatorBalanceType);
91
99
  export type RandaoResponse = ValueOf<typeof RandaoResponseType>;
92
100
  export type FinalityCheckpoints = ValueOf<typeof FinalityCheckpointsType>;
93
101
  export type ValidatorResponse = ValueOf<typeof ValidatorResponseType>;
102
+ export type BuilderResponse = ValueOf<typeof BuilderResponseType>;
94
103
  export type EpochCommitteeResponse = ValueOf<typeof EpochCommitteeResponseType>;
95
104
  export type ValidatorBalance = ValueOf<typeof ValidatorBalanceType>;
96
105
  export type EpochSyncCommitteeResponse = ValueOf<typeof EpochSyncCommitteeResponseType>;
97
106
 
98
107
  export type ValidatorResponseList = ValueOf<typeof ValidatorResponseListType>;
108
+ export type BuilderResponseList = ValueOf<typeof BuilderResponseListType>;
99
109
  export type ValidatorIdentities = ValueOf<typeof ValidatorIdentitiesType>;
100
110
  export type EpochCommitteeResponseList = ValueOf<typeof EpochCommitteeResponseListType>;
101
111
  export type ValidatorBalanceList = ValueOf<typeof ValidatorBalanceListType>;
@@ -204,6 +214,30 @@ export type Endpoints = {
204
214
  ExecutionOptimisticAndFinalizedMeta
205
215
  >;
206
216
 
217
+ /**
218
+ * Get builders from state
219
+ *
220
+ * Returns filterable list of builders with their status and index.
221
+ *
222
+ * Information will be returned for all indices or public keys that match known builders. If an index or public key does not
223
+ * match any known builder, no information will be returned but this will not cause an error. There are no guarantees for the
224
+ * returned data in terms of ordering; both the index and public key are returned for each builder, and can be used to confirm
225
+ * for which inputs a response has been returned.
226
+ *
227
+ * Returns 400 if the requested state is prior to Gloas.
228
+ */
229
+ getStateBuilders: Endpoint<
230
+ "POST",
231
+ StateArgs & {
232
+ /** Either hex encoded public key (any bytes48 with 0x prefix) or builder index */
233
+ builderIds?: BuilderId[];
234
+ statuses?: BuilderStatus[];
235
+ },
236
+ {params: {state_id: string}; body: {ids?: string[]; statuses?: BuilderStatus[]}},
237
+ BuilderResponseList,
238
+ ExecutionOptimisticAndFinalizedMeta
239
+ >;
240
+
207
241
  /**
208
242
  * Get validator identities from state
209
243
  *
@@ -489,6 +523,33 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions<Endpo
489
523
  meta: ExecutionOptimisticAndFinalizedCodec,
490
524
  },
491
525
  },
526
+ getStateBuilders: {
527
+ url: "/eth/v1/beacon/states/{state_id}/builders",
528
+ method: "POST",
529
+ req: JsonOnlyReq({
530
+ writeReqJson: ({stateId, builderIds, statuses}) => ({
531
+ params: {state_id: stateId.toString()},
532
+ body: {
533
+ ids: toBuilderIdsStr(builderIds),
534
+ statuses,
535
+ },
536
+ }),
537
+ parseReqJson: ({params, body = {}}) => ({
538
+ stateId: params.state_id,
539
+ builderIds: fromBuilderIdsStr(body.ids),
540
+ statuses: body.statuses ?? undefined,
541
+ }),
542
+ schema: {
543
+ params: {state_id: Schema.StringRequired},
544
+ body: Schema.Object,
545
+ },
546
+ }),
547
+ resp: {
548
+ onlySupport: WireFormat.json,
549
+ data: BuilderResponseListType,
550
+ meta: ExecutionOptimisticAndFinalizedCodec,
551
+ },
552
+ },
492
553
  postStateValidatorIdentities: {
493
554
  url: "/eth/v1/beacon/states/{state_id}/validator_identities",
494
555
  method: "POST",
@@ -110,6 +110,8 @@ export enum EventType {
110
110
  executionPayloadBid = "execution_payload_bid",
111
111
  /** The node has received a `SignedProposerPreferences` (from P2P or API) that passes gossip validation on the `proposer_preferences` topic */
112
112
  proposerPreferences = "proposer_preferences",
113
+ /** The node has received a `PayloadAttestationMessage` (from P2P or API) that passes validation rules of the `payload_attestation_message` topic */
114
+ payloadAttestationMessage = "payload_attestation_message",
113
115
  /** The node has executed the Fast Confirmation Rule and produced a confirmed beacon block */
114
116
  fastConfirmation = "fast_confirmation",
115
117
  }
@@ -137,6 +139,7 @@ export const eventTypes: {[K in EventType]: K} = {
137
139
  [EventType.executionPayloadAvailable]: EventType.executionPayloadAvailable,
138
140
  [EventType.executionPayloadBid]: EventType.executionPayloadBid,
139
141
  [EventType.proposerPreferences]: EventType.proposerPreferences,
142
+ [EventType.payloadAttestationMessage]: EventType.payloadAttestationMessage,
140
143
  [EventType.fastConfirmation]: EventType.fastConfirmation,
141
144
  };
142
145
 
@@ -206,6 +209,7 @@ export type EventData = {
206
209
  };
207
210
  [EventType.executionPayloadBid]: {version: ForkName; data: gloas.SignedExecutionPayloadBid};
208
211
  [EventType.proposerPreferences]: {version: ForkName; data: gloas.SignedProposerPreferences};
212
+ [EventType.payloadAttestationMessage]: {version: ForkName; data: gloas.PayloadAttestationMessage};
209
213
  [EventType.fastConfirmation]: {
210
214
  block: RootHex;
211
215
  slot: Slot;
@@ -408,6 +412,7 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type
408
412
  ),
409
413
  [EventType.executionPayloadBid]: WithVersion((fork) => getPostGloasForkTypes(fork).SignedExecutionPayloadBid),
410
414
  [EventType.proposerPreferences]: WithVersion((fork) => getPostGloasForkTypes(fork).SignedProposerPreferences),
415
+ [EventType.payloadAttestationMessage]: WithVersion((fork) => getPostGloasForkTypes(fork).PayloadAttestationMessage),
411
416
  [EventType.fastConfirmation]: new ContainerType(
412
417
  {
413
418
  block: stringType,
@@ -106,6 +106,7 @@ export enum NodeHealth {
106
106
  */
107
107
  export enum ClientCode {
108
108
  BU = "BU", // besu
109
+ CN = "CN", // caplin
109
110
  EJ = "EJ", // ethereumJS
110
111
  EG = "EG", // erigon
111
112
  EX = "EX", // ethrex
@@ -114,7 +115,8 @@ export enum ClientCode {
114
115
  LH = "LH", // lighthouse
115
116
  LS = "LS", // lodestar
116
117
  NM = "NM", // nethermind
117
- NB = "NB", // nimbus
118
+ NB = "NB", // nimbus CL
119
+ NE = "NE", // nimbus EL
118
120
  TE = "TE", // trin-execution
119
121
  TK = "TK", // teku
120
122
  PM = "PM", // prysm
@@ -4,6 +4,8 @@ import {
4
4
  ForkPostDeneb,
5
5
  ForkPostGloas,
6
6
  ForkPreDeneb,
7
+ MIN_SEED_LOOKAHEAD,
8
+ SLOTS_PER_EPOCH,
7
9
  VALIDATOR_REGISTRY_LIMIT,
8
10
  isForkPostDeneb,
9
11
  isForkPostElectra,
@@ -246,6 +248,10 @@ export const SignedValidatorRegistrationV1ListType = ArrayOf(
246
248
  ssz.bellatrix.SignedValidatorRegistrationV1,
247
249
  VALIDATOR_REGISTRY_LIMIT
248
250
  );
251
+ export const SignedProposerPreferencesListType = ArrayOf(
252
+ ssz.gloas.SignedProposerPreferences,
253
+ (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
254
+ );
249
255
 
250
256
  export type ValidatorIndices = ValueOf<typeof ValidatorIndicesType>;
251
257
  export type AttesterDuty = ValueOf<typeof AttesterDutyType>;
@@ -273,6 +279,13 @@ export type SyncCommitteeSelectionList = ValueOf<typeof SyncCommitteeSelectionLi
273
279
  export type LivenessResponseData = ValueOf<typeof LivenessResponseDataType>;
274
280
  export type LivenessResponseDataList = ValueOf<typeof LivenessResponseDataListType>;
275
281
  export type SignedValidatorRegistrationV1List = ValueOf<typeof SignedValidatorRegistrationV1ListType>;
282
+ export type SignedProposerPreferencesList = ValueOf<typeof SignedProposerPreferencesListType>;
283
+
284
+ // The beacon node does not return any data if there is no canonical block at the requested slot (missed slot).
285
+ // In this case, we receive a success response (204) which is not handled as an error. The generic response
286
+ // handler already checks the status code and will not attempt to parse the body, but it will return no value.
287
+ // It is important that this type indicates that there might be no value to ensure it is properly handled downstream.
288
+ export type MaybePayloadAttestationData = gloas.PayloadAttestationData | undefined;
276
289
 
277
290
  export type Endpoints = {
278
291
  /**
@@ -482,8 +495,8 @@ export type Endpoints = {
482
495
  /** The slot for which payload attestation data should be created */
483
496
  slot: Slot;
484
497
  },
485
- {params: {slot: Slot}},
486
- gloas.PayloadAttestationData,
498
+ {query: {slot: Slot}},
499
+ MaybePayloadAttestationData,
487
500
  VersionMeta
488
501
  >;
489
502
 
@@ -646,6 +659,20 @@ export type Endpoints = {
646
659
  EmptyResponseData,
647
660
  EmptyMeta
648
661
  >;
662
+
663
+ /**
664
+ * Submit signed proposer preferences
665
+ *
666
+ * Verifies given signed proposer preferences and publishes them on the `proposer_preferences`
667
+ * gossipsub topic. Supersedes `prepareBeaconProposer` and `registerValidator` from Gloas onwards.
668
+ */
669
+ submitProposerPreferences: Endpoint<
670
+ "POST",
671
+ {signedProposerPreferences: SignedProposerPreferencesList},
672
+ {body: unknown; headers: {[MetaHeader.Version]: string}},
673
+ EmptyResponseData,
674
+ EmptyMeta
675
+ >;
649
676
  };
650
677
 
651
678
  export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoints> {
@@ -972,17 +999,17 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
972
999
  },
973
1000
  },
974
1001
  producePayloadAttestationData: {
975
- url: "/eth/v1/validator/payload_attestation_data/{slot}",
1002
+ url: "/eth/v1/validator/payload_attestation_data",
976
1003
  method: "GET",
977
1004
  req: {
978
- writeReq: ({slot}) => ({params: {slot}}),
979
- parseReq: ({params}) => ({slot: params.slot}),
1005
+ writeReq: ({slot}) => ({query: {slot}}),
1006
+ parseReq: ({query}) => ({slot: query.slot}),
980
1007
  schema: {
981
- params: {slot: Schema.UintRequired},
1008
+ query: {slot: Schema.UintRequired},
982
1009
  },
983
1010
  },
984
1011
  resp: {
985
- data: ssz.gloas.PayloadAttestationData,
1012
+ data: WithVersion<MaybePayloadAttestationData, VersionMeta>(() => ssz.gloas.PayloadAttestationData),
986
1013
  meta: VersionCodec,
987
1014
  },
988
1015
  },
@@ -1210,6 +1237,33 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
1210
1237
  requestWireFormat: WireFormat.ssz,
1211
1238
  },
1212
1239
  },
1240
+ submitProposerPreferences: {
1241
+ url: "/eth/v1/validator/proposer_preferences",
1242
+ method: "POST",
1243
+ req: {
1244
+ writeReqJson: ({signedProposerPreferences}) => ({
1245
+ body: SignedProposerPreferencesListType.toJson(signedProposerPreferences),
1246
+ headers: {[MetaHeader.Version]: config.getForkName(signedProposerPreferences[0]?.message.proposalSlot ?? 0)},
1247
+ }),
1248
+ parseReqJson: ({body, headers}) => {
1249
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1250
+ return {signedProposerPreferences: SignedProposerPreferencesListType.fromJson(body)};
1251
+ },
1252
+ writeReqSsz: ({signedProposerPreferences}) => ({
1253
+ body: SignedProposerPreferencesListType.serialize(signedProposerPreferences),
1254
+ headers: {[MetaHeader.Version]: config.getForkName(signedProposerPreferences[0]?.message.proposalSlot ?? 0)},
1255
+ }),
1256
+ parseReqSsz: ({body, headers}) => {
1257
+ toForkName(fromHeaders(headers, MetaHeader.Version));
1258
+ return {signedProposerPreferences: SignedProposerPreferencesListType.deserialize(body)};
1259
+ },
1260
+ schema: {
1261
+ body: Schema.ObjectArray,
1262
+ headers: {[MetaHeader.Version]: Schema.String},
1263
+ },
1264
+ },
1265
+ resp: EmptyResponseCodec,
1266
+ },
1213
1267
  };
1214
1268
  }
1215
1269
 
@@ -75,6 +75,9 @@ export function fromValidatorIdsStr(ids?: string[]): (string | number)[] | undef
75
75
  return ids?.map((id) => (typeof id === "string" && id.startsWith("0x") ? id : fromU64Str(id)));
76
76
  }
77
77
 
78
+ export const toBuilderIdsStr = toValidatorIdsStr;
79
+ export const fromBuilderIdsStr = fromValidatorIdsStr;
80
+
78
81
  const GRAFFITI_HEX_LENGTH = 66;
79
82
 
80
83
  export function toGraffitiHex(utf8?: string): string | undefined {
@@ -111,6 +111,11 @@ export function createFastifyHandler<E extends Endpoint>(
111
111
  resp.statusCode = response.status;
112
112
  }
113
113
 
114
+ // A 204 No Content response has no body
115
+ if (resp.statusCode === 204) {
116
+ return;
117
+ }
118
+
114
119
  switch (responseWireFormat) {
115
120
  case WireFormat.json: {
116
121
  const metaHeaders = definition.resp.meta.toHeadersObject(response?.meta);