aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/gifts-wallet.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/gifts-wallet.js')
-rw-r--r--src/features/gifts-wallet.js22
1 files changed, 22 insertions, 0 deletions
diff --git a/src/features/gifts-wallet.js b/src/features/gifts-wallet.js
index bf8a82d..c25e85a 100644
--- a/src/features/gifts-wallet.js
+++ b/src/features/gifts-wallet.js
@@ -5,6 +5,7 @@
import * as btc from '@scure/btc-signer';
import { hex, base64urlnopad, base32nopad } from '@scure/base';
+import { encryptGiftPayload } from '../nostr.js';
import { utxoId } from '../wallet.js';
import { p2wpkh } from '@scure/btc-signer/payment';
import { concatBytes } from '@noble/hashes/utils';
@@ -467,3 +468,24 @@ export function buildClaimTx(code, toAddress, feeRate, net) {
// public (anyone can see what it is and who it's for), but the claim payload (the
// gift code) is NIP-44-encrypted to the recipient via an ephemeral key — so the
// link can be shared openly yet only the recipient's nostr key can claim it.
+// v2 wraps an ARK gift code (v1 wrapped on-chain PSBT gifts and is retired —
+// its links no longer exist in the wild). Two ways in, same plaintext: the
+// one-time claim code DM'd to the recipient, or a NIP-07 extension holding
+// the recipient's key decrypting the ephemeral-key ciphertext in place.
+const LOCKED_GIFT_VERSION = 2;
+// Encrypt the gift's claim payload under a one-time code. The code is delivered
+// to the recipient via a nostr DM; the public link carries only the ciphertext.
+// Returns { blob (for the /lg/ link), claimCode (to DM) }.
+export function lockGift(code, amount, recipientPkHex) {
+ const { code: claimCode, ctCode, eph, ctKey } = encryptGiftPayload(code, recipientPkHex);
+ const blob = base64urlnopad.encode(new TextEncoder().encode(JSON.stringify({ v: LOCKED_GIFT_VERSION, amount, to: recipientPkHex, ct: ctCode, eph, ctKey })));
+ return { blob, claimCode };
+}
+// The public fields of a locked-gift blob (or null). Decryption needs the code.
+export function previewLockedGift(blob) {
+ try {
+ const o = JSON.parse(new TextDecoder().decode(base64urlnopad.decode(blob)));
+ if (o.v === LOCKED_GIFT_VERSION && typeof o.amount === 'number' && o.to && o.ct) return o;
+ } catch {}
+ return null;
+}