Skip to content

fix(scan): parse versioned SIP:1/SIP:2 announcements via SDK parseAnnouncement (#313)#314

Merged
rz1989s merged 1 commit into
mainfrom
fix/scan-sip2-memo-parse
Jun 9, 2026
Merged

fix(scan): parse versioned SIP:1/SIP:2 announcements via SDK parseAnnouncement (#313)#314
rz1989s merged 1 commit into
mainfrom
fix/scan-sip2-memo-parse

Conversation

@rz1989s

@rz1989s rz1989s commented Jun 9, 2026

Copy link
Copy Markdown
Member

Closes #313.

The bug (broader than the issue framed)

Both /v1/scan/payments and /v1/scan/payments/batch parsed stealth announcements with:

const parts = memoMatch[1].replace('SIP:', '').split(':')
const [ephemeralB58, viewTagHex, stealthB58] = parts

This assumes an unversioned SIP:<eph>:<viewTag>:<stealth> memo. But git history shows the SIP SDK has always emitted versioned announcements:

  • pre-flip: createAnnouncementMemoSIP:1:<eph>:<viewTag>, parseAnnouncement required startsWith('SIP:1:')
  • post EIP-5564 canonical flip (sdk 0.10/0.11): SIP:2:<eph>:<viewTag>[:<stealth>]

For a real memo, .replace('SIP:', '') leaves the version digit as parts[0]new PublicKey('1'|'2') throws → the announcement is silently skipped. So the route missed every real on-chain announcement (SIP:1 and SIP:2), not just SIP:2 as the issue estimated. The unversioned form the code parsed is fictional — nothing in the SIP ecosystem emits it (verified: sipher writes no SIP: memos; the SDK always versions).

The fix

Adopt the SDK's canonical, version-aware parseAnnouncement at both parse sites. It resolves SIP:1: + SIP:2: and validates the ephemeral/viewTag/stealth components. The fictional unversioned form is intentionally no longer matched (a test documents this). Downstream logic (PublicKey→hex, viewTag range, checkEd25519StealthAddress, response shape) is unchanged — no API contract change.

SDK ESM-export gap (found en route — filed separately)

parseAnnouncement/createAnnouncementMemo are exported from the SDK's ESM build, but the constants SIP_MEMO_PREFIX_ANY / SIP_MEMO_PREFIX_V2 exist only in the SDK's CJS runtime bundle (dist/index.js) — they're absent from the ESM bundle (dist/index.mjs) and from both type-declaration files (dist/index.d.ts, dist/index.d.mts). So no ESM/TS consumer can import them: tsc errors ("no exported member"), and a bundler that tolerates missing named ESM imports yields undefined at runtime (caught here during TDD). The SIP: pre-filter therefore uses a local constant; parseAnnouncement does the authoritative parsing. Filed upstream: sip-protocol/sip-protocol#1123.

Tests (TDD — red first)

  • POST /v1/scan/payments — SIP:2 view-only round-trip (built via the SDK's createAnnouncementMemo)
  • POST /v1/scan/payments/batch — SIP:2 view-only round-trip (the batch route has the same bug)
  • guard: legacy unversioned SIP: memo is ignored

Verification

  • pnpm exec vitest run tests/scan.test.ts → 15/15
  • pnpm exec vitest run (root) → 558/558 (+2 net new)
  • pnpm typecheck → clean (app + packages/sdk + packages/agent)

Notes

@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
sipher Ready Ready Preview, Comment Jun 9, 2026 1:47am

…ouncement

The /v1/scan/payments and /scan/payments/batch routes parsed stealth
announcements with `.replace('SIP:', '').split(':')`, assuming an unversioned
`SIP:<eph>:<viewTag>:<stealth>` memo. But the SIP SDK has always emitted
versioned announcements — `SIP:1:` (legacy) and, since the EIP-5564 canonical
flip (sdk 0.10/0.11), `SIP:2:`. The naive parse leaves the version digit as
parts[0], so `new PublicKey('1'|'2')` throws and the announcement is silently
skipped. The route therefore missed every real on-chain announcement, not just
SIP:2.

Replace both parse sites with the SDK's canonical, version-aware
`parseAnnouncement`, which resolves SIP:1 + SIP:2 and validates the
ephemeral/viewTag/stealth components. The unversioned form is a fictional format
no producer emits and is intentionally no longer matched.

The `SIP:` log pre-filter uses a local constant because the SDK's
`SIP_MEMO_PREFIX_ANY` currently exists only in the SDK's CJS runtime bundle —
absent from the ESM build and from both type-declaration files, so an ESM/TS
consumer can't import it; `parseAnnouncement` does the authoritative parsing.

Tests: SIP:2 view-only round-trips for both routes + a guard that the legacy
unversioned form is ignored.

Closes #313
@rz1989s rz1989s force-pushed the fix/scan-sip2-memo-parse branch from 225ed1d to 5638dfc Compare June 9, 2026 01:47
@rz1989s rz1989s merged commit 72dc822 into main Jun 9, 2026
8 checks passed
@rz1989s rz1989s deleted the fix/scan-sip2-memo-parse branch June 9, 2026 02:13
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.

scan.ts: /v1/scan/payments mis-parses SIP:2 versioned memo announcements

1 participant