Skip to main content

Overview

RWA (Real-World Asset) settlement enables off-chain service execution with on-chain payment settlement. Services register with SLA terms, agents request execution, providers submit cryptographic proofs, and payments are released or refunded based on SLA compliance. Key Insight: RWAs in RelayCore are not tokenized assets—they are processes that agents settle with provable outcomes.

Complete Settlement Flow

1

1. Service Registration with SLA

Provider registers an RWA service with explicit SLA terms:
import { getRWASettlementAgent } from '@relaycore/sdk';

const rwaAgent = getRWASettlementAgent();

const serviceId = await rwaAgent.registerService({
  name: 'KYC Verification Service',
  serviceType: 'kyc_verification',
  description: 'Identity verification with document attestation',
  provider: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
  endpoint: 'https://api.kyc-service.com/verify',
  pricePerCall: '5.00', // 5 USDC
  sla: {
    maxLatencyMs: 30000, // 30 seconds max
    requiredFields: ['documentHash', 'verificationStatus', 'timestamp'],
    proofFormat: 'signed',
    refundConditions: ['timeout', 'invalid_proof', 'missing_fields'],
    validityPeriodSeconds: 300 // 5 minutes
  },
  verificationMethod: 'signature'
});
SLA Terms Breakdown:
  • maxLatencyMs: Maximum time from request to proof submission
  • requiredFields: Fields that must be present in proof result
  • proofFormat: 'json' (basic), 'signed' (EIP-191), or 'hashed' (keccak256)
  • refundConditions: Conditions that trigger automatic refund
  • validityPeriodSeconds: How long proof remains valid
2

2. Agent Requests Execution

Agent requests off-chain execution with escrow-backed payment:
const request = await rwaAgent.requestExecution(
  serviceId,
  sessionId, // Escrow session with funds
  agentAddress,
  {
    documentId: 'passport_12345',
    userId: 'user_abc',
    verificationType: 'identity'
  }
);

console.log('Request ID:', request.requestId);
console.log('Status:', request.status); // 'pending' or 'rejected'
What Happens:
  1. System checks escrow session has sufficient funds
  2. If funds available: creates execution request in rwa_execution_requests table
  3. If insufficient: returns rejected status
  4. Funds remain locked in escrow until settlement
3

3. Provider Executes Off-Chain

Provider performs real-world service (KYC check, shipping verification, etc.):
// Provider's backend service
async function handleKYCRequest(requestId: string, input: any) {
  // Perform actual KYC verification
  const verification = await verifyIdentityDocument(input.documentId);
  
  // Generate proof
  const proof = {
    serviceId,
    requestId,
    timestamp: Date.now(),
    result: {
      documentHash: verification.hash,
      verificationStatus: verification.status, // 'verified' | 'rejected'
      timestamp: verification.completedAt,
      confidence: verification.confidence
    },
    providerAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
  };
  
  // Sign proof
  proof.signature = await wallet.signMessage(JSON.stringify({
    requestId: proof.requestId,
    timestamp: proof.timestamp,
    result: proof.result
  }));
  
  return proof;
}
4

4. Provider Submits Proof

Provider submits cryptographic proof for verification:
const verification = await rwaAgent.submitProof({
  serviceId,
  requestId: request.requestId,
  timestamp: Date.now(),
  result: {
    documentHash: '0xabc123...',
    verificationStatus: 'verified',
    timestamp: 1706000000,
    confidence: 0.95
  },
  signature: '0x1a2b3c...',
  providerAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
});

console.log('Proof valid:', verification.valid);
console.log('SLA metrics:', verification.slaMetrics);
Verification Response:
{
  "valid": true,
  "slaMetrics": {
    "latencyMs": 12500,
    "fieldsPresent": ["documentHash", "verificationStatus", "timestamp"],
    "fieldsMissing": [],
    "proofFormatValid": true,
    "withinValidity": true
  }
}
5

5. System Verifies SLA Compliance

RelayCore automatically verifies proof against SLA terms:
// From: src/services/rwa/rwa-settlement-agent.ts
private verifySLA(proof: ExecutionProof, sla: SLATerms, requestedAt: string) {
  const latencyMs = proof.timestamp - new Date(requestedAt).getTime();
  
  // Check latency
  const latencyOk = latencyMs <= sla.maxLatencyMs;
  
  // Check required fields
  const resultKeys = Object.keys(proof.result);
  const fieldsMissing = sla.requiredFields.filter(f => !resultKeys.includes(f));
  const fieldsOk = fieldsMissing.length === 0;
  
  // Check proof format
  let proofFormatValid = false;
  if (sla.proofFormat === 'signed') {
    const message = JSON.stringify({
      requestId: proof.requestId,
      timestamp: proof.timestamp,
      result: proof.result
    });
    const recoveredAddress = ethers.verifyMessage(message, proof.signature);
    proofFormatValid = recoveredAddress.toLowerCase() === proof.providerAddress.toLowerCase();
  }
  
  // Check validity period
  const withinValidity = (Date.now() - proof.timestamp) < (sla.validityPeriodSeconds * 1000);
  
  return {
    valid: latencyOk && fieldsOk && proofFormatValid && withinValidity,
    slaMetrics: { latencyMs, fieldsPresent, fieldsMissing, proofFormatValid, withinValidity }
  };
}
SLA Verification Checks:
  1. Latency: latencyMs <= maxLatencyMs
  2. Fields: All requiredFields present in proof.result
  3. Signature: EIP-191 signature verifies to providerAddress
  4. Validity: Proof submitted within validityPeriodSeconds
6

6. Settlement

System settles based on SLA verification:
const settlement = await rwaAgent.settle(request.requestId);

if (settlement.success) {
  console.log('Payment released to provider');
  console.log('Amount:', settlement.payment.amount);
  console.log('TX Hash:', settlement.payment.txHash);
} else {
  console.log('Payment refunded to requester');
  console.log('Reason:', settlement.refund.reason);
  console.log('Amount:', settlement.refund.amount);
}
If SLA Met (proof valid):
// Release payment to provider
await escrow.releasePayment(
  sessionId,
  providerAddress,
  pricePerCall,
  requestId
);

// Update request status
await supabase
  .from('rwa_execution_requests')
  .update({ status: 'settled' })
  .eq('request_id', requestId);
If SLA Violated (proof invalid):
// Refund to session owner
await escrow.refund(sessionId);

// Update request status
await supabase
  .from('rwa_execution_requests')
  .update({ status: 'refunded' })
  .eq('request_id', requestId);

RWA Service Types

TypeDescriptionTypical SLA
compliance_checkKYC/AML verification30s latency, signed proof
market_reportReal-time market data10s latency, hashed proof
trade_confirmationTrade execution proof60s latency, signed proof
settlement_reconciliationPayment reconciliation120s latency, signed proof
price_verificationOracle price attestation5s latency, signed proof
kyc_verificationIdentity verification30s latency, signed proof
execution_proofService execution proof60s latency, signed proof
data_attestationData integrity proof15s latency, hashed proof

State Machine Integration

RWA settlement can also use the state machine for complex multi-step processes:
import { rwaStateMachineService, RWAState, AgentRole } from '@relaycore/sdk';

// 1. Create state machine
const stateMachine = await rwaStateMachineService.createStateMachine(
  'property_verification_123',
  {
    propertyId: 'prop_456',
    address: '123 Main St',
    owner: '0xOwner...'
  }
);

// 2. Transition: created → verified
const verifyResult = await rwaStateMachineService.transition({
  rwaId: 'property_verification_123',
  toState: RWAState.VERIFIED,
  agentAddress: '0xVerifier...',
  agentRole: AgentRole.VERIFIER,
  sessionId: 1,
  proof: {
    documentHash: '0xabc...',
    verificationStatus: 'verified'
  }
});

// 3. Transition: verified → escrowed
const escrowResult = await rwaStateMachineService.transition({
  rwaId: 'property_verification_123',
  toState: RWAState.ESCROWED,
  agentAddress: '0xEscrowManager...',
  agentRole: AgentRole.ESCROW_MANAGER,
  sessionId: 1
});

// 4. Continue through states: in_process → fulfilled → settled
State Machine States:
  • CREATEDVERIFIEDESCROWEDIN_PROCESSFULFILLEDSETTLED
  • Each transition requires specific AgentRole
  • Each transition costs USDC (deducted from session)
  • Invalid transitions are rejected
Transition Costs:
const TRANSITION_COSTS = {
  'created→verified': '0.10',
  'verified→escrowed': '0.50',
  'escrowed→in_process': '0.20',
  'in_process→fulfilled': '0.30',
  'fulfilled→settled': '1.00'
};

Complete Example: KYC Verification

// 1. Provider registers KYC service
const serviceId = await rwaAgent.registerService({
  name: 'KYC Pro',
  serviceType: 'kyc_verification',
  provider: '0xProvider...',
  endpoint: 'https://api.kycpro.com/verify',
  pricePerCall: '5.00',
  sla: {
    maxLatencyMs: 30000,
    requiredFields: ['documentHash', 'verificationStatus', 'timestamp'],
    proofFormat: 'signed',
    refundConditions: ['timeout', 'invalid_proof'],
    validityPeriodSeconds: 300
  },
  verificationMethod: 'signature'
});

// 2. Agent creates session with budget
const session = await createSession({
  maxSpend: '100.00',
  duration: 86400
});

// 3. Agent requests KYC verification
const request = await rwaAgent.requestExecution(
  serviceId,
  session.session_id,
  '0xAgent...',
  {
    documentId: 'passport_12345',
    userId: 'user_abc'
  }
);

// 4. Provider executes KYC (off-chain)
const kycResult = await performKYCVerification('passport_12345');

// 5. Provider submits proof
const proof = {
  serviceId,
  requestId: request.requestId,
  timestamp: Date.now(),
  result: {
    documentHash: kycResult.hash,
    verificationStatus: kycResult.status,
    timestamp: kycResult.completedAt
  },
  signature: await wallet.signMessage(JSON.stringify({
    requestId: request.requestId,
    timestamp: Date.now(),
    result: kycResult
  })),
  providerAddress: '0xProvider...'
};

const verification = await rwaAgent.submitProof(proof);

// 6. System settles automatically
const settlement = await rwaAgent.settle(request.requestId);

if (settlement.success) {
  console.log('KYC verified and provider paid');
  console.log('Payment TX:', settlement.payment.txHash);
} else {
  console.log('KYC failed, requester refunded');
  console.log('Reason:', settlement.refund.reason);
}

SLA Violation Scenarios

Scenario 1: Latency Exceeded

// SLA: maxLatencyMs = 30000 (30 seconds)
// Actual: latencyMs = 45000 (45 seconds)

const verification = await rwaAgent.submitProof(proof);
// Returns: { valid: false, reason: "Latency 45000ms exceeds SLA 30000ms" }

const settlement = await rwaAgent.settle(requestId);
// Refunds to requester

Scenario 2: Missing Required Fields

// SLA: requiredFields = ['documentHash', 'verificationStatus', 'timestamp']
// Proof: result = { documentHash: '0x...', verificationStatus: 'verified' }
// Missing: 'timestamp'

const verification = await rwaAgent.submitProof(proof);
// Returns: { valid: false, reason: "Missing required fields: timestamp" }

Scenario 3: Invalid Signature

// Proof signature doesn't verify to providerAddress

const verification = await rwaAgent.submitProof(proof);
// Returns: { valid: false, reason: "Invalid proof format: expected signed" }

Scenario 4: Proof Expired

// SLA: validityPeriodSeconds = 300 (5 minutes)
// Proof submitted 10 minutes after execution

const verification = await rwaAgent.submitProof(proof);
// Returns: { valid: false, reason: "Proof expired" }

Database Schema

rwa_execution_requests

CREATE TABLE rwa_execution_requests (
  id SERIAL PRIMARY KEY,
  request_id TEXT UNIQUE NOT NULL,
  service_id TEXT NOT NULL,
  session_id INTEGER NOT NULL,
  agent_address TEXT NOT NULL,
  input JSONB NOT NULL,
  price TEXT NOT NULL,
  sla_terms JSONB NOT NULL,
  status TEXT NOT NULL, -- 'pending' | 'verified' | 'settled' | 'refunded' | 'failed'
  proof JSONB,
  verification JSONB,
  requested_at TIMESTAMPTZ NOT NULL,
  verified_at TIMESTAMPTZ,
  settled_at TIMESTAMPTZ
);

rwa_state_machines

CREATE TABLE rwa_state_machines (
  id SERIAL PRIMARY KEY,
  rwa_id TEXT UNIQUE NOT NULL,
  current_state TEXT NOT NULL,
  previous_state TEXT,
  metadata JSONB NOT NULL,
  created_at TIMESTAMPTZ NOT NULL,
  updated_at TIMESTAMPTZ NOT NULL
);

rwa_state_transitions

CREATE TABLE rwa_state_transitions (
  id SERIAL PRIMARY KEY,
  rwa_id TEXT NOT NULL,
  from_state TEXT NOT NULL,
  to_state TEXT NOT NULL,
  agent_address TEXT NOT NULL,
  agent_role TEXT NOT NULL,
  payment_hash TEXT NOT NULL,
  proof JSONB NOT NULL,
  transitioned_at TIMESTAMPTZ NOT NULL
);

Best Practices

1. Set Realistic SLA Terms

// ❌ Bad: Unrealistic latency
sla: {
  maxLatencyMs: 1000, // 1 second for KYC is impossible
  requiredFields: ['result']
}

// ✅ Good: Realistic latency with buffer
sla: {
  maxLatencyMs: 30000, // 30 seconds with 10s buffer
  requiredFields: ['documentHash', 'verificationStatus', 'timestamp'],
  validityPeriodSeconds: 300
}

2. Always Sign Proofs

// ❌ Bad: No proof
await rwaAgent.submitProof({
  serviceId,
  requestId,
  timestamp: Date.now(),
  result: { data: 'result' },
  providerAddress
});

// ✅ Good: Signed proof
const message = JSON.stringify({ requestId, timestamp, result });
const signature = await wallet.signMessage(message);

await rwaAgent.submitProof({
  serviceId,
  requestId,
  timestamp: Date.now(),
  result,
  signature,
  providerAddress
});

3. Handle Refunds Gracefully

const settlement = await rwaAgent.settle(requestId);

if (!settlement.success) {
  logger.warn('RWA settlement failed', {
    requestId,
    reason: settlement.refund.reason
  });
  
  // Notify user
  await notifyUser({
    type: 'rwa_refund',
    requestId,
    reason: settlement.refund.reason,
    amount: settlement.refund.amount
  });
}

Next Steps

RWA SDK

Build RWA services with SDK

Session Management

Use escrow sessions for RWA

x402 Protocol

Understanding payment flow

State Machine

Multi-step RWA processes