Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7058592
fix(sdk): preserve safe integer Merkle path offsets
sirdeggen Sep 8, 2026
d633740
chore(sdk): align candidate version registry
sirdeggen Sep 8, 2026
e2d6980
feat(overlay): harden BASM protocol validation and Go interop
sirdeggen Sep 8, 2026
752464d
fix(overlay): bind BASM admitted indices without coinbase maturity
sirdeggen Sep 8, 2026
e419d8e
fix(sonar): overlay BASM protocol findings
sirdeggen Sep 9, 2026
3fd2bf5
Merge remote-tracking branch 'origin/main' into codex/basm-protocol-h…
sirdeggen Sep 9, 2026
ea1e532
fix(ci): override js-yaml 3.15.2 for GHSA-2883
sirdeggen Sep 9, 2026
e5aeed5
fix(ci): record js-yaml override removal rehearsal
sirdeggen Sep 9, 2026
029908f
fix(ci): ratchet override count for js-yaml 3.15.2
sirdeggen Sep 9, 2026
e19009a
fix(ci): raise SDK and wallet platform bundle budgets
sirdeggen Sep 9, 2026
26ca890
fix(ci): raise remaining SDK-consumer bundle budgets
sirdeggen Sep 9, 2026
27dbc21
fix(ci): raise wallet client esbuild brotli budget
sirdeggen Sep 9, 2026
0ce1684
fix(ci): raise did-client UMD budget for SDK helper growth
sirdeggen Sep 9, 2026
060a1a8
fix(ci): prettier browser budget JSON
sirdeggen Sep 9, 2026
54591e0
fix(ci): raise did-client esbuild budget
sirdeggen Sep 9, 2026
8aec890
fix(ci): raise did-client vite budget
sirdeggen Sep 9, 2026
b7715bd
fix(ci): raise SDK vite and esbuild compressed budgets
sirdeggen Sep 9, 2026
a2a32b9
test(overlay): cover BASM remote limits for patch coverage
sirdeggen Sep 9, 2026
53a1765
fix(ci): parent and finalize Codecov coverage reports
sirdeggen Sep 10, 2026
89d8670
docs: reverify resource and TTN rollout gates
sirdeggen Sep 10, 2026
743e29f
fix(ci): require HTTPS for Codecov polling
sirdeggen Sep 10, 2026
98453b2
Merge latest main and resolve PR #520 integration conflicts
sirdeggen Sep 10, 2026
958f7fe
test(overlay): advertise the Go interop listener as explicit loopback
sirdeggen Sep 10, 2026
15db752
Merge origin/main into BASM hardening
sirdeggen Sep 14, 2026
5c4506c
Merge latest BASM hardening changes
sirdeggen Sep 14, 2026
e46fefc
fix: avoid eager fetch dependency in broadcaster
sirdeggen Sep 14, 2026
740e36a
fix: defer unavailable fetch failure until send
sirdeggen Sep 14, 2026
9b7c99d
test: provide fetch spy target in node
sirdeggen Sep 14, 2026
49e1063
test: match complete admission acknowledgment
sirdeggen Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1115,11 +1115,43 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.cov.outputs.files }}
name: ts-stack
# Make the current attempt distinguishable from a previous rerun of
# the same commit when waiting for Codecov to finish processing it.
name: ts-stack-${{ github.run_id }}-${{ github.run_attempt }}
slug: bsv-blockchain/ts-stack
# CI checks out GitHub's synthetic PR merge commit but Codecov
# reports against the branch head. Give Codecov the actual base
# parent so it does not fall back to its last covered main commit.
commit_parent: ${{ github.event.pull_request.base.sha || github.event.before }}
# Coverage upload is non-blocking: Codecov CLI GPG-verify/outages must not gate CI.
fail_ci_if_error: false

- name: Wait for Codecov to merge the uploaded report
if: steps.cov.outputs.has-coverage == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
env:
CODECOV_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
CODECOV_BUILD_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
CODECOV_UPLOAD_NAME: ts-stack-${{ github.run_id }}-${{ github.run_attempt }}
run: |
deadline=$((SECONDS + 300))
commit_url="https://api.codecov.io/api/v2/github/bsv-blockchain/repos/ts-stack/commits/${CODECOV_COMMIT}/"
uploads_url="${commit_url}uploads/"
while true; do
if commit=$(curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --connect-timeout 5 --max-time 15 "$commit_url") &&
[ "$(jq --raw-output '.state // empty' <<<"$commit")" = 'complete' ] &&
uploads=$(curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --connect-timeout 5 --max-time 15 "$uploads_url") &&
[ "$(jq --arg build_url "$CODECOV_BUILD_URL" --arg name "$CODECOV_UPLOAD_NAME" '[.results[]? | select(.build_url == $build_url and .name == $name and .state_name == "MERGED")] | length' <<<"$uploads")" -gt 0 ]; then
echo "Codecov merged ${CODECOV_UPLOAD_NAME} for ${CODECOV_COMMIT}."
break
fi

if [ "$SECONDS" -ge "$deadline" ]; then
echo "::error::Codecov did not merge coverage for ${CODECOV_COMMIT} within five minutes."
exit 1
fi
sleep 5
done

- name: Publish finalized Codecov notifications
if: steps.cov.outputs.has-coverage == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
Expand Down
2 changes: 1 addition & 1 deletion docs/packages/overlays/overlay-discovery-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: '@bsv/overlay-discovery-services'
kind: package
domain: overlays
npm: '@bsv/overlay-discovery-services'
version: '2.2.1'
version: '2.2.2'
last_updated: '2026-08-27'
last_verified: '2026-08-27'
review_cadence_days: 30
Expand Down
2 changes: 1 addition & 1 deletion docs/packages/overlays/overlay-express.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: '@bsv/overlay-express'
kind: package
domain: overlays
npm: '@bsv/overlay-express'
version: '2.6.1'
version: '2.7.0'
last_updated: '2026-08-27'
last_verified: '2026-08-27'
review_cadence_days: 30
Expand Down
2 changes: 1 addition & 1 deletion docs/packages/overlays/overlay-topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: '@bsv/overlay-topics'
kind: package
domain: overlays
npm: '@bsv/overlay-topics'
version: '1.7.1'
version: '1.7.2'
last_updated: '2026-08-27'
last_verified: '2026-08-27'
review_cadence_days: 30
Expand Down
2 changes: 1 addition & 1 deletion docs/packages/overlays/overlay.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: '@bsv/overlay'
kind: package
domain: overlays
npm: '@bsv/overlay'
version: '2.3.1'
version: '2.4.0'
last_updated: '2026-08-27'
last_verified: '2026-08-27'
review_cadence_days: 30
Expand Down
6 changes: 6 additions & 0 deletions docs/packages/sdk/bsv-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ repo: 'https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk'

# @bsv/sdk

The unpublished 2.5.0 candidate corrects BUMP offset arithmetic above 32 bits
through `Number.MAX_SAFE_INTEGER`, preserving existing wire encodings. Root
calculation, extraction, combination and trimming use the same exact numeric
domain; malformed non-integer and unsafe offsets fail explicitly. No consumer
migration is needed for this correction.

The foundational cryptographic and transaction library for the BSV blockchain. Zero external dependencies — all cryptographic primitives have been validated by a third-party auditor. Every other library in the ts-stack builds on top of `@bsv/sdk`. <!-- audio: ts-stack.m4a @ 27:00 -->

Provides low-level primitives (keys, signatures, hashing), script construction and execution, transaction creation and signing, and integration interfaces for wallets and overlay networks.
Expand Down
104 changes: 52 additions & 52 deletions docs/reference/package-api-migrations.md

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions governance/package-release-notes.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions packages/helpers/did-client/browser-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@
"prohibitedExports": [],
"maximumBytes": {
"vite": {
"raw": 490000,
"gzip": 126000,
"brotli": 104000
"raw": 530000,
"gzip": 140000,
"brotli": 115000
},
"esbuild": {
"raw": 380000,
"gzip": 116000,
"brotli": 98000
"raw": 400000,
"gzip": 125000,
"brotli": 105000
}
},
"umd": {
"path": "dist/umd/bundle.js",
"global": "did-client",
"maximumBytes": {
"raw": 375000,
"gzip": 112000,
"brotli": 95000
"raw": 400000,
"gzip": 120000,
"brotli": 102000
}
}
}
3 changes: 3 additions & 0 deletions packages/overlays/overlay-discovery-services/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

- Advances the packed overlay dependency candidate for BASM validation hardening.
Package runtime behavior is unchanged; no consumer migration is required.

### Added

- Support `ttn` WalletAdvertiser instances with chain-isolated TerraTestNet
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bsv/overlay-discovery-services",
"version": "2.2.1",
"version": "2.2.2",
"sideEffects": false,
"engines": {
"node": ">=22"
Expand Down
6 changes: 6 additions & 0 deletions packages/overlays/overlay-express/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

- Validated BASM heights, hashes and txid sets while retaining numeric-string
heights, public CORS, topic-free raw transactions, configured limits and sync
defaults. Added explicit unsupported-capability codes and optional trusted
full-block transaction counts on injected header resolvers. No deployment or
storage migration is required.

### Added

- Added the `ttn` runtime network, TTN SHIP/SLAP roots, and explicit
Expand Down
14 changes: 14 additions & 0 deletions packages/overlays/overlay-express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,20 @@ import OverlayExpress, { OverlayMonitor } from '@bsv/overlay-express'
const { default: OverlayExpress, OverlayMonitor } = require('@bsv/overlay-express')
```

The five public BASM JSON POST routes validate nonnegative safe-integer heights
(including existing numeric strings), 32-byte hexadecimal hashes/txids, and
request count limits. Empty raw-transaction requests remain valid; compound
proof requests require txids. The raw-transaction route does not require
`x-bsv-topic`. Missing engine/storage BASM capabilities retain HTTP 400 with
`{ status: 'error', message, code: 'BASM_UNSUPPORTED' }`. CORS, access rules,
configured limits, and automatic synchronization defaults are unchanged.

An injected topic-anchor header resolver may additionally return
`blockTransactionCount` obtained independently for the same canonical block
hash. Existing Chaintracks/provider adapters remain header-only; they do not
claim this stronger position evidence. See the core engine's
[BASM validation and recovery limits](../overlay/README.md#basm-peer-validation-and-current-recovery-limits).

## Example Usage

Here's a quick example:
Expand Down
2 changes: 1 addition & 1 deletion packages/overlays/overlay-express/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bsv/overlay-express",
"version": "2.6.1",
"version": "2.7.0",
"sideEffects": false,
"engines": {
"node": ">=22"
Expand Down
115 changes: 90 additions & 25 deletions packages/overlays/overlay-express/src/OverlayExpress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ export type TopicAnchorHeaderResolver = (blockHeight: number) => Promise<
blockHeight: number
blockHash: string
merkleRoot?: string
/** Independent full block count bound to blockHash; never an overlay subset count. */
blockTransactionCount?: number
}
| undefined
>
Expand Down Expand Up @@ -251,6 +253,15 @@ class PublicRequestError extends Error {
}
}

class UnsupportedBasmCapabilityError extends PublicRequestError {
readonly code = 'BASM_UNSUPPORTED'

constructor() {
super('BASM capability is not supported by this Overlay engine')
this.name = 'UnsupportedBasmCapabilityError'
}
}

function publicErrorMessage(
error: unknown,
fallback: string = 'Request could not be processed'
Expand Down Expand Up @@ -2249,6 +2260,13 @@ export default class OverlayExpress {
return res.status(200).json(await handler(req))
} catch (error) {
console.error(chalk.red(`Error in ${path}:`), error)
if (error instanceof Error && 'code' in error && error.code === 'BASM_UNSUPPORTED') {
return res.status(400).json({
status: 'error',
message: 'BASM capability is not supported by this Overlay engine',
code: error.code
})
}
return res.status(400).json({
status: 'error',
message: publicErrorMessage(error)
Expand All @@ -2261,59 +2279,106 @@ export default class OverlayExpress {
)
}

const requireTxids = (value: unknown): string[] => {
if (!Array.isArray(value) || !value.every(txid => typeof txid === 'string')) {
throw new PublicRequestError('txids must be an array of strings')
type BasmCapability =
| 'provideTopicAnchorTip'
| 'provideTopicAnchorRange'
| 'provideAdmittedList'
| 'provideCompoundMerklePath'
| 'provideRawTransactions'
const requireBasmCapability = (capability: BasmCapability): void => {
if (typeof (engine as Partial<BASMCapableEngine>)[capability] !== 'function') {
throw new UnsupportedBasmCapabilityError()
}
}

const requireBasmHeight = (value: unknown, field: string): number => {
if (
(typeof value !== 'number' && typeof value !== 'string') ||
(typeof value === 'string' && value.trim().length === 0)
) {
throw new PublicRequestError(`${field} must be a nonnegative safe integer`)
}
const height = Number(value)
if (!Number.isSafeInteger(height) || height < 0) {
throw new PublicRequestError(`${field} must be a nonnegative safe integer`)
}
return height
}

const requireBlockHash = (value: unknown): string | undefined => {
if (value === undefined) return undefined
if (typeof value !== 'string' || !/^[0-9a-fA-F]{64}$/.test(value)) {
throw new PublicRequestError('blockHash must be a 32-byte hexadecimal string')
}
return value.toLowerCase()
}

const requireTxids = (value: unknown, requireAtLeastOne: boolean = true): string[] => {
if (!Array.isArray(value) || (requireAtLeastOne && value.length === 0)) {
throw new PublicRequestError('txids must be a non-empty array')
}
if (maxBasmTxids !== -1 && value.length > maxBasmTxids) {
throw new PublicRequestError(`txids must contain at most ${maxBasmTxids} entries`)
}
return value
const seen = new Set<string>()
const txids: string[] = []
for (const txid of value) {
if (typeof txid !== 'string' || !/^[0-9a-fA-F]{64}$/.test(txid)) {
throw new PublicRequestError('txids must contain 32-byte hexadecimal transaction IDs')
}
const normalized = txid.toLowerCase()
if (seen.has(normalized)) {
throw new PublicRequestError('txids must not contain duplicates')
}
seen.add(normalized)
txids.push(normalized)
}
return txids
}

registerJsonRoute(
'/requestTopicAnchorTip',
async req => await basmEngine.provideTopicAnchorTip(readBasmTopic(req))
)
registerJsonRoute('/requestTopicAnchorTip', async req => {
requireBasmCapability('provideTopicAnchorTip')
return await basmEngine.provideTopicAnchorTip(readBasmTopic(req))
})

registerJsonRoute('/requestTopicAnchorRange', async req => {
const { fromHeight, toHeight } = req.body
const from = Number(fromHeight)
const to = Number(toHeight)
if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to) || from < 0 || to < from) {
const from = requireBasmHeight(fromHeight, 'fromHeight')
const to = requireBasmHeight(toHeight, 'toHeight')
if (to < from) {
throw new PublicRequestError('fromHeight and toHeight must define a valid ascending range')
}
if (maxBasmAnchorRange !== -1 && to - from + 1 > maxBasmAnchorRange) {
throw new PublicRequestError(
`topic anchor range must contain at most ${maxBasmAnchorRange} blocks`
)
}
requireBasmCapability('provideTopicAnchorRange')
return await basmEngine.provideTopicAnchorRange(readBasmTopic(req), from, to)
})

registerJsonRoute('/requestAdmittedList', async req => {
const { blockHeight, blockHash } = req.body
return await basmEngine.provideAdmittedList(
readBasmTopic(req),
Number(blockHeight),
typeof blockHash === 'string' ? blockHash : undefined
)
const height = requireBasmHeight(blockHeight, 'blockHeight')
const hash = requireBlockHash(blockHash)
requireBasmCapability('provideAdmittedList')
return await basmEngine.provideAdmittedList(readBasmTopic(req), height, hash)
})

registerJsonRoute('/requestCompoundMerklePath', async req => {
const topic = readBasmTopic(req)
const { blockHeight, txids } = req.body
return await basmEngine.provideCompoundMerklePath(
topic,
Number(blockHeight),
requireTxids(txids)
)
const height = requireBasmHeight(blockHeight, 'blockHeight')
const requestedTxids = requireTxids(txids)
requireBasmCapability('provideCompoundMerklePath')
return await basmEngine.provideCompoundMerklePath(topic, height, requestedTxids)
})

registerJsonRoute(
'/requestRawTransactions',
async req => await basmEngine.provideRawTransactions(requireTxids(req.body.txids))
)
registerJsonRoute('/requestRawTransactions', async req => {
const txids = requireTxids(req.body.txids, false)
requireBasmCapability('provideRawTransactions')
return await basmEngine.provideRawTransactions(txids)
})

/**
* ============== ADMIN ROUTES ==============
Expand Down
Loading
Loading