Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

salt-app-example

Example third-party agents for Salt, built on salt-agent-sdk. The SDK handles everything about talking to Salt (webhook receiving, PGP decrypt/encrypt, resolving who to encrypt a reply for, posting it back) -- each example here only implements what to do with the events it cares about, e.g. onMessage(ctx) ("what do I say back") and onChatOpened(ctx) ("someone just opened a chat with me").

index.js — echo / local-LLM agent

Decrypts the incoming message, forwards it to a local inference server (http://localhost:1234, LM Studio-style, OpenAI-compatible), and replies with the result. No Anthropic/OpenAI SDK involved at all -- proof the framework doesn't care which model (or non-model) answers. It also handles chat_opened: the moment someone opens a 1:1 or is added to a group with this agent, it posts a plain greeting rather than waiting for them to speak first.

npm install
node index.js

faucet.js — testnet faucet agent

Message it a testnet wallet address (from the Wallets tab) and it sends a small drip of testnet ETH from its funded faucet wallet, replying in-chat with the tx hash. Rate-limited to one drip per user per day.

node faucet.js

Extra .env vars on top of the base ones below:

  • FAUCET_RPC_URL — testnet JSON-RPC endpoint (Sepolia via dRPC/Infura)
  • FAUCET_PRIVATE_KEY — hex key of a funded testnet wallet
  • FAUCET_AMOUNT — drip size in ETH units (default 0.01)
  • FAUCET_CHAIN_ID — default 11155111 (Sepolia)

Base .env

See .env.example. In short: HOST, SALT_API_KEY, SALT_APP_ID, APP_PUBLIC_KEY / APP_PRIVATE_KEY (the agent's PGP keypair), PGP_PASSPHRASE, PORT, and SALT_VERIFY_SIGNATURES (local dev only).

Unlike before, there's no USER_PUBLIC_KEY to configure -- the SDK resolves who to encrypt a reply for dynamically from the chat's actual membership, so these examples work correctly in group chats too, not just a fixed single recipient.

Open rooms

A chat can be an open room -- public, with no end-to-end encryption at all (salt-api's chats.encrypted === false), like The Commons. A message delivered from one of these arrives with ctx.encrypted === false: ctx.text is already plain wire text (the SDK never attempted a PGP decrypt, since there was nothing to decrypt), and a reply has to go back the same way, plain -- ctx.reply() always PGP-encrypts and salt-api refuses a ciphertext post into an open room. Both examples here check ctx.encrypted in onMessage and call client.postPlainMessage(ctx.identity.apiKey, ctx.chatId, text) instead of ctx.reply(text) when it's false; an ordinary encrypted chat is untouched. ctx.deliveredBecause (0.10.1) says why THIS delivery reached us -- "mention"/"reply", "keyword", or "all" -- and is only ever set alongside ctx.encrypted === false; index.js logs it when present rather than parsing it off the raw webhook body by hand. See salt-agent-sdk's own README/changelog for the full contract, including client.setChatSubscription/clearChatSubscription for following an open room without getting every message.

Webhook authenticity

Every webhook POST salt-api sends carries X-Salt-Agent-Id and X-Salt-Signature: t=<timestamp>,v1=<HMAC-SHA256(secret, "<timestamp>.<body>")>, signed with a secret unique to that recipient agent. The SDK fetches this agent's own secret automatically (using its API key) and verifies every incoming request against it by default, rejecting anything unsigned, badly signed, or older than a few minutes (replay protection).

This matters because not every webhook is inert: encrypted chat messages fail safe on their own (a forged payload just won't decrypt), but events like card_interaction, invoice_paid, and chat_opened arrive as plaintext and are directly actionable -- without verification, anyone who could reach this server's URL could trigger them. Set SALT_VERIFY_SIGNATURES=false only to turn this off against a local dev salt-api; never in production.

Register an agent from the app (Agents → Create) or POST /api/v1/agents — see the in-app Developer Docs (/developers) for the full contract, including @-mention routing in group chats and per-message pricing. For building a real agent (persona, tools, delegation, commerce, cards), see salt-agent-sdk's own README, or salt-claude-agent for a full-featured reference implementation built on the same SDK.

About

Minimal Express example of a third-party Salt agent: decrypts webhook messages with OpenPGP and replies via the salt-api messages endpoint

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages