Skip to content

feat(agent): sip.sol tools — resolveSNS + sendPrivateToSNS#252

Merged
rz1989s merged 3 commits into
mainfrom
feat/sip-sol-integration
May 11, 2026
Merged

feat(agent): sip.sol tools — resolveSNS + sendPrivateToSNS#252
rz1989s merged 3 commits into
mainfrom
feat/sip-sol-integration

Conversation

@rz1989s
Copy link
Copy Markdown
Member

@rz1989s rz1989s commented May 11, 2026

Summary

Phase D of the sip.sol foundation — adds two new tools to the SIPHER agent so HERALD/Sipher chat can route private payments by .sol name.

  • resolveSNS — read-only .sol → SIP-STEALTH meta-address lookup via @sip-protocol/sns-stealth@^0.1.0. Returns discriminated union: resolved | not-found | malformed | network-error. NOT in FUND_MOVING_TOOLS — pure read.
  • sendPrivateToSNS — composite tool. Resolves the domain, builds sip:solana:0x<spending>:0x<viewing> URI (matching send.ts's 0x-hex contract — NOT bs58), then delegates to executeSend. Returns ok with composite resolved + send blocks or cannot-send with reason.
  • SENTINEL preflight integrationsendPrivateToSNS added to FUND_MOVING_TOOLS. Input field named recipient (the .sol domain) so the existing gate at agent.ts:169 reads it unchanged for blacklist / known-repeat / dust evaluation before execution. agent.ts registry + SYSTEM_PROMPT updated.

Tests: +47 (resolve-sns: 23, send-private-to-sns: 22, registry updates: 2). pnpm test 1446 → 1495 across 124 files.

Plan deviations from 2026-05-11-sip-sol-foundation.md lines 2118–2472

  1. Task 18 — wrapper architecture inverted. Plan called for a module-level new Connection(process.env.SIPHER_SOLANA_RPC ?? 'mainnet-beta') at the top of the tool file. That crashes at test load because loadNetworkConfig() throws fatal on unset SIPHER_NETWORK/SIPHER_HELIUS_API_KEY. Adapted to construct the Connection via createConnection(loadNetworkConfig().clusterName) inside the executor — matches send.ts precedent, picks up the configured cluster, and works under vitest (which presets these env vars).
  2. Tool shape. Plan used Pi-style { name, description, inputSchema, handler }. Sipher's convention is AnthropicTool { name, description, input_schema } paired with a separate executeXxx(params) function (per send.ts, deposit.ts, etc.). All new tools follow the established shape.
  3. Test location. Plan put tests at src/tools/<name>.test.ts (co-located). Sipher's convention is tests/<kebab-name>.test.ts (per all existing tool tests). Moved accordingly.
  4. URI hex, not bs58. The Phase C handoff suggested bs58.encode(meta.spending) for the sip-URI build, but sipher's send.ts:166-167 rejects non-0x-prefixed keys: parts[2].startsWith('0x'). The composite builds sip:solana:0x${hex}:0x${hex} to satisfy the existing contract.
  5. Task 20 scoped down — Plan's HERALD smoke test (handleHeraldDM, expecting toolsCalled to include sendPrivateToSNS) targeted a non-existent entry point and would have required adding sendPrivateToSNS to HERALD_TOOLS, expanding HERALD's X-only surface into fund-moving territory. Replaced with the composite integration test built into send-private-to-sns.test.ts — exercises resolveSNS + URI build + delegate-to-send end-to-end via mocks, without coupling to LLM intent classification.

Test Plan

  • cd packages/agent && pnpm test -- --run — 1495 / 1495 passing
  • pnpm test -- --run (root REST suite) — 555 / 555 passing
  • cd packages/agent && npx tsc --noEmit — clean
  • pnpm typecheck (root) — clean
  • Manual sanity once mainnet/devnet .sol domain is registered with SIP-STEALTH record:
    • Hit /v1/health to confirm process boots with the new tools registered
    • From Command Center chat: ask Sipher to "resolve rector.sol" and confirm the LLM dispatches resolveSNS
    • From chat: "send 0.001 SOL to rector.sol privately" and confirm SENTINEL preflight runs (advisory/yolo mode) and the unsigned tx is returned for the connected wallet to sign

Notes

  • @sip-protocol/sns-stealth@^0.1.0 was published to npm in Phase A (PR sip-protocol#1082 5a85d11). pnpm-lock now references that version.
  • Phase B (sip-app) + Phase C (sip-mobile) used a separate src/lib/sns-stealth-*.ts wrapper because their UI consumers imported it from multiple call sites. Sipher has only the two tools as callers, so the wrapper is inlined into resolve-sns.tsexecuteResolveSNS is the wrapper boundary.
  • bytesToHex is inlined as a small helper (matching send.ts's inline hexToBytes) rather than pulling in @noble/hashes/utils as a direct dep — it's not in packages/agent/package.json explicitly.
  • HERALD remains X-only. Decision deferred on whether to add sendPrivateToSNS to HERALD_TOOLS (would need DM-triggered fund-movement trust review).

Phase D acceptance

  • Task 18 ✅ resolveSNS tool + 23 tests, registered in agent loop
  • Task 19 ✅ sendPrivateToSNS composite tool + 22 tests, SENTINEL preflight wired
  • Task 20 ✅ Composite integration test (option (a), per RECTOR's confirmation)

rz1989s added 2 commits May 12, 2026 06:18
Resolves a .sol domain to its SIP-STEALTH meta-address (spending + viewing
hex keys) via @sip-protocol/sns-stealth. Read-only, intentionally NOT
registered in FUND_MOVING_TOOLS — the composite sendPrivateToSNS tool
(next commit) is the fund-mover and goes through SENTINEL preflight.

- Tool descriptor matches sipher's AnthropicTool + executeXxx convention
- Connection sourced via createConnection(loadNetworkConfig().clusterName)
- Returns discriminated union: resolved | not-found | malformed | network-error
- NetworkError caught + reified; unknown throws propagate
- Domain normalization (trim + lowercase) before resolver call
- 23 tests covering descriptor, validation, all 4 result paths, service interaction
sendPrivateToSNS routes a .sol domain to its SIP-STEALTH meta-address via
executeResolveSNS, builds the sip:solana:0x<spending>:0x<viewing> URI in the
0x-hex form sendTool expects, then delegates to executeSend (which derives
the one-time stealth address, builds Pedersen commitment, encrypts amount +
blinding, serializes the unsigned withdraw tx).

- Returns discriminated union: status=ok with composite resolved + send blocks,
  or status=cannot-send with reason ∈ {no-domain, no-record, malformed-record,
  network-error} when resolution fails
- Input field named `recipient` (the .sol domain) so the preflight gate's
  input.recipient read at preflight-gate.ts works unchanged
- Registered in sentinel/preflight-rules.ts FUND_MOVING_TOOLS — blacklist,
  known-repeat, dust rules apply against the .sol domain before execution
- Registered in agent.ts TOOLS + TOOL_EXECUTORS + SYSTEM_PROMPT alongside
  resolveSNS (Task 18) so the LLM can discover and dispatch both
- 22 unit tests covering descriptor, validation, happy path with URI build
  + arg forwarding, all 4 cannot-send paths, service-error propagation from
  both delegated tools
- Registry tests (tools.test.ts, pi-migration.test.ts) updated for new
  count: 22 → 24 tools

Replaces plan's Task 20 HERALD smoke test — HERALD has X-only tools and
no fund-moving surface; the composite unit test exercises the resolve +
URI build + delegate path end-to-end without coupling to LLM determinism.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sipher Ready Ready Preview, Comment May 11, 2026 11:38pm

Phase D added @sip-protocol/sns-stealth which transitively pulls in
@bonfida/spl-name-service@3.0.21. That package ships a broken ESM bundle
(vendored borsh path resolution confuses tsx's ESM loader despite the file
correctly exporting `serialize`). Plain Node ESM handles it fine, but tsx
fails to instantiate the module graph at boot.

This broke playwright in CI because its webServer used
`pnpm --filter @sipher/agent dev` (tsx-based). Production is unaffected —
Docker uses `node dist/index.js`, and the root sipher dev path uses
tsup → node. Unit tests are unaffected — they mock sns-stealth.

Fix scope (minimum):
- playwright.config.ts webServer command: `agent dev` → `agent start`
  (start = `node dist/index.js`, identical to production boot path)
- .github/workflows/e2e.yml: explicit `pnpm --filter @sipher/agent build`
  step before "Run Playwright" so the start command has a `dist/` to run

Agent dev mode (`pnpm --filter @sipher/agent dev` directly) remains
broken under tsx. A follow-up issue will track that separately —
switching the agent's `dev` script to tsup or similar is out of scope
for the Phase D feature PR.
@rz1989s rz1989s merged commit d87b0a7 into main May 11, 2026
8 checks passed
@rz1989s rz1989s deleted the feat/sip-sol-integration branch May 11, 2026 23:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant