@polygonlabs/staker-pool-allocations-schemas 0.1.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 (114) hide show
  1. package/MIGRATION.md +5 -0
  2. package/dist/allocation-history.d.ts +30 -0
  3. package/dist/allocation-history.d.ts.map +1 -0
  4. package/dist/allocation-history.js +29 -0
  5. package/dist/allocation-history.js.map +1 -0
  6. package/dist/allocation.d.ts +31 -0
  7. package/dist/allocation.d.ts.map +1 -0
  8. package/dist/allocation.js +40 -0
  9. package/dist/allocation.js.map +1 -0
  10. package/dist/codegen.d.ts +6 -0
  11. package/dist/codegen.d.ts.map +1 -0
  12. package/dist/codegen.js +32 -0
  13. package/dist/codegen.js.map +1 -0
  14. package/dist/common.d.ts +19 -0
  15. package/dist/common.d.ts.map +1 -0
  16. package/dist/common.js +63 -0
  17. package/dist/common.js.map +1 -0
  18. package/dist/distribution.d.ts +60 -0
  19. package/dist/distribution.d.ts.map +1 -0
  20. package/dist/distribution.js +43 -0
  21. package/dist/distribution.js.map +1 -0
  22. package/dist/error.d.ts +10 -0
  23. package/dist/error.d.ts.map +1 -0
  24. package/dist/error.js +15 -0
  25. package/dist/error.js.map +1 -0
  26. package/dist/index.d.ts +13 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +17 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/management.d.ts +80 -0
  31. package/dist/management.d.ts.map +1 -0
  32. package/dist/management.js +126 -0
  33. package/dist/management.js.map +1 -0
  34. package/dist/proof.d.ts +10 -0
  35. package/dist/proof.d.ts.map +1 -0
  36. package/dist/proof.js +25 -0
  37. package/dist/proof.js.map +1 -0
  38. package/dist/registry-management.d.ts +525 -0
  39. package/dist/registry-management.d.ts.map +1 -0
  40. package/dist/registry-management.js +33 -0
  41. package/dist/registry-management.js.map +1 -0
  42. package/dist/registry-public.d.ts +1323 -0
  43. package/dist/registry-public.d.ts.map +1 -0
  44. package/dist/registry-public.js +31 -0
  45. package/dist/registry-public.js.map +1 -0
  46. package/dist/routes-management/distributions.d.ts +137 -0
  47. package/dist/routes-management/distributions.d.ts.map +1 -0
  48. package/dist/routes-management/distributions.js +72 -0
  49. package/dist/routes-management/distributions.js.map +1 -0
  50. package/dist/routes-management/excluded-addresses.d.ts +132 -0
  51. package/dist/routes-management/excluded-addresses.d.ts.map +1 -0
  52. package/dist/routes-management/excluded-addresses.js +84 -0
  53. package/dist/routes-management/excluded-addresses.js.map +1 -0
  54. package/dist/routes-management/operational.d.ts +48 -0
  55. package/dist/routes-management/operational.d.ts.map +1 -0
  56. package/dist/routes-management/operational.js +47 -0
  57. package/dist/routes-management/operational.js.map +1 -0
  58. package/dist/routes-public/addresses.d.ts +79 -0
  59. package/dist/routes-public/addresses.d.ts.map +1 -0
  60. package/dist/routes-public/addresses.js +45 -0
  61. package/dist/routes-public/addresses.js.map +1 -0
  62. package/dist/routes-public/allocations.d.ts +155 -0
  63. package/dist/routes-public/allocations.d.ts.map +1 -0
  64. package/dist/routes-public/allocations.js +81 -0
  65. package/dist/routes-public/allocations.js.map +1 -0
  66. package/dist/routes-public/distributions.d.ts +123 -0
  67. package/dist/routes-public/distributions.d.ts.map +1 -0
  68. package/dist/routes-public/distributions.js +55 -0
  69. package/dist/routes-public/distributions.js.map +1 -0
  70. package/dist/routes-public/operational.d.ts +46 -0
  71. package/dist/routes-public/operational.d.ts.map +1 -0
  72. package/dist/routes-public/operational.js +36 -0
  73. package/dist/routes-public/operational.js.map +1 -0
  74. package/dist/routes-public/proofs.d.ts +50 -0
  75. package/dist/routes-public/proofs.d.ts.map +1 -0
  76. package/dist/routes-public/proofs.js +34 -0
  77. package/dist/routes-public/proofs.js.map +1 -0
  78. package/dist/routes-public/wire.d.ts +60 -0
  79. package/dist/routes-public/wire.d.ts.map +1 -0
  80. package/dist/routes-public/wire.js +62 -0
  81. package/dist/routes-public/wire.js.map +1 -0
  82. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  83. package/dist/unclaimed-proofs.d.ts +20 -0
  84. package/dist/unclaimed-proofs.d.ts.map +1 -0
  85. package/dist/unclaimed-proofs.js +22 -0
  86. package/dist/unclaimed-proofs.js.map +1 -0
  87. package/dist/zod.d.ts +3 -0
  88. package/dist/zod.d.ts.map +1 -0
  89. package/dist/zod.js +8 -0
  90. package/dist/zod.js.map +1 -0
  91. package/openapi.json +1215 -0
  92. package/package.json +49 -0
  93. package/src/allocation-history.ts +35 -0
  94. package/src/allocation.ts +51 -0
  95. package/src/codegen.ts +50 -0
  96. package/src/common.ts +76 -0
  97. package/src/distribution.ts +52 -0
  98. package/src/error.ts +17 -0
  99. package/src/index.ts +16 -0
  100. package/src/management.ts +158 -0
  101. package/src/proof.ts +27 -0
  102. package/src/registry-management.ts +36 -0
  103. package/src/registry-public.ts +33 -0
  104. package/src/routes-management/distributions.ts +87 -0
  105. package/src/routes-management/excluded-addresses.ts +103 -0
  106. package/src/routes-management/operational.ts +60 -0
  107. package/src/routes-public/addresses.ts +58 -0
  108. package/src/routes-public/allocations.ts +104 -0
  109. package/src/routes-public/distributions.ts +69 -0
  110. package/src/routes-public/operational.ts +55 -0
  111. package/src/routes-public/proofs.ts +46 -0
  112. package/src/routes-public/wire.ts +74 -0
  113. package/src/unclaimed-proofs.ts +27 -0
  114. package/src/zod.ts +9 -0
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@polygonlabs/staker-pool-allocations-schemas",
3
+ "version": "0.1.0",
4
+ "description": "Zod request/response schemas and OpenAPI registry for the PIP-85 staker pool API + management surfaces.",
5
+ "type": "module",
6
+ "files": [
7
+ "src",
8
+ "dist",
9
+ "openapi.json",
10
+ "MIGRATION.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./codegen": {
18
+ "types": "./dist/codegen.d.ts",
19
+ "import": "./dist/codegen.js"
20
+ },
21
+ "./openapi.json": "./openapi.json"
22
+ },
23
+ "dependencies": {
24
+ "@asteasolutions/zod-to-openapi": "^8.0.0",
25
+ "@polygonlabs/openapi-registry": "^2.1.1",
26
+ "zod": "^4.3.6"
27
+ },
28
+ "devDependencies": {
29
+ "@tsconfig/node-ts": "^23.0.0",
30
+ "@tsconfig/node24": "^24.0.0",
31
+ "@types/node": "^24.0.0",
32
+ "typescript": "^5.5.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/0xPolygon/staker-pool-allocations.git",
37
+ "directory": "packages/schemas"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "pnpm run typecheck && tsc -b tsconfig.lib.json && node --conditions=@polygonlabs/source scripts/generate-spec.ts",
44
+ "build:clean": "pnpm run typecheck && rm -rf dist out-tsc *.tsbuildinfo && tsc -b tsconfig.lib.json && node --conditions=@polygonlabs/source scripts/generate-spec.ts",
45
+ "typecheck": "tsc -b",
46
+ "clean": "rm -rf dist out-tsc node_modules *.tsbuildinfo",
47
+ "codegen-drift-check": "pnpm run build && git diff --exit-code -- openapi.json"
48
+ }
49
+ }
@@ -0,0 +1,35 @@
1
+ import { AddressSchema, DistributionIdSchema, Hex32Schema, WeiSchema } from './common.ts';
2
+ import { z } from './zod.ts';
3
+
4
+ // One row in the per-address cross-distribution allocation history.
5
+ // Distinct from `AllocationRowSchema` because the history view carries
6
+ // distribution-scoped fields (id, merkleRoot, completedAt) and the
7
+ // claim status derived live from the `pos-staker-pool-claims`
8
+ // subgraph (spec §3.6, D10).
9
+ export const AllocationHistoryRowSchema = z
10
+ .object({
11
+ distributionId: DistributionIdSchema,
12
+ address: AddressSchema,
13
+ amount: WeiSchema,
14
+ // null for excluded addresses: stored allocation, excluded from the
15
+ // merkle tree (settled off-chain), not claimable on-chain.
16
+ leafIndex: z.number().int().nonnegative().nullable(),
17
+ merkleRoot: Hex32Schema,
18
+ completedAt: z.iso.datetime(),
19
+ status: z.enum(['claimed', 'unclaimed']).openapi({
20
+ description:
21
+ "Derived live at request time by diffing the address's allocations against the " +
22
+ 'pos-staker-pool-claims subgraph. Never persisted (spec D10).'
23
+ })
24
+ })
25
+ .openapi('AllocationHistoryRow');
26
+
27
+ export type AllocationHistoryRow = z.infer<typeof AllocationHistoryRowSchema>;
28
+
29
+ export const AllocationHistoryResponseSchema = z
30
+ .object({
31
+ items: z.array(AllocationHistoryRowSchema)
32
+ })
33
+ .openapi('AllocationHistoryResponse');
34
+
35
+ export type AllocationHistoryResponse = z.infer<typeof AllocationHistoryResponseSchema>;
@@ -0,0 +1,51 @@
1
+ import { AddressSchema, WeiSchema } from './common.ts';
2
+ import { z } from './zod.ts';
3
+
4
+ export const AllocationRowSchema = z
5
+ .object({
6
+ address: AddressSchema,
7
+ amount: WeiSchema,
8
+ // null for excluded addresses: they have a stored allocation but are
9
+ // excluded from the merkle tree (settled off-chain) and cannot claim.
10
+ leafIndex: z.number().int().nonnegative().nullable()
11
+ })
12
+ .openapi('AllocationRow');
13
+
14
+ export type AllocationRow = z.infer<typeof AllocationRowSchema>;
15
+
16
+ export const AllocationListResponseSchema = z
17
+ .object({
18
+ items: z.array(AllocationRowSchema),
19
+ nextCursor: z.string().nullable()
20
+ })
21
+ .openapi('AllocationListResponse');
22
+
23
+ export type AllocationListResponse = z.infer<typeof AllocationListResponseSchema>;
24
+
25
+ // A single off-chain (excluded-from-merkle) allocation. These addresses
26
+ // (excluded addresses) receive a stored allocation but no merkle leaf — they
27
+ // cannot claim on-chain and are settled manually. No `leafIndex` (always
28
+ // null), so it's omitted from this shape entirely.
29
+ export const ExcludedAllocationRowSchema = z
30
+ .object({
31
+ address: AddressSchema,
32
+ amount: WeiSchema
33
+ })
34
+ .openapi('ExcludedAllocationRow');
35
+
36
+ export type ExcludedAllocationRow = z.infer<typeof ExcludedAllocationRowSchema>;
37
+
38
+ // The full set of off-chain allocations for a distribution — the manual
39
+ // settlement list. Not paginated: the excluded set is the configured list (e.g. LST contracts)
40
+ // contracts (a small, bounded set), returned in one response. `totalAmount`
41
+ // is the sum to settle off-chain and equals the distribution's
42
+ // `total_allocated - claimable_total`.
43
+ export const ExcludedAllocationsResponseSchema = z
44
+ .object({
45
+ items: z.array(ExcludedAllocationRowSchema),
46
+ total: z.number().int().nonnegative(),
47
+ totalAmount: WeiSchema
48
+ })
49
+ .openapi('ExcludedAllocationsResponse');
50
+
51
+ export type ExcludedAllocationsResponse = z.infer<typeof ExcludedAllocationsResponseSchema>;
package/src/codegen.ts ADDED
@@ -0,0 +1,50 @@
1
+ // Codegen-facing surface: registered name === exported binding name.
2
+ //
3
+ // The @polygonlabs/zod-to-openapi-heyapi plugin emits
4
+ // `import { <RegisteredName> } from '<schemasFrom>'` for every schema a
5
+ // public-registry route response `$ref`s, and its codegen-time audit fails
6
+ // the client build when a registered name has no matching named export of
7
+ // the `schemasFrom` module. The generated client's response transformer
8
+ // then calls `<RegisteredName>.parseAsync(data)` at runtime against these
9
+ // exact values.
10
+ //
11
+ // This lives on its own `./codegen` subpath (the plugin README's
12
+ // "schemas exposed under a subpath export" case) rather than the main
13
+ // barrel: the barrel star-exports `z.infer` *types* under these same
14
+ // names (`AllocationRow`, `DistributionSummary`, …), and TypeScript
15
+ // rejects a re-exported value alias alongside a re-exported type of the
16
+ // same name (TS2300). A dedicated module sidesteps the collision without
17
+ // renaming anything the worker and rest-api already import.
18
+ //
19
+ // Response slots in the public registry reference the wire variants (see
20
+ // `routes-public/wire.ts` for why `z.encode` can't take the canonical
21
+ // address-bearing schemas), so those names bind the wire values — the
22
+ // client validates exactly the shape the service emits. Schemas that are
23
+ // already encode-safe bind their canonical definition.
24
+ export {
25
+ AllocationHistoryResponseWireSchema as AllocationHistoryResponse,
26
+ AllocationHistoryRowWireSchema as AllocationHistoryRow,
27
+ AllocationListResponseWireSchema as AllocationListResponse,
28
+ AllocationRowWireSchema as AllocationRow,
29
+ ExcludedAllocationRowWireSchema as ExcludedAllocationRow,
30
+ ExcludedAllocationsResponseWireSchema as ExcludedAllocationsResponse,
31
+ ProofResponseWireSchema as ProofResponse
32
+ } from './routes-public/wire.ts';
33
+ export { OperationalStatusSchema as OperationalStatus } from './routes-public/operational.ts';
34
+ export {
35
+ DistributionDetailSchema as DistributionDetail,
36
+ DistributionListResponseSchema as DistributionListResponse,
37
+ DistributionSummarySchema as DistributionSummary
38
+ } from './distribution.ts';
39
+ export {
40
+ UnclaimedProofBatchSchema as UnclaimedProofBatch,
41
+ UnclaimedProofsResponseSchema as UnclaimedProofsResponse
42
+ } from './unclaimed-proofs.ts';
43
+
44
+ // Canonical error response schemas, renamed to drop the `Schema` suffix so
45
+ // the binding matches the OpenAPI registered name — the TypedRegistry
46
+ // auto-injects these into every route's 400/500 response slots.
47
+ export {
48
+ ErrorResponseSchema as ErrorResponse,
49
+ ValidationErrorResponseSchema as ValidationErrorResponse
50
+ } from '@polygonlabs/openapi-registry/error-schemas';
package/src/common.ts ADDED
@@ -0,0 +1,76 @@
1
+ import { z } from './zod.ts';
2
+
3
+ // Network discriminator surfaced as a path parameter (`/v1/:network/...`)
4
+ // and persisted on every domain row. The set is closed — adding a third
5
+ // network is a schema + migration change, not a config flip.
6
+ export const NetworkSchema = z.enum(['mainnet', 'amoy']).openapi({
7
+ description:
8
+ 'Polygon network the distribution covers. `mainnet` is PoS mainnet, `amoy` is the PoS testnet.',
9
+ example: 'mainnet'
10
+ });
11
+
12
+ export type Network = z.infer<typeof NetworkSchema>;
13
+
14
+ // Lowercased 0x-prefixed 40-hex EVM address. Transform normalises so
15
+ // downstream code never has to think about case.
16
+ export const AddressSchema = z
17
+ .string()
18
+ .regex(/^0x[0-9a-fA-F]{40}$/)
19
+ .transform((s) => s.toLowerCase())
20
+ .openapi({ example: '0xabc0000000000000000000000000000000000001' });
21
+
22
+ // Distributions are named by the month they cover, e.g. "2026-04". The
23
+ // spec deliberately picks a human-readable scheme rather than UUIDs so
24
+ // the URL path is meaningful to operators.
25
+ export const DistributionIdSchema = z
26
+ .string()
27
+ .regex(/^[0-9]{4}-[0-9]{2}$/)
28
+ .openapi({ example: '2026-04' });
29
+
30
+ // All POL amounts cross the API boundary as decimal strings — uint256
31
+ // can't fit in a JS number, and JSON has no native bigint. Callers cast
32
+ // to BigInt at the seam.
33
+ export const WeiSchema = z
34
+ .string()
35
+ .regex(/^[0-9]+$/)
36
+ .openapi({ example: '1234567890000000000' });
37
+
38
+ // 32-byte hex with the 0x prefix — used for merkle roots, inputs
39
+ // hashes, and subgraph block hashes.
40
+ export const Hex32Schema = z
41
+ .string()
42
+ .regex(/^0x[0-9a-fA-F]{64}$/)
43
+ .openapi({ example: '0x' + 'a'.repeat(64) });
44
+
45
+ // POL/USD price as a decimal string with up to 6 fractional digits (the
46
+ // micro-USD precision the worker and storage use). String, not number, so
47
+ // it round-trips through the API and the inputs hash without float drift —
48
+ // `0.42` always means `0.420000`. Used both for the trigger override and the
49
+ // stored price on the distribution detail.
50
+ export const PolUsdPriceSchema = z
51
+ .string()
52
+ .regex(/^[0-9]+(\.[0-9]{1,6})?$/)
53
+ .openapi({ example: '0.423100' });
54
+
55
+ // Cursor-based pagination. The cursor is an opaque base64-encoded
56
+ // JSON object — decoded server-side only. The shape inside differs
57
+ // per endpoint (e.g. `{ id }` for distributions, `{ leafIndex }` for
58
+ // allocations); the encoder/decoder lives next to each route.
59
+ export const PaginationQuerySchema = z.object({
60
+ cursor: z.string().min(1).optional(),
61
+ limit: z.coerce.number().int().min(1).max(100).default(50)
62
+ });
63
+
64
+ export type PaginationQuery = z.infer<typeof PaginationQuerySchema>;
65
+
66
+ // Opaque cursor encode/decode pair. Forces every endpoint to round-trip
67
+ // the same way so the cursor format is consistent across the API.
68
+ export function encodeCursor<T>(payload: T): string {
69
+ return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');
70
+ }
71
+
72
+ export function decodeCursor<T>(cursor: string): T {
73
+ // Throws SyntaxError on malformed JSON / base64; route handlers wrap
74
+ // this in a typed 400 via the error envelope.
75
+ return JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as T;
76
+ }
@@ -0,0 +1,52 @@
1
+ import { DistributionIdSchema, Hex32Schema, PolUsdPriceSchema, WeiSchema } from './common.ts';
2
+ import { z } from './zod.ts';
3
+
4
+ const DistributionStatusSchema = z
5
+ .enum(['pending', 'running', 'completed', 'failed'])
6
+ .openapi({ example: 'completed' });
7
+
8
+ // List-view shape. Drops the full subgraph-pin block hashes (visible in
9
+ // the detail endpoint only) and the error blob.
10
+ export const DistributionSummarySchema = z
11
+ .object({
12
+ distributionId: DistributionIdSchema,
13
+ status: DistributionStatusSchema,
14
+ startPoSBlock: WeiSchema,
15
+ endPoSBlock: WeiSchema,
16
+ merkleRoot: Hex32Schema.nullable(),
17
+ totalAllocated: WeiSchema.nullable(),
18
+ completedAt: z.iso.datetime().nullable()
19
+ })
20
+ .openapi('DistributionSummary');
21
+
22
+ export type DistributionSummary = z.infer<typeof DistributionSummarySchema>;
23
+
24
+ // Detail view extends the summary with subgraph pins + checkpoint
25
+ // count + the inputs hash. Used by `GET /v1/distributions/:id` and
26
+ // (with the `error` field) by the management variant.
27
+ export const DistributionDetailSchema = DistributionSummarySchema.extend({
28
+ checkpointCount: z.number().int().nonnegative(),
29
+ // POL/USD price applied this run and the USD claim ceiling (decimal
30
+ // strings, up to 6 dp). `maxClaimableUsdThreshold` is null when the
31
+ // threshold was inert for the run. Both null until completion.
32
+ polUsdPrice: PolUsdPriceSchema.nullable(),
33
+ maxClaimableUsdThreshold: PolUsdPriceSchema.nullable(),
34
+ inputsHash: Hex32Schema.nullable(),
35
+ posStakingSubgraphBlock: WeiSchema.nullable(),
36
+ posStakingSubgraphHash: Hex32Schema.nullable(),
37
+ posCheckpointsSubgraphBlock: WeiSchema.nullable(),
38
+ posCheckpointsSubgraphHash: Hex32Schema.nullable(),
39
+ posPriorityFeesSubgraphBlock: WeiSchema.nullable(),
40
+ posPriorityFeesSubgraphHash: Hex32Schema.nullable()
41
+ }).openapi('DistributionDetail');
42
+
43
+ export type DistributionDetail = z.infer<typeof DistributionDetailSchema>;
44
+
45
+ export const DistributionListResponseSchema = z
46
+ .object({
47
+ items: z.array(DistributionSummarySchema),
48
+ nextCursor: z.string().nullable()
49
+ })
50
+ .openapi('DistributionListResponse');
51
+
52
+ export type DistributionListResponse = z.infer<typeof DistributionListResponseSchema>;
package/src/error.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { z } from './zod.ts';
2
+
3
+ // Canonical error envelope every 4xx and 5xx response uses. Per spec
4
+ // §7.1: 4xx responses populate `details` from VError.info; 5xx wipe
5
+ // both `details` and the stack to avoid leaking internal state to
6
+ // untrusted callers.
7
+ export const ErrorEnvelopeSchema = z
8
+ .object({
9
+ error: z.object({
10
+ code: z.string().openapi({ example: 'DISTRIBUTION_NOT_FOUND' }),
11
+ message: z.string().openapi({ example: 'Distribution not found.' }),
12
+ details: z.record(z.string(), z.unknown()).optional()
13
+ })
14
+ })
15
+ .openapi('ErrorEnvelope');
16
+
17
+ export type ErrorEnvelope = z.infer<typeof ErrorEnvelopeSchema>;
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ // Package barrel: the extended Zod instance (with the OpenAPI registry) plus
2
+ // every request/response schema. Consumers (the api + worker apps) import the
3
+ // schemas they need from here; internal files still reference `./common.ts`
4
+ // and `./zod.ts` by relative path.
5
+ export * from './zod.ts';
6
+ export * from './common.ts';
7
+ export * from './error.ts';
8
+ export * from './distribution.ts';
9
+ export * from './allocation.ts';
10
+ export * from './allocation-history.ts';
11
+ export * from './unclaimed-proofs.ts';
12
+ export * from './proof.ts';
13
+ export * from './management.ts';
14
+ export * from './registry-public.ts';
15
+ export * from './registry-management.ts';
16
+ export * from './routes-management/operational.ts';
@@ -0,0 +1,158 @@
1
+ import {
2
+ AddressSchema,
3
+ DistributionIdSchema,
4
+ NetworkSchema,
5
+ PolUsdPriceSchema,
6
+ WeiSchema
7
+ } from './common.ts';
8
+ import { z } from './zod.ts';
9
+
10
+ // Body shape for `POST /v1/:network/management/distributions`.
11
+ // `distributionId` is optional — when omitted, the handler derives the
12
+ // monthly id (`YYYY-MM`) from the current date in UTC. Providing it
13
+ // makes the call idempotent: re-POSTing with an existing id returns
14
+ // 202 with the existing status rather than enqueueing a duplicate.
15
+ //
16
+ // `force` is the escape hatch for re-running a completed distribution
17
+ // — operator confirms the inputs really changed, the prior completed
18
+ // row + its allocations get hard-deleted, and the worker reruns. The
19
+ // audit trail relies on the surrounding ops log + the persisted
20
+ // `inputsHash`; no soft-delete or version history here in Phase 1.
21
+ export const DistributionTriggerRequestSchema = z
22
+ .object({
23
+ startPoSBlock: WeiSchema.openapi({
24
+ description: 'Inclusive lower bound of the PoS block range to allocate over.'
25
+ }),
26
+ endPoSBlock: WeiSchema.openapi({
27
+ description: 'Exclusive upper bound of the PoS block range to allocate over.'
28
+ }),
29
+ distributionId: DistributionIdSchema.optional().openapi({
30
+ description:
31
+ 'Optional. When omitted, derived as the current `YYYY-MM` (UTC). ' +
32
+ 'Providing it makes the call idempotent — repeat POSTs with the ' +
33
+ 'same id return the existing status.'
34
+ }),
35
+ force: z
36
+ .boolean()
37
+ .optional()
38
+ .openapi({
39
+ description:
40
+ 'Re-run a previously completed distribution covering the same range. ' +
41
+ 'Hard-deletes the prior completed row + allocations before the worker ' +
42
+ 'starts. Use only after operator-confirmed input drift.'
43
+ }),
44
+ polUsdPrice: PolUsdPriceSchema.optional().openapi({
45
+ description:
46
+ 'Optional POL/USD price override (decimal string, up to 6 dp). When ' +
47
+ 'omitted, the worker fetches the spot price from CoinGecko at run ' +
48
+ "start. Pass a prior run's stored `polUsdPrice` to reproduce its " +
49
+ 'merkle root and leaves on a `force` re-run — the price feeds the ' +
50
+ 'USD-threshold exclusion that decides which allocations stay on-chain.'
51
+ })
52
+ })
53
+ .openapi('DistributionTriggerRequest');
54
+
55
+ export type DistributionTriggerRequest = z.infer<typeof DistributionTriggerRequestSchema>;
56
+
57
+ // 202 response — the worker runs out-of-band on the same pod after
58
+ // the handler returns. Operators poll the public distributions
59
+ // endpoint (or the management `:id` view, Phase 1 follow-up) to
60
+ // observe the terminal status.
61
+ export const DistributionTriggerResponseSchema = z
62
+ .object({
63
+ network: NetworkSchema,
64
+ distributionId: DistributionIdSchema,
65
+ status: z.enum(['pending', 'running', 'completed', 'failed']).openapi({
66
+ description:
67
+ 'Status snapshot at the moment the trigger returned. A freshly ' +
68
+ 'enqueued run starts at `pending`; an idempotent hit on an ' +
69
+ 'existing id returns whatever the current row says.'
70
+ })
71
+ })
72
+ .openapi('DistributionTriggerResponse');
73
+
74
+ export type DistributionTriggerResponse = z.infer<typeof DistributionTriggerResponseSchema>;
75
+
76
+ // 200 response for `DELETE /v1/:network/management/distributions/:id`.
77
+ // `priorStatus` is the state the row was in before removal — never
78
+ // `completed`, which the handler refuses (those are published roots).
79
+ export const DistributionDeleteResponseSchema = z
80
+ .object({
81
+ network: NetworkSchema,
82
+ distributionId: DistributionIdSchema,
83
+ removed: z.boolean(),
84
+ priorStatus: z.enum(['pending', 'running', 'failed']),
85
+ deletedAllocations: z
86
+ .number()
87
+ .int()
88
+ .nonnegative()
89
+ .openapi({
90
+ description:
91
+ 'Number of allocation rows removed alongside the distribution. Normally 0 — ' +
92
+ 'allocations are only persisted at completion, and completed rows cannot be deleted.'
93
+ })
94
+ })
95
+ .openapi('DistributionDeleteResponse');
96
+
97
+ export type DistributionDeleteResponse = z.infer<typeof DistributionDeleteResponseSchema>;
98
+
99
+ // Body for `POST /v1/:network/management/excluded-addresses`. Adds (or
100
+ // re-labels, on conflict) an address that receives an allocation but is
101
+ // excluded from the merkle tree and settled off-chain (LST contracts and
102
+ // other manually-handled addresses).
103
+ export const AddExcludedAddressRequestSchema = z
104
+ .object({
105
+ address: AddressSchema.openapi({
106
+ description: 'Address to exclude from the merkle tree (settled off-chain).'
107
+ }),
108
+ label: z.string().min(1).max(256).openapi({
109
+ description: 'Human-readable note for the entry, e.g. the LST protocol name.'
110
+ }),
111
+ createdBy: z
112
+ .string()
113
+ .min(1)
114
+ .max(128)
115
+ .optional()
116
+ .openapi({
117
+ description:
118
+ 'Actor id recorded on the row (who added it). Defaults to "management-api" ' +
119
+ 'when omitted — the x-api-key gate carries no operator identity.'
120
+ })
121
+ })
122
+ .openapi('AddExcludedAddressRequest');
123
+
124
+ export type AddExcludedAddressRequest = z.infer<typeof AddExcludedAddressRequestSchema>;
125
+
126
+ // A single excluded-address row as returned by the management API.
127
+ export const ManagedExcludedAddressSchema = z
128
+ .object({
129
+ network: NetworkSchema,
130
+ address: AddressSchema,
131
+ label: z.string(),
132
+ createdBy: z.string(),
133
+ createdAt: z.iso.datetime(),
134
+ updatedAt: z.iso.datetime()
135
+ })
136
+ .openapi('ManagedExcludedAddress');
137
+
138
+ export type ManagedExcludedAddress = z.infer<typeof ManagedExcludedAddressSchema>;
139
+
140
+ export const ExcludedAddressListResponseSchema = z
141
+ .object({
142
+ items: z.array(ManagedExcludedAddressSchema)
143
+ })
144
+ .openapi('ExcludedAddressListResponse');
145
+
146
+ export type ExcludedAddressListResponse = z.infer<typeof ExcludedAddressListResponseSchema>;
147
+
148
+ // `DELETE` is idempotent: `removed` is false when the address wasn't in
149
+ // the list (no 404), so re-running a delete is safe.
150
+ export const RemoveExcludedAddressResponseSchema = z
151
+ .object({
152
+ network: NetworkSchema,
153
+ address: AddressSchema,
154
+ removed: z.boolean()
155
+ })
156
+ .openapi('RemoveExcludedAddressResponse');
157
+
158
+ export type RemoveExcludedAddressResponse = z.infer<typeof RemoveExcludedAddressResponseSchema>;
package/src/proof.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { AddressSchema, Hex32Schema, WeiSchema } from './common.ts';
2
+ import { z } from './zod.ts';
3
+
4
+ // Response for `GET /v1/:network/distributions/:id/allocations/:addr/proof`.
5
+ // `proof` is the array of 32-byte hex sibling hashes the on-chain
6
+ // `MultiRootDistributor` needs to reconstruct the merkle root from the leaf
7
+ // `keccak256(abi.encodePacked(leafIndex, address, amount))`. Generated on
8
+ // demand from the persisted allocations table — no proof is stored at
9
+ // distribution time (spec D9). Pass `leafIndex`, `address`, `amount`, and
10
+ // `proof` straight to `claim(root, index, account, amount, proof)`.
11
+ export const ProofResponseSchema = z
12
+ .object({
13
+ merkleRoot: Hex32Schema.openapi({
14
+ description: 'Distribution merkle root — same value as `DistributionDetail.merkleRoot`.'
15
+ }),
16
+ address: AddressSchema,
17
+ amount: WeiSchema,
18
+ leafIndex: z.number().int().nonnegative().openapi({
19
+ description: 'Index of this leaf in the canonical (lowercased-address-sorted) tree.'
20
+ }),
21
+ proof: z.array(Hex32Schema).openapi({
22
+ description: 'Sibling-hash array. Pass straight to the claimer contract.'
23
+ })
24
+ })
25
+ .openapi('ProofResponse');
26
+
27
+ export type ProofResponse = z.infer<typeof ProofResponseSchema>;
@@ -0,0 +1,36 @@
1
+ import { TypedRegistry } from '@polygonlabs/openapi-registry';
2
+
3
+ import { addManagementDistributionRoutes } from './routes-management/distributions.ts';
4
+ import { addExcludedAddressRoutes } from './routes-management/excluded-addresses.ts';
5
+ import { addOperationalRoutes } from './routes-management/operational.ts';
6
+
7
+ // Registry composition for the WORKER service's management surface (the
8
+ // public read API has its own registry). `TypedRegistry` accumulates the
9
+ // registered operations and security schemes into its *type* via chained
10
+ // method returns — the chain's final value is what `buildManagementRegistry`
11
+ // returns, and the worker derives the registry-driven router, the typed
12
+ // handler binding (`HandlerMapFor<typeof buildManagementRegistry, ...>`),
13
+ // and the served OpenAPI document from that one inferred type.
14
+ //
15
+ // One chained expression on purpose: discarding any link's return drops the
16
+ // type-level narrow even though the runtime registration still happens (see
17
+ // @polygonlabs/openapi-registry's "The one rule"). To add a route, append a
18
+ // `.registerPath({...})` inside the relevant helper under
19
+ // `./routes-management/`, or add a new domain helper and `.with(...)` here.
20
+ export const buildManagementRegistry = () =>
21
+ new TypedRegistry()
22
+ // Operations opt in via `security: [{ ApiKeyAuth: [] }]`; the
23
+ // registry-driven router then requires an `ApiKeyAuth` handler at
24
+ // compile time and runs it before request validation. The operational
25
+ // probes declare no security and stay unauthenticated.
26
+ .registerSecurityScheme('ApiKeyAuth', {
27
+ type: 'apiKey',
28
+ in: 'header',
29
+ name: 'x-api-key',
30
+ description:
31
+ 'Management API key (`MANAGEMENT_API_KEY`, Secret-Manager-backed). ' +
32
+ 'Required on every management operation.'
33
+ })
34
+ .with(addOperationalRoutes)
35
+ .with(addManagementDistributionRoutes)
36
+ .with(addExcludedAddressRoutes);
@@ -0,0 +1,33 @@
1
+ // Registry composition for the public REST API (`packages/rest-api`).
2
+ //
3
+ // `TypedRegistry` accumulates registered operations into its type via
4
+ // chained method returns — `registerPath` and `with(fn)` each return a
5
+ // `TypedRegistry` narrowed with what was just registered. The chain's
6
+ // final value is what `buildPublicRegistry` returns; the rest-api service
7
+ // derives its typed handler map from it via
8
+ // `HandlerMapFor<typeof buildPublicRegistry>` and the OpenAPI document is
9
+ // generated from the same registry's `definitions`, so the served spec
10
+ // and the mounted routes cannot drift.
11
+ //
12
+ // To add a route: append a `.registerPath({...})` call inside the relevant
13
+ // per-domain helper under `./routes-public/`, or add a new domain helper
14
+ // and `.with(addNewDomainRoutes)` here. Always chain or capture every
15
+ // registration — see `@polygonlabs/openapi-registry`'s "The one rule".
16
+ //
17
+ // The worker's management surface composes its own registry; this one
18
+ // carries only the read-only public endpoints.
19
+ import { TypedRegistry } from '@polygonlabs/openapi-registry';
20
+
21
+ import { addAddressRoutes } from './routes-public/addresses.ts';
22
+ import { addAllocationRoutes } from './routes-public/allocations.ts';
23
+ import { addDistributionRoutes } from './routes-public/distributions.ts';
24
+ import { addOperationalRoutes } from './routes-public/operational.ts';
25
+ import { addProofRoutes } from './routes-public/proofs.ts';
26
+
27
+ export const buildPublicRegistry = () =>
28
+ new TypedRegistry()
29
+ .with(addOperationalRoutes)
30
+ .with(addDistributionRoutes)
31
+ .with(addAllocationRoutes)
32
+ .with(addAddressRoutes)
33
+ .with(addProofRoutes);