# `@decentnetwork/peer`

Pure TypeScript / Node.js port of the [Elastos Carrier Native SDK][carrier-c]
(Carrier-flavored toxcore). Wire-compatible with the C SDK and iOS Beagle —
the same DHT, onion routing, FlatBuffers app payloads, TCP relay protocol,
and Express HTTP store-and-forward relay. End-to-end interop with iOS Beagle
on iPad is verified working.

[carrier-c]: https://github.com/elastos/Elastos.NET.Carrier.Native.SDK

```bash
pnpm add @decentnetwork/peer
# or
npm install @decentnetwork/peer
```

## 30-second example

```ts
import { Peer } from "@decentnetwork/peer";

const peer = await Peer.create({
  keyFile: "./peer.save",
  bootstrapNodes: [
    {
      host: "47.100.103.201",
      port: 33445,
      pk: "CX1XH419p4xJ5SV4KvDxBeKYSRdMJW9QpdWJY8owUxHd"
    }
    // (more bootstraps recommended — see docs/USAGE_GUIDE.md)
  ],
  compatibilityMode: "legacy"
});

await peer.start();
await peer.joinNetwork();

console.log("my address:", peer.address());

peer.onText((msg) => {
  console.log(`from ${msg.pubkey}: ${msg.text}`);
});

// Send a friend request to a peer (their address comes from their UI):
await peer.sendFriendRequest(
  "ZJxuWL9SDqdvunnCSMLUd5jyGCaBV44G6THYaQS7ZaZAz1wmt4nz",
  "hello!"
);

// Once they accept and the session establishes, send a message:
await peer.sendText(
  "FhbohSLrj5UjdyFKCEYNeEWAPq3QD9hRg6hsso5ipag2",
  "first message via @decentnetwork/peer"
);

await peer.stop();
```

## Optional CLI

For users who just want to run a peer without writing JS, the package
also ships an optional `decent-peer` binary (the library export is
unchanged — the CLI is additive):

```bash
npm install -g @decentnetwork/peer
decent-peer init                              # creates ~/.decent-peer/
decent-peer address                           # print your address
decent-peer listen                            # daemon mode (Ctrl-C to stop)
decent-peer send <addr> "hello"               # one-shot send
decent-peer add-friend <addr> "hi from me"    # one-shot friend request
decent-peer accept <pubkey>                   # accept a pending request

# zero-install:
npx @decentnetwork/peer listen
```

Config lives at `~/.decent-peer/config.json` (override with the
`DECENT_PEER_CONFIG` env var). The keyfile at `~/.decent-peer/peer.save`
is your identity — back it up.

## Documentation

- **Usage guide**: full developer-facing tutorial with sample apps —
  see [`docs/USAGE_GUIDE.md`](https://github.com/0xli/peer/blob/main/docs/USAGE_GUIDE.md)
- **Protocol overview**: actors, identities, lifecycle —
  [`docs/PROTOCOL_OVERVIEW.md`](https://github.com/0xli/peer/blob/main/docs/PROTOCOL_OVERVIEW.md)
- **Carrier vs Tox/Onion**: where Carrier diverges from upstream toxcore —
  [`docs/CARRIER_VS_TOX.md`](https://github.com/0xli/peer/blob/main/docs/CARRIER_VS_TOX.md)
- **Discovery flow**: the four discovery stages with timing budgets —
  [`docs/DISCOVERY_FLOW.md`](https://github.com/0xli/peer/blob/main/docs/DISCOVERY_FLOW.md)
- **iOS interop playbook**: real-world bug catalog and debug markers —
  [`docs/IOS_INTEROP_PLAYBOOK.md`](https://github.com/0xli/peer/blob/main/docs/IOS_INTEROP_PLAYBOOK.md)

## License

GPL-3.0-or-later. Same as upstream toxcore.

## Tuning peer discovery

Discovery is the loop that finds a friend's current UDP endpoint when there is
no live session. It costs real CPU — every onion request is three X25519
scalar multiplications, one per layer — so the defaults are a compromise, and
a node with unusual friend counts or hardware may want to move them.

All are environment variables read at startup.

| Axis | Variable | Default | What it controls |
|---|---|---|---|
| **Interval** | `DECENT_DHT_PK_ANNOUNCE_COOLDOWN_MS` | `25000` | Minimum gap between lookups for the same friend |
| **Backoff cap** | `DECENT_ONION_LOOKUP_MAX_BACKOFF_MS` | `120000` | Ceiling once a friend keeps failing to resolve |
| **Breadth** | `DECENT_FRIEND_ROUTE_MAX_ATTEMPTS` | `24` | Nodes queried per sweep |
| **Depth** | `DECENT_ONION_DATA_ATTEMPTS` | `5` | Retries per onion data packet |
| Self-announce interval | `DECENT_SELF_ANNOUNCE_INTERVAL_MS` | `20000` | How often we re-announce ourselves |
| Self-announce breadth | `DECENT_SELF_ANNOUNCE_TARGETS` | `16` | Nodes we announce to |

### Which knob to turn

**Turn the interval, not the breadth.** Cost is breadth x frequency, so both
reduce CPU — but they fail differently. Narrowing breadth was tried and
reverted: iOS peers appear in short windows and are only caught by a wide
sweep, so a narrow one loses them outright rather than finding them late.
Lengthening the interval degrades gracefully — the worst case is simply
`ONION_LOOKUP_MAX_BACKOFF_MS`, the longest you can go without noticing a peer
that came back.

Read the backoff cap as exactly that: **the worst-case time to notice a friend
returning.** Raising it to save CPU is a direct trade against how quickly the
node reacts.

### Symptoms and responses

- **High steady CPU with offline friends.** Expected: each unreachable friend
  is swept on its own schedule, so cost scales with how many friends are *not*
  there. Raise `DECENT_ONION_LOOKUP_MAX_BACKOFF_MS`. Measured on a 2-core box
  with ~5 unreachable friends, a flat 25s interval held the daemon at ~22% of
  a core purely on lookups.
- **A peer is online but never seen.** Lower the interval, or raise breadth if
  it was lowered. Do not lower breadth further.
- **Small or battery-powered device.** Raise the interval and the backoff cap
  together; leave breadth alone.
