syntax = "proto3";

// Onchain HTLC (Hash-Time-Locked Contract) operations for cross-chain
// atomic swaps.  Provides lock, claim, refund, and verification RPCs
// that work across Bitcoin (P2WSH) and EVM (HtlcFactory contract).

package hydra_app;

import "primitives.proto";
import "transaction.proto";

service HtlcService {
  // Lock funds into a new onchain HTLC.
  rpc CreateHtlc(CreateHtlcRequest) returns (CreateHtlcResponse) {}

  // Lock funds into multiple HTLCs in a single transaction (where supported).
  rpc BatchCreateHtlcs(BatchCreateHtlcsRequest)
      returns (BatchCreateHtlcsResponse) {}

  // Claim an HTLC by revealing the preimage.
  rpc ClaimHtlc(ClaimHtlcRequest) returns (ClaimHtlcResponse) {}

  // Refund an expired HTLC back to the sender.
  rpc RefundHtlc(RefundHtlcRequest) returns (RefundHtlcResponse) {}

  // Build an UNSIGNED HTLC lock transaction (calldata / PSBT) for an external
  // party to sign and broadcast. Unlike CreateHtlc, the node does not sign,
  // broadcast, or key the refund branch to itself — the caller owns signing and
  // supplies `refund_address`.
  rpc CreateHtlcLockTx(CreateHtlcLockTxRequest)
      returns (HtlcTransactionRequest) {}

  // Build an UNSIGNED HTLC claim transaction for an external signer.
  rpc CreateHtlcClaimTx(CreateHtlcClaimTxRequest)
      returns (HtlcTransactionRequest) {}

  // Build an UNSIGNED HTLC refund transaction for an external signer.
  rpc CreateHtlcRefundTx(CreateHtlcRefundTxRequest)
      returns (HtlcTransactionRequest) {}

  // Verify an onchain HTLC matches expected parameters.
  rpc VerifyHtlc(VerifyHtlcRequest) returns (VerifyHtlcResponse) {}

  // Read an HTLC's current state directly from the chain by its lock
  // transaction — a raw read with no expectation checks and no record
  // persistence (VerifyHtlc is the term-judging read). The response carries
  // no HTLC when the chain cannot produce it: unknown lock transaction, or
  // parameters the chain does not reveal (e.g. a Bitcoin HTLC that is unspent,
  // or Taproot-spent with the unused leaf hidden).
  rpc GetChainHtlc(GetChainHtlcRequest) returns (GetChainHtlcResponse) {}

  // Verify multiple HTLCs.
  rpc BatchVerifyHtlcs(BatchVerifyHtlcsRequest)
      returns (BatchVerifyHtlcsResponse) {}

  // Fetch the node's own HTLC public key on a network — the key its locks
  // refund to on timeout, and the identity counterparties lock to.
  rpc GetHtlcPubkey(GetHtlcPubkeyRequest) returns (GetHtlcPubkeyResponse) {}

  // Reserve a fresh, unique HTLC public key. On UTXO protocols each call
  // returns a key never handed out before; account-based protocols return their
  // single stable key.
  rpc GetUniqueHtlcPubkey(GetHtlcPubkeyRequest)
      returns (GetHtlcPubkeyResponse) {}

  // Watch an externally-locked HTLC the node did not create itself, so its
  // lifecycle transitions arrive on the node event stream.  Fire-and-forget.
  rpc WatchHtlc(WatchHtlcRequest) returns (WatchHtlcResponse) {}

  // Stop observing an HTLC — abort and deregister its watcher. Idempotent.
  rpc UnwatchHtlc(UnwatchHtlcRequest) returns (UnwatchHtlcResponse) {}

  // Build an UNSIGNED HTLC settlement transaction spending an HTLC to a fixed
  // output, for an external signer to pre-sign. Applies only to signature-gated
  // (UTXO-model) protocols; account-model protocols settle permissionlessly and
  // reject this with FAILED_PRECONDITION.
  rpc CreateHtlcSettlementTx(CreateHtlcSettlementTxRequest)
      returns (CreateHtlcSettlementTxResponse) {}

  // Broadcast a user-pre-signed HTLC settlement transaction, injecting the swap
  // secret (the preimage, for a hashlock claim) if the branch requires it.
  // UTXO-model protocols only.
  rpc BroadcastHtlcSettlement(BroadcastHtlcSettlementRequest)
      returns (BroadcastHtlcSettlementResponse) {}

  // The address the HTLC parameters commit to under the node's configured
  // script kind — the address an external wallet pays to fund the HTLC.
  // UTXO-model protocols only (account-model HTLCs have no fund-by-address
  // form); rejected with FAILED_PRECONDITION otherwise.
  rpc DeriveHtlcAddress(DeriveHtlcAddressRequest)
      returns (DeriveHtlcAddressResponse) {}

  // Locate the HTLC output inside the attested funding transaction, verify it
  // against the expected parameters, seed the node's record, and return the
  // full HTLC (carrying its chain-native id). The entry point for an
  // externally-funded HTLC whose outpoint the funder cannot know in advance.
  // UTXO-model protocols only; rejected with FAILED_PRECONDITION otherwise.
  rpc VerifyHtlcByLockTxid(VerifyHtlcByLockTxidRequest)
      returns (VerifyHtlcByLockTxidResponse) {}
}

// Which branch of an HTLC a settlement transaction spends.
enum SettlementKind {
  SETTLEMENT_KIND_UNSPECIFIED = 0;
  SETTLEMENT_KIND_CLAIM = 1;
  SETTLEMENT_KIND_REFUND = 2;
}

message CreateHtlcSettlementTxRequest {
  Network network = 1;
  // Chain-native HTLC id the settlement spends.
  string htlc_id = 2;
  SettlementKind kind = 3;
  // The output the settlement pays (protocol-specific encoding). Empty asks the
  // node to pay the spending party's own key — the claim recipient or the
  // refund sender — as a P2WPKH, for a caller that cannot construct chain
  // addresses.
  string output = 4;
}

message CreateHtlcSettlementTxResponse {
  // The unsigned settlement transaction in the leg protocol's wire form (e.g. a
  // base64 PSBT) for the external signer to pre-sign.
  bytes unsigned_tx = 1;
}

message BroadcastHtlcSettlementRequest {
  Network network = 1;
  // The user-pre-signed settlement transaction collected earlier.
  bytes signed_tx = 2;
  // The 32-byte preimage to inject for a CLAIM; empty for a REFUND.
  bytes secret = 3;
}

message BroadcastHtlcSettlementResponse {
  // The broadcast transaction id.
  string txid = 1;
}

// Same lock parameters as CreateHtlcLockTx — the address is fully determined
// by them under the node's configured script kind.
message DeriveHtlcAddressRequest {
  Network network = 1;
  // The asset's canonical id; the zero address for the chain's native asset.
  string asset_id = 2;
  // Lock amount in the asset's DISPLAY unit (the dust floor is enforced).
  DecimalString amount = 3;
  // SHA-256 payment hash (32 bytes).
  bytes payment_hash = 4;
  // Recipient address or public key (protocol-specific encoding).
  string recipient = 5;
  // Absolute Unix-epoch-seconds deadline at which the timelock expires.
  uint64 timelock_unix_seconds = 6;
  // The refund/sender public key the timeout branch pays.
  string refund_address = 7;
  // Canonical protocol-defined lock-type token (e.g. Bitcoin "taproot" /
  // "p2wsh"); empty = the node's signer-derived default.
  string lock_type = 8;
}

message DeriveHtlcAddressResponse {
  // The chain address an external wallet pays to fund the HTLC.
  string address = 1;
}

