@@ -195,6 +195,10 @@ function classifyDepositObject(obj) {
195195 if ( obj . state ?. profile === 'mono.mrc20.v0.1' && obj . prevState ) {
196196 return { type : 'mrc20' , state : obj . state , prevState : obj . prevState , anchor : obj . anchor } ;
197197 }
198+ // Claim deposit: user sent sats to pod's address, claiming with txid
199+ if ( obj . txid && obj . vout !== undefined ) {
200+ return { type : 'claim' , txid : obj . txid , vout : parseInt ( obj . vout , 10 ) , chain : obj . chain } ;
201+ }
198202 // Fall back to TXO URI in .txo field
199203 if ( obj . txo ) {
200204 return { type : 'sats' , txo : obj . txo } ;
@@ -398,11 +402,62 @@ export function createPayHandler(options = {}) {
398402 } ) ;
399403 }
400404
405+ // --- Claim deposit: user sent sats to pod's address ---
406+ if ( deposit . type === 'claim' ) {
407+ const kp = await loadOrCreateKeypair ( ) ;
408+ const chainId = deposit . chain || ( payChains ? payChains [ 0 ] : 'tbtc4' ) ;
409+ if ( payChains && ! payChains . includes ( chainId ) ) {
410+ return reply . code ( 400 ) . send ( { error : `Chain '${ chainId } ' not enabled` , enabledChains : payChains } ) ;
411+ }
412+ const chain = CHAIN_REGISTRY [ chainId ] ;
413+ if ( ! chain ) {
414+ return reply . code ( 400 ) . send ( { error : `Unknown chain: ${ chainId } ` } ) ;
415+ }
416+
417+ // Derive pod's address for this chain
418+ const network = chainId === 'btc' ? 'mainnet' : ( chainId === 'tbtc3' ? 'testnet' : 'testnet4' ) ;
419+ const podAddress = btAddress ( kp . pubkey , [ ] , network ) ;
420+
421+ // Fetch transaction from mempool
422+ const txResp = await fetch ( `${ chain . explorer } /tx/${ deposit . txid } ` ) ;
423+ if ( ! txResp . ok ) {
424+ return reply . code ( 400 ) . send ( { error : 'Transaction not found' } ) ;
425+ }
426+ const txData = await txResp . json ( ) ;
427+ const output = txData . vout ?. [ deposit . vout ] ;
428+ if ( ! output ) {
429+ return reply . code ( 400 ) . send ( { error : `Output ${ deposit . vout } not found` } ) ;
430+ }
431+
432+ // Verify output pays our address
433+ if ( output . scriptpubkey_address !== podAddress ) {
434+ return reply . code ( 400 ) . send ( { error : 'Output does not pay this pod\'s address' , expected : podAddress } ) ;
435+ }
436+
437+ const amount = output . value ;
438+ const currency = chain . unit ;
439+ const didUri = pubkeyToDidNostr ( pubkey ) ;
440+ const ledger = await readLedger ( ) ;
441+ const newBalance = credit ( ledger , didUri , amount , currency ) ;
442+ await writeLedger ( ledger ) ;
443+
444+ return reply . send ( {
445+ did : didUri ,
446+ deposited : amount ,
447+ balance : newBalance ,
448+ unit : currency ,
449+ chain : chainId ,
450+ txid : deposit . txid ,
451+ address : podAddress
452+ } ) ;
453+ }
454+
401455 return reply . code ( 400 ) . send ( {
402- error : 'Invalid deposit format. Send a TXO URI string or MRC20 state proof.' ,
456+ error : 'Invalid deposit format. Send a TXO URI string, MRC20 state proof, or claim {txid, vout} .' ,
403457 formats : {
404458 sats : 'POST body: "<txid>:<vout>" or {"txo": "<txid>:<vout>"}' ,
405- mrc20 : 'POST body: {"type": "mrc20", "state": {...}, "prevState": {...}}'
459+ mrc20 : 'POST body: {"type": "mrc20", "state": {...}, "prevState": {...}}' ,
460+ claim : 'POST body: {"txid": "...", "vout": 0, "chain": "tbtc4"}'
406461 }
407462 } ) ;
408463 }
0 commit comments