Skip to content

Petah1/TrustLink

 
 

Repository files navigation

TrustLink - On-Chain Attestation & Verification System

TrustLink is a Soroban smart contract that provides a reusable trust layer for the Stellar blockchain. It enables trusted issuers (anchors, fintech apps, institutions) to create, manage, and revoke attestations about wallet addresses, allowing other contracts and applications to verify claims before executing financial operations.

Overview

TrustLink solves the problem of decentralized identity verification and trust establishment on-chain. Instead of each application building its own KYC/verification system, TrustLink provides a shared attestation infrastructure that can be queried by any smart contract or dApp.

Key Features

  • Authorized Issuers: Admin-controlled registry of trusted attestation issuers
  • Claim Type Registry: Admin-managed registry of standard claim types with descriptions
  • Flexible Claims: Support for any claim type (KYC_PASSED, ACCREDITED_INVESTOR, MERCHANT_VERIFIED, etc.)
  • Expiration Support: Optional time-based expiration for attestations
  • Revocation: Issuers can revoke attestations at any time
  • Deterministic IDs: Attestations have unique, reproducible identifiers
  • Event Emission: All state changes emit events for off-chain indexing
  • Query Interface: Easy verification of claims for other contracts
  • Pagination: Efficient listing of attestations per subject or issuer

Architecture

Core Components

src/
├── lib.rs          # Main contract implementation
├── types.rs        # Data structures and error definitions
├── storage.rs      # Storage patterns and key management
├── validation.rs   # Authorization and access control
├── events.rs       # Event emission for indexers
└── test.rs         # Comprehensive unit tests

Data Model

Attestation Structure:

{
    id: String,              // Deterministic hash-based ID
    issuer: Address,         // Who issued the attestation
    subject: Address,        // Who the attestation is about
    claim_type: String,      // Type of claim (e.g., "KYC_PASSED")
    timestamp: u64,          // When it was created
    expiration: Option<u64>, // Optional expiration time
    revoked: bool            // Revocation status
}

Storage Keys:

  • Admin: Contract administrator address
  • Issuer(Address): Authorized issuer registry
  • Attestation(String): Individual attestation data
  • SubjectAttestations(Address): Index of attestations per subject
  • IssuerAttestations(Address): Index of attestations per issuer
  • ClaimType(String): Registered claim type info keyed by identifier
  • ClaimTypeList: Ordered list of all registered claim type identifiers

Usage

Initialization

// Deploy and initialize with admin
contract.initialize(&admin_address);

Register Issuers

// Admin registers a trusted issuer
contract.register_issuer(&admin, &issuer_address);

// Check if address is authorized
let is_authorized = contract.is_issuer(&issuer_address);

Claim Type Registry

The contract ships with a set of standard claim types that the admin can pre-register on deployment.

Claim Type Description
KYC_PASSED Subject has passed KYC identity verification
ACCREDITED_INVESTOR Subject qualifies as an accredited investor
MERCHANT_VERIFIED Subject is a verified merchant
AML_CLEARED Subject has passed AML screening
SANCTIONS_CHECKED Subject has been checked against sanctions lists
// Admin registers a claim type
contract.register_claim_type(
    &admin,
    &String::from_str(&env, "KYC_PASSED"),
    &String::from_str(&env, "Subject has passed KYC identity verification"),
);

// Look up a description
let desc = contract.get_claim_type_description(&String::from_str(&env, "KYC_PASSED"));

// List registered types (paginated)
let page1 = contract.list_claim_types(&0, &10);

Create Attestations

// Issuer creates a KYC attestation
let attestation_id = contract.create_attestation(
    &issuer,
    &user_address,
    &String::from_str(&env, "KYC_PASSED"),
    &None  // No expiration
);

// Create attestation with expiration
let expiration_time = current_timestamp + 365 * 24 * 60 * 60; // 1 year
let attestation_id = contract.create_attestation(
    &issuer,
    &user_address,
    &String::from_str(&env, "ACCREDITED_INVESTOR"),
    &Some(expiration_time)
);

Verify Claims

// Check if user has valid KYC
let has_kyc = contract.has_valid_claim(
    &user_address,
    &String::from_str(&env, "KYC_PASSED")
);

if has_kyc {
    // Proceed with financial operation
}

Verify Any of Multiple Claims

has_any_claim(env: Env, subject: Address, claim_types: Vec<String>) -> bool

Parameter Type Description
env Env Soroban environment (ledger time, storage)
subject Address The address whose attestations are queried
claim_types Vec<String> One or more claim type identifiers to check

Returns true if the subject holds at least one valid attestation matching any of the listed claim types; false otherwise.

Behavior:

  • Uses OR-logic — returns true on the first valid match found (short-circuit evaluation)
  • An empty claim_types list always returns false
  • Revoked, expired, and pending attestations are excluded from matching
// Check if user has either KYC or an accredited investor credential
let claim_types = vec![
    &env,
    String::from_str(&env, "KYC_PASSED"),
    String::from_str(&env, "ACCREDITED_INVESTOR"),
    String::from_str(&env, "MERCHANT_VERIFIED"),
];
let has_any = contract.has_any_claim(&user_address, &claim_types);

if has_any {
    // Proceed — user satisfies at least one required credential
}

Relationship to has_valid_claim: Calling has_any_claim with a single-element list is equivalent to calling has_valid_claim with that same claim type. Use has_valid_claim when checking a single claim type, and has_any_claim when OR-logic across multiple claim types is needed.

Revoke Attestations

// Issuer revokes an attestation
contract.revoke_attestation(&issuer, &attestation_id);

Query Attestations

// Get specific attestation
let attestation = contract.get_attestation(&attestation_id);

// Check status
let status = contract.get_attestation_status(&attestation_id);
// Returns: Valid, Expired, or Revoked

// Find the most recent valid attestation by subject + claim type
let attestation = contract.get_attestation_by_type(&user_address, &String::from_str(&env, "KYC_PASSED"));

// Count queries — returns total count, no pagination needed
let total = contract.get_subject_attestation_count(&user_address); // all attestations (incl. revoked/expired)
let issued = contract.get_issuer_attestation_count(&issuer_address); // all issued by this issuer
let valid  = contract.get_valid_claim_count(&user_address);          // only non-revoked, non-expired

// List user's attestations (paginated)
let attestations = contract.get_subject_attestations(&user_address, &0, &10);

// List issuer's attestations
let issued = contract.get_issuer_attestations(&issuer_address, &0, &10);

Integration Example

Here's how another contract would verify attestations:

use soroban_sdk::{contract, contractimpl, Address, Env, String};

#[contract]
pub struct LendingContract;

#[contractimpl]
impl LendingContract {
    pub fn borrow(
        env: Env,
        borrower: Address,
        trustlink_contract: Address,
        amount: i128
    ) -> Result<(), Error> {
        borrower.require_auth();
        
        // Create client for TrustLink contract
        let trustlink = trustlink::Client::new(&env, &trustlink_contract);
        
        // Verify borrower has valid KYC
        let kyc_claim = String::from_str(&env, "KYC_PASSED");
        let has_kyc = trustlink.has_valid_claim(&borrower, &kyc_claim);
        
        if !has_kyc {
            return Err(Error::KYCRequired);
        }
        
        // Proceed with lending logic
        // ...
        
        Ok(())
    }
}

Error Handling

TrustLink defines clear error types:

  • AlreadyInitialized: Contract already initialized
  • NotInitialized: Contract not yet initialized
  • Unauthorized: Caller lacks required permissions
  • NotFound: Attestation doesn't exist
  • DuplicateAttestation: Attestation with same hash already exists
  • AlreadyRevoked: Attestation already revoked
  • Expired: Attestation has expired

Events

TrustLink emits events for off-chain indexing:

AttestationCreated:

topics: ["created", subject_address]
data: (attestation_id, issuer, claim_type, timestamp)

AttestationRevoked:

topics: ["revoked", issuer_address]
data: attestation_id

AttestationRenewed:

topics: ["renewed", issuer_address]
data: (attestation_id, new_expiration)

IssuerRegistered:

topics: ["iss_reg", issuer_address]
data: admin_address

IssuerRemoved:

topics: ["iss_rem", issuer_address]
data: admin_address
**ClaimTypeRegistered:**
```rust
topics: ["clmtype"]
data: (claim_type, description)

Building and Testing

Prerequisites

  • Rust 1.70+
  • Soroban CLI
  • wasm32-unknown-unknown target

Commands

# Run tests
make test

# Build contract
make build

# Build optimized version
make optimize

# Clean artifacts
make clean

# Format code
make fmt

# Run linter
make clippy

Running Tests

cargo test

Tests cover:

  • Initialization and admin management
  • Issuer registration and removal
  • Attestation creation with validation
  • Duplicate prevention
  • Revocation logic
  • Expiration handling
  • Authorization enforcement
  • Pagination
  • Cross-contract verification

Security Considerations

  1. Authorization: Only admin can manage issuers; only issuers can create attestations
  2. Deterministic IDs: Prevents replay attacks and ensures uniqueness
  3. Immutable History: Attestations are never deleted, only marked as revoked
  4. Time-based Expiration: Automatic invalidation of expired claims
  5. Event Transparency: All changes are logged for auditability

Use Cases

  • DeFi Protocols: Verify KYC before lending/borrowing
  • Token Sales: Ensure accredited investor status
  • Payment Systems: Verify merchant credentials
  • Governance: Validate voter eligibility
  • Marketplaces: Confirm seller reputation
  • Insurance: Verify policyholder identity

Deployment

# Build optimized contract
make optimize

# Deploy to network
soroban contract deploy \
  --wasm target/wasm32-unknown-unknown/release/trustlink.wasm \
  --network testnet

# Initialize
soroban contract invoke \
  --id <CONTRACT_ID> \
  --network testnet \
  -- initialize \
  --admin <ADMIN_ADDRESS>

Integration Guide

For a step-by-step walkthrough covering Rust cross-contract patterns, JavaScript/TypeScript usage, error handling, and testnet testing, see docs/integration-guide.md.

License

MIT

Changelog

See CHANGELOG.md for a history of notable changes.

Contributing

Contributions welcome! See CONTRIBUTING.md for setup instructions, code style requirements, and the PR process.

Support

For issues or questions, please open a GitHub issue.

About

TrustLink is a Rust Soroban smart contract + SDK that lets anchors, fintechs, and marketplaces create on-chain trust attestations.

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Rust 98.3%
  • Other 1.7%