Core principle: The covenant is permissionless. The client is opinionated.
Why this matters: Separating “what’s possible” (covenant) from “what’s recommended” (client) gives users maximum sovereignty while maintaining good UX.
Status (August 14, 2026):
Note: This document explains the architectural principle. Client layer code examples show future patterns (Nostr, auto-refund monitoring). Phase 0 implements manual refund only.
What it does: Defines what’s cryptographically possible.
Example (v2.5 refund):
function refund(sig senderSig) {
require(checkSig(senderSig, sender));
// Output 0: Payment → sender
// Output 1: Buffer → seller
}
No restrictions: No time check, no price check, no oracle. Just signature verification.
Why: Emergency escape. If client fails, user can manually refund with raw transaction. Permissionless.
What it does: Enforces what’s appropriate.
Example (client auto-refund logic):
async function shouldAutoRefund() {
const timeExpired = currentTime >= expiryTime;
const priceDropped = currentPrice < priceFloor;
return timeExpired || priceDropped; // Only refund if legitimate
}
// UI hides manual refund button
<button disabled={!shouldAutoRefund()}>Refund</button>
Restrictions: Time, price, user intent. Only auto-refunds when conditions met.
Why: Good UX. Prevents accidental/malicious refunds. Protects reputation.
Option A: Enforce everything in covenant (complex)
function refund(sig senderSig, datasig oracleSig, bytes oracleMessage) {
require(checkSig(senderSig, sender));
require(checkDataSig(oracleSig, oracleMessage, oraclePubkey));
// Parse oracle data
int oracleTimestamp = int(oracleMessage.split(8)[0]);
int currentPrice = int(oracleMessage.split(8)[1]);
// Enforce time OR price condition
bool expired = oracleTimestamp >= expiryOracleTime;
bool dropped = currentPrice < floorPrice;
require(expired || dropped); // ← What if oracle offline?
// Refund outputs...
}
Problems:
Option B: Allow everything in covenant (unsafe)
function refund(sig senderSig) {
require(checkSig(senderSig, sender));
// That's it! No restrictions.
}
Problems:
Covenant: Allow everything (permissionless)
Client: Guide users (opinionated)
Benefits:
Bitcoin protocol (Layer 1):
Bitcoin wallets (Layer 2):
Same pattern: Protocol is permissionless, wallet is opinionated.
Result: Power users can do anything. Normal users get guided UX.
5 functions, 4 actors, permissionless:
| Function | Restrictions | Purpose |
|---|---|---|
claim |
Oracle + time + price | Normal recipient flow |
merchantCashout |
Oracle + time + price + merchant sig | Cash pickup option |
refund |
None | Emergency sender escape |
abort |
Oracle + price (≤93.5%) | Emergency exit when buffer exhausted |
sellerRecoverBuffer |
Oracle + time (post-expiry) | Seller capital recovery |
Key insight: Only claim and merchantCashout have restrictions (they move funds to new parties). refund, abort, and sellerRecoverBuffer return funds to original funders—should be permissionless.
v2.6 addition: abort fixes fund locking when price drops below buffer capacity. The overlap zone (93.5% threshold) ensures both abort and refund work at critical prices, preventing lock scenarios.
Auto-refund monitoring:
lifecycleScope.launch {
while (isActive) {
covenants.forEach { covenant ->
if (shouldAutoRefund(covenant)) {
// Build refund transaction
val tx = buildRefundTx(covenant)
// Sign with sender's key
val signedTx = signTx(tx, senderWif)
// Broadcast to network
electrumClient.broadcast(signedTx)
// Log to Nostr (transparency)
publishRefundEvent(covenant, "auto-refund: expired")
}
}
delay(5.minutes)
}
}
fun shouldAutoRefund(covenant: Covenant): Boolean {
val timeExpired = System.currentTimeMillis() >= covenant.expiryTime
// Compute floor price from covenant parameters
// floorPrice = initialBchPriceInCents * minPricePercent / 100
val floorPrice = covenant.initialBchPriceInCents * covenant.minPricePercent / 100
val priceDropped = getCurrentPrice() < floorPrice
return timeExpired || priceDropped
}
Manual refund (hidden by default):
// Only show button if user enables advanced mode
if (settings.advancedMode) {
Button(onClick = {
showConfirmation("Are you sure? This will damage your reputation.")
}) {
Text("Manual Refund (Emergency)")
}
}
Nostr monitoring (social layer):
// Publish all refunds to Nostr
fun publishRefundEvent(covenant: Covenant, reason: String) {
val event = NostrEvent(
kind = 30078, // Asgaya covenant event
content = json {
"covenant_address" to covenant.address
"action" to "refund"
"reason" to reason
"timestamp" to System.currentTimeMillis()
},
pubkey = senderPubkey
)
nostrClient.publish(event)
}
// Recipient's client monitors
nostrClient.subscribe(filter = {
authors = listOf(senderPubkey)
kinds = listOf(30078)
}) { event ->
if (event.content.action == "refund") {
if (event.content.reason == "auto-refund: expired") {
// Legitimate, ignore
} else {
// Manual refund! Show warning
showNotification("⚠️ Sender refunded manually. Reputation: -1")
}
}
}
✅ User sovereignty: Emergency escape always available
✅ Updateable logic: Fix bugs without redeploying covenant
✅ Simple covenant: Smaller attack surface
✅ Flexible UX: Add new conditions (e.g., “refund if recipient offline 24hr”)
✅ Social layer: Nostr monitoring enables reputation without on-chain enforcement
⚠️ Two-layer mental model: Developers must understand covenant ≠ client
⚠️ Trust assumption: Recipient trusts sender won’t abuse early refund
⚠️ Client diversity: Different clients may enforce different policies
Historical precedent: Bitcoin, Lightning, Ethereum—all have protocol vs application separation.
User choice: If sender’s client is too restrictive, they can switch clients or use raw transactions.
Market forces: Abusive clients lose users. Good UX wins.
Gradual ossification: As ecosystem matures, best practices converge. Early flexibility enables discovery.
UX hides manual refund:
shouldAutoRefund() == trueNostr transparency:
Result: Most users never see manual refund option. Advanced users can access if needed.
Client refuses to sign manual refunds:
fun refund(covenant: Covenant, manual: Boolean = false) {
if (manual && !shouldAutoRefund(covenant)) {
throw Error("Refund conditions not met")
}
// Build and broadcast refund tx
}
Problem: User has no emergency escape if client bug prevents auto-refund. Violates user sovereignty.
Asgaya’s choice: Soft enforcement. Trust users, monitor via Nostr.
Day 1-30: Manual refund available anytime (onboarding grace period)
Day 31+: Manual refund requires 2FA + cooldown (24 hours warning)
Post-dispute: Manual refund disabled for 7 days (reputation recovery)
Result: Flexible onboarding, social consequences for abuse, time-based trust building.
Covenant refund: No oracle needed (permissionless)
Client logic: Detects oracle downtime, triggers auto-refund after timeout
User experience: Funds returned, no manual intervention
Covenant refund: Still works (user can sign raw transaction)
Mitigation: Client diversity (multiple implementations)
Backstop: Manual refund instructions in documentation
Covenant refund: Works on any chain (sender has private key)
Client logic: Detects fork, waits for confirmation
Social layer: Nostr on both chains (reputation intact)
Covenant refund: Works anytime (permissionless by design)
Client defense: Nostr publishes refund, recipient warned
Long-term: Reputation system prevents future covenants with this sender
Key insight: On-chain enforcement can’t prevent determined malicious actors. Social layer (reputation + Nostr) is more effective than complex covenants.
This document explains: Covenant vs client separation (architectural principle).
What it does NOT explain:
Related principles:
Covenant = Permissionless
What’s cryptographically possible. Allows emergency escapes.
Client = Opinionated
What’s recommended. Guides users, enforces fairness.
Why both?
Covenant is immutable (on-chain), client is updateable (off-chain). Separation enables sovereignty + good UX.
The pattern:
Same as Bitcoin (protocol vs wallet), Lightning (BOLT vs implementation), Ethereum (EVM vs dApp).
The realization:
Moving enforcement from covenant to client solved all edge cases. Covenant stays simple, client handles complexity.
Last updated: July 30, 2026
Related: Manual Construction | Asgaya Trinity | Covenant Simplicity