feat(withdraw): track creator pubkey on withdraw links
Store the Nostr pubkey of the user who creates a withdraw link so the LNURL callback debits the correct user's balance instead of the app owner's. Pass userPubkey through from RPC handler to WithdrawManager. - Add creator_pubkey column (migration v4) - Store creatorPubkey on link creation - Pass creator_pubkey to payInvoice on LNURL callback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
55077d818e
commit
9d352e5f07
4 changed files with 26 additions and 6 deletions
|
|
@ -114,8 +114,8 @@ export default class WithdrawExtension implements Extension {
|
||||||
*/
|
*/
|
||||||
private registerRpcMethods(ctx: ExtensionContext): void {
|
private registerRpcMethods(ctx: ExtensionContext): void {
|
||||||
// Create withdraw link
|
// Create withdraw link
|
||||||
ctx.registerMethod('withdraw.createLink', async (req, appId) => {
|
ctx.registerMethod('withdraw.createLink', async (req, appId, userPubkey) => {
|
||||||
const link = await this.manager.create(appId, req as CreateWithdrawLinkRequest)
|
const link = await this.manager.create(appId, req as CreateWithdrawLinkRequest, userPubkey)
|
||||||
const stats = await this.manager.getWithdrawalStats(link.id)
|
const stats = await this.manager.getWithdrawalStats(link.id)
|
||||||
return {
|
return {
|
||||||
link,
|
link,
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ interface WithdrawLinkRow {
|
||||||
is_unique: number
|
is_unique: number
|
||||||
uses_csv: string
|
uses_csv: string
|
||||||
open_time: number
|
open_time: number
|
||||||
|
creator_pubkey: string | null
|
||||||
webhook_url: string | null
|
webhook_url: string | null
|
||||||
webhook_headers: string | null
|
webhook_headers: string | null
|
||||||
webhook_body: string | null
|
webhook_body: string | null
|
||||||
|
|
@ -87,6 +88,7 @@ function rowToLink(row: WithdrawLinkRow): WithdrawLink {
|
||||||
is_unique: row.is_unique === 1,
|
is_unique: row.is_unique === 1,
|
||||||
uses_csv: row.uses_csv,
|
uses_csv: row.uses_csv,
|
||||||
open_time: row.open_time,
|
open_time: row.open_time,
|
||||||
|
creator_pubkey: row.creator_pubkey || undefined,
|
||||||
webhook_url: row.webhook_url || undefined,
|
webhook_url: row.webhook_url || undefined,
|
||||||
webhook_headers: row.webhook_headers || undefined,
|
webhook_headers: row.webhook_headers || undefined,
|
||||||
webhook_body: row.webhook_body || undefined,
|
webhook_body: row.webhook_body || undefined,
|
||||||
|
|
@ -150,7 +152,7 @@ export class WithdrawManager {
|
||||||
/**
|
/**
|
||||||
* Create a new withdraw link
|
* Create a new withdraw link
|
||||||
*/
|
*/
|
||||||
async create(applicationId: string, req: CreateWithdrawLinkRequest): Promise<WithdrawLinkWithLnurl> {
|
async create(applicationId: string, req: CreateWithdrawLinkRequest, creatorPubkey?: string): Promise<WithdrawLinkWithLnurl> {
|
||||||
// Validation
|
// Validation
|
||||||
if (req.uses < 1 || req.uses > 250) {
|
if (req.uses < 1 || req.uses > 250) {
|
||||||
throw new Error('Uses must be between 1 and 250')
|
throw new Error('Uses must be between 1 and 250')
|
||||||
|
|
@ -200,6 +202,7 @@ export class WithdrawManager {
|
||||||
is_unique: req.is_unique || false,
|
is_unique: req.is_unique || false,
|
||||||
uses_csv: usesCsv,
|
uses_csv: usesCsv,
|
||||||
open_time: now,
|
open_time: now,
|
||||||
|
creator_pubkey: creatorPubkey,
|
||||||
webhook_url: req.webhook_url,
|
webhook_url: req.webhook_url,
|
||||||
webhook_headers: req.webhook_headers,
|
webhook_headers: req.webhook_headers,
|
||||||
webhook_body: req.webhook_body,
|
webhook_body: req.webhook_body,
|
||||||
|
|
@ -212,13 +215,15 @@ export class WithdrawManager {
|
||||||
id, application_id, title, description,
|
id, application_id, title, description,
|
||||||
min_withdrawable, max_withdrawable, uses, used, wait_time,
|
min_withdrawable, max_withdrawable, uses, used, wait_time,
|
||||||
unique_hash, k1, is_unique, uses_csv, open_time,
|
unique_hash, k1, is_unique, uses_csv, open_time,
|
||||||
|
creator_pubkey,
|
||||||
webhook_url, webhook_headers, webhook_body,
|
webhook_url, webhook_headers, webhook_body,
|
||||||
created_at, updated_at
|
created_at, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[
|
[
|
||||||
link.id, link.application_id, link.title, link.description || null,
|
link.id, link.application_id, link.title, link.description || null,
|
||||||
link.min_withdrawable, link.max_withdrawable, link.uses, link.used, link.wait_time,
|
link.min_withdrawable, link.max_withdrawable, link.uses, link.used, link.wait_time,
|
||||||
link.unique_hash, link.k1, link.is_unique ? 1 : 0, link.uses_csv, link.open_time,
|
link.unique_hash, link.k1, link.is_unique ? 1 : 0, link.uses_csv, link.open_time,
|
||||||
|
link.creator_pubkey || null,
|
||||||
link.webhook_url || null, link.webhook_headers || null, link.webhook_body || null,
|
link.webhook_url || null, link.webhook_headers || null, link.webhook_body || null,
|
||||||
link.created_at, link.updated_at
|
link.created_at, link.updated_at
|
||||||
]
|
]
|
||||||
|
|
@ -502,11 +507,12 @@ export class WithdrawManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pay the invoice
|
// Pay the invoice from the creator's balance (if created via Nostr RPC)
|
||||||
const payment = await this.ctx.payInvoice(
|
const payment = await this.ctx.payInvoice(
|
||||||
link.application_id,
|
link.application_id,
|
||||||
params.pr,
|
params.pr,
|
||||||
link.max_withdrawable
|
link.max_withdrawable,
|
||||||
|
link.creator_pubkey
|
||||||
)
|
)
|
||||||
|
|
||||||
// Record the withdrawal
|
// Record the withdrawal
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,17 @@ export const migrations: Migration[] = [
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: 4,
|
||||||
|
name: 'add_creator_pubkey_column',
|
||||||
|
up: async (db: ExtensionDatabase) => {
|
||||||
|
// Store the Nostr pubkey of the user who created the withdraw link
|
||||||
|
// so that when the LNURL callback fires, we debit the correct user's balance
|
||||||
|
await db.execute(`
|
||||||
|
ALTER TABLE withdraw_links ADD COLUMN creator_pubkey TEXT
|
||||||
|
`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,9 @@ export interface WithdrawLink {
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
open_time: number // Unix timestamp when next use is allowed
|
open_time: number // Unix timestamp when next use is allowed
|
||||||
|
|
||||||
|
// Creator identity (for Nostr RPC-created links)
|
||||||
|
creator_pubkey?: string // Nostr pubkey of the user who created this link
|
||||||
|
|
||||||
// Webhook notifications
|
// Webhook notifications
|
||||||
webhook_url?: string
|
webhook_url?: string
|
||||||
webhook_headers?: string // JSON string
|
webhook_headers?: string // JSON string
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue