aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-19 22:13:54 -0700
committerAdam Soltys <[email protected]>2026-10-19 22:13:54 -0700
commita0829612b8751e85f28d066fbaa0c2308a85fbda (patch)
treee9d81ffe0d37023cefbecd1bdf35cf0bf39a5e83 /tools
parent1a85347b0a37886e550497eab5ece05410fce923 (diff)
downloadcoinosv3-a0829612b8751e85f28d066fbaa0c2308a85fbda.tar.gz
coinosv3-a0829612b8751e85f28d066fbaa0c2308a85fbda.zip
Zaps are signed as the person zapping: the login identity (extension, bunker, passkey, Google) when there is one, else the wallet's key. They were always signed by the wallet's own key, so a signed-in person's zaps showed in stream chats and streamers' overlays under a stranger npub. Zap requests also carry created_at now (a real signer refused them without it).
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'tools')
-rw-r--r--tools/zap-signer-test.js43
1 files changed, 43 insertions, 0 deletions
diff --git a/tools/zap-signer-test.js b/tools/zap-signer-test.js
new file mode 100644
index 0000000..13f6008
--- /dev/null
+++ b/tools/zap-signer-test.js
@@ -0,0 +1,43 @@
+// A zap is signed as the person zapping: with a login identity (extension,
+// bunker, passkey…) the zap request's pubkey is that identity, not the
+// wallet's own key — a signed-in person's zaps showed in stream chats (and
+// streamers' overlays) under the wallet key's stranger npub. Without a
+// login, the wallet key signs as before.
+// Run: bun tools/zap-signer-test.js
+import assert from 'node:assert/strict';
+import { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure';
+import { zapsFeature } from '../src/features/zaps.js';
+
+const check = (name, ok, detail = '') => { assert(ok, name + (detail ? ' — ' + detail : '')); console.log(' ✓ ' + name); };
+const walletSk = generateSecretKey(), loginSk = generateSecretKey(), host = getPublicKey(generateSecretKey());
+let asked = null;
+globalThis.fetch = async (url) => {
+ const u = new URL(url);
+ if (u.pathname.startsWith('/.well-known/lnurlp/')) return Response.json({ tag: 'payRequest', callback: 'https://ln.example/cb', minSendable: 1000, maxSendable: 1e9, allowsNostr: true, nostrPubkey: host });
+ if (u.pathname === '/cb') { asked = JSON.parse(u.searchParams.get('nostr')); return Response.json({ pr: 'lnbc210n1fake' }); }
+ return new Response('no', { status: 404 });
+};
+async function zapWith(login) {
+ asked = null;
+ let settled = null;
+ const ctx = {
+ h: () => null, ui: {}, render: () => {}, toast: (m) => console.log('toast:', m), fmtAmount: String, unitLabel: () => 'sats', parseAmount: Number, getUnit: () => 'sat', unitTag: () => '', blankSend: () => {},
+ wallet: { watchOnly: false, nostrRelays: () => ['wss://relay.example'], nostrProfile: async () => ({ lud16: '[email protected]' }), nostrSign: (e) => finalizeEvent(e, walletSk) },
+ hook(name, ...a) {
+ if (name === 'canLnPay' || name === 'arkReady') return true;
+ if (name === 'nostrLoginIdentity') return login;
+ if (name === 'arkPayInvoice') return Promise.resolve({});
+ if (name === 'zapSettled') { settled = a; return null; }
+ return null;
+ },
+ };
+ zapsFeature(ctx).lnZapNpub(host, null, 'e'.repeat(64), 21);
+ for (let i = 0; i < 50 && !settled; i++) await new Promise((r) => setTimeout(r, 20));
+ return { req: asked, settled };
+}
+const signedIn = await zapWith({ pubkey: getPublicKey(loginSk), signer: { signEvent: async (e) => finalizeEvent(e, loginSk) } });
+check('signed in: the zap goes through', signedIn.settled && signedIn.settled[1] === true, JSON.stringify(signedIn.settled));
+check('...and its request is signed by the login identity', signedIn.req && signedIn.req.kind === 9734 && signedIn.req.pubkey === getPublicKey(loginSk), signedIn.req && signedIn.req.pubkey);
+const plain = await zapWith(null);
+check('no login: the wallet\'s own key signs it', plain.req && plain.req.pubkey === getPublicKey(walletSk));
+console.log('\n✅ zaps are signed as the person zapping');