Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"approvals": [
{
"gate": "definition",
"actor": "root",
"timestamp": 1784816273,
"digest": "78d9bd335e4b5648193d9a3e4a72f2c2d2afd66773072d54079e398897099655",
"note": null
},
{
"gate": "definition",
"actor": "root",
"timestamp": 1784816474,
"digest": "effbc7e6c71de18e59757fd6aaea21f2f58c5e3922800ea0db306f68d88e00e6",
"note": null
},
{
"gate": "definition",
"actor": "root",
"timestamp": 1784816561,
"digest": "effbc7e6c71de18e59757fd6aaea21f2f58c5e3922800ea0db306f68d88e00e6",
"note": null
},
{
"gate": "definition",
"actor": "root",
"timestamp": 1784816588,
"digest": "a32bd58be419b336be43fda605edf89dd564954802fdcc85c5154f0f3d1a6697",
"note": null
},
{
"gate": "acceptance",
"actor": "root",
"timestamp": 1784816603,
"digest": "4f23e9f28f4fec5fead7970174f62f7a37737c4251a23dd65c4ad276c1d47af3",
"note": null
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
id: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
state: accepted
type: feature
base_commit: 63432d0f56abc1d8641867940f9e0d062da7bf6d
---

# Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds

## Intent

Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds

## Affected Canonical Specs

- `algochat`

## Acceptance Criteria

- Mailbox put and fan-out app args ARC-4-encode the envelope as uint16_be(len) followed by the bytes; burn and reclaim resolve the depositor from the mailbox box header and include it in foreign accounts so the inner MBR refund succeeds
- omitting accounts when the mailbox is absent; arc4EncodeDynamicBytes is exported and unit-tested; all tests plus the fledge verify lane pass

## No-spec Rationale

Not applicable
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: context
---

# Context

## Why this change exists

CHG-0006 shipped MailboxRouterTransport against a mocked algod. The first run against a real LocalNet algod and the deployed RavenRouter (app 1002) exposed two wire-format bugs:

1. The raven router declares the mailbox envelope parameter as a dynamic ARC-4 `byte[]`. The transport passed raw envelope bytes, so the AVM rejected every real `mailboxPut` call. Dynamic ARC-4 byte arrays must be sent as `uint16_be(length)` followed by the bytes.
2. `mailboxBurn` and `mailboxReclaim` refund the depositor via an inner payment. The AVM requires the refund receiver to be in the foreign accounts array (unless it is the transaction sender), so burns and reclaims failed on real algod.

This change fixes both so the transport works against real algod, verified by a LocalNet round-trip.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: design
---

# Design

## Design

- `arc4EncodeDynamicBytes` lives in `src/blockchain/mailbox.ts` beside the other pure protocol helpers; it throws `MailboxEnvelopeError` above 65535 bytes (uint16 bound).
- `foreignAccountsForRefund` reuses the off-chain `read` so burn/reclaim stay single-call for the user; when the box is absent (idempotent burn) no foreign accounts are attached.
- Static `byte[32]` args (mailbox id, msg key) remain raw per ARC-4.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: docs
---

# Docs

## Docs

- Spec `specs/algochat/algochat.spec.md`: `arc4EncodeDynamicBytes` added to the Public API table.
- TSDoc on `arc4EncodeDynamicBytes` documents the `uint16_be(len) ‖ bytes` layout and the static `byte[32]` exception.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: plan
---

# Plan

## Plan

1. Add `arc4EncodeDynamicBytes` to `src/blockchain/mailbox.ts` and export it from `src/index.ts`.
2. Use it for the envelope app arg in `submitPutGroup` (covers both `send` and `sendFanout`).
3. Add `foreignAccountsForRefund` to the transport: read the mailbox box, return the depositor when it exists.
4. Wire foreign accounts into `burn` and `reclaim`.
5. Update unit tests and the spec's Public API table.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: requirements
---

# Requirements

## Acceptance Criteria

- Mailbox put and fan-out app args ARC-4-encode the envelope as `uint16_be(len)` followed by the envelope bytes.
- `arc4EncodeDynamicBytes` is exported from the package root and rejects payloads over 65535 bytes with `MailboxEnvelopeError`.
- Burn and reclaim resolve the depositor from the mailbox box header and include it in the foreign accounts array so the inner MBR refund succeeds; absent mailboxes omit foreign accounts (idempotent burn).
- All existing and new unit tests pass, and the fledge verify lane is green.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: research
---

# Research

## Research

- ARC-4 ABI: dynamic `byte[]` app args are encoded `uint16_be(length) ‖ bytes`; static arrays are passed raw.
- AVM resource availability: an inner payment receiver must be in the outer transaction's foreign accounts unless it is the sender.
- Failure reproduced on LocalNet against RavenRouter app 1002 before the fix; round-trip passes after.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"schema_version": 1,
"id": "CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d",
"slug": "fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d",
"title": "Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds",
"description": "Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds",
"kind": "feature",
"state": "accepted",
"base_commit": "63432d0f56abc1d8641867940f9e0d062da7bf6d",
"created_at": 1784816074,
"updated_at": 1784816603,
"affected_specs": [
"algochat"
],
"affected_paths": [
"src/blockchain/mailbox.ts",
"src/blockchain/mailbox.test.ts",
"src/services/mailbox-router.service.ts",
"src/services/mailbox-router.service.test.ts",
"src/index.ts"
],
"no_spec_change": false,
"no_spec_change_rationale": null,
"acceptance_criteria": [
"Mailbox put and fan-out app args ARC-4-encode the envelope as uint16_be(len) followed by the bytes; burn and reclaim resolve the depositor from the mailbox box header and include it in foreign accounts so the inner MBR refund succeeds",
"omitting accounts when the mailbox is absent; arc4EncodeDynamicBytes is exported and unit-tested; all tests plus the fledge verify lane pass"
],
"selected_artifacts": [
"context",
"requirements",
"plan",
"tasks",
"testing",
"docs",
"design"
],
"dependencies": [],
"answers": {
"architecture_risk": "no",
"public_contract": "yes"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: tasks
---

# Tasks

## Tasks

- [x] Implement `arc4EncodeDynamicBytes` with uint16 big-endian length prefix
- [x] Export it from `src/index.ts` and document it in the spec
- [x] ARC-4-encode put envelopes in `submitPutGroup`
- [x] Include depositor in burn/reclaim foreign accounts
- [x] Unit tests: encoding, put args, burn with/without box, reclaim
- [x] LocalNet e2e round-trip against deployed RavenRouter (app 1002): put/read/burn, 3-leg fan-out, 2049 B negative — 18/18 PASS
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
change: CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d
artifact: testing
---

# Testing

## Evidence

- `bun test`: full suite green, including new tests for `arc4EncodeDynamicBytes` (length prefix, passthrough), put/fan-out ARC-4 args, burn foreign accounts with an existing box, burn without accounts for an absent mailbox, and reclaim foreign accounts.
- `fledge lanes run verify` (build + test): green.
- LocalNet e2e against deployed RavenRouter (app 1002): put/read/burn round-trip, 3-recipient fan-out, and 2049-byte negative case — 18/18 PASS.

## Requirement evidence map

- `REQ-algochat-024`: `arc4EncodeDynamicBytes` length-prefix tests in `src/blockchain/mailbox.test.ts`; put and fan-out ARC-4 arg assertions in `src/services/mailbox-router.service.test.ts`.
- `REQ-algochat-025`: burn-with-depositor, burn-absent-mailbox, and reclaim foreign-account cases in `src/services/mailbox-router.service.test.ts`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"timestamp": 1784816591,
"commit": "63432d0f56abc1d8641867940f9e0d062da7bf6d",
"contract_digest": "a32bd58be419b336be43fda605edf89dd564954802fdcc85c5154f0f3d1a6697",
"workspace_digest": "92588df3d2e2d306d5ee6f3c7156211aa66c3d9398601ae939511cd7f57c9f15",
"acceptance_input_digest": "ecb7ff691f90ad82b2b6dd96bb13bb2bac04192af3f3585b4b925da859746844",
"passed": true,
"commands": [
{
"command": "fledge lanes run verify",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-algochat-024",
"REQ-algochat-025"
]
}
5 changes: 4 additions & 1 deletion specs/algochat/algochat.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: algochat
version: 4
version: 5
status: stable
files:
- src/index.ts
Expand Down Expand Up @@ -214,6 +214,7 @@ Provides the TypeScript implementation of the AlgoChat encrypted-messaging proto
| `planMailboxPut` | Pure mailbox put planning operation defined below. |
| `planMailboxFanout` | Pure mailbox atomic fan-out planning operation defined below. |
| `mailboxMethodSelector` | ARC-4 method selector derivation defined below. |
| `arc4EncodeDynamicBytes` | ARC-4-encodes a dynamic `byte[]` app arg as `uint16_be(len) ‖ bytes` for router puts. |
| `MAILBOX_METHODS` | Published protocol value, size boundary, preset, or search default. |
| `MAILBOX_MSG_KEY_DOMAIN` | Published protocol value, size boundary, preset, or search default. |
| `MAILBOX_ID_DOMAIN` | Published protocol value, size boundary, preset, or search default. |
Expand Down Expand Up @@ -295,3 +296,5 @@ Then SendQueue processes eligible entries in order, records failures for retry,
| 2 | 2026-07-14 | Added the stable full-library contract for the existing implementation and tests |
| 3 | 2026-07-14 | CHG-0002-replace-the-incomplete-no-spec-rationale-with-a-stable-full-library-algochat-con: Replace the incomplete no-spec rationale with a stable full-library AlgoChat contract covering every existing source, export, invariant, failure mode, and native test boundary |
| 2026-07-19 | CHG-0006-add-an-opt-in-mailboxroutertransport-speaking-the-raven-mailbox-protocol-per-co: Add an opt-in MailboxRouterTransport speaking the raven mailbox protocol: per-counter key derivation, MBR-exact put groups, atomic N-recipient fan-out, burn/reclaim/status, off-chain box reads, gated behind service config |
| 2026-07-19 | Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds |
| 2026-07-23 | CHG-0007-fix-mailboxroutertransport-for-real-algod-arc-4-encode-put-envelopes-include-d: Fix MailboxRouterTransport for real algod: ARC-4-encode put envelopes; include depositor in burn/reclaim foreign accounts for inner refunds |
14 changes: 14 additions & 0 deletions specs/algochat/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,17 @@ The mailbox transport SHALL be strictly opt-in — exposed by `AlgorandService`
Acceptance Criteria
- Service tests with a stubbed algod client verify single put, atomic fan-out put, burn, and reclaim group shapes with correct ABI selectors, arguments, and box references; off-chain box reads returning depositor, write round, and envelope; and that the transport is absent unless configured.

### REQ-algochat-024

Mailbox put envelopes SHALL be transmitted as ARC-4 dynamic byte arrays — `uint16_be(length) ‖ bytes` — for both single puts and fan-out legs, and the encoder SHALL reject payloads longer than 65535 bytes with a typed error.

Acceptance Criteria
- Unit tests verify the big-endian uint16 length prefix and verbatim payload passthrough, and stub-algod tests confirm put and fan-out app args carry the ARC-4-encoded envelope while static `byte[32]` args remain raw.

### REQ-algochat-025

Mailbox burn and reclaim SHALL include the depositor address — parsed from the mailbox box header — in the foreign accounts array so the contract's inner MBR refund succeeds, and SHALL omit foreign accounts when the mailbox box is absent (idempotent burn).

Acceptance Criteria
- Stub-algod tests verify burn includes the depositor for an existing mailbox, omits foreign accounts for an absent mailbox, and reclaim includes the depositor.

12 changes: 12 additions & 0 deletions src/blockchain/mailbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
InvalidViewSecretError,
deriveMailboxId,
deriveMsgKey,
arc4EncodeDynamicBytes,
mailboxMbr,
mailboxMethodSelector,
planMailboxFanout,
Expand Down Expand Up @@ -171,6 +172,17 @@ describe('mailbox put planning', () => {
});
});

describe('ARC-4 encoding', () => {
test('arc4EncodeDynamicBytes prefixes a big-endian uint16 length', () => {
const payload = new Uint8Array([1, 2, 3, 4]);
const encoded = arc4EncodeDynamicBytes(payload);
expect(encoded.length).toBe(6);
expect(encoded[0]).toBe(0);
expect(encoded[1]).toBe(4);
expect(Buffer.from(encoded.subarray(2)).equals(Buffer.from(payload))).toBe(true);
});
});

describe('ARC-4 selectors', () => {
test('mailboxMethodSelector matches algosdk for every router method', () => {
const expectations: Array<[string, algosdk.ABIMethodParams]> = [
Expand Down
18 changes: 18 additions & 0 deletions src/blockchain/mailbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ export function mailboxMethodSelector(signature: string): Uint8Array {
return sha512_256(new TextEncoder().encode(signature)).slice(0, 4);
}

/**
* ARC-4-encodes a dynamic `byte[]` application argument as
* `uint16_be(length) ‖ bytes`. Required for raven router `mailboxPut`
* envelopes (static `byte[32]` args are passed raw).
*
* @param bytes - Raw payload bytes (length must fit in a uint16)
* @returns Length-prefixed ARC-4 dynamic bytes
*/
export function arc4EncodeDynamicBytes(bytes: Uint8Array): Uint8Array {
if (bytes.length > 0xffff) {
throw new MailboxEnvelopeError(bytes.length);
}
const encoded = new Uint8Array(2 + bytes.length);
new DataView(encoded.buffer).setUint16(0, bytes.length, false);
encoded.set(bytes, 2);
return encoded;
}

/**
* Derives the per-message msg key from a shared view secret and counter.
*
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ export {
planMailboxPut,
planMailboxFanout,
mailboxMethodSelector,
arc4EncodeDynamicBytes,
type MailboxLeg,
type MailboxLegPlan,
} from './blockchain/mailbox.js';
Expand Down
Loading
Loading