Skip to main content

Capability Agreements

When a requester finds a suitable provider through discovery, they form a bilateral agreement. Agreements are between two parties only — no network-wide registration required.

Cost Structure

Agreements use a discriminated cost model that adapts to different capability types:

CostStructure: enum {
PerByte {
cost_per_byte: u64, // μMHR per byte transferred
},
PerInvocation {
cost_per_call: u64, // μMHR per function invocation
max_input_bytes: u32, // cost covers up to this input size
},
PerDuration {
cost_per_epoch: u64, // μMHR per epoch of service
},
PerCycle {
cost_per_million_cycles: u64, // μMHR per million compute cycles
max_cycles: u64, // hard limit
},
}
CapabilityTypical CostStructure
Relay / BandwidthPerByte
StoragePerDuration
Compute (contract)PerCycle
Compute (function)PerInvocation
Internet gatewayPerByte or PerDuration

Agreement Structure

CapabilityAgreement {
provider: NodeID,
consumer: NodeID,
capability: CapabilityType, // compute, storage, relay, proxy
payment_channel: ChannelID, // existing bilateral channel
cost: CostStructure,
valid_until: Timestamp,

proof_method: enum {
DeliveryReceipt, // for relay
ChallengeResponse, // for storage (random read challenges)
ResultHash, // for compute (hash of output)
Heartbeat, // for ongoing services
},

signatures: (Sig_Provider, Sig_Consumer),
}

Key Properties

Bilateral

Agreements are strictly between two parties. This means:

  • No central registry of agreements
  • No third party needs to be involved or informed
  • Agreements can be formed and dissolved without network-wide coordination
  • Privacy is preserved — only the two parties know the terms

Payment-Linked

Every agreement references an existing payment channel between the two parties. Payment flows automatically as the service is delivered.

Time-Bounded

Agreements have an expiration (valid_until). This prevents stale agreements from persisting when nodes move or go offline. Parties can renew by forming a new agreement.

Proof-Verified

Each agreement specifies how the consumer verifies that the provider is actually delivering. See Verification for details on each proof method.

Agreement Lifecycle

Agreement states:
Active: now < valid_until — service is being delivered
Expired: now >= valid_until — no new service; grace period begins
Grace: expired + up to 1 gossip round (60s) — allows in-flight operations to complete
Closed: after grace period — agreement is fully terminated

Expiry Behavior

CapabilityOn ExpiryGrace Period
Relay/BandwidthNo new packets routed after valid_untilIn-flight packets in queue are delivered (up to 60s drain)
StorageNo new writes acceptedData remains stored for 1 additional epoch; then subject to garbage collection
ComputeNo new invocations acceptedRunning invocations complete; results remain retrievable for 60s
Internet GatewayConnection torn down at valid_untilIn-flight TCP streams drained for up to 60s

Renewal

To renew, the consumer sends a new CapabilityRequest before the current agreement expires. If terms are unchanged, the provider can respond with a CapabilityOffer that extends valid_until — no full re-negotiation needed. If terms change, both parties sign a new CapabilityAgreement.

Billing Boundaries

  • PerByte / PerInvocation / PerCycle: Charged for actual usage up to valid_until. No charge after expiry.
  • PerDuration: Charged for completed epochs only. Partial epochs at agreement end are not billed.

Agreement Types

CapabilityTypical DurationProof MethodExample
Relay/BandwidthPer-packet or ongoingDelivery Receipt"Route my packets for the next hour"
StorageHours to monthsChallenge-Response"Store this 10 MB file for 30 days"
ComputePer-invocationResult Hash"Run Whisper on this audio file"
Internet GatewayOngoingHeartbeat"Proxy my traffic to the internet"

Negotiation

Negotiation is single-round (take-it-or-leave-it) and strictly local — no auction, no bidding, no global price discovery:

Negotiation protocol:
1. Consumer sends CapabilityRequest to provider
2. Provider responds with CapabilityOffer (or Reject)
3. Consumer accepts (signs) or walks away
4. If accepted: both signatures form the CapabilityAgreement
5. Service begins; payment flows through the channel

CapabilityRequest {
consumer: NodeID,
capability: CapabilityType, // compute, storage, relay, proxy
desired_cost: CostStructure, // max cost consumer will accept
desired_duration: u32, // seconds
payment_channel: ChannelID, // existing channel with this provider
proof_preference: ProofMethod, // DeliveryReceipt, ChallengeResponse, etc.
nonce: u64, // replay prevention
}
// Signed by consumer

CapabilityOffer {
request_nonce: u64, // matches the request
provider: NodeID,
actual_cost: CostStructure, // provider's terms (≤ desired_cost, or reject)
valid_until: Timestamp, // agreement expiration
proof_method: ProofMethod, // may differ from preference
constraints: Option<Vec<u8>>, // provider-specific (e.g., max object size)
}
// Signed by provider

Timeout: If the provider doesn't respond within 30 seconds (or 3 gossip rounds on constrained links), the request is considered rejected. The consumer may retry with a different provider.

No counter-offers: The provider either meets or undercuts the consumer's desired cost, or rejects. This keeps negotiation to a single round-trip — critical for LoRa where each message takes seconds. If the consumer wants to negotiate, they send a new request with adjusted terms.

Prices are set by providers based on their own cost structure. Within trust neighborhoods, trusted peers often offer discounted or free services.