diff --git a/docs/typescript.md b/docs/typescript.md
index 8435cae..362400e 100644
--- a/docs/typescript.md
+++ b/docs/typescript.md
@@ -122,6 +122,52 @@ const updatedTransactionMeta = {
};
```
+#### Derive types from authoritative sources instead of re-declaring them
+
+When a type already exists at an authoritative source — a controller's state type, a function's return, a package's exported type, a schema — reference and **derive** from it rather than hand-writing a fresh type that restates its shape. This is the type-level counterpart to preferring inference: indexed access (`State['field']`), `typeof`, `ReturnType` / `Parameters`, and utility types (`Pick` / `Omit` / `Partial`) let a derived type track its source automatically.
+
+- A hand-written type that restates an existing one **duplicates** the shape: every reader must reconcile the two, and every change has to touch both.
+- The restatement is a _guess_ at the source's shape, and the guess is almost always **wider** than the real type — it admits values the authoritative type would reject (see [Avoid unintentionally widening an inferred type with a type annotation](#avoid-unintentionally-widening-an-inferred-type-with-a-type-annotation)). Marking every field optional is the most common way this happens.
+- Because the copy is hard-coded rather than derived, it is **brittle against code drift** (see [Prefer type inference over annotations and assertions](#prefer-type-inference-over-annotations-and-assertions)): when the source evolves the copy silently goes stale, and the compiler cannot flag the divergence, so the failure surfaces at runtime rather than at build.
+
+Define a fresh type only when no authoritative source exists — a genuinely new shape at a boundary the code owns. Before defining, look for the source: the type is often already exported from the `@metamask/*` package the value comes from.
+
+**Example ([🔗 permalink](#example-5f6f1503-1ce6-432f-8d38-61d0f44a03ce)):**
+
+🚫 A dependency typed too wide, which forces every consumer to re-cast the shape by hand:
+
+```typescript
+type Dependencies = {
+ // `Record` is a placeholder, not a type: it discards the real
+ // state shape, so nothing downstream can be read without a cast.
+ getMetaMaskState: () => Record;
+};
+
+// Reading a field requires re-declaring its shape at the use site …
+const { tokensChainsCache } = getMetaMaskState() as {
+ tokensChainsCache?: Record }>;
+};
+
+// … and passing the state to a typed selector requires an `as never` to force
+// the mismatch through:
+getTokensControllerAllTokens({ metamask: getMetaMaskState() } as never);
+```
+
+✅ Type the dependency from the authoritative controller state; the field is then derivable and the selector type-checks, with no casts:
+
+```typescript
+import type { TokenListState } from '@metamask/assets-controllers';
+
+type Dependencies = {
+ // Composed from the controller-state types the module actually reads.
+ getMetaMaskState: () => TokenListState /* & …other controller state… */;
+};
+
+const { tokensChainsCache } = getMetaMaskState(); // typed; no cast
+```
+
+The `Record` did not save work — it moved the work downstream into a cast at every use site, including an `as never` that silently defeats the selector's parameter type. Deriving the dependency from the authoritative state removes both.
+
### Type Annotations
An explicit type annotation may be used to override an inferred type if: