Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 6 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,18 @@ Foundry. Key components include:
- predeployed in the Besu genesis so its address stays stable in the local
sandbox
- bond lifecycle contracts under `contracts/src/norges-bank/`
- `BondManager`: issuer-controlled entrypoint for creating bonds and auctions
and for finalising auctions with DvP settlement
- `BondManager`: issuer-controlled entrypoint for creating bonds and auctions,
finalising auctions with DvP settlement, and paying coupons, buybacks, and
redemptions; every cash leg settles in `Wnok` against one government
reserve account fixed at deployment (`GOV_RESERVE`), see ADR 0004
- `BondAuction`: sealed-bid auction contract that accepts encrypted bids and
publishes allocations during finalisation
- `BondToken`: partitioned bond token keyed by ISIN
- `BondDvP`: settlement component that coordinates the cash leg against the
bond leg
- cash-side token
- `Wnok` (`contracts/src/norges-bank/Wnok.sol`): mock cash token used for
local settlement flows
- `Wnok` (`contracts/src/norges-bank/Wnok.sol`): mock central-bank cash token
used for every bond cash leg and for local settlement flows

> **Planned — decided, not yet implemented:** this ERC-1410-based security-token
> design is slated to migrate to canonical **ERC-3643 (T-REX)** — a shared on-chain
Expand Down
2 changes: 1 addition & 1 deletion docs/DOCUMENTATION_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This file lists the documentation that is most likely to need updates when behav
- `docs/decisions/0001-local-chain-besu-clique-london-baseline.md`: Why the local chain runs single-node Hyperledger Besu on Clique PoA pinned to the London EVM milestone with a zero-base-fee genesis — and the `PUSH0`, Foundry-fee, and transaction-only block-clock consequences.
- `docs/decisions/0002-adopt-erc-3643-for-tokenized-securities.md`: Why securities (bonds + equities) move to canonical ERC-3643 (T-REX) with a shared identity registry + modular compliance, retiring the ERC-1410 partitioned `BondToken` and the bespoke ERC-20 `StockToken` — including the migration cost, cash (`Wnok`) staying on its own allowlist, and the redirect of the incremental-adoption plan.
- `docs/decisions/0003-adopt-besu-qbft-osaka-with-archive-rpc.md`: Why the sandbox replaces Clique/London with Besu QBFT/Osaka, separates the validator from a Forest archive/RPC node, rejects a beacon client, and requires a destructive chain reset.
- `docs/decisions/0004-settle-all-bond-cash-legs-in-wnok.md`: Why every bond cash leg (issuance, buyback, coupon, redemption) settles in `Wnok` from a government reserve account, removing the government-nominated `Tbd` from the bond stack while keeping the allowance-based authorization for the closed-loop plan to replace. Proposed.
- `docs/decisions/0004-settle-all-bond-cash-legs-in-wnok.md`: Why every bond cash leg (issuance, buyback, coupon, redemption) settles in `Wnok` from a government reserve account, removing the government-nominated `Tbd` from the bond stack while keeping the allowance-based authorization for the closed-loop plan to replace. Accepted.
- `docs/plans/README.md`: How implementation plans are laid out — new plans are folders with `intent.md`, `design.md`, `plan.md`, and a committed `progress.md`; existing single-file plans stay as they are.
- `docs/plans/bond-cash-leg-wnok/`: Plan folder (intent, design, plan, progress) to pay bond coupon, buyback, and redemption in wNOK from the government reserve account instead of the government-nominated TBD, so primary dealers buy and are paid in central-bank money; covers `BondManager`, deploy wiring, tests, the Central Bank API field and UI, and the two related known issues.
- `docs/plans/closed-loop-settlement-and-omnibus-custody-plan.md`: Plan to settle the primary auction cash leg by central-bank authority instead of a bidder ERC-20 allowance (fixes the `ERC20InsufficientAllowance` / `FailureReason.Cash` finalisation failure), then a phased path to omnibus broker custody, a unified `PrimaryDealerRegistry`, and two-tier coupon/redemption. Phase 1 is diff-level (Roles + Wnok `settle()` + BondDvP cash-leg swap + deploy wiring); Phases 2–6 are roadmap.
Expand Down
48 changes: 21 additions & 27 deletions docs/KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,16 @@
(`CouponPaymentBalanceMismatch` / the redeem zero-supply check), so
the manager cannot be skipped;
- including the manager fails the cash leg with
`SettlementFailure(AllowlistViolation)` because the government
settlement TBD's allowlist (correctly) does not include the manager
contract.
`SettlementFailure(AllowlistViolation)` because the WNOK allowlist
(correctly) does not include the manager contract.
- The API surfaces these reverts as readable 409 details (nested custom
errors decoded, with a treasury-specific hint), and the payout modal
flags treasury-held units with a warning before the transaction is
attempted.
- Sandbox workarounds: allowlist the BondManager on the government TBD
via the Banking page (the payment then succeeds; the treasury's own
coupon cash accrues to the manager contract), or use fully-allocated
auctions when coupon/redemption flows are being tested.
- Sandbox workarounds: allowlist the BondManager on WNOK via the Central
Bank page (the payment then succeeds; the treasury's own coupon cash
accrues to the manager contract), or use fully-allocated auctions when
coupon/redemption flows are being tested.
- Planned follow-up (contract-side decision): burn unsold units at
finalisation, or skip self-held units in `payCoupon` / `redeem`
on-chain.
Expand Down Expand Up @@ -336,23 +335,18 @@ visible at a glance.
verify both `nb-bond-api` and `nb-ui` build. The Dependabot PRs were closed
(not merged) with this rationale.

## BondManager hard-codes the government settlement bank (`GOV_TBD`)
- `BondManager` stores the government's cash-leg settlement bank as an `immutable`
— `GOV_TBD`, with `_GOV_RESERVE` derived from `ITbd(GOV_TBD).govReserve()`
(`contracts/src/norges-bank/BondManager.sol:46-94`). It is the TBD (tokenized
bank deposit) whose tokens settle bond coupon and redemption payments
(`payCoupon`, `redeem`); in the local sandbox it is wired to Nordea
(`TBD_NORDEA_CONTRACT_NAME`, resolved from GlobalRegistry at deploy —
`contracts/script/norges-bank/10_Bond.s.sol:26`, `11_BondSetup.s.sol:32`).
- Because it is `immutable`, switching the government's agent bank requires
redeploying `BondManager` and re-wiring the bond stack. The source of truth is
also split: the deploy resolves the bank from a GlobalRegistry name, then freezes
it on `BondManager`. This is fine for the single-bank sandbox but is the wrong
long-term home — "who settles government cash" is a settlement / governance
concern, not a bond-contract detail.
- Planned follow-up: move the designation to mutable, well-modelled state — either a
stable, resolvable GlobalRegistry entry (e.g. a `Gov TBD` name) or the settlement
layer / `PrimaryDealerRegistry` introduced by
[`docs/plans/closed-loop-settlement-and-omnibus-custody-plan.md`](plans/closed-loop-settlement-and-omnibus-custody-plan.md),
so the agent bank can change without a redeploy. The operator Central Bank page
surfaces the current value by reading `BondManager.GOV_TBD()`.
## BondManager fixes the government reserve account at deployment (`GOV_RESERVE`)
- `BondManager` stores the government reserve account as an `immutable`,
`GOV_RESERVE` (`contracts/src/norges-bank/BondManager.sol`). It is the WNOK
account that receives issuance proceeds and pays buyback, coupon, and
redemption cash (ADR 0004). In the local sandbox it is the fixture reserve
key (`PK_GOV_RESERVE`), passed by `contracts/script/norges-bank/10_Bond.s.sol`.
- Because it is `immutable`, changing the reserve account requires redeploying
`BondManager` and re-wiring the bond stack. This is fine for the sandbox, but
"which account pays the state's obligations" is a governance concern that may
deserve mutable, role-gated state.
- Planned follow-up: decide whether the designation becomes mutable (admin
setter with its own ADR) or moves to the settlement layer introduced by
[`docs/plans/closed-loop-settlement-and-omnibus-custody-plan.md`](plans/closed-loop-settlement-and-omnibus-custody-plan.md).
The operator Central Bank page surfaces the current account and its WNOK
balance by reading `BondManager.GOV_RESERVE()`.
2 changes: 1 addition & 1 deletion docs/decisions/0004-settle-all-bond-cash-legs-in-wnok.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0004. Settle every bond cash leg in wNOK from a government reserve account