message VerifyHtlcByLockTxidRequest {
  Network network = 1;
  // The locker's attested funding transaction id — the HTLC output is located
  // inside it (the funder chooses the output order, so the vout is discovered).
  string lock_txid = 2;
  HtlcExpectation expected = 3;
}

message VerifyHtlcByLockTxidResponse {
  // The located and verified HTLC, carrying its chain-native id (txid:vout).
  Htlc htlc = 1;
}

// ─── Requests & Responses ────────────────────────────────

message CreateHtlcRequest {
  Network network = 1;
  // The asset's canonical id (the token address); the zero address
  // (`0x000…000`) for the chain's native asset.
  string asset_id = 2;
  // Lock amount in the asset's DISPLAY unit (e.g. BTC, ETH). The node scales it
  // to the smallest unit by the asset's decimals on receipt.
  DecimalString amount = 3;
  // SHA-256 payment hash (32 bytes).
  bytes payment_hash = 4;
  // Recipient address or public key (protocol-specific encoding).
  string recipient = 5;
  // Absolute Unix-epoch-seconds deadline at which the timelock expires.
  uint64 timelock_unix_seconds = 6;
  // Canonical protocol-defined lock-type token (e.g. Bitcoin "taproot" /
  // "p2wsh"); empty = the node's signer-derived default. Protocols with a
  // single canonical lock construction reject any token.
  string lock_type = 7;
}

message CreateHtlcResponse {
  Htlc htlc = 1;
  // The broadcast lock transaction. The HTLC state is built before the lock
  // is signed and broadcast, so this — not the state's own lock_txid — is the
  // authoritative lock transaction id.
  string txid = 2;
}

message CreateHtlcLockTxRequest {
  Network network = 1;
  // The asset's canonical id; the zero address for the chain's native asset.
  string asset_id = 2;
  // Lock amount in the asset's DISPLAY unit.
  DecimalString amount = 3;
  // SHA-256 payment hash (32 bytes).
  bytes payment_hash = 4;
  // Recipient address or public key (protocol-specific encoding).
  string recipient = 5;
  // Absolute Unix-epoch-seconds deadline at which the timelock expires.
  uint64 timelock_unix_seconds = 6;
  // The refund/sender public key the timeout branch pays — the EXTERNAL
  // signer's own key (CreateHtlc instead keys the refund branch to the node).
  string refund_address = 7;
  // Canonical protocol-defined lock-type token (e.g. Bitcoin "taproot" /
  // "p2wsh"); empty = the node's signer-derived default. Protocols with a
  // single canonical lock construction reject any token.
  string lock_type = 8;
}

message CreateHtlcClaimTxRequest {
  Network network = 1;
  // Chain-native HTLC id of the lock being claimed.
  string htlc_id = 2;
  // The 32-byte preimage that unlocks the HTLC.
  bytes preimage = 3;
}

message CreateHtlcRefundTxRequest {
  Network network = 1;
  // Chain-native HTLC id of the expired lock being refunded.
  string htlc_id = 2;
}

message BatchCreateHtlcsRequest {
  Network network = 1;
  repeated HtlcCreateParams params = 2;
}

message HtlcCreateParams {
  // The asset's canonical id (the token address); the zero address
  // (`0x000…000`) for the chain's native asset.
  string asset_id = 1;
  // Lock amount in the asset's DISPLAY unit.
  DecimalString amount = 2;
  bytes payment_hash = 3;
  string recipient = 4;
  // Absolute Unix-epoch-seconds deadline at which the timelock expires.
  uint64 timelock_unix_seconds = 5;
  // Canonical protocol-defined lock-type token (e.g. Bitcoin "taproot" /
  // "p2wsh"); empty = the node's signer-derived default.
  string lock_type = 6;
}

message BatchCreateHtlcsResponse {
  repeated Htlc htlcs = 1;
  // The one broadcast lock transaction carrying every HTLC in the batch.
  string txid = 2;
}

message ClaimHtlcRequest {
  Network network = 1;
  // Protocol-specific HTLC identifier.
  string htlc_id = 2;
  // The 32-byte preimage whose SHA-256 hash matches the payment hash.
  bytes preimage = 3;
}

message ClaimHtlcResponse {
  // Transaction ID of the claim transaction.
  string tx_id = 1;
}

message RefundHtlcRequest {
  Network network = 1;
  string htlc_id = 2;
}

message RefundHtlcResponse {
  string tx_id = 1;
}

message VerifyHtlcRequest {
  Network network = 1;
  string htlc_id = 2;
  HtlcExpectation expected = 3;
}

message HtlcExpectation {
  string recipient = 1;
  bytes payment_hash = 2;
  DecimalString min_amount = 3;
  // The asset's canonical id (the token address); the zero address
  // (`0x000…000`) for the chain's native asset.
  string token = 4;
  // Minimum acceptable absolute Unix-epoch-seconds deadline.
  uint64 min_timelock_unix_seconds = 5;
  // Expected refund/sender public key — the key the HTLC's refund branch is
  // locked to (the locker's key).
  string refund_address = 6;
  // The locker's attested lock transaction id. The verifier's cold-fetch key
  // when it has no local record of the HTLC: the lock is read directly by
  // transaction id (any age, no event-history scans). Empty when the
  // verifier locked the HTLC itself.
  string lock_txid = 7;
  // Canonical lock-type token the lock is REQUIRED to have (e.g. Bitcoin
  // "taproot" / "p2wsh") — a receiving party pins the type its signer can
  // settle. Empty = any of the protocol's lock types passes.
  string lock_type = 8;
}

message VerifyHtlcResponse {
  Htlc htlc = 1;
}

message GetChainHtlcRequest {
  Network network = 1;
  // Chain-native HTLC id to read.
  string htlc_id = 2;
  // The transaction that locked the HTLC — the bounded chain-side handle
  // (a direct receipt/transaction lookup, never an event-history scan).
  string lock_txid = 3;
}

message GetChainHtlcResponse {
  // The chain's view of the HTLC; unset when the chain cannot produce it.
  Htlc htlc = 1;
}

message BatchVerifyHtlcsRequest {
  Network network = 1;
  repeated VerifyHtlcItem items = 2;
}

message VerifyHtlcItem {
  string htlc_id = 1;
  HtlcExpectation expected = 2;
}

message BatchVerifyHtlcsResponse {
  repeated Htlc htlcs = 1;
}

message GetHtlcPubkeyRequest {
  Network network = 1;
}

message GetHtlcPubkeyResponse {
  // The node's canonical HTLC public key string (EVM `0x…` address, Bitcoin
  // public key Display); round-trips via `FromStr`.
  string htlc_pubkey = 1;
}

message WatchHtlcRequest {
  Network network = 1;
  // Protocol-specific identifier of the externally-locked HTLC to watch.
  string htlc_id = 2;
  // Stop observing a never-mined lock at this instant (its timelock); absent =
  // observe until the lock reaches a terminal status.
  optional uint64 abandon_at_unix_seconds = 3;
  // Finality depth at which the node emits `Locked` for this HTLC — the
  // caller (e.g. an orderbook sizing depth to swap value) is authoritative
  // when present; absent = the network's default depth.
  optional uint32 confirmation_depth = 4;
}

// Empty: watching is fire-and-forget; transitions arrive on the node event
// stream.
message WatchHtlcResponse {}

message UnwatchHtlcRequest {
  Network network = 1;
  // Protocol-specific identifier of the watched HTLC to stop observing.
  string htlc_id = 2;
}

// Empty: unwatching is fire-and-forget and idempotent.
message UnwatchHtlcResponse {}

// ─── HTLC state ──────────────────────────────────────────

// The on-chain state of a single HTLC. An HTLC is single-output — one
// `recipient`, one `amount`; a multi-recipient lock is several `Htlc`s carried
// by one `HtlcTransactionRequest`.
message Htlc {
  // Chain-native HTLC id — EVM: the 32-byte keccak commitment (hex); Bitcoin:
  // "txid:vout".
  string htlc_id = 1;
  // The locked asset's canonical id; the zero address for the native asset.
  string asset_id = 2;
  // The claim key/address funds are released to on claim.
  string recipient = 3;
  // The locked amount, in the asset's display unit.
  DecimalString amount = 4;
  // The 32-byte SHA-256 hashlock.
  bytes payment_hash = 5;
  // The refund/sender key the timeout branch pays.
  string refund_address = 6;
  // The absolute timelock after which the refund branch opens.
  Timelock expiry = 7;
  // The id of the transaction that locked this HTLC.
  string lock_txid = 8;
  // The ledger depth the lock confirmed at; unset while still unconfirmed
  // (in mempool / below the required commitment level).
  optional LedgerDepth lock_depth = 9;
  // The lifecycle status (locked / claimed / refunded).
  HtlcStatus status = 10;
}

// A protocol-native absolute timelock. `kind` fixes how `value` is interpreted.
message Timelock {
  uint64 value = 1;
  TimelockKind kind = 2;
}

// The semantic kind of a `Timelock.value`.
enum TimelockKind {
  TIMELOCK_KIND_UNSPECIFIED = 0;
  TIMELOCK_KIND_UNIX_TIMESTAMP = 1;
  TIMELOCK_KIND_BLOCK_HEIGHT = 2;
  TIMELOCK_KIND_SLOT = 3;
  TIMELOCK_KIND_SLOT_EPOCH = 4;
  TIMELOCK_KIND_CHECKPOINT = 5;
  TIMELOCK_KIND_ROUND = 6;
  TIMELOCK_KIND_LEDGER_SEQUENCE = 7;
}

// A chain-native confirmation depth. `kind` fixes how `value` is interpreted.
message LedgerDepth {
  uint64 value = 1;
  LedgerDepthKind kind = 2;
}

// The semantic kind of a `LedgerDepth.value`.
enum LedgerDepthKind {
  LEDGER_DEPTH_KIND_UNSPECIFIED = 0;
  LEDGER_DEPTH_KIND_BLOCK_HEIGHT = 1;
  LEDGER_DEPTH_KIND_SLOT = 2;
  LEDGER_DEPTH_KIND_CHECKPOINT = 3;
  LEDGER_DEPTH_KIND_ROUND = 4;
  LEDGER_DEPTH_KIND_LEDGER_SEQUENCE = 5;
}

// The lifecycle status of an HTLC.
message HtlcStatus {
  oneof status {
    Locked locked = 1;
    Claimed claimed = 2;
    Refunded refunded = 3;
  }

  // Locked on-chain. Use the `Htlc.lock_depth` to distinguish confirmed
  // (present) from unconfirmed/mempool (absent).
  message Locked {}

  // Claimed by the recipient using the preimage.
  message Claimed {
    // The claim transaction id.
    string claim_txid = 1;
    // The ledger depth the claim confirmed at; unset if still unconfirmed.
    optional LedgerDepth claim_depth = 2;
    // The 32-byte preimage the claim revealed (the swap's shared secret).
    bytes preimage = 3;
  }

  // Refunded to the sender after timelock expiry.
  message Refunded {
    // The refund transaction id.
    string refund_txid = 1;
    // The ledger depth the refund confirmed at; unset if still unconfirmed.
    optional LedgerDepth refund_depth = 2;
  }
}

// An unsigned HTLC transaction bundled with the pre-computed state of every
// HTLC it creates or spends. The `htlcs` list is never empty for a successfully
// built request.
message HtlcTransactionRequest {
  TransactionRequest transaction_request = 1;
  repeated Htlc htlcs = 2;
}
