# Post with an app key

> Cast from a test account through your own node with a gasless app key.

Add an app key to a test account from the app on your own computer, then post, like and follow through your node.

:::note[What has been tested]
Adding an app key and posting a cast ran on mainnet ([Proof](https://sudharshanx.github.io/runfarcaster/proof/#posted-to-mainnet)). Likes, recasts and follows ran against a mock node only. If you test them, report what happened ([Vouches](https://sudharshanx.github.io/runfarcaster/proof/#vouches)).
:::

You need:
- A **test** Farcaster account and its recovery phrase. Read [What the app does with your recovery phrase](#what-the-app-does-with-your-recovery-phrase) first.
- Node.js 22.12 or later (22.x, 24.x or 26+) and this repo on your computer.
- Your node from the [Full run](https://sudharshanx.github.io/runfarcaster/quickstart/), and the HTTPS address that `deploy-client.sh` printed.

:::note[If you started the watchdog]
The watchdog from step 1 ([A trial server that deletes itself](https://sudharshanx.github.io/runfarcaster/quickstart/#try-it)) deletes the server, the volume and the firewall 24 hours after it started, even if the node is healthy. To keep the node: `touch .provision-state/watchdog.disarm`. Within a minute, `tail -n 1 .provision-state/watchdog.log` should show `disarmed by …`.
:::

## Why run the app on your own computer

Add the key from the app on your own computer at `localhost`. The address `deploy-client.sh` prints is usually `<ip-with-dashes>.sslip.io`, and sslip.io is a DNS service someone else runs. Whoever controls that DNS, or the server, could serve a changed page that sends your recovery phrase elsewhere. A page served from `localhost` comes from files on your own disk, built from code you can read.

The local dev server passes `/v1` requests on to your node's HTTPS address, so you need no SSH tunnel, and ports 3381 and 3383 stay closed. The add-key steps show a **Check the address** warning whenever the app is not on `localhost`.

## What the app does with your recovery phrase

:::danger[The recovery phrase controls the whole account]
Use a test account, on your own computer. Only paste a seed phrase into software from developers you trust, or code you have read. Delegated signers are preferred: if an app you already trust can give you an app key, use that instead. Never paste the phrase into an AI assistant or a chat.
:::

The code is in [client/src/signer.ts](https://github.com/sudharshanx/runfarcaster/blob/main/client/src/signer.ts):

1. Your browser makes a new ed25519 app key.
2. The browser uses the phrase once to sign one gasless `KEY_ADD` with your custody address. There is no transaction and no gas.
3. Nothing stores, logs or sends the phrase. Only the signed `KEY_ADD` goes to your node.
4. The app key is saved in this browser's `localStorage`. **Remove from this browser** deletes it there; the key stays valid on the network until it expires.

JavaScript cannot promise that the browser has wiped the phrase from memory.

## What the app key can and cannot do

| The app key can | The app key cannot |
|---|---|
| Post casts (`CAST_ADD`) | Move money or sign transactions |
| Like and recast (`REACTION_ADD`) | Delete casts, unfollow or edit your profile; the app does not ask for those scopes |
| Follow (`LINK_ADD`) | Approve or cancel other keys (`KEY_ADD` and `KEY_REMOVE` are never scopes) |
| Work until it expires: 7 days by default (pick 1 day for a test), 90 at most, renewed each time it is used | Keep working after your custody address cancels it |

[SECURITY.md](https://github.com/sudharshanx/runfarcaster/blob/main/SECURITY.md) has the full list.

## 1. Run the app locally

From the repo folder, start the app. Put your node's HTTPS address in `SNAPCHAIN_NODE_URL`:

```bash title="your computer"
cd client
npm ci --ignore-scripts
SNAPCHAIN_NODE_URL=https://<your node> npm run dev
```

Leave it running. You should see `ready` and `Local: http://localhost:5317/`. Ctrl-C stops it.

`npm ci` installs the exact versions in `package-lock.json`. `--ignore-scripts` stops packages from running their own install scripts. The app builds and its tests pass without them.

Open http://localhost:5317. The status at the top should say **Connected to your node**; **Details** shows the version and how far behind each shard is. To see your feed, type your FID or username under **Enter your FID to see your feed** and press **Show my feed**.

The app uses port 5317, not Vite's usual 5173, because a browser keeps one `localStorage` per address and port. If 5317 is taken, `npm run dev` stops with an error instead of moving to another port.

The page's Content-Security-Policy lets it connect only to its own address, so a changed library cannot send data elsewhere.

## 2. Add an app key

Before you type the phrase, check that the address bar shows exactly `localhost:5317`, close other tabs, and turn off clipboard history and screen sharing.

In the post box at the top (**To post, add an app key first.**), press **Add app key**. A dialog walks you through 4 steps:

1. **What an app key is**: press **Next**.
2. **Before you start**: read it. There must be no yellow **Check the address.** box; if there is, you are not on `localhost`, so stop. Press **I understand, continue**.
3. **Sign and send**: type the test account's recovery phrase (12 to 24 words) under **Recovery phrase**. If you paste it instead, clear your clipboard afterwards: clipboard history and sync tools can keep a copy, and the app cannot clear it. Under **Key expires after**, pick **1 day** for a test (the default is 7 days). Press **Add app key**. You see **Signed for FID** with your FID, then **Sending to your node**. If it shows another FID, the phrase belongs to another account.
4. **App key added**: the phrase field is gone. Press **Close** or **Write a cast**.

From the phrase, the app works out your custody address, asks the node which FID that address owns, and reads `currentUserNonce`. Then it signs one gasless `KEY_ADD` and submits it.

The **App key** card shows **Waiting for your node**, then **Active** when `GET /v1/signer` returns the key. The app checks every 4 seconds. Post only after it says **Active**; a cast sent earlier is rejected and marked **Failed**.

Check it from the terminal:

```bash
curl -s "https://<your node>/v1/signersByFid?fid=<your-fid>" | jq '.currentUserNonce, [.signers[] | .source]'
```

`currentUserNonce` should be one higher than before, with one more signer in the list.

The app key is saved in the browser for `localhost:5317` only, so post from the local app. The copy at your HTTPS address has no key, and adding one there means typing the phrase into a page served through sslip.io ([Why run the app on your own computer](#why-run-the-app-on-your-own-computer)).

## 3. Cast, follow, like

| Action | Where | Message type |
|---|---|---|
| Cast | Type in the post box at the top (**New cast as FID** …), press **Post** (a standard cast, up to 320 bytes) | `CAST_ADD` |
| Follow | In **Settings**, **Follow someone**: type an FID in **FID to follow**, press **Follow** | `LINK_ADD` (type `follow`) |
| Like, recast | The heart and repeat buttons under each cast | `REACTION_ADD` |

A new cast shows first in the feed, with a state badge. Every message also shows up in the **Sent** panel (right column, or the **Settings** tab on a narrow window) with the same state:

| State | Meaning |
|---|---|
| **Sending** | The app is sending it to your node |
| **Waiting for a block** | The node accepted it and sent it on |
| **Retrying** | Not seen yet, so the app sent it again |
| **Confirmed** | The node returns it |
| **Failed** | Still missing after 4 sends, or the node rejected it |

A failed message shows the reason and a **Retry** button. [Your node passes messages on](https://sudharshanx.github.io/runfarcaster/how-farcaster-works/#your-node-passes-messages-on) explains these states.

The app can only add. It has no button to delete a cast, unfollow or unlike.

## 4. Look for it in another app

Open your profile in any other Farcaster app. The app does not show hashes; ask your node:

```bash
curl -s "https://<your node>/v1/castsByFid?fid=<your-fid>&pageSize=1&reverse=true" \
  | jq '.messages[0] | {hash, text: .data.castAddBody.text}'
```

## Remove the key

- **Remove from this browser** (Settings, App key) deletes the key from this browser. The network still accepts it until its lifetime runs out.
- This app cannot revoke keys yet. When it says your account's wallet can revoke the key, it means a `KEY_REMOVE` message signed by your custody address and sent to a node, which is how a gasless key is cancelled. Removing a key through the KeyRegistry contract does not apply to it. This guide has no tested way to send a `KEY_REMOVE`, so pick a 1-day expiry for a test key and let it run out.

When you are done on `localhost`, press **Remove from this browser**, so no other page on `localhost:5317` can read the key later. Then stop the dev server with Ctrl-C.