- **Status:** Proposed
- **Status:** Accepted
- **Date:** 2026-09-07
- **Deciders:** sandbox operator
- **Tags:** contracts, cash-settlement, wnok, bonds
Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ ADRs **complement** the other docs, they do not replace them:
| [0001](0001-local-chain-besu-clique-london-baseline.md) | Local chain runs Hyperledger Besu on a Clique + London baseline | Superseded by 0003 |
| [0002](0002-adopt-erc-3643-for-tokenized-securities.md) | Adopt ERC-3643 (T-REX) for tokenized securities, retiring the ERC-1410 model | Accepted |
| [0003](0003-adopt-besu-qbft-osaka-with-archive-rpc.md) | Adopt Besu QBFT and Osaka with a separate archive/RPC node | Accepted |
| [0004](0004-settle-all-bond-cash-legs-in-wnok.md) | Settle every bond cash leg in wNOK from a government reserve account | Proposed |
| [0004](0004-settle-all-bond-cash-legs-in-wnok.md) | Settle every bond cash leg in wNOK from a government reserve account | Accepted |
4 changes: 2 additions & 2 deletions docs/diagrams/processes/auction-sequence.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ sequenceDiagram
participant BT as BondToken
actor Bidder as Primary dealer
participant DVP as BondDvP
participant Cash as WNOK or government TBD
participant Cash as WNOK

Operator->>UI: Create bond or schedule auction
UI->>API: POST /v1/bonds or POST /v1/bonds/{isin}/auctions
Expand Down Expand Up @@ -81,7 +81,7 @@ sequenceDiagram
end
else BUYBACK
loop Each allocation
BM->>DVP: settle bond burn + government TBD payment
BM->>DVP: settle bond burn + government reserve WNOK payment
DVP->>BT: buybackRedeemFor(bidder)
DVP->>Cash: transferFrom(government reserve, bidder)
end
Expand Down
17 changes: 9 additions & 8 deletions docs/diagrams/processes/coupon-redemption-sequence.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Coupon and Redemption Sequence

Coupon and redemption cash is the government-nominated TBD, paid from its
configured government reserve. The API derives holders from the chain
projection unless the operator supplies an explicit list.
Coupon and redemption cash is WNOK, paid from the government reserve account
fixed on `BondManager` at deployment (`GOV_RESERVE`). Payouts move existing
WNOK; nothing is minted. The API derives holders from the chain projection
unless the operator supplies an explicit list.

```mermaid
sequenceDiagram
Expand All @@ -14,7 +15,7 @@ sequenceDiagram
participant BM as BondManager
participant BT as BondToken
participant DVP as BondDvP
participant TBD as Government-nominated TBD
participant WNOK as Wnok
actor Holder as Bond holder

Note over Operator,BT: RATE auction has enabled the bond,<br/>set yield, and started its maturity timer
Expand All @@ -30,8 +31,8 @@ sequenceDiagram
loop Every supplied holder with balance
BM->>BT: balanceOfByPartition(partition, holder)
BM->>DVP: settle cash-only coupon
DVP->>TBD: transferFrom(government reserve, holder, amount)
TBD-->>Holder: Tokenized-deposit balance increases
DVP->>WNOK: transferFrom(government reserve, holder, amount)
WNOK-->>Holder: WNOK balance increases
end

BM->>BT: Verify processed balance equals total supply
Expand All @@ -52,8 +53,8 @@ sequenceDiagram
BM->>BT: balanceOfByPartition(partition, holder)
BM->>DVP: settle redemption<br/>nominal cash + bond burn
DVP->>BT: redeemFor(holder, isin, balance, operator)
DVP->>TBD: transferFrom(government reserve, holder, nominal)
TBD-->>Holder: Tokenized-deposit balance increases
DVP->>WNOK: transferFrom(government reserve, holder, nominal)
WNOK-->>Holder: WNOK balance increases
end

BM->>BT: Require partition totalSupply == 0
Expand Down
52 changes: 41 additions & 11 deletions docs/plans/bond-cash-leg-wnok/progress.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
# Bond cash leg in wNOK — Progress

**Plan:** [`plan.md`](plan.md)
**Last updated:** 2026-09-08 — Phase 1 done: `BondManager` settles every cash leg in WNOK from `GOV_RESERVE`; deploy scripts, tests, contract docs, and the API ABI artifact updated
**Current phase:** Phase 2: API contract and revert decoding
**Next action:** Open PR 1 from `feature/bond-cash-leg-wnok` (Phases 0 and 1); then Phase 2 step 1 — replace `getGovSettlementBank` with `getGovReserve` in `services/nb-bond-api/src/banking-tbd.ts`
**Last updated:** 2026-09-08 — Phases 2 to 5 done on `feature/bond-cash-leg-wnok-api`; fresh-sandbox lifecycle run recorded
**Current phase:** All phases done; waiting on PR #272 to merge before PR 2
**Next action:** After PR #272 merges, rebase `feature/bond-cash-leg-wnok-api` onto `development`, rerun both package gates, open PR 2; archive this folder with `Status: Implemented` once PR 2 merges

## Phase Log

| Phase | Status | Evidence | PR |
|---|---|---|---|
| 0 — Baseline and characterization | Done | baseline `forge test`: 24 suites, 390 passed; after adding assertions `BondManager.t.sol`: 47 passed (2026-09-07) | PR 1 |
| 1 — BondManager cutover, deploy wiring, contract docs, ADR | Done | `forge build`, `forge test`: 25 suites, 393 passed (3 new cases); verify-mapping check passed; `grep private-bank` on `BondManager.sol` empty; nb-bond-api lint, format, and 244 jest tests green with the refreshed ABI (2026-09-08) | PR 1 |
| 2 — API contract and revert decoding | Not started | | |
| 3 — UI alignment | Not started | | |
| 4 — Fresh local sandbox validation | Not started | | |
| 5 — Architecture docs, known issues, index | Not started | | |
| 1 — BondManager cutover, deploy wiring, contract docs, ADR | Done (PR #272 open, all checks green) | `forge build`, `forge test`: 25 suites, 393 passed (3 new cases); verify-mapping check passed; `grep private-bank` on `BondManager.sol` empty; nb-bond-api lint, format, and 244 jest tests green with the refreshed ABI (2026-09-08) | PR 1 |
| 2 — API contract and revert decoding | Done | `govReserve {address, wnokBalance}` replaces `govSettlementBank`; `openapi.json` regenerated (one resource changed); coupon, redemption, and finalisation decode with the WNOK ABI and publish `bidders` + `central-bank`; nb-bond-api lint, format, build, 245 jest tests green (2026-09-08) | PR 2 |
| 3 — UI alignment | Done | Central Bank KPI shows the reserve's WNOK balance and address; coupon warning names the WNOK allowlist; nb-ui format, lint, build, 121 vitest tests green (2026-09-08) | PR 2 |
| 4 — Fresh local sandbox validation | Done | Fresh `./sandbox.sh start` on the feature branch; `GOV_RESERVE()` equals the fixture reserve, `WNOK()` the registered token, reserve→BondDvP allowance is max, reserve allowlisted; full lifecycle through the API with the balance trail below (2026-09-08) | PR 2 |
| 5 — Architecture docs, known issues, index | Done | `ARCHITECTURE.md`, both process diagrams, two known-issue entries rewritten, closed-loop plan Phase 6 note, ADR 0004 set to Accepted in the ADR index and documentation index; hygiene and link checks green (2026-09-08) | PR 2 |

## Deviations From the Plan

- Phase 4 drove the lifecycle through the NB Bond API's bidder routes instead of the reference
bid CLI, and needed no `./sandbox.sh delete` because no sandbox for this repository existed.
- Phase 1: the `GovReserveAddressZero` constructor test lives in a second, tiny test contract
at the end of `BondManager.t.sol`. A second `new BondManager` inside `BondManagerTest`
makes solc 0.8.36 (via-IR) fail with "Tag too large for reserved space", so the large test
Expand Down Expand Up @@ -50,9 +52,27 @@
- The API degrades `govSettlementBank` to `null` when the manager call fails — verified in
`services/nb-bond-api/src/app.ts` on 2026-09-07.

## Phase 4 Balance Trail (local sandbox, 2026-09-08)

Bond `NO0000000WNK`, 100 units at 1000 WNOK nominal, RATE auction cleared at 425 bps
(coupon per unit 42 after integer truncation), maturity 2 coupon periods of 60 s.

| Step | Reserve WNOK | Dealer A WNOK | Dealer B WNOK | WNOK supply | Bond supply |
|---|---|---|---|---|---|
| Baseline | 10 000 000 | 1 200 000 | 1 100 000 | 12 300 000 | 0 |
| Issuance (A 60 units, B 40 units) | 10 100 000 | 1 140 000 | 1 060 000 | 12 300 000 | 100 |
| Buyback (A sells 30 units at 98.00) | 10 070 600 | 1 169 400 | 1 060 000 | 12 300 000 | 70 |
| Coupon 1 (70 units × 42) | 10 067 660 | 1 170 660 | 1 061 680 | 12 300 000 | 70 |
| Coupon 2, bond matured | 10 064 720 | 1 171 920 | 1 063 360 | 12 300 000 | 70 |
| Redemption (70 units × 1000) | 9 994 720 | 1 201 920 | 1 103 360 | 12 300 000 | 0 |

Every cash movement is WNOK between the reserve and the dealers; WNOK supply never changes;
no TBD balance is involved. The Central Bank resource reported the reserve balance at each
step and the Banking page's TBD listing was unaffected.

## Blocked / Waiting On

- Phase 4 needs an explicit go-ahead to delete and recreate the local sandbox.
- PR #272 merge (operator), then rebase and open PR 2.

## Follow-ups Found Along the Way

Expand All @@ -62,14 +82,24 @@
- Test fixtures allowlist `BondManager` on wNOK with a stale comment while the deploy script
does not — `contracts/test/norges-bank/BondManager.t.sol` setup — align once the
treasury-held-units contract fix is decided.
- The redemption route maps an unknown nested revert (here `NotMatured` inside
`SettlementFailure`) to a 500 with raw calldata, while the coupon route decodes it into a
409 — `services/nb-bond-api/src/app.ts` redemption handler — pre-existing; give redemption
the same `describeRevert` treatment.
- Time-gated calls (`payCoupon`, `redeem`) are gas-estimated against the latest block, whose
timestamp only advances when a transaction is mined, so a coupon that is due by wall clock
can still fail `CouponNotReady` until any transaction lands — sandbox chain behaviour
already noted in ADR 0001's consequences; consider a note in the API's 409 detail or docs.
- Treasury-held units still deadlock coupon payout, now on the wNOK allowlist — existing entry
in `docs/KNOWN_ISSUES.md`; contract-side fix (burn or skip self-held units) remains open.

## Session Handoff

- Slither did not run locally (arm64); the Contracts CI job runs it on the PR.
- The API still calls `BondManager.GOV_TBD()` in `banking-tbd.ts`; with the refreshed ABI the
call rejects and the Central Bank route degrades the field to `null` until Phase 2 lands.
- `feature/bond-cash-leg-wnok-api` branches from `feature/bond-cash-leg-wnok` because PR #272
was not yet merged; rebase onto `development` once it is.
- The new `getGovReserve` lives in `services/nb-bond-api/src/central-bank.ts` (a WNOK read),
not in the TBD roster module the old lookup came from.
- The working tree on `development` carried unrelated uncommitted work when this plan was
written: `ITbd` gained `mint`/`burn` declarations and an ERC-165 registration, and a new
`contracts/src/settlement/` interbank-settlement set with its test. This plan does not touch
Expand Down
Loading