• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

decentraland / lamb2 / 25053009309

28 Apr 2026 12:33PM UTC coverage: 83.013% (+1.8%) from 81.198%
25053009309

Pull #462

github

LautaroPetaccio
fix: address review comments on the new endpoints

p1:
- validate-signature: catch the syntaxerror from request.json() and
  surface it as invalidrequesterror so a malformed body returns 400
  instead of 500. integration test sends '{invalid' and asserts the
  status + 'JSON' message.
- validate-signature: cap authchain length at 10. unauthenticated
  endpoint x per-link eth_call x shared l1provider was a real cost-
  amplification vector. real chains are 2-5 links. unit tests cover
  empty, longer-than-cap, and the happy path within the cap.

p2:
- validate-signature: per-element shape guard on authchain - each
  entry must be an object with string type, payload and signature.
  three new unit tests for non-object element, missing field, and
  wrong field type.
- fetch-items-by-filters: prune deprecated networks - drop ropsten,
  kovan, rinkeby, goerli from l1_networks and mumbai from l2_networks.
  none have appeared in production urns since 2023.
- fetch-items-by-filters: extract the magic 1000 in the collections
  graphql query into max_collections_per_query, with a comment that
  it is independent of the per-item limit.
- wearables-catalog: comment why `remaining >= 0` is inclusive of
  zero - off-chain exactly filling the limit still requires one
  on-chain probe to detect hasmore.
- items-by-owner: stop echoing the raw collectionid in the
  invalidrequesterror message - describe the constraint instead.
- items-by-owner: drop the dead `?? undefined` on definitions[i],
  the array element is already typed t | undefined.

caught a real bug in the array body case while writing the array
test - typeof [] === 'object' so the original check let arrays
through to the authchain guard. the new check rejects them up-front
with the right message.
Pull Request #462: feat: implement legacy lambdas endpoints in lamb2

729 of 973 branches covered (74.92%)

Branch coverage included in aggregate %.

212 of 215 new or added lines in 10 files covered. (98.6%)

5 existing lines in 1 file now uncovered.

2032 of 2353 relevant lines covered (86.36%)

40.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.58
/src/components.ts
1
import { l1Contracts, L1Network, L2Network } from '@dcl/catalyst-contracts'
23✔
2
import { createDotEnvConfigComponent } from '@well-known-components/env-config-provider'
23✔
3
import {
23✔
4
  createServerComponent,
5
  createStatusCheckComponent,
6
  instrumentHttpServerWithPromClientRegistry
7
} from '@well-known-components/http-server'
8
import { createLogComponent } from '@well-known-components/logger'
23✔
9
import { createMetricsComponent } from '@well-known-components/metrics'
23✔
10
import { createContentClient } from 'dcl-catalyst-client'
23✔
11
import { HTTPProvider } from 'eth-connect'
23✔
12
import { createCatalystsFetcher } from './adapters/catalysts-fetcher'
23✔
13
import { createContentServerUrl } from './adapters/content-server-url'
23✔
14
import {
23✔
15
  createEmoteDefinitionsFetcherComponent,
16
  createWearableDefinitionsFetcherComponent
17
} from './adapters/definitions-fetcher'
18
import { createElementsFetcherComponent, createLegacyElementsFetcherComponent } from './adapters/elements-fetcher'
23✔
19
import { createEntitiesFetcherComponent } from './adapters/entities-fetcher'
23✔
20
import { createNameDenylistFetcher } from './adapters/name-denylist-fetcher'
23✔
21
import { createPOIsFetcher } from './adapters/pois-fetcher'
23✔
22
import { createResourcesStatusComponent } from './adapters/resource-status'
23✔
23
import { createStatusComponent } from './adapters/status'
23✔
24
import { fetchBaseWearables } from './logic/fetch-elements/fetch-base-items'
23✔
25
import { fetchEmotes, fetchWearables } from './logic/fetch-elements/fetch-items'
23✔
26
import { fetchLands } from './logic/fetch-elements/fetch-lands'
23✔
27
import { fetchNames } from './logic/fetch-elements/fetch-names'
23✔
28
import { fetchAllThirdPartyWearables } from './logic/fetch-elements/fetch-third-party-wearables'
23✔
29
import { metricDeclarations } from './metrics'
23✔
30
import { createFetchComponent } from './ports/fetch'
23✔
31
import { createOwnershipCachesComponent } from './ports/ownership-caches'
23✔
32
import { createTheGraphComponent, TheGraphComponent } from './ports/the-graph'
23✔
33
import { AppComponents, BaseWearable, GlobalContext } from './types'
34
import { createThirdPartyProvidersGraphFetcherComponent } from './adapters/third-party-providers-graph-fetcher'
23✔
35
import { createThirdPartyProvidersStorage } from './logic/third-party-providers-storage'
23✔
36
import { createProfilesComponent } from './adapters/profiles'
23✔
37
import { IFetchComponent } from '@well-known-components/interfaces'
38
import { createAlchemyNftFetcher } from './adapters/alchemy-nft-fetcher'
23✔
39
import { createMarketplaceApiFetcher } from './adapters/marketplace-api-fetcher'
23✔
40
import { createThirdPartyContractRegistry } from './ports/ownership-checker/third-party-contract-registry'
23✔
41
import { createThirdPartyItemChecker } from './ports/ownership-checker/third-party-item-checker'
23✔
42
import { createParcelRightsComponent } from './adapters/parcel-rights-fetcher'
23✔
43
import { fetchNameOwner } from './logic/fetch-elements/fetch-name-owner'
23✔
44
import { fetchAllPermissions } from './logic/fetch-elements/fetch-permissions'
23✔
45
import { createThirdPartyCollectionsCacheWarmer } from './adapters/third-party-collections-cache-warmer'
23✔
46

47
// Initialize all the components of the app
48
export async function initComponents(
23✔
49
  fetchComponent?: IFetchComponent,
50
  theGraphComponent?: TheGraphComponent
51
): Promise<AppComponents> {
52
  const config = await createDotEnvConfigComponent({ path: ['.env.default', '.env'] })
43✔
53
  const logs = await createLogComponent({})
43✔
54
  const server = await createServerComponent<GlobalContext>(
43✔
55
    { config, logs },
56
    {
57
      cors: {
58
        maxAge: 36000
59
      }
60
    }
61
  )
62
  const statusChecks = await createStatusCheckComponent({ server, config })
43✔
63
  const fetch = fetchComponent ? fetchComponent : await createFetchComponent()
43!
64
  const metrics = await createMetricsComponent(metricDeclarations, { config })
43✔
65
  await instrumentHttpServerWithPromClientRegistry({ server, metrics, config, registry: metrics.registry! })
43✔
66

67
  const contentServerUrl = await createContentServerUrl({ config })
43✔
68
  const content = createContentClient({ url: contentServerUrl, fetcher: fetch })
43✔
69

70
  const theGraph = theGraphComponent
43!
71
    ? theGraphComponent
72
    : await createTheGraphComponent({ config, logs, fetch, metrics })
73

74
  const ownershipCaches = await createOwnershipCachesComponent({ config })
43✔
75

76
  const wearableDefinitionsFetcher = await createWearableDefinitionsFetcherComponent({
43✔
77
    config,
78
    logs,
79
    content,
80
    contentServerUrl
81
  })
82

83
  // TODO: use content client for collection items fetching. prevent injecting contentServerUrl and fetch components.
84
  const entitiesFetcher = await createEntitiesFetcherComponent({ config, logs, content, contentServerUrl, fetch })
43✔
85

86
  // Create marketplace API fetcher for primary data source
87
  const marketplaceApiFetcher = await createMarketplaceApiFetcher({ config, fetch, logs })
43✔
88

89
  const emoteDefinitionsFetcher = await createEmoteDefinitionsFetcherComponent({
43✔
90
    config,
91
    logs,
92
    content,
93
    contentServerUrl
94
  })
95
  const baseWearablesFetcher = createElementsFetcherComponent<BaseWearable>(
43✔
96
    { logs, theGraph, marketplaceApiFetcher },
97
    async (_deps, _address) => {
UNCOV
98
      const elements = await fetchBaseWearables({ entitiesFetcher })
×
99
      return { elements, totalAmount: elements.length }
×
100
    }
101
  )
102

103
  const wearablesFetcher = createElementsFetcherComponent({ logs, theGraph, marketplaceApiFetcher }, fetchWearables)
43✔
104

105
  const emotesFetcher = createElementsFetcherComponent({ logs, theGraph, marketplaceApiFetcher }, fetchEmotes)
43✔
106

107
  const namesFetcher = createElementsFetcherComponent({ logs, theGraph, marketplaceApiFetcher }, fetchNames)
43✔
108

109
  const landsFetcher = createLegacyElementsFetcherComponent({ logs }, async (address) => fetchLands(theGraph, address))
43✔
110

111
  const landsPermissionsFetcher = createElementsFetcherComponent({ logs, theGraph }, async (_deps, address) => {
43✔
112
    const elements = await fetchAllPermissions({ theGraph }, address)
7✔
113
    return { elements, totalAmount: elements.length }
6✔
114
  })
115

116
  const resourcesStatusCheck = createResourcesStatusComponent({ logs })
43✔
117
  const status = await createStatusComponent({ logs, fetch })
43✔
118

119
  const l1Network: L1Network = ((await config.getString('ETH_NETWORK')) ?? 'mainnet') as L1Network
43✔
120
  const contracts = l1Contracts[l1Network]
43✔
121
  if (!contracts) {
43!
UNCOV
122
    throw new Error(`Invalid ETH_NETWORK ${l1Network}`)
×
123
  }
124
  const l2Network: L2Network = l1Network === 'mainnet' ? 'polygon' : 'amoy'
43!
125
  const l1Provider = new HTTPProvider(`https://rpc.decentraland.org/${encodeURIComponent(l1Network)}?project=lamb2`, {
43✔
126
    fetch: fetch.fetch
127
  })
128
  const l2Provider = new HTTPProvider(`https://rpc.decentraland.org/${encodeURIComponent(l2Network)}?project=lamb2`, {
43✔
129
    fetch: fetch.fetch
130
  })
131
  const catalystsFetcher = await createCatalystsFetcher({ l1Provider }, l1Network)
43✔
132
  const poisFetcher = await createPOIsFetcher({ l2Provider }, l2Network)
43✔
133
  const nameDenylistFetcher = await createNameDenylistFetcher({ l1Provider }, l1Network)
43✔
134
  const parcelRightsFetcher = await createParcelRightsComponent(
43✔
135
    {
136
      logs,
137
      theGraph
138
    },
139
    l1Network
140
  )
141

142
  const l1ThirdPartyContractRegistry = await createThirdPartyContractRegistry(logs, l1Provider, l1Network as any, '.')
43✔
143
  const l2ThirdPartyContractRegistry = await createThirdPartyContractRegistry(logs, l2Provider, l2Network as any, '.')
43✔
144
  const l1ThirdPartyItemChecker = await createThirdPartyItemChecker(
43✔
145
    { entitiesFetcher, logs },
146
    l1Provider,
147
    l1ThirdPartyContractRegistry
148
  )
149
  const l2ThirdPartyItemChecker = await createThirdPartyItemChecker(
43✔
150
    { entitiesFetcher, logs },
151
    l2Provider,
152
    l2ThirdPartyContractRegistry
153
  )
154

155
  const thirdPartyProvidersGraphFetcher = createThirdPartyProvidersGraphFetcherComponent({ theGraph })
43✔
156
  const thirdPartyProvidersStorage = await createThirdPartyProvidersStorage({
43✔
157
    logs,
158
    thirdPartyProvidersGraphFetcher
159
  })
160

161
  const thirdPartyWearablesFetcher = createElementsFetcherComponent(
43✔
162
    { logs, theGraph, marketplaceApiFetcher },
163
    async (_deps, address) => {
UNCOV
164
      const elements = await fetchAllThirdPartyWearables(
×
165
        { alchemyNftFetcher, contentServerUrl, thirdPartyProvidersStorage, fetch, entitiesFetcher, metrics },
166
        address
167
      )
UNCOV
168
      return { elements, totalAmount: elements.length }
×
169
    }
170
  )
171

172
  const alchemyNftFetcher = await createAlchemyNftFetcher({ config, logs, fetch })
43✔
173

174
  const nameOwnerFetcher = createElementsFetcherComponent({ logs, theGraph }, async (_deps, name) => {
43✔
UNCOV
175
    const { owner } = await fetchNameOwner({ theGraph }, name)
×
176
    return { elements: owner ? [{ owner }] : [], totalAmount: owner ? 1 : 0 }
×
177
  })
178

179
  const profiles = await createProfilesComponent({
43✔
180
    alchemyNftFetcher,
181
    metrics,
182
    content,
183
    contentServerUrl,
184
    entitiesFetcher,
185
    theGraph,
186
    config,
187
    fetch,
188
    ownershipCaches,
189
    thirdPartyProvidersStorage,
190
    logs,
191
    wearablesFetcher,
192
    emotesFetcher,
193
    namesFetcher,
194
    l1ThirdPartyItemChecker,
195
    l2ThirdPartyItemChecker
196
  })
197

198
  // Create cache warmer component
199
  const thirdPartyCollectionsCacheWarmer = await createThirdPartyCollectionsCacheWarmer({
43✔
200
    config,
201
    logs,
202
    thirdPartyProvidersStorage,
203
    entitiesFetcher,
204
    fetch,
205
    contentServerUrl
206
  })
207

208
  return {
43✔
209
    config,
210
    logs,
211
    server,
212
    statusChecks,
213
    fetch,
214
    metrics,
215
    content,
216
    theGraph,
217
    ownershipCaches,
218
    baseWearablesFetcher,
219
    wearablesFetcher,
220
    wearableDefinitionsFetcher,
221
    emoteDefinitionsFetcher,
222
    entitiesFetcher,
223
    thirdPartyWearablesFetcher,
224
    emotesFetcher,
225
    namesFetcher,
226
    landsFetcher,
227
    landsPermissionsFetcher,
228
    parcelRightsFetcher,
229
    thirdPartyProvidersGraphFetcher,
230
    thirdPartyProvidersStorage,
231
    contentServerUrl,
232
    resourcesStatusCheck,
233
    status,
234
    l1Provider,
235
    l2Provider,
236
    catalystsFetcher,
237
    poisFetcher,
238
    nameDenylistFetcher,
239
    profiles,
240
    alchemyNftFetcher,
241
    l1ThirdPartyItemChecker,
242
    l2ThirdPartyItemChecker,
243
    marketplaceApiFetcher,
244
    nameOwnerFetcher,
245
    thirdPartyCollectionsCacheWarmer
246
  }
247
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc