From 542cd08f1551be496d0252ea7d35cf599d9c1e4e Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 22 May 2026 19:24:39 +0200 Subject: [PATCH 1/5] feat(included_deposits): filter by withdrawal credential type Add 0x00/0x01/0x02/0x03 checkboxes to the included deposits filter form. Selected types are OR-combined and matched against the first byte of deposits.withdrawalcredentials. Checkbox labels use the same color coding as the rendered credential prefix. --- db/deposits.go | 13 +++++ dbtypes/other.go | 25 +++++----- handlers/included_deposits.go | 47 +++++++++++++------ services/chainservice_deposits.go | 7 +-- .../included_deposits/included_deposits.html | 23 +++++++++ types/models/included_deposits.go | 19 ++++---- 6 files changed, 96 insertions(+), 38 deletions(-) diff --git a/db/deposits.go b/db/deposits.go index 54cee6eca..c41ac7040 100644 --- a/db/deposits.go +++ b/db/deposits.go @@ -139,6 +139,19 @@ func GetDepositsFiltered(ctx context.Context, offset uint64, limit uint32, canon filterOp = "AND" } + if len(txFilter.WithdrawalCredTypes) > 0 { + fmt.Fprintf(&sql, " %v (", filterOp) + for i, credType := range txFilter.WithdrawalCredTypes { + if i > 0 { + fmt.Fprintf(&sql, " OR ") + } + args = append(args, []byte{credType}) + fmt.Fprintf(&sql, " SUBSTRING(deposits.withdrawalcredentials, 1, 1) = $%v", len(args)) + } + fmt.Fprintf(&sql, " )") + filterOp = "AND" + } + if len(txFilter.Address) > 0 { args = append(args, txFilter.Address) fmt.Fprintf(&sql, " %v deposit_txs.tx_sender = $%v", filterOp, len(args)) diff --git a/dbtypes/other.go b/dbtypes/other.go index b9f054e1a..51da517ce 100644 --- a/dbtypes/other.go +++ b/dbtypes/other.go @@ -105,18 +105,19 @@ type MevBlockFilter struct { } type DepositTxFilter struct { - MinIndex uint64 - MaxIndex uint64 - Address []byte - TargetAddress []byte - PublicKey []byte - PublicKeys [][]byte - WithdrawalAddress []byte - ValidatorName string - MinAmount uint64 - MaxAmount uint64 - WithOrphaned uint8 - WithValid uint8 + MinIndex uint64 + MaxIndex uint64 + Address []byte + TargetAddress []byte + PublicKey []byte + PublicKeys [][]byte + WithdrawalAddress []byte + WithdrawalCredTypes []uint8 + ValidatorName string + MinAmount uint64 + MaxAmount uint64 + WithOrphaned uint8 + WithValid uint8 } type DepositFilter struct { diff --git a/handlers/included_deposits.go b/handlers/included_deposits.go index 2ce086102..a9b82c553 100644 --- a/handlers/included_deposits.go +++ b/handlers/included_deposits.go @@ -52,6 +52,7 @@ func IncludedDeposits(w http.ResponseWriter, r *http.Request) { var withOrphaned uint64 var address string var withValid uint64 + var credTypes []uint8 if urlArgs.Has("f") { if urlArgs.Has("f.mini") { @@ -81,6 +82,17 @@ func IncludedDeposits(w http.ResponseWriter, r *http.Request) { if urlArgs.Has("f.valid") { withValid, _ = strconv.ParseUint(urlArgs.Get("f.valid"), 10, 64) } + if vals, ok := urlArgs["f.cred"]; ok { + seen := map[uint8]bool{} + for _, v := range vals { + t, err := strconv.ParseUint(v, 10, 8) + if err != nil || t > 3 || seen[uint8(t)] { + continue + } + seen[uint8(t)] = true + credTypes = append(credTypes, uint8(t)) + } + } } else { withOrphaned = 1 withValid = 1 @@ -88,7 +100,7 @@ func IncludedDeposits(w http.ResponseWriter, r *http.Request) { var pageError error pageError = services.GlobalCallRateLimiter.CheckCallLimit(r, 2) if pageError == nil { - data.Data, pageError = getFilteredIncludedDepositsPageData(pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, uint8(withOrphaned), address, uint8(withValid)) + data.Data, pageError = getFilteredIncludedDepositsPageData(pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, uint8(withOrphaned), address, uint8(withValid), credTypes) } if pageError != nil { handlePageError(w, r, pageError) @@ -100,11 +112,11 @@ func IncludedDeposits(w http.ResponseWriter, r *http.Request) { } } -func getFilteredIncludedDepositsPageData(pageIdx uint64, pageSize uint64, minIndex uint64, maxIndex uint64, publickey string, vname string, minAmount uint64, maxAmount uint64, withOrphaned uint8, address string, withValid uint8) (*models.IncludedDepositsPageData, error) { +func getFilteredIncludedDepositsPageData(pageIdx uint64, pageSize uint64, minIndex uint64, maxIndex uint64, publickey string, vname string, minAmount uint64, maxAmount uint64, withOrphaned uint8, address string, withValid uint8, credTypes []uint8) (*models.IncludedDepositsPageData, error) { pageData := &models.IncludedDepositsPageData{} - pageCacheKey := fmt.Sprintf("included_deposits:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v", pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, withOrphaned, address, withValid) + pageCacheKey := fmt.Sprintf("included_deposits:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v:%v", pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, withOrphaned, address, withValid, credTypes) pageRes, pageErr := services.GlobalFrontendCache.ProcessCachedPage(pageCacheKey, true, pageData, func(pageCall *services.FrontendCacheProcessingPage) interface{} { - pageData, cacheTimeout := buildFilteredIncludedDepositsPageData(pageCall.CallCtx, pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, withOrphaned, address, withValid) + pageData, cacheTimeout := buildFilteredIncludedDepositsPageData(pageCall.CallCtx, pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, withOrphaned, address, withValid, credTypes) pageCall.CacheTimeout = cacheTimeout return pageData }) @@ -118,7 +130,7 @@ func getFilteredIncludedDepositsPageData(pageIdx uint64, pageSize uint64, minInd return pageData, pageErr } -func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, pageSize uint64, minIndex uint64, maxIndex uint64, publickey string, vname string, minAmount uint64, maxAmount uint64, withOrphaned uint8, address string, withValid uint8) (*models.IncludedDepositsPageData, time.Duration) { +func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, pageSize uint64, minIndex uint64, maxIndex uint64, publickey string, vname string, minAmount uint64, maxAmount uint64, withOrphaned uint8, address string, withValid uint8, credTypes []uint8) (*models.IncludedDepositsPageData, time.Duration) { filterArgs := url.Values{} if minIndex != 0 { filterArgs.Add("f.mini", fmt.Sprintf("%v", minIndex)) @@ -147,6 +159,11 @@ func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, if withValid != 0 { filterArgs.Add("f.valid", fmt.Sprintf("%v", withValid)) } + credTypeSet := make(map[uint8]bool, len(credTypes)) + for _, t := range credTypes { + credTypeSet[t] = true + filterArgs.Add("f.cred", fmt.Sprintf("%v", t)) + } pageData := &models.IncludedDepositsPageData{ FilterMinIndex: minIndex, @@ -158,6 +175,7 @@ func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, FilterWithOrphaned: withOrphaned, FilterAddress: address, FilterWithValid: withValid, + FilterCredTypes: credTypeSet, } cacheTimeout := 5 * time.Minute logrus.Debugf("included_deposits page called: %v:%v [%v,%v,%v,%v,%v,%v]", pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount) @@ -183,15 +201,16 @@ func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, // Update to use new filter structure depositFilter := &services.CombinedDepositRequestFilter{ Filter: &dbtypes.DepositTxFilter{ - MinIndex: minIndex, - MaxIndex: maxIndex, - PublicKey: common.FromHex(publickey), - ValidatorName: vname, - MinAmount: minAmount, - MaxAmount: maxAmount, - WithOrphaned: withOrphaned, - Address: common.FromHex(address), - WithValid: withValid, + MinIndex: minIndex, + MaxIndex: maxIndex, + PublicKey: common.FromHex(publickey), + ValidatorName: vname, + MinAmount: minAmount, + MaxAmount: maxAmount, + WithOrphaned: withOrphaned, + Address: common.FromHex(address), + WithValid: withValid, + WithdrawalCredTypes: credTypes, }, } diff --git a/services/chainservice_deposits.go b/services/chainservice_deposits.go index 2db9d1c43..81eaafd00 100644 --- a/services/chainservice_deposits.go +++ b/services/chainservice_deposits.go @@ -121,9 +121,10 @@ func (bs *ChainService) GetDepositRequestsByFilter(ctx context.Context, filter * } txFilter := &dbtypes.DepositTxFilter{ - Address: filter.Filter.Address, - TargetAddress: filter.Filter.TargetAddress, - WithValid: filter.Filter.WithValid, + Address: filter.Filter.Address, + TargetAddress: filter.Filter.TargetAddress, + WithValid: filter.Filter.WithValid, + WithdrawalCredTypes: filter.Filter.WithdrawalCredTypes, } dbOperations, totalReqResults := bs.GetDepositOperationsByFilter(ctx, operationFilter, txFilter, pageOffset, pageSize) diff --git a/templates/included_deposits/included_deposits.html b/templates/included_deposits/included_deposits.html index 9b25aaf09..21cbda8e6 100644 --- a/templates/included_deposits/included_deposits.html +++ b/templates/included_deposits/included_deposits.html @@ -112,6 +112,29 @@

+
+
+ Credential Type +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/types/models/included_deposits.go b/types/models/included_deposits.go index b36b76815..c3bb34cc5 100644 --- a/types/models/included_deposits.go +++ b/types/models/included_deposits.go @@ -6,15 +6,16 @@ import ( // IncludedDepositsPageData is a struct to hold info for the included_deposits page type IncludedDepositsPageData struct { - FilterMinIndex uint64 `json:"filter_mini"` - FilterMaxIndex uint64 `json:"filter_maxi"` - FilterPubKey string `json:"filter_publickey"` - FilterValidatorName string `json:"filter_vname"` - FilterMinAmount uint64 `json:"filter_mina"` - FilterMaxAmount uint64 `json:"filter_maxa"` - FilterWithOrphaned uint8 `json:"filter_orphaned"` - FilterWithValid uint8 `json:"filter_valid"` - FilterAddress string `json:"filter_address"` + FilterMinIndex uint64 `json:"filter_mini"` + FilterMaxIndex uint64 `json:"filter_maxi"` + FilterPubKey string `json:"filter_publickey"` + FilterValidatorName string `json:"filter_vname"` + FilterMinAmount uint64 `json:"filter_mina"` + FilterMaxAmount uint64 `json:"filter_maxa"` + FilterWithOrphaned uint8 `json:"filter_orphaned"` + FilterWithValid uint8 `json:"filter_valid"` + FilterAddress string `json:"filter_address"` + FilterCredTypes map[uint8]bool `json:"filter_cred_types"` Deposits []*IncludedDepositsPageDataDeposit `json:"deposits"` DepositCount uint64 `json:"deposit_count"` From f43c0ae80e0f2b1c7a3f1e740d46c6d76e572be6 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 22 May 2026 19:43:08 +0200 Subject: [PATCH 2/5] included_deposits: include credTypes in debug log --- handlers/included_deposits.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/included_deposits.go b/handlers/included_deposits.go index a9b82c553..a83e67f44 100644 --- a/handlers/included_deposits.go +++ b/handlers/included_deposits.go @@ -178,7 +178,7 @@ func buildFilteredIncludedDepositsPageData(ctx context.Context, pageIdx uint64, FilterCredTypes: credTypeSet, } cacheTimeout := 5 * time.Minute - logrus.Debugf("included_deposits page called: %v:%v [%v,%v,%v,%v,%v,%v]", pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount) + logrus.Debugf("included_deposits page called: %v:%v [%v,%v,%v,%v,%v,%v,%v]", pageIdx, pageSize, minIndex, maxIndex, publickey, vname, minAmount, maxAmount, credTypes) if pageIdx == 1 { pageData.IsDefaultPage = true } else { From cfbf4a0cac3e7d746d860ad1f1e7afe548b79040 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 22 May 2026 19:44:47 +0200 Subject: [PATCH 3/5] included_deposits: apply cred type filter to cached deposits Recent deposits served from the indexer cache bypassed the new SQL clause. Mirror the filter in the in-memory loop so cached and DB results agree. --- services/chainservice_deposits.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/services/chainservice_deposits.go b/services/chainservice_deposits.go index 81eaafd00..fe124a77d 100644 --- a/services/chainservice_deposits.go +++ b/services/chainservice_deposits.go @@ -317,6 +317,13 @@ func (bs *ChainService) GetDepositOperationsByFilter(ctx context.Context, filter } } + if len(txFilter.WithdrawalCredTypes) > 0 { + wdcreds := depositWithTx.WithdrawalCredentials + if len(wdcreds) == 0 || !slices.Contains(txFilter.WithdrawalCredTypes, wdcreds[0]) { + continue + } + } + filteredMatches = append(filteredMatches, depositWithTx) } From 3be64280c1fe2e2ad999eaf54e60a73500dd85b8 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 22 May 2026 19:47:38 +0200 Subject: [PATCH 4/5] api(deposits_included): expose cred_type filter Match the new included_deposits UI filter: parse repeatable cred_type query parameter (values 0-3, deduplicated) and forward into DepositTxFilter.WithdrawalCredTypes. --- handlers/api/deposits_included_v1.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/handlers/api/deposits_included_v1.go b/handlers/api/deposits_included_v1.go index 18c811d15..25c83dfb7 100644 --- a/handlers/api/deposits_included_v1.go +++ b/handlers/api/deposits_included_v1.go @@ -64,6 +64,7 @@ type APIDepositIncludedInfo struct { // @Param with_orphaned query int false "Include orphaned deposits (0=canonical only, 1=include all, 2=orphaned only)" // @Param address query string false "Filter by depositor address" // @Param with_valid query int false "Filter by signature validity (0=invalid only, 1=valid only, 2=all)" +// @Param cred_type query []int false "Filter by withdrawal credential type prefix byte (0-3). Repeat the parameter to include multiple types, e.g. cred_type=1&cred_type=2." collectionFormat(multi) // @Success 200 {object} APIDepositsIncludedResponse // @Failure 400 {object} map[string]string "Invalid parameters" // @Failure 500 {object} map[string]string "Internal server error" @@ -170,6 +171,19 @@ func APIDepositsIncludedV1(w http.ResponseWriter, r *http.Request) { } } + // Withdrawal credential type filter (repeatable: cred_type=0&cred_type=1) + if credVals, ok := query["cred_type"]; ok { + seen := map[uint8]bool{} + for _, v := range credVals { + t, err := strconv.ParseUint(v, 10, 8) + if err != nil || t > 3 || seen[uint8(t)] { + continue + } + seen[uint8(t)] = true + depositFilter.WithdrawalCredTypes = append(depositFilter.WithdrawalCredTypes, uint8(t)) + } + } + // Get deposits included in blocks using the proper service method combinedFilter := &services.CombinedDepositRequestFilter{ Filter: depositFilter, From 0049b21e479602e8390152d28094f22ae1bb0b49 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Fri, 22 May 2026 19:54:22 +0200 Subject: [PATCH 5/5] api-docs: regenerate swagger for cred_type param --- docs/docs.go | 10 ++++++++++ docs/swagger.json | 10 ++++++++++ docs/swagger.yaml | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/docs/docs.go b/docs/docs.go index 65dd330df..ac8457357 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -425,6 +425,16 @@ const docTemplate = `{ "description": "Filter by signature validity (0=invalid only, 1=valid only, 2=all)", "name": "with_valid", "in": "query" + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "multi", + "description": "Filter by withdrawal credential type prefix byte (0-3). Repeat the parameter to include multiple types, e.g. cred_type=1\u0026cred_type=2.", + "name": "cred_type", + "in": "query" } ], "responses": { diff --git a/docs/swagger.json b/docs/swagger.json index 1954bd4fd..c9ea9ae6a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -422,6 +422,16 @@ "description": "Filter by signature validity (0=invalid only, 1=valid only, 2=all)", "name": "with_valid", "in": "query" + }, + { + "type": "array", + "items": { + "type": "integer" + }, + "collectionFormat": "multi", + "description": "Filter by withdrawal credential type prefix byte (0-3). Repeat the parameter to include multiple types, e.g. cred_type=1\u0026cred_type=2.", + "name": "cred_type", + "in": "query" } ], "responses": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 886c1729e..e9eb20022 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2116,6 +2116,14 @@ paths: in: query name: with_valid type: integer + - collectionFormat: multi + description: Filter by withdrawal credential type prefix byte (0-3). Repeat + the parameter to include multiple types, e.g. cred_type=1&cred_type=2. + in: query + items: + type: integer + name: cred_type + type: array produces: - application/json responses: