diff --git a/src/client/api/deposit.ts b/src/client/api/deposit.ts index 4281e52d..23ffe251 100644 --- a/src/client/api/deposit.ts +++ b/src/client/api/deposit.ts @@ -1,17 +1,23 @@ -import { EscrowClient } from '../class/client.js' -import { validate_account_req, validate_register_req } from '@/validators/index.js' +import { EscrowClient } from '../class/client.js' + +import { + validate_account_req, + validate_covenant, + validate_register_req, + validate_spend_req +} from '@/validators/index.js' import { CovenantData, - ReturnData, ApiResponse, AccountRequest, AccountDataResponse, DepositDataResponse, DepositListResponse, FundingDataResponse, - RegisterRequest + RegisterRequest, + SpendRequest } from '@/types/index.js' import * as assert from '@/assert.js' @@ -104,6 +110,8 @@ function list_deposit_api (client : EscrowClient) { pubkey : string, token : string ) : Promise> => { + // Validate the pubkey. + assert.is_hash(pubkey) // Formulate the request. const url = `${client.host}/api/deposit/list/${pubkey}` // Return the response. @@ -116,26 +124,32 @@ function commit_funds_api (client : EscrowClient) { dpid : string, covenant : CovenantData ) : Promise> => { + // Validate the deposit id. + assert.is_hash(dpid) + // Validate the covenant. + validate_covenant(covenant) + // Create the request url. const url = `${client.host}/api/deposit/${dpid}/commit` - const body = JSON.stringify(covenant) + // Create the request object. const init = { - body, + body : JSON.stringify(covenant), method : 'POST', headers : { 'content-type' : 'application/json' } } + // Fetch and return a response. return client.fetcher({ url, init }) } } function close_deposit_api (client : EscrowClient) { return async ( - req : ReturnData + dpid : string, + req : SpendRequest ) : Promise> => { - const dpid = req.dpid + validate_spend_req(req) const url = `${client._host}/api/deposit/${dpid}/close` - const body = JSON.stringify(req) const init = { - body, + body : JSON.stringify(req), headers : { 'content-type': 'application/json' }, method : 'POST' } diff --git a/src/lib/deposit.ts b/src/lib/deposit.ts index bab3b4ab..1b2ce557 100644 --- a/src/lib/deposit.ts +++ b/src/lib/deposit.ts @@ -1,5 +1,7 @@ -import { get_return_script } from './return.js' +import { parse_extkey } from '@cmdcode/crypto-tools/hd' import { validate_deposit } from '@/validators/deposit.js' +import { get_return_script } from './return.js' +import { RETURN_TX_VSIZE } from '@/config.js' import { get_object_id, @@ -12,6 +14,7 @@ import { } from '@cmdcode/musig2' import { + create_tx_template, get_address, get_tapkey, parse_timelock @@ -160,3 +163,22 @@ export function get_spend_state ( // Return the spend state. return state } + +export function get_ext_pubkey (xpub : string) { + return parse_extkey(xpub).pubkey +} + +export function get_close_tx_template ( + value : number, + xpub : string, + feerate : number +) { + // Parse the return pubkey. + const return_pk = get_ext_pubkey(xpub) + // Create locking script. + const script = [ 'OP_1', return_pk ] + // Calculate the transaction fee. + const txfee = Math.ceil(RETURN_TX_VSIZE * feerate) + // Return a transaction using the provided params. + return create_tx_template(script, value - txfee) +} diff --git a/src/schema/deposit.ts b/src/schema/deposit.ts index 67bc6f52..068d2112 100644 --- a/src/schema/deposit.ts +++ b/src/schema/deposit.ts @@ -58,6 +58,12 @@ const reg_req = z.object({ utxo : txspend }) +const spend_req = z.object({ + pnonce : nonce, + psig : hex, + txfee : num +}) + const data = z.object({ status, agent_id : hash, @@ -73,4 +79,4 @@ const data = z.object({ updated_at : stamp }).and(state).and(spend_state).and(close_state).and(txspend) -export default { account, covenant, data, state, acct_req, reg_req, status } +export default { account, covenant, data, state, acct_req, reg_req, spend_req, status } diff --git a/src/types/deposit.ts b/src/types/deposit.ts index b1362465..dbba5d47 100644 --- a/src/types/deposit.ts +++ b/src/types/deposit.ts @@ -101,6 +101,12 @@ export interface RegisterRequest { utxo : TxOutput } +export interface SpendRequest { + feerate : number + pnonce : string + psig : string +} + export interface ExtendedKey { prefix : number depth : number diff --git a/src/validators/covenant.ts b/src/validators/covenant.ts index 1c542a9d..58551947 100644 --- a/src/validators/covenant.ts +++ b/src/validators/covenant.ts @@ -3,6 +3,8 @@ import { parse_txout } from '@/lib/tx.js' import { parse_covenant } from '@/lib/parse.js' import { get_entry } from '../lib/util.js' +import { get_close_tx_template } from '@/lib/deposit.js' + import { get_path_mutexes, get_return_mutex, @@ -15,9 +17,9 @@ import { CovenantData, DepositContext, DepositData, - ReturnData, MutexEntry, - SignerAPI + SignerAPI, + SpendRequest } from '../types/index.js' import * as assert from '../assert.js' @@ -52,16 +54,16 @@ export function verify_covenant ( check_deposit_psigs(entries, covenant.psigs) } -export function verify_refund ( +export function verify_spend_req ( dp_agent : SignerAPI, deposit : DepositData, - refund : ReturnData + request : SpendRequest ) { - const { dpid, agent_pn } = deposit - const { pnonce, psig, txhex } = refund - assert.ok(dpid === refund.dpid, 'deposit_id does not match') + const { agent_pn, spend_xpub, value } = deposit + const { feerate, pnonce, psig } = request check_deposit_agent(dp_agent, deposit) const pnonces = [ pnonce, agent_pn ] + const txhex = get_close_tx_template(value, spend_xpub, feerate) const mutex = get_return_mutex(deposit, pnonces, txhex) verify_mutex_psig(mutex, psig) } diff --git a/src/validators/deposit.ts b/src/validators/deposit.ts index 6be4a2e6..aa134ef2 100644 --- a/src/validators/deposit.ts +++ b/src/validators/deposit.ts @@ -5,6 +5,7 @@ import { DepositContext, DepositData, RegisterRequest, + SpendRequest, TxOutput } from '../types/index.js' @@ -23,6 +24,12 @@ export function validate_register_req ( schema.deposit.reg_req.parse(template) } +export function validate_spend_req ( + template : unknown +) : asserts template is SpendRequest { + schema.deposit.spend_req.parse(template) +} + export function validate_deposit ( deposit : Record ) : asserts deposit is DepositData {