# Stellar Attestation Contract Source: https://docs.attest.so/chains/stellar/overview AttestProtocol Stellar contract implementation using Soroban smart contracts with native Rust architecture Stellar Stellar The AttestProtocol Stellar contract leverages Soroban smart contracts to provide efficient attestation services with deterministic storage keys and native Stellar integration. *** ## Contract Architecture AttestProtocol on Stellar consists of two main Soroban smart contracts: ### Protocol Contract The main attestation protocol contract handles core attestation operations: ```rust theme={null} #[contract] pub struct AttestationContract; #[contractimpl] impl AttestationContract { // Contract initialization pub fn initialize(env: Env, admin: Address) -> Result<(), Error> // Schema management pub fn register(env: Env, caller: Address, schema_definition: String, resolver: Option
, revocable: bool) -> Result, Error> pub fn get_schema(env: Env, schema_uid: BytesN<32>) -> Result // Direct attestation operations pub fn attest(env: Env, attester: Address, schema_uid: BytesN<32>, value: String, expiration_time: Option) -> Result, Error> pub fn revoke(env: Env, revoker: Address, attestation_uid: BytesN<32>) -> Result<(), Error> pub fn get_attestation(env: Env, attestation_uid: BytesN<32>) -> Result // Delegated operations (gas-less transactions) pub fn attest_by_delegation(env: Env, submitter: Address, request: DelegatedAttestationRequest) -> Result<(), Error> pub fn revoke_by_delegation(env: Env, submitter: Address, request: DelegatedRevocationRequest) -> Result<(), Error> // BLS signature support pub fn register_bls_key(env: Env, attester: Address, public_key: BytesN<192>) -> Result<(), Error> pub fn get_bls_key(env: Env, attester: Address) -> Result pub fn get_attester_nonce(env: Env, attester: Address) -> u64 } ``` ### Authority Contract Handles authority registration with payment verification and resolver functionality: ```rust theme={null} #[contract] pub struct AuthorityResolverContract; #[contractimpl] impl AuthorityResolverContract { // Initialization pub fn initialize(env: Env, admin: Address, token_contract_id: Address, token_wasm_hash: BytesN<32>) -> Result<(), Error> // Authority management pub fn register_authority(env: Env, caller: Address, authority_to_reg: Address, metadata: String) -> Result<(), Error> pub fn is_authority(env: Env, authority: Address) -> Result // Payment verification pub fn pay_verification_fee(env: Env, payer: Address, ref_id: String, token_address: Address) -> Result<(), Error> pub fn has_confirmed_payment(env: Env, payer: Address) -> bool // Resolver interface pub fn attest(env: Env, attestation: Attestation) -> Result pub fn revoke(env: Env, attestation: Attestation) -> Result // Fee withdrawal pub fn withdraw_levies(env: Env, caller: Address) -> Result<(), Error> pub fn withdraw_fees(env: Env, caller: Address) -> Result<(), Error> } ``` *** ## Data Structures ### Storage Key System Deterministic storage keys for efficient data organization: ```rust theme={null} #[contracttype] #[derive(Clone)] pub enum DataKey { Admin, // Contract admin address Authority(Address), // Authority information by address Schema(BytesN<32>), // Schema data by UID AttestationUID(BytesN<32>), // Attestation by UID AttesterNonce(Address), // Nonce for replay attack prevention AttesterPublicKey(Address), // BLS public key for delegated operations } ``` ### Attestation Complete attestation record with support for delegated operations: ```rust theme={null} #[contracttype] #[derive(Clone, Debug, PartialEq)] pub struct Attestation { pub uid: BytesN<32>, // Unique attestation identifier pub schema_uid: BytesN<32>, // Schema identifier pub subject: Address, // Attestation target pub attester: Address, // Attestation creator pub value: String, // Attestation content pub nonce: u64, // Unique nonce for this attestation pub timestamp: u64, // Creation timestamp pub expiration_time: Option, // Optional expiration pub revoked: bool, // Revocation status pub revocation_time: Option, // Revocation timestamp } ``` ### Schema Schema structure with authority and resolver support: ```rust theme={null} #[contracttype] #[derive(Clone, Debug, PartialEq)] pub struct Schema { pub authority: Address, // Schema creator pub definition: String, // Schema structure (supports XDR or JSON) pub resolver: Option
, // Optional validation contract pub revocable: bool, // Schema-level revocation setting } ``` ### Authority Authority registration and metadata: ```rust theme={null} #[contracttype] #[derive(Debug, Clone)] pub struct Authority { pub address: Address, // Authority Stellar address pub metadata: String, // Authority description (JSON) } ``` ### Delegated Operations Support for gas-less transactions through delegated signing: ```rust theme={null} #[contracttype] #[derive(Clone)] pub struct DelegatedAttestationRequest { pub schema_uid: BytesN<32>, pub subject: Address, pub attester: Address, pub value: String, pub nonce: u64, pub deadline: u64, pub expiration_time: Option, pub signature: BytesN<96>, // BLS12-381 signature } #[contracttype] #[derive(Clone)] pub struct DelegatedRevocationRequest { pub attestation_uid: BytesN<32>, pub schema_uid: BytesN<32>, pub subject: Address, pub nonce: u64, pub revoker: Address, pub deadline: u64, pub signature: BytesN<96>, // BLS12-381 signature } ``` ### BLS Public Key BLS12-381 key for advanced cryptographic operations: ```rust theme={null} #[contracttype] #[derive(Clone, Debug, PartialEq)] pub struct BlsPublicKey { pub key: BytesN<192>, // BLS12-381 G2 public key pub registered_at: u64, // Registration timestamp } ``` *** ## Core Operations ### Schema Registration Register new attestation schemas with validation rules: ```rust theme={null} pub fn register( env: Env, caller: Address, // Authority address (must authorize) schema_definition: String, // Schema structure definition resolver: Option
, // Optional resolver contract revocable: bool // Schema revocation capability ) -> Result, Error> // Returns schema UID ``` **Process**: 1. Caller authorizes the transaction 2. Generate deterministic schema UID using SHA-256 hash of definition 3. Store schema data using `DataKey::Schema(uid)` 4. Emit schema registration event 5. Return generated schema UID ### Attestation Operations **Direct Attestation** Create attestations where the attester is the subject: ```rust theme={null} pub fn attest( env: Env, attester: Address, // Attester address (must authorize) schema_uid: BytesN<32>, // Target schema UID value: String, // Attestation data content expiration_time: Option // Optional expiration timestamp ) -> Result, Error> // Returns attestation UID ``` **Flow**: 1. Validate schema exists and caller authorization 2. Generate unique attestation UID 3. Call resolver contract if specified 4. Store attestation with `DataKey::AttestationUID(uid)` 5. Emit attestation created event 6. Return attestation UID **Delegated Attestation** Gas-less attestations via signed requests: ```rust theme={null} pub fn attest_by_delegation( env: Env, submitter: Address, // Transaction submitter (pays fees) request: DelegatedAttestationRequest // Signed attestation request ) -> Result<(), Error> ``` **Benefits**: * Users don't need native tokens for gas * Any party can submit on behalf of the attester * BLS signature verification ensures authenticity * Nonce prevents replay attacks **Retrieve Attestation** Query attestations by UID: ```rust theme={null} pub fn get_attestation( env: Env, attestation_uid: BytesN<32> // Attestation identifier ) -> Result ``` **Revoke Attestation** Invalidate existing attestations: ```rust theme={null} pub fn revoke( env: Env, revoker: Address, // Must be original attester attestation_uid: BytesN<32> // Attestation identifier ) -> Result<(), Error> ``` ### Delegated Operations **BLS Key Registration** Register BLS public key for delegated signing: ```rust theme={null} pub fn register_bls_key( env: Env, attester: Address, // Key owner (must authorize) public_key: BytesN<192> // BLS12-381 G2 public key ) -> Result<(), Error> ``` **Get Attester Nonce** Retrieve nonce for replay attack prevention: ```rust theme={null} pub fn get_attester_nonce( env: Env, attester: Address ) -> u64 // Returns next expected nonce ``` *** ## Storage Architecture ### Persistent Storage Soroban's persistent storage for long-term data retention: ```rust theme={null} // Schema storage env.storage().persistent().set(&DataKey::Schema(schema_uid), &schema); // Attestation storage env.storage().persistent().set(&DataKey::Attestation(schema_uid, subject, reference), &attestation); // Authority storage env.storage().persistent().set(&DataKey::Authority(authority_address), &authority); ``` ### Storage Benefits * **Persistent State**: Data survives contract upgrades * **Efficient Queries**: Direct key-based lookups * **Deterministic Keys**: Predictable storage locations * **Gas Optimization**: Minimal storage operations *** ## Authority System The Authority contract manages authority registration with payment verification: ### Payment-Gated Registration ```rust theme={null} pub fn pay_verification_fee( env: Env, payer: Address, ref_id: String, token_address: Address ) -> Result<(), Error> ``` **Fee**: 100 XLM registration fee for enhanced trust and spam prevention ### Authority Registration After payment confirmation, register as an authority: ```rust theme={null} pub fn register_authority( env: Env, caller: Address, authority_to_reg: Address, metadata: String ) -> Result<(), Error> ``` ### Authority Verification Check if an address is a registered authority: ```rust theme={null} pub fn is_authority(env: Env, authority: Address) -> Result ``` *** ## Resolver Integration Schemas can specify optional resolver contracts for custom validation logic: ### Resolver Methods The Authority contract implements the resolver interface: ```rust theme={null} // Called before attestation is created pub fn attest(env: Env, attestation: Attestation) -> Result // Called before attestation is revoked pub fn revoke(env: Env, attestation: Attestation) -> Result ``` **Use Cases**: * Payment verification before attestation * Custom authorization logic * Conditional attestation rules * Integration with external systems *** ## Error Handling Comprehensive error types for all contract operations: ```rust theme={null} pub enum Error { AlreadyInitialized, // Contract already initialized Unauthorized, // Insufficient permissions SchemaNotFound, // Invalid schema UID AttestationNotFound, // Attestation doesn't exist NotRevocable, // Schema/attestation cannot be revoked AlreadyRevoked, // Already revoked ExpiredAttestation, // Past expiration time InvalidSignature, // Invalid BLS signature InvalidNonce, // Incorrect nonce for replay prevention DeadlineExpired, // Delegated request deadline passed BlsKeyAlreadyExists, // BLS key already registered } ``` *** ## Events The contracts emit events for all major operations: * **Schema Registration**: `schema_registered` * **Attestation Created**: `attestation_created` * **Attestation Revoked**: `attestation_revoked` * **Authority Registered**: `authority_registered` * **Payment Received**: `payment_received` * **Fees Withdrawn**: `levy_withdrawn` These events are indexed by the Horizon indexer for efficient querying and real-time monitoring. *** ## Network Information ### Deployment Addresses | Network | Contract Address | Status | | ----------- | ---------------------------------------------------- | ------- | | **Testnet** | `CB7QHNAXAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAEAQCAIBAE` | Active | | **Mainnet** | TBD | Planned | ### Network Configuration **Testnet Setup**: ```bash theme={null} # Network passphrase TESTNET_PASSPHRASE="Test SDF Network ; September 2015" # RPC endpoint TESTNET_RPC="https://soroban-testnet.stellar.org" # Horizon endpoint TESTNET_HORIZON="https://horizon-testnet.stellar.org" ``` **Mainnet Setup**: ```bash theme={null} # Network passphrase MAINNET_PASSPHRASE="Public Global Stellar Network ; September 2015" # RPC endpoint MAINNET_RPC="https://soroban-mainnet.stellar.org" # Horizon endpoint MAINNET_HORIZON="https://horizon.stellar.org" ``` *** ## Development ### Build and Deploy ```bash theme={null} # Navigate to stellar contracts cd contracts/stellar # Build contracts cd protocol && make build cd ../authority && make build # Run tests cd protocol && make test cd ../authority && make test # Deploy to testnet ./deploy.sh testnet ``` ### Testing Comprehensive test suites are available in each contract directory for validation of all operations. *** ## Next Steps
Solana Solana
Compare with the Anchor-based Solana implementation
View the complete Soroban contract source
# Attestations Source: https://docs.attest.so/concepts/attestations Core concept: what attestations are and how they work ## What is an Attestation? A signed statement by an issuer about a subject, stored on-chain. **Example**: "KYC Provider X attests that Wallet Y is verified at level Basic" ## Attestation Properties | Property | Description | | ---------------- | ------------------------------ | | `uid` | Unique identifier (32 bytes) | | `schemaUid` | Reference to schema structure | | `attester` | Who created the attestation | | `subject` | Who the attestation is about | | `value` | Attestation data (JSON string) | | `timestamp` | When created | | `expirationTime` | When it expires (optional) | | `revoked` | Whether invalidated | ## Lifecycle ```mermaid theme={null} stateDiagram-v2 [*] --> Created Created --> Active Active --> Expired Active --> Revoked ``` **Created**: Attestation issued and stored on-chain **Active**: Valid and queryable **Expired**: Past expiration time (still on-chain) **Revoked**: Explicitly invalidated by attester ## Use Cases * **KYC/Identity**: Verify user identity * **Credentials**: Professional certifications * **Reputation**: On-chain reputation scores * **Access Control**: Gate access to services * **Compliance**: Regulatory attestations ## Schema Relationship Every attestation references a schema that defines its structure: ```typescript theme={null} // Schema defines structure 'struct KYC { bool verified; string level; }' // Attestation follows structure { verified: true, level: 'basic' } ``` ## Next Steps Learn about schema design Off-chain signing with BLS keys # Authorities Source: https://docs.attest.so/concepts/authorities Schema ownership and verified issuer status ## What is an Authority? In AttestProtocol, an **authority** is simply the wallet address that created a schema. When you deploy a schema, you become its authority — the permanent owner of that schema definition. Schemas and attestations are **permissionless by default**. Anyone can create schemas, and anyone can issue attestations to any schema — unless a resolver restricts access. ## The Permissionless Model AttestProtocol is designed to be open: | Action | Default Behavior | With Resolver | | ---------------------- | ------------------------------- | ------------------------ | | **Create Schema** | Anyone can create | Anyone can create | | **Issue Attestation** | Anyone can attest to any schema | Resolver controls access | | **Revoke Attestation** | Original attester can revoke | Resolver controls access | ```mermaid theme={null} flowchart TD subgraph PD["PERMISSIONLESS BY DEFAULT"] SC["Schema Creation"] --> SC1["Any wallet can deploy a schema"] SC1 --> SC2["Creator becomes the "authority" (owner)"] AI["Attestation Issuance"] --> AI1["Any wallet can attest to any schema"] AI1 --> AI2["Unless a resolver restricts access"] AC["Access Control (Optional)"] --> AC1["Attach a resolver to control who can attest"] AC1 --> AC2["Fee requirements"] AC1 --> AC3["Allowlists"] AC1 --> AC4["Custom validation logic"] end ``` ## Schema Authority When you deploy a schema, your wallet address is recorded as its **authority**. This gives you: * **Ownership record** — Your address is permanently linked to the schema * **Schema definition control** — You defined the structure and resolver at creation Being a schema authority does **not** mean only you can attest. By default, anyone can issue attestations to your schema. Use a [resolver](/concepts/resolvers) if you need access control. ### Creating a Schema ```typescript theme={null} import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; const client = new StellarAttestationClient({ network: 'testnet' // resolves to the current contract }); // Deploy a schema - you become its authority const schemaUid = await client.createSchema({ definition: 'string name, bool verified, uint64 timestamp', resolver: null, // No resolver = permissionless attestations revocable: true }); // Anyone can now attest to this schema ``` On testnet, `network: 'testnet'` resolves to the current protocol contract, . Pass `contractId` only to pin an older version. ### Restricting Access with Resolvers If you want to control who can attest to your schema, attach a resolver: ```typescript theme={null} // Deploy a schema with access control const schemaUid = await client.createSchema({ definition: 'string credential, uint64 issuedAt', resolver: 'CRESOLVER...', // Resolver controls who can attest revocable: true }); // Now only addresses approved by the resolver can attest ``` See [Resolvers](/concepts/resolvers) for details on implementing access control. ## Verified Authority (Optional) Separately from schema authority, you can register as a **Verified Authority** on the AttestProtocol platform. This is completely optional and provides: * **Platform badge** — Visual indicator that you're a verified issuer * **Trust signals** — Users can see your verification status * **Discovery** — Easier for verifiers to find trusted attestation sources ### Verification Methods For Stellar-native organizations, verify using your domain's stellar.toml file Use decentralized identifiers and external credentials for verification ### Registering as a Verified Authority ```typescript theme={null} import { AttestProtocolAuthority } from '@attestprotocol/stellar-sdk'; // Initialize authority client const authority = new AttestProtocolAuthority(config, authorityClient); // Register for verified authority status (optional) const result = await authority.registerAuthority( 'GAUTHORITY...', // Your address JSON.stringify({ name: 'Acme Verification Inc.', website: 'https://acme.com', description: 'KYC and identity verification provider' }) ); ``` ### Using the CLI ```bash theme={null} # Register for verified authority status pnpm cli authority --chain stellar --action register --key-file ./keypair.json ``` ## Authority Metadata When registering as a verified authority, include relevant information: ```json theme={null} { "name": "Your Organization Name", "website": "https://your-domain.com", "description": "Brief description of what you attest to", "logo": "https://your-domain.com/logo.png", "contact": "attestations@your-domain.com", "categories": ["identity", "kyc", "credentials"] } ``` ## Checking Authority Status ### Check Verified Authority Status ```typescript theme={null} // Check if an address is a verified authority const isVerified = await authority.isAuthority('GADDRESS...'); // Fetch full authority details const authorityData = await authority.fetchAuthority('GADDRESS...'); console.log('Authority:', authorityData.data); ``` ### Check Attestation Issuer Every attestation includes the attester address: ```typescript theme={null} // Get attestation details const attestation = await client.getAttestation(attestationUid); console.log('Issued by:', attestation.data.attester); ``` ## Delegates Authorities can delegate attestation signing to other addresses using BLS delegation. This enables: * **Gasless attestations** — Users sign off-chain, a relayer submits on-chain * **Batch operations** — Issue many attestations in one transaction * **Separation of concerns** — Keep hot wallets separate from main keys See [Delegates](/concepts/delegates) for detailed documentation. ## Authority vs Resolver | Concept | Purpose | Controls | | ------------- | --------------------------- | --------------------------- | | **Authority** | Schema ownership record | Who created the schema | | **Resolver** | Access control & validation | Who can attest, fees, rules | * **Authority** = "Who owns this schema definition" * **Resolver** = "What rules apply when attesting" Without a resolver, attestations are permissionless. With a resolver, you can enforce any access control logic you need. See [Resolvers](/concepts/resolvers) for implementation details. ## Security Considerations Keep your private keys secure. While attestations are permissionless, your verified authority status and any resolver admin rights are tied to your address. ### Best Practices 1. **Use resolvers for access control** — Don't assume only you will attest to your schema 2. **Secure your keys** — Use hardware wallets or multi-sig for production 3. **Verify your domain** — Set up stellar.toml for additional trust 4. **Monitor attestations** — Track what's being issued to your schemas ## Next Steps Add access control to your schemas Define attestation structures Enable off-chain signing with BLS delegation See real-world implementations # Delegates Source: https://docs.attest.so/concepts/delegates Off-chain signing with BLS keys for scalable attestation issuance ## What are Delegates? Delegates are addresses authorized to submit attestations on behalf of an authority. The authority signs attestation data off-chain using BLS keys, and the delegate submits the transaction on-chain. ```mermaid theme={null} sequenceDiagram participant A as Authority
(BLS private key) participant D as Delegate
(Stellar wallet) participant C as Smart Contract A->>A: 1. Sign attestation data off-chain Note over A: BLS Signature A->>D: 2. Submit with signature D->>C: Submit attestation C->>C: Verifies signature
Creates attestation ``` The authority can also act as their own delegate. This is useful when you want to use BLS signing for your own submissions, bear gas fees directly, or keep the workflow simple without involving third parties. ## Attester vs Subject When an attestation is created through delegation, two key fields are set: | Field | Description | | ------------ | -------------------------------------------------------------------------------- | | **attester** | The delegate or authority who submits the transaction on-chain | | **subject** | The recipient of the attestation — the individual or entity being attested about | ```typescript theme={null} // Example: Authority signs, delegate submits const request = await createDelegatedAttestationRequest({ schemaUid: Buffer.from('abc123...', 'hex'), subject: 'GUSER123...', // The person receiving the attestation data: JSON.stringify({ verified: true }) }, blsPrivateKey, client.getClientInstance()); // When submitted by delegate: // attestation.attester = 'GDELEGATE...' (who submitted) // attestation.subject = 'GUSER123...' (who the attestation is about) ``` The **subject** always remains the individual or entity the attestation describes, regardless of who submits the transaction. ## Why Use Delegates? ### Scalability Issue thousands of attestations without the authority signing each transaction. The authority pre-signs batches; delegates handle submission. ### Security Keep authority keys in cold storage or HSMs. Only BLS signatures leave the secure environment, never private keys. ### Cost Efficiency Delegates pay transaction fees. Authorities don't need to hold tokens for gas. ### Flexibility Multiple delegates can submit on behalf of one authority. Useful for geographic distribution or redundancy. ## Self-Delegation An authority can submit their own delegated attestations. This pattern is useful for: * **Gas management** — Authority pays fees directly from their wallet * **Simplicity** — No need to coordinate with separate delegate infrastructure * **Testing** — Validate the delegation flow before involving third parties ```typescript theme={null} // Authority acts as their own delegate const authority = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GAUTHORITY...' }); // Sign the request const request = await createDelegatedAttestationRequest({ schemaUid: Buffer.from('abc...', 'hex'), subject: 'GSUBJECT...', data: JSON.stringify({ verified: true }) }, blsPrivateKey, authority.getClientInstance()); // Submit as self (authority is the delegate) const result = await authority.attestByDelegation(request, { signer: authoritySigner }); // Result: attester = authority, subject = GSUBJECT ``` ## BLS Keys AttestProtocol uses BLS12-381 signatures for delegation. BLS offers: * **Aggregation**: Multiple signatures can be combined into one * **Deterministic**: Same message + key always produces same signature * **Compact**: 48-byte signatures (compressed) ### Key Generation ```typescript theme={null} import { generateBlsKeys } from '@attestprotocol/stellar-sdk'; const { publicKey, privateKey } = generateBlsKeys(); // publicKey: 192 bytes (uncompressed) // privateKey: 32 bytes ``` ### Key Registration Before using delegation, register your BLS public key on-chain: ```typescript theme={null} await client.registerBlsKey(publicKey, { signer }); ``` This links your Stellar address to your BLS public key. ## Delegated Attestation Flow ### 1. Authority: Create and Sign Request ```typescript theme={null} import { createDelegatedAttestationRequest } from '@attestprotocol/stellar-sdk'; // Authority signs off-chain const request = await createDelegatedAttestationRequest({ schemaUid: Buffer.from('abc123...', 'hex'), subject: 'GSUBJECT...', data: JSON.stringify({ verified: true }), expirationTime: Math.floor(Date.now() / 1000) + 86400 // 24h }, blsPrivateKey, client.getClientInstance()); ``` ### 2. Delegate: Submit On-chain ```typescript theme={null} // Delegate submits the pre-signed request await client.attestByDelegation(request, { signer: delegateSigner }); ``` The contract verifies the BLS signature matches the registered authority before creating the attestation. ## Delegated Revocation Same pattern for revoking attestations: ```typescript theme={null} import { createDelegatedRevocationRequest } from '@attestprotocol/stellar-sdk'; // Authority signs revocation const revokeRequest = await createDelegatedRevocationRequest({ attestationUid: Buffer.from('def456...', 'hex') }, blsPrivateKey, client.getClientInstance()); // Delegate submits await client.revokeByDelegation(revokeRequest, { signer: delegateSigner }); ``` ## Security Considerations ### Nonce Management Each delegation request includes a nonce to prevent replay attacks. The contract tracks used nonces per authority. ### Deadline Enforcement Requests include a deadline timestamp. Submissions after the deadline are rejected. ### Domain Separation Different operations (attest, revoke) use different domain separation tags (DST), preventing signature reuse across operations. ## Architecture Patterns ### Batch Issuance Authority pre-signs many attestations, sends signatures to a queue. Workers pull and submit. ```mermaid theme={null} flowchart LR A["Authority"] -->|Sign 1000 attestations| Q["Queue"] Q --> W1["Worker 1"] --> S1["Submit"] Q --> W2["Worker 2"] --> S2["Submit"] Q --> W3["Worker 3"] --> S3["Submit"] ``` ### Event-Driven Authority runs a signing service. When events occur (user completes KYC), sign and queue for submission. ### Multi-Region Deploy delegates in multiple regions. Authority in one secure location, delegates distributed globally for lower latency. ## Complete Example ```typescript theme={null} import { StellarAttestationClient, generateBlsKeys, createDelegatedAttestationRequest } from '@attestprotocol/stellar-sdk'; // Setup const authority = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GAUTHORITY...' }); // One-time: Generate and register BLS keys const { publicKey, privateKey } = generateBlsKeys(); await authority.registerBlsKey(publicKey, { signer: authoritySigner }); // Authority signs attestation request const request = await createDelegatedAttestationRequest({ schemaUid: Buffer.from('abc...', 'hex'), subject: 'GSUBJECT...', data: JSON.stringify({ verified: true, level: 'premium' }) }, privateKey, authority.getClientInstance()); // Delegate submits (can be different wallet or same as authority) const delegate = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GDELEGATE...' }); const result = await delegate.attestByDelegation(request, { signer: delegateSigner }); ``` ## Next Steps Schema ownership and trust management Real-world integration examples # How It Works Source: https://docs.attest.so/concepts/how-it-works Architecture overview of AttestProtocol ## Four Actors ```mermaid theme={null} flowchart LR Issuer -->|Creates schemas & attestations| Protocol Protocol -->|Stores attestations on-chain| Holder Verifier -->|Queries & validates| Holder ``` **Issuer**: Creates schemas and issues attestations (e.g., KYC provider) **Protocol**: On-chain smart contract storing attestations **Holder**: Receives attestations linked to their address **Verifier**: Queries and validates attestations ## Attestation Flow 1. **Schema Creation** - Issuer defines data structure 2. **Attestation** - Issuer creates signed claim about a subject 3. **Storage** - Protocol stores attestation on-chain 4. **Verification** - Anyone can query and verify ## Key Features ### BLS Delegation Sign attestations off-chain, submit on-chain. The submitter pays gas, not the attester. ### Economic Security Authority registration requires stake, preventing Sybil attacks. ### Resolvers Optional validation logic before attestations are stored (e.g., payment verification). ## Contract Architecture **Protocol Contract**: Core attestation engine * Schema registration * Attestation creation/revocation * BLS signature verification **Authority Contract**: Trust management * Authority registration with payment * Resolver interface for custom validation ## Next Steps Create your first attestation Learn about schema design # Resolvers Source: https://docs.attest.so/concepts/resolvers Define rules about who can attest to a schema ## What are Resolvers? Resolvers are smart contracts that define **custom validation rules** for attestations. When you attach a resolver to a schema, every attestation must pass through the resolver's validation logic before being created. Schemas and attestations are **permissionless by default**. Resolvers are how you add access control, fees, or custom rules when you need them. ## Why Use Resolvers? By default, **anyone can attest to any schema**. This permissionless model is intentional — it enables open, composable attestations. But sometimes you need control: Resolvers let you: * **Control access** — Only allow specific addresses to attest * **Collect fees** — Require payment before attestation creation * **Distribute rewards** — Incentivize attestation with token rewards * **Enforce rules** — Implement custom business logic ```mermaid theme={null} flowchart LR subgraph W1["WITHOUT RESOLVER (Permissionless)"] direction LR P1["Anyone"] --> P2["Attest to Any Schema"] --> P3["✅ Attestation Created"] end ``` ```mermaid theme={null} flowchart TD subgraph W2["WITH RESOLVER (Access Control)"] direction TB R1["1. Attestation Request"] --> R2["2. Resolver.onattest()"] R2 -->|Validation: rejected| RE["❌ Error"] R2 -->|Validation: approved| R3["3. Store Attestation"] R3 --> R4["4. Resolver.onresolve()"] R4 -->|Post-processing| R5["✅ Attestation Created"] end ``` ## Resolver Interface All resolvers implement the same interface, making them pluggable and interchangeable: ```rust theme={null} pub trait ResolverInterface { // Validate before attestation creation fn onattest(env: Env, attestation: ResolverAttestationData) -> Result; // Validate before attestation revocation fn onrevoke(env: Env, attestation: ResolverAttestationData) -> Result; // Post-processing callback (after creation/revocation) fn onresolve(env: Env, attestation_uid: BytesN<32>, attester: Address) -> Result<(), ResolverError>; // Resolver metadata for discovery fn metadata(env: Env) -> ResolverMetadata; } ``` ### Hook Functions | Function | When Called | Purpose | Critical? | | ----------- | ----------------------------- | ---------------------------------- | --------- | | `onattest` | Before attestation creation | Validate and gate access | Yes | | `onrevoke` | Before attestation revocation | Validate revocation permission | Yes | | `onresolve` | After creation/revocation | Post-processing (rewards, cleanup) | No | | `metadata` | On query | Return resolver information | No | **Critical vs Non-Critical:** If `onattest` or `onrevoke` fails, the operation is aborted. If `onresolve` fails, the attestation still succeeds — it's for side effects only. ## Attestation Data Resolvers receive complete attestation data for validation: ```rust theme={null} pub struct ResolverAttestationData { pub uid: BytesN<32>, // Unique attestation ID pub schema_uid: BytesN<32>, // Associated schema pub recipient: Address, // Who is being attested about pub attester: Address, // Who is attesting pub time: u64, // Creation timestamp pub expiration_time: u64, // When it expires (0 = never) pub revocation_time: u64, // When revoked (0 = not revoked) pub revocable: bool, // Can be revoked? pub ref_uid: Bytes, // Reference to another attestation pub data: Bytes, // Encoded attestation data pub value: i128, // Optional payment value } ``` ## Resolver Types ### Default Resolver The simplest resolver — validates basic rules without economic requirements. ```rust theme={null} impl ResolverInterface for DefaultResolver { fn onattest(env: Env, attestation: ResolverAttestationData) -> Result { // Require attester authorization attestation.attester.require_auth(); // Prevent self-attestation if attestation.attester == attestation.recipient { return Err(ResolverError::ValidationFailed); } // Validate expiration if attestation.expiration_time > 0 && attestation.expiration_time < env.ledger().timestamp() { return Err(ResolverError::InvalidAttestation); } Ok(true) } } ``` **Use cases:** * Development and testing * Schemas without special requirements * Base implementation for custom resolvers ### Fee Collection Resolver Requires payment before attestation creation. ```rust theme={null} impl ResolverInterface for FeeCollectionResolver { fn onattest(env: Env, attestation: ResolverAttestationData) -> Result { attestation.attester.require_auth(); // Check payment amount let required_fee = get_attestation_fee(&env); if attestation.value < required_fee { return Err(ResolverError::InsufficientFunds); } // Collect the fee collect_fee(&env, &attestation.attester, attestation.value)?; Ok(true) } } ``` **Use cases:** * Monetizing attestation services * Spam prevention through economic cost * Revenue generation for attestation providers ### Token Reward Resolver Distributes token rewards for attestation creation. ```rust theme={null} impl ResolverInterface for TokenRewardResolver { fn onattest(_env: Env, _attestation: ResolverAttestationData) -> Result { // Permissionless - gas cost provides spam resistance Ok(true) } fn onresolve(env: Env, _attestation_uid: BytesN<32>, attester: Address) -> Result<(), ResolverError> { // Distribute reward after attestation let reward_amount = get_reward_amount(&env); distribute_reward(&env, &attester, reward_amount)?; Ok(()) } } ``` **Use cases:** * Incentivizing attestation creation * Community engagement programs * Protocol growth mechanics ### Authority Resolver Permission-based access control with verification. ```rust theme={null} impl ResolverInterface for AuthorityResolver { fn onattest(env: Env, attestation: ResolverAttestationData) -> Result { attestation.attester.require_auth(); // Check if attester is a registered authority if !is_registered_authority(&env, &attestation.attester) { return Err(ResolverError::NotAuthorized); } Ok(true) } } ``` **Use cases:** * Restricted attestation environments * Verified issuer programs * Credentialed attestation systems ## Creating a Custom Resolver ### Step 1: Implement the Interface ```rust theme={null} use soroban_sdk::{contract, contractimpl, Env, Address, BytesN, String}; use resolvers::{ResolverInterface, ResolverAttestationData, ResolverError, ResolverMetadata, ResolverType}; #[contract] pub struct MyCustomResolver; #[contractimpl] impl ResolverInterface for MyCustomResolver { fn onattest(env: Env, attestation: ResolverAttestationData) -> Result { // Your validation logic here attestation.attester.require_auth(); // Example: Only allow attestations on weekdays // (This is just an example - implement your own logic) Ok(true) } fn onrevoke(env: Env, attestation: ResolverAttestationData) -> Result { attestation.attester.require_auth(); if !attestation.revocable { return Err(ResolverError::ValidationFailed); } Ok(true) } fn onresolve(_env: Env, _attestation_uid: BytesN<32>, _attester: Address) -> Result<(), ResolverError> { // Optional post-processing Ok(()) } fn metadata(env: Env) -> ResolverMetadata { ResolverMetadata { name: String::from_str(&env, "My Custom Resolver"), version: String::from_str(&env, "1.0.0"), description: String::from_str(&env, "Custom validation for my use case"), resolver_type: ResolverType::Custom, } } } ``` ### Step 2: Build the Contract ```bash theme={null} cargo build --target wasm32v1-none --release ``` ### Step 3: Deploy to Stellar ```bash theme={null} stellar contract deploy \ --wasm target/wasm32v1-none/release/my_resolver.wasm \ --source YOUR_IDENTITY \ --network testnet ``` ### Step 4: Attach to a Schema ```typescript theme={null} const schemaUid = await client.createSchema({ definition: 'string credential, uint64 issuedAt', resolver: 'CRESOLVER...', // Your deployed resolver address revocable: true }); ``` ## Binding Resolvers to Schemas When you create a schema, you can optionally attach a resolver: ```typescript theme={null} import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; const client = new StellarAttestationClient({ network: 'testnet' }); // Schema with a resolver const schemaWithResolver = await client.createSchema({ definition: 'string credential, bool verified', resolver: 'CRESOLVER...', // Resolver contract address revocable: true }); // Schema without a resolver (permissionless - anyone can attest) const schemaWithoutResolver = await client.createSchema({ definition: 'string note', resolver: null, // No resolver = anyone can attest revocable: false }); ``` Once a schema is created, its resolver cannot be changed. Choose your resolver carefully before deploying to production. ## Error Handling Resolvers use a standard error enum: ```rust theme={null} #[contracterror] pub enum ResolverError { NotAuthorized = 1, // Caller lacks permission InvalidAttestation = 2, // Attestation data is invalid InvalidSchema = 3, // Schema mismatch InsufficientFunds = 4, // Payment too low TokenTransferFailed = 5,// Token operation failed StakeRequired = 6, // Staking requirement not met ValidationFailed = 7, // Generic validation failure CustomError = 8, // Custom error condition } ``` ## Security Considerations Resolver bugs can lead to unauthorized attestations or denial of service. Test thoroughly before production deployment. ### Best Practices 1. **Always require auth** — Call `require_auth()` on the attester 2. **Validate all inputs** — Don't trust data from the protocol 3. **Handle failures gracefully** — Return proper error codes 4. **Avoid state dependencies** — Don't rely on external state that can be manipulated 5. **Test edge cases** — Expired attestations, revocations, etc. ### Common Pitfalls | Pitfall | Issue | Solution | | ------------------ | -------------------------- | --------------------------------------- | | Missing auth check | Anyone can attest | Always call `require_auth()` | | Self-attestation | Users attest to themselves | Check `attester != recipient` | | Expired data | Using outdated timestamps | Validate against current time | | Reentrancy | Token callbacks | Use checks-effects-interactions pattern | ## Testing Resolvers ```rust theme={null} #[test] fn test_reject_self_attestation() { let (env, resolver) = setup(); let user = Address::generate(&env); let attestation = build_attestation(&env, &user, &user, 0); let result = resolver.try_onattest(&attestation); assert!(matches!(result.err(), Some(Ok(ResolverError::ValidationFailed)))); } #[test] fn test_accept_valid_attestation() { let (env, resolver) = setup(); let attester = Address::generate(&env); let recipient = Address::generate(&env); let attestation = build_attestation(&env, &attester, &recipient, 0); assert!(resolver.onattest(&attestation)); } ``` ## Built-in Resolvers The protocol provides pre-built resolvers you can use: | Resolver | Purpose | Build Command | | -------------- | ------------------ | ------------------------------------------- | | Default | Basic validation | `--features export-default-resolver` | | Token Reward | Distribute rewards | `--features export-token-reward-resolver` | | Fee Collection | Collect fees | `--features export-fee-collection-resolver` | ```bash theme={null} # Build the default resolver cargo build --target wasm32v1-none --release --features export-default-resolver # Deploy stellar contract deploy \ --wasm target/wasm32v1-none/release/resolvers.wasm \ --source YOUR_IDENTITY \ --network testnet ``` ## Next Steps Understand the trust layer for attestation issuers Define attestation structures with resolver bindings Deep dive into Stellar contract architecture Browse resolver source code # Schemas Source: https://docs.attest.so/concepts/schemas Define the structure and meaning of attestation data ## What is a Schema? A schema is a template that defines what data an attestation contains. Every attestation references a schema, ensuring data consistency and enabling verification. ```mermaid theme={null} flowchart LR S["Schema: KYC
verified: bool
level: string
timestamp: u64"] -->|references| A1["Attestation #1
verified: true
level: "premium"
timestamp: 1701234567"] ``` ## Why Schemas Matter ### Data Consistency Schemas enforce structure. An attestation using a KYC schema will always have the same fields, making it predictable for verifiers. ### Interoperability Different applications can issue attestations against the same schema. A "KYC verified" attestation from Provider A is structurally identical to one from Provider B. ### Discoverability Schemas are registered on-chain with unique identifiers. Applications can query for all attestations of a specific type. ## Schema Components | Component | Description | | -------------- | --------------------------------------- | | **Definition** | Field names and types | | **Authority** | Who created the schema | | **Resolver** | Optional contract for custom validation | | **Revocable** | Whether attestations can be revoked | | **UID** | Deterministic identifier | ## Field Types Schemas support these primitive types: | Type | Size | Description | | ----------- | -------- | ----------------------------- | | `bool` | 1 bit | True/false | | `string` | Variable | Text data | | `u32` | 32 bits | Unsigned integer | | `u64` | 64 bits | Unsigned integer (timestamps) | | `i32` | 32 bits | Signed integer | | `i64` | 64 bits | Signed integer | | `i128` | 128 bits | Large signed integer | | `bytes` | Variable | Binary data | | `address` | 56 chars | Stellar wallet address | | `symbol` | Variable | Soroban symbol type | | `timestamp` | 64 bits | Unix timestamp | | `amount` | Variable | Token amounts | ## Encoding Formats The SDK's `SorobanSchemaEncoder` supports multiple encoding formats. Your choice of format affects how data is stored and indexed. These encoding formats determine how your schema and attestation data are stored on-chain and indexed by our explorer. Choose the format that best fits your use case. ### Typed Schema (Default) Native typed schema with validation rules. Best for most use cases. ```typescript theme={null} import { SorobanSchemaEncoder, StellarDataType } from '@attestprotocol/stellar-sdk'; const encoder = new SorobanSchemaEncoder({ name: 'KYC', description: 'Know Your Customer verification', fields: [ { name: 'verified', type: StellarDataType.BOOL }, { name: 'level', type: StellarDataType.STRING, validation: { enum: ['basic', 'enhanced', 'premium'] } }, { name: 'verifiedAt', type: StellarDataType.TIMESTAMP }, { name: 'subject', type: StellarDataType.ADDRESS } ] }); // Encode attestation data with validation const encoded = await encoder.encodeData({ verified: true, level: 'premium', verifiedAt: Date.now(), subject: 'GUSER...' }); ``` **Features:** * Type validation at encode time * Optional field validation (min, max, pattern, enum) * Auto-conversion for timestamps and addresses ### JSON Schema Standard JSON Schema format for interoperability with external tools. ```typescript theme={null} // Convert typed schema to JSON Schema const jsonSchema = encoder.toJSONSchema(); // Create encoder from existing JSON Schema const fromJson = SorobanSchemaEncoder.fromJSONSchema({ title: 'EventAttendance', type: 'object', properties: { eventId: { type: 'string' }, attended: { type: 'boolean' }, timestamp: { type: 'number' } }, required: ['eventId', 'attended'] }); ``` **Use cases:** * Integration with JSON Schema validators * Generating forms from schemas * API documentation and OpenAPI specs ### XDR Format Stellar's native binary format for efficient storage and **selective disclosure**. ```typescript theme={null} // Encode schema to XDR const xdrString = encoder.toXDR(); // Returns: "XDR:AAAA..." (base64-encoded binary) // Decode XDR back to schema const decoded = SorobanSchemaEncoder.fromXDR(xdrString); ``` **Benefits:** * **Compact storage** — Binary format reduces on-chain storage costs * **Selective disclosure** — Reveal only specific fields without exposing entire attestation * **Native Soroban compatibility** — Direct integration with contract data structures XDR provides selective disclosure, not encryption. Data is structured so you can reveal individual fields while keeping others private. For true privacy, combine with off-chain encrypted storage. ### Format Comparison | Format | Storage Size | Indexer Support | Use Case | | --------- | ------------ | --------------- | ------------------------------------- | | **Typed** | Medium | Full | General purpose, validation needed | | **JSON** | Larger | Full | Interoperability, external tools | | **XDR** | Smallest | Full | Compact storage, selective disclosure | All three formats are fully supported by the AttestProtocol indexer and explorer. Your attestation data will be queryable regardless of encoding choice. ## Schema UID Every schema has a unique identifier derived from: ``` UID = hash(definition + authority + resolver) ``` This means: * Same definition by different authorities = different UIDs * Same authority, different definition = different UIDs * Identical inputs always produce the same UID ## Resolvers A resolver is an optional smart contract that validates attestations before they're created. Use cases: * **Payment gates**: Require fee payment before attestation * **Eligibility checks**: Verify the subject meets criteria * **Rate limiting**: Prevent spam attestations * **Custom logic**: Any business rules ## Common Schema Patterns ### Identity Verification ``` verified: bool level: string // "basic", "enhanced", "premium" provider: string // "plaid", "jumio", etc. expiry: u64 ``` ### Credential ``` title: string // "AWS Solutions Architect" issuer: string // "Amazon Web Services" issuedAt: u64 expiresAt: u64 ``` ### Membership ``` organization: string role: string joinedAt: u64 active: bool ``` ### Reputation ``` score: u32 category: string // "trustworthiness", "expertise" updatedAt: u64 ``` ## Schema Lifecycle 1. **Create**: Authority registers schema on-chain 2. **Discover**: Others find schema by UID or query 3. **Use**: Attesters create attestations referencing schema 4. **Verify**: Verifiers decode attestation data using schema definition ## Best Practices Only include fields you need. Smaller schemas = lower costs. `u64` for timestamps, `bool` for flags, `string` for text. Set `revocable: true` for credentials that may need invalidation. Include version in name: `KYC_v2` for breaking changes. ## Next Steps Step-by-step guide for Stellar Complete API documentation # DAO Membership Source: https://docs.attest.so/examples/dao-membership Verify membership status and issue DAO credentials ## Overview DAO membership attestations prove someone is a member of a decentralized organization with a specific role. This guide covers verifying membership and managing roles. ## Schema ```typescript theme={null} const MEMBERSHIP_SCHEMA = { name: 'DAOMembership', fields: [ { name: 'daoId', type: 'string' }, { name: 'daoName', type: 'string' }, { name: 'role', type: 'string' }, // "member", "contributor", "core", "admin" { name: 'joinedAt', type: 'u64' }, { name: 'active', type: 'bool' } ] }; ``` *** ## Verifier Workflow Check membership status before granting access to DAO resources. ### 1. Verify Membership ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const MEMBERSHIP_SCHEMA = { name: 'DAOMembership', fields: [ { name: 'daoId', type: 'string' }, { name: 'daoName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'joinedAt', type: 'u64' }, { name: 'active', type: 'bool' } ] }; async function verifyMembership(attestationUid: string, expectedDaoId?: string) { const attestation = await getAttestationByUid(attestationUid); if (!attestation) { return { isMember: false, reason: 'Membership not found' }; } if (attestation.revoked) { return { isMember: false, reason: 'Membership revoked' }; } const encoder = new SorobanSchemaEncoder(MEMBERSHIP_SCHEMA); const data = await encoder.decodeData(attestation.value); if (!data.active) { return { isMember: false, reason: 'Membership inactive' }; } if (expectedDaoId && data.daoId !== expectedDaoId) { return { isMember: false, reason: 'Wrong DAO' }; } return { isMember: true, daoId: data.daoId, daoName: data.daoName, role: data.role, member: attestation.subject, joinedAt: new Date(data.joinedAt) }; } ``` ### 2. Check Role Permissions ```typescript theme={null} type DAORole = 'member' | 'contributor' | 'core' | 'admin'; const ROLE_HIERARCHY: Record = { member: 1, contributor: 2, core: 3, admin: 4 }; function hasPermission(userRole: string, requiredRole: DAORole): boolean { const userLevel = ROLE_HIERARCHY[userRole as DAORole] || 0; const requiredLevel = ROLE_HIERARCHY[requiredRole]; return userLevel >= requiredLevel; } async function canAccessResource( attestationUid: string, daoId: string, requiredRole: DAORole ) { const result = await verifyMembership(attestationUid, daoId); if (!result.isMember) { return { allowed: false, reason: result.reason }; } if (!hasPermission(result.role, requiredRole)) { return { allowed: false, reason: `Requires ${requiredRole} role or higher` }; } return { allowed: true, role: result.role }; } ``` ### 3. Get All Memberships ```typescript theme={null} async function getUserMemberships( userAddress: string, membershipSchemaUid: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: userAddress }); const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: userAddress, limit: 100 }); const membershipAttestations = attestations.filter( a => a.schemaUid.toString('hex') === membershipSchemaUid && !a.revoked ); const encoder = new SorobanSchemaEncoder(MEMBERSHIP_SCHEMA); const memberships = await Promise.all( membershipAttestations.map(async (a) => { const data = await encoder.decodeData(a.value); return { uid: a.uid.toString('hex'), daoId: data.daoId, daoName: data.daoName, role: data.role, active: data.active, joinedAt: new Date(data.joinedAt) }; }) ); // Filter to active memberships only return memberships.filter(m => m.active); } ``` ### 4. Verify DAO Admin Issued ```typescript theme={null} const DAO_ADMINS: Record = { 'stellar-community': ['GADMIN1...', 'GADMIN2...'], 'soroban-builders': ['GADMIN3...'] }; async function verifyOfficialMembership(attestationUid: string, daoId: string) { const result = await verifyMembership(attestationUid, daoId); if (!result.isMember) { return result; } const attestation = await getAttestationByUid(attestationUid); const admins = DAO_ADMINS[daoId]; if (!admins?.includes(attestation.attester)) { return { isMember: false, reason: 'Not issued by DAO admin' }; } return result; } ``` ### Complete Verifier Example ```typescript theme={null} import { SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const MEMBERSHIP_SCHEMA = { name: 'DAOMembership', fields: [ { name: 'daoId', type: 'string' }, { name: 'daoName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'joinedAt', type: 'u64' }, { name: 'active', type: 'bool' } ] }; // Gate voting access async function canVote(attestationUid: string, daoId: string) { const attestation = await getAttestationByUid(attestationUid); if (!attestation || attestation.revoked) { return { canVote: false, reason: 'Invalid membership' }; } const encoder = new SorobanSchemaEncoder(MEMBERSHIP_SCHEMA); const data = await encoder.decodeData(attestation.value); if (data.daoId !== daoId) { return { canVote: false, reason: 'Not a member of this DAO' }; } if (!data.active) { return { canVote: false, reason: 'Membership inactive' }; } // Only contributors and above can vote const votingRoles = ['contributor', 'core', 'admin']; if (!votingRoles.includes(data.role)) { return { canVote: false, reason: 'Members cannot vote, must be contributor+' }; } return { canVote: true, role: data.role, member: attestation.subject }; } ``` *** ## Issuer Workflow Issue and manage DAO memberships. ### 1. Register Schema (One-time) ```typescript theme={null} async function registerMembershipSchema(client: StellarAttestationClient, signer: any) { const result = await client.createSchema({ definition: 'struct DAOMembership { string daoId; string daoName; string role; u64 joinedAt; bool active; }', revocable: true, // Critical for membership management options: { signer } }); return result.schemaUid.toString('hex'); } ``` ### 2. Issue Membership ```typescript theme={null} async function issueMembership( client: StellarAttestationClient, schemaUid: string, memberAddress: string, dao: { id: string; name: string }, role: 'member' | 'contributor' | 'core' | 'admin', signer: any ) { const encoder = new SorobanSchemaEncoder(MEMBERSHIP_SCHEMA); const payload = await encoder.encodeData({ daoId: dao.id, daoName: dao.name, role, joinedAt: Date.now(), active: true }); const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject: memberAddress, value: payload.encodedData, options: { signer } }); return { membershipUid: result.attestationUid?.toString('hex'), txHash: result.hash }; } ``` ### 3. Revoke Membership ```typescript theme={null} async function revokeMembership( client: StellarAttestationClient, membershipUid: string, signer: any ) { const result = await client.revoke({ attestationUid: Buffer.from(membershipUid, 'hex'), options: { signer } }); return { txHash: result.hash }; } ``` ### 4. Upgrade Role Issue a new attestation with the upgraded role (old one remains but new takes precedence). ```typescript theme={null} async function upgradeRole( client: StellarAttestationClient, schemaUid: string, memberAddress: string, dao: { id: string; name: string }, newRole: 'contributor' | 'core' | 'admin', originalJoinDate: number, signer: any ) { const encoder = new SorobanSchemaEncoder(MEMBERSHIP_SCHEMA); const payload = await encoder.encodeData({ daoId: dao.id, daoName: dao.name, role: newRole, joinedAt: originalJoinDate, // Preserve original join date active: true }); const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject: memberAddress, value: payload.encodedData, options: { signer } }); return { newMembershipUid: result.attestationUid?.toString('hex'), txHash: result.hash }; } ``` ### Complete Issuer Example ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; const MEMBERSHIP_SCHEMA_UID = 'ghi789...'; async function onMemberJoin( memberAddress: string, role: 'member' | 'contributor' | 'core' | 'admin' = 'member' ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GDAO_ADMIN...' }); const encoder = new SorobanSchemaEncoder({ name: 'DAOMembership', fields: [ { name: 'daoId', type: 'string' }, { name: 'daoName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'joinedAt', type: 'u64' }, { name: 'active', type: 'bool' } ] }); const payload = await encoder.encodeData({ daoId: 'stellar-community', daoName: 'Stellar Community DAO', role, joinedAt: Date.now(), active: true }); const result = await client.attest({ schemaUid: Buffer.from(MEMBERSHIP_SCHEMA_UID, 'hex'), subject: memberAddress, value: payload.encodedData, options: { signer } }); console.log('Membership issued:', result.attestationUid?.toString('hex')); } ``` *** ## Next Steps Identity attestations Scale issuance with BLS delegation # Event Attendance Source: https://docs.attest.so/examples/event-attendance Verify attendance and issue event badges ## Overview Event attendance attestations prove someone attended a specific event (conference, hackathon, workshop). This guide covers verifying attendance and issuing badges. ## Schema ```typescript theme={null} const EVENT_SCHEMA = { name: 'EventAttendance', fields: [ { name: 'eventId', type: 'string' }, { name: 'eventName', type: 'string' }, { name: 'role', type: 'string' }, // "attendee", "speaker", "sponsor" { name: 'timestamp', type: 'u64' } ] }; ``` *** ## Verifier Workflow Check if a user attended a specific event or has any attendance history. ### 1. Verify Event Attendance ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const EVENT_SCHEMA = { name: 'EventAttendance', fields: [ { name: 'eventId', type: 'string' }, { name: 'eventName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }; async function verifyAttendance(attestationUid: string, expectedEventId?: string) { // Fetch attestation const attestation = await getAttestationByUid(attestationUid); if (!attestation) { return { attended: false, reason: 'Attestation not found' }; } if (attestation.revoked) { return { attended: false, reason: 'Attendance revoked' }; } // Decode data const encoder = new SorobanSchemaEncoder(EVENT_SCHEMA); const data = await encoder.decodeData(attestation.value); // Optionally verify specific event if (expectedEventId && data.eventId !== expectedEventId) { return { attended: false, reason: 'Wrong event' }; } return { attended: true, eventId: data.eventId, eventName: data.eventName, role: data.role, attendee: attestation.subject, issuer: attestation.attester }; } ``` ### 2. Get Attendance History ```typescript theme={null} async function getAttendanceHistory( userAddress: string, eventSchemaUid: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: userAddress }); const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: userAddress, limit: 100 }); // Filter by event schema const eventAttestations = attestations.filter( a => a.schemaUid.toString('hex') === eventSchemaUid && !a.revoked ); // Decode each const encoder = new SorobanSchemaEncoder(EVENT_SCHEMA); const history = await Promise.all( eventAttestations.map(async (a) => { const data = await encoder.decodeData(a.value); return { uid: a.uid.toString('hex'), ...data, issuer: a.attester }; }) ); return history; } ``` ### 3. Verify by Event Organizer ```typescript theme={null} const TRUSTED_ORGANIZERS: Record = { 'stellar-meridian-2024': ['GORGANIZER1...'], 'soroban-hackathon': ['GORGANIZER2...', 'GORGANIZER3...'] }; async function verifyOfficialAttendance( attestationUid: string, eventId: string ) { const result = await verifyAttendance(attestationUid, eventId); if (!result.attended) { return result; } const trustedIssuers = TRUSTED_ORGANIZERS[eventId]; if (!trustedIssuers?.includes(result.issuer)) { return { attended: false, reason: 'Not issued by official organizer' }; } return result; } ``` ### 4. Check Speaker/VIP Status ```typescript theme={null} type EventRole = 'attendee' | 'speaker' | 'sponsor' | 'organizer'; async function hasRole( userAddress: string, eventSchemaUid: string, eventId: string, requiredRole: EventRole ): Promise { const history = await getAttendanceHistory(userAddress, eventSchemaUid); return history.some( event => event.eventId === eventId && event.role === requiredRole ); } // Usage: Check if user was a speaker const wasSpeaker = await hasRole(userAddress, schemaUid, 'meridian-2024', 'speaker'); ``` ### Complete Verifier Example ```typescript theme={null} import { SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const EVENT_SCHEMA = { name: 'EventAttendance', fields: [ { name: 'eventId', type: 'string' }, { name: 'eventName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }; // Gate access based on event attendance async function canAccessAlumniChannel(attestationUid: string) { const attestation = await getAttestationByUid(attestationUid); if (!attestation || attestation.revoked) { return { allowed: false, reason: 'Invalid attendance proof' }; } const encoder = new SorobanSchemaEncoder(EVENT_SCHEMA); const data = await encoder.decodeData(attestation.value); // Only allow speakers and organizers if (!['speaker', 'organizer'].includes(data.role)) { return { allowed: false, reason: 'Speakers and organizers only' }; } return { allowed: true, eventName: data.eventName, role: data.role }; } ``` *** ## Issuer Workflow Issue attendance badges after event check-in. ### 1. Register Schema (One-time) ```typescript theme={null} async function registerEventSchema(client: StellarAttestationClient, signer: any) { const result = await client.createSchema({ definition: 'struct EventAttendance { string eventId; string eventName; string role; u64 timestamp; }', revocable: true, // Allow revoking fraudulent claims options: { signer } }); return result.schemaUid.toString('hex'); } ``` ### 2. Issue Attendance Badge ```typescript theme={null} async function issueAttendanceBadge( client: StellarAttestationClient, schemaUid: string, attendeeAddress: string, event: { id: string; name: string }, role: 'attendee' | 'speaker' | 'sponsor' | 'organizer', signer: any ) { const encoder = new SorobanSchemaEncoder(EVENT_SCHEMA); const payload = await encoder.encodeData({ eventId: event.id, eventName: event.name, role, timestamp: Date.now() }); const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject: attendeeAddress, value: payload.encodedData, options: { signer } }); return { badgeUid: result.attestationUid?.toString('hex'), txHash: result.hash }; } ``` ### 3. Batch Issue (Event Check-in) ```typescript theme={null} async function batchIssueAttendance( client: StellarAttestationClient, schemaUid: string, event: { id: string; name: string }, attendees: Array<{ address: string; role: string }>, signer: any ) { const encoder = new SorobanSchemaEncoder(EVENT_SCHEMA); const results = []; for (const attendee of attendees) { const payload = await encoder.encodeData({ eventId: event.id, eventName: event.name, role: attendee.role, timestamp: Date.now() }); try { const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject: attendee.address, value: payload.encodedData, options: { signer } }); results.push({ address: attendee.address, success: true, badgeUid: result.attestationUid?.toString('hex') }); } catch (error) { results.push({ address: attendee.address, success: false, error: error.message }); } } return results; } ``` ### Complete Issuer Example ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; const EVENT_SCHEMA_UID = 'def456...'; // Your registered schema async function onCheckIn( attendeeAddress: string, role: 'attendee' | 'speaker' | 'sponsor' | 'organizer' ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GORGANIZER...' }); const encoder = new SorobanSchemaEncoder({ name: 'EventAttendance', fields: [ { name: 'eventId', type: 'string' }, { name: 'eventName', type: 'string' }, { name: 'role', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }); const payload = await encoder.encodeData({ eventId: 'meridian-2024', eventName: 'Stellar Meridian 2024', role, timestamp: Date.now() }); const result = await client.attest({ schemaUid: Buffer.from(EVENT_SCHEMA_UID, 'hex'), subject: attendeeAddress, value: payload.encodedData, options: { signer } }); console.log('Badge issued:', result.attestationUid?.toString('hex')); } ``` *** ## Next Steps Membership verification and roles Identity attestations # Identity Source: https://docs.attest.so/examples/identity Verify identity claims and issue identity attestations ## Overview Identity attestations link on-chain addresses to verified real-world or digital identities. This guide covers verifying identity claims and issuing attestations. ## Schema ```typescript theme={null} const IDENTITY_SCHEMA = { name: 'Identity', fields: [ { name: 'type', type: 'string' }, // "twitter", "github", "email", "domain" { name: 'identifier', type: 'string' }, // @handle, email, domain { name: 'verifiedAt', type: 'u64' }, { name: 'proofUrl', type: 'string' } // Link to verification proof ] }; ``` *** ## Verifier Workflow Verify that an address owns a specific identity (social account, email, domain). ### 1. Verify Identity Claim ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const IDENTITY_SCHEMA = { name: 'Identity', fields: [ { name: 'type', type: 'string' }, { name: 'identifier', type: 'string' }, { name: 'verifiedAt', type: 'u64' }, { name: 'proofUrl', type: 'string' } ] }; async function verifyIdentity(attestationUid: string) { const attestation = await getAttestationByUid(attestationUid); if (!attestation) { return { verified: false, reason: 'Identity attestation not found' }; } if (attestation.revoked) { return { verified: false, reason: 'Identity attestation revoked' }; } const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); const data = await encoder.decodeData(attestation.value); return { verified: true, type: data.type, identifier: data.identifier, owner: attestation.subject, verifiedAt: new Date(data.verifiedAt), proofUrl: data.proofUrl, issuer: attestation.attester }; } ``` ### 2. Check Specific Identity Type ```typescript theme={null} type IdentityType = 'twitter' | 'github' | 'email' | 'domain' | 'discord'; async function hasIdentity( userAddress: string, identitySchemaUid: string, identityType: IdentityType ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: userAddress }); const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: userAddress, limit: 100 }); const identityAttestations = attestations.filter( a => a.schemaUid.toString('hex') === identitySchemaUid && !a.revoked ); const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); for (const attestation of identityAttestations) { const data = await encoder.decodeData(attestation.value); if (data.type === identityType) { return { found: true, identifier: data.identifier, attestationUid: attestation.uid.toString('hex') }; } } return { found: false }; } ``` ### 3. Get All Linked Identities ```typescript theme={null} async function getLinkedIdentities( userAddress: string, identitySchemaUid: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: userAddress }); const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: userAddress, limit: 100 }); const identityAttestations = attestations.filter( a => a.schemaUid.toString('hex') === identitySchemaUid && !a.revoked ); const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); const identities = await Promise.all( identityAttestations.map(async (a) => { const data = await encoder.decodeData(a.value); return { uid: a.uid.toString('hex'), type: data.type, identifier: data.identifier, verifiedAt: new Date(data.verifiedAt), proofUrl: data.proofUrl }; }) ); return identities; } ``` ### 4. Verify Trusted Issuer ```typescript theme={null} const TRUSTED_IDENTITY_PROVIDERS: Record = { twitter: ['GTWITTER_VERIFIER...'], github: ['GGITHUB_VERIFIER...'], email: ['GEMAIL_VERIFIER1...', 'GEMAIL_VERIFIER2...'], domain: ['GDOMAIN_VERIFIER...'] }; async function verifyTrustedIdentity(attestationUid: string) { const result = await verifyIdentity(attestationUid); if (!result.verified) { return result; } const trustedIssuers = TRUSTED_IDENTITY_PROVIDERS[result.type]; if (!trustedIssuers?.includes(result.issuer)) { return { verified: false, reason: 'Untrusted identity provider' }; } return result; } ``` ### 5. Reverse Lookup (Identity to Address) ```typescript theme={null} async function findAddressByIdentity( identityType: string, identifier: string, schemaUid: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GQUERY...' // Any valid address for querying }); // Fetch recent attestations for this schema const attestations = await client.fetchAttestations(100); const matching = attestations.filter( a => a.schemaUid.toString('hex') === schemaUid && !a.revoked ); const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); for (const attestation of matching) { const data = await encoder.decodeData(attestation.value); if (data.type === identityType && data.identifier === identifier) { return { found: true, address: attestation.subject, attestationUid: attestation.uid.toString('hex') }; } } return { found: false }; } ``` ### Complete Verifier Example ```typescript theme={null} import { SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const IDENTITY_SCHEMA = { name: 'Identity', fields: [ { name: 'type', type: 'string' }, { name: 'identifier', type: 'string' }, { name: 'verifiedAt', type: 'u64' }, { name: 'proofUrl', type: 'string' } ] }; const TRUSTED_PROVIDERS = ['GPROVIDER1...', 'GPROVIDER2...']; // Gate access to verified users only async function canAccessPremiumFeatures(attestationUid: string) { const attestation = await getAttestationByUid(attestationUid); if (!attestation || attestation.revoked) { return { allowed: false, reason: 'No valid identity' }; } if (!TRUSTED_PROVIDERS.includes(attestation.attester)) { return { allowed: false, reason: 'Untrusted identity provider' }; } const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); const data = await encoder.decodeData(attestation.value); // Require Twitter or GitHub verification if (!['twitter', 'github'].includes(data.type)) { return { allowed: false, reason: 'Twitter or GitHub identity required' }; } return { allowed: true, identityType: data.type, identifier: data.identifier }; } ``` *** ## Issuer Workflow Issue identity attestations after verification. ### 1. Register Schema (One-time) ```typescript theme={null} async function registerIdentitySchema(client: StellarAttestationClient, signer: any) { const result = await client.createSchema({ definition: 'struct Identity { string type; string identifier; u64 verifiedAt; string proofUrl; }', revocable: true, options: { signer } }); return result.schemaUid.toString('hex'); } ``` ### 2. Issue Identity Attestation ```typescript theme={null} async function issueIdentity( client: StellarAttestationClient, schemaUid: string, userAddress: string, identity: { type: 'twitter' | 'github' | 'email' | 'domain' | 'discord'; identifier: string; proofUrl: string; }, signer: any ) { const encoder = new SorobanSchemaEncoder(IDENTITY_SCHEMA); const payload = await encoder.encodeData({ type: identity.type, identifier: identity.identifier, verifiedAt: Date.now(), proofUrl: identity.proofUrl }); const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject: userAddress, value: payload.encodedData, options: { signer } }); return { identityUid: result.attestationUid?.toString('hex'), txHash: result.hash }; } ``` ### 3. Twitter Verification Flow ```typescript theme={null} // 1. User posts tweet with their Stellar address // 2. Your backend verifies the tweet exists and contains the address // 3. Issue attestation async function verifyTwitterAndAttest( client: StellarAttestationClient, schemaUid: string, userAddress: string, twitterHandle: string, tweetUrl: string, signer: any ) { // Verify tweet exists and contains address (your backend logic) const tweetValid = await verifyTweetContainsAddress(tweetUrl, userAddress); if (!tweetValid) { throw new Error('Tweet verification failed'); } return issueIdentity(client, schemaUid, userAddress, { type: 'twitter', identifier: twitterHandle, proofUrl: tweetUrl }, signer); } ``` ### 4. GitHub Verification Flow ```typescript theme={null} // 1. User creates a gist with their Stellar address // 2. Your backend verifies the gist exists // 3. Issue attestation async function verifyGitHubAndAttest( client: StellarAttestationClient, schemaUid: string, userAddress: string, githubUsername: string, gistUrl: string, signer: any ) { // Verify gist exists and contains address (your backend logic) const gistValid = await verifyGistContainsAddress(gistUrl, userAddress); if (!gistValid) { throw new Error('Gist verification failed'); } return issueIdentity(client, schemaUid, userAddress, { type: 'github', identifier: githubUsername, proofUrl: gistUrl }, signer); } ``` ### Complete Issuer Example ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; const IDENTITY_SCHEMA_UID = 'jkl012...'; async function onIdentityVerified( userAddress: string, identityType: 'twitter' | 'github' | 'email' | 'domain', identifier: string, proofUrl: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GVERIFIER...' }); const encoder = new SorobanSchemaEncoder({ name: 'Identity', fields: [ { name: 'type', type: 'string' }, { name: 'identifier', type: 'string' }, { name: 'verifiedAt', type: 'u64' }, { name: 'proofUrl', type: 'string' } ] }); const payload = await encoder.encodeData({ type: identityType, identifier, verifiedAt: Date.now(), proofUrl }); const result = await client.attest({ schemaUid: Buffer.from(IDENTITY_SCHEMA_UID, 'hex'), subject: userAddress, value: payload.encodedData, options: { signer } }); console.log('Identity attested:', result.attestationUid?.toString('hex')); } ``` *** ## Next Steps Build verification UI Scale identity issuance # KYC Verification Source: https://docs.attest.so/examples/kyc-verification Verify KYC status and issue KYC attestations ## Overview KYC (Know Your Customer) attestations prove a user has completed identity verification. This guide covers verifying existing KYC attestations and issuing new ones. ## Schema ```typescript theme={null} const KYC_SCHEMA = { name: 'KYC', fields: [ { name: 'verified', type: 'bool' }, { name: 'level', type: 'string' }, // "basic", "enhanced", "premium" { name: 'provider', type: 'string' }, // "plaid", "jumio", etc. { name: 'timestamp', type: 'u64' } ] }; ``` *** ## Verifier Workflow Check if a user has valid KYC before granting access. ### 1. Fetch Attestations by Subject ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder, getSchemaByUid } from '@attestprotocol/stellar-sdk'; async function getKYCStatus(subjectAddress: string, schemaUid: string) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: subjectAddress }); // Fetch attestations for this subject const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: subjectAddress, limit: 100 }); // Filter by KYC schema const kycAttestations = attestations.filter( a => a.schemaUid.toString('hex') === schemaUid ); return kycAttestations; } ``` ### 2. Validate Attestation ```typescript theme={null} async function validateKYC(attestation: any): Promise<{ isValid: boolean; reason?: string; data?: any; }> { // Check revocation if (attestation.revoked) { return { isValid: false, reason: 'KYC has been revoked' }; } // Check expiration if (attestation.expirationTime) { const expiry = new Date(attestation.expirationTime); if (expiry < new Date()) { return { isValid: false, reason: 'KYC has expired' }; } } // Decode and verify data const encoder = new SorobanSchemaEncoder(KYC_SCHEMA); const data = await encoder.decodeData(attestation.value); if (!data.verified) { return { isValid: false, reason: 'KYC verification failed' }; } return { isValid: true, data }; } ``` ### 3. Check KYC Level ```typescript theme={null} type KYCLevel = 'basic' | 'enhanced' | 'premium'; function meetsKYCRequirement( data: { level: string }, requiredLevel: KYCLevel ): boolean { const levels: Record = { basic: 1, enhanced: 2, premium: 3 }; return levels[data.level as KYCLevel] >= levels[requiredLevel]; } // Usage const { data } = await validateKYC(attestation); if (meetsKYCRequirement(data, 'enhanced')) { // Grant access to enhanced features } ``` ### 4. Verify Attester Trust ```typescript theme={null} const TRUSTED_KYC_PROVIDERS = [ 'GKYC_PROVIDER_1...', 'GKYC_PROVIDER_2...' ]; function isTrustedProvider(attesterAddress: string): boolean { return TRUSTED_KYC_PROVIDERS.includes(attesterAddress); } // Full verification async function verifyKYC(subjectAddress: string, schemaUid: string) { const attestations = await getKYCStatus(subjectAddress, schemaUid); for (const attestation of attestations) { // Check if from trusted provider if (!isTrustedProvider(attestation.attester)) { continue; } const result = await validateKYC(attestation); if (result.isValid) { return { verified: true, level: result.data.level, provider: result.data.provider, attestationUid: attestation.uid.toString('hex') }; } } return { verified: false }; } ``` ### Complete Verifier Example ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder, getAttestationByUid } from '@attestprotocol/stellar-sdk'; const KYC_SCHEMA = { name: 'KYC', fields: [ { name: 'verified', type: 'bool' }, { name: 'level', type: 'string' }, { name: 'provider', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }; const TRUSTED_PROVIDERS = ['GPROVIDER1...', 'GPROVIDER2...']; async function checkUserKYC(attestationUid: string) { // 1. Fetch attestation const attestation = await getAttestationByUid(attestationUid); if (!attestation) { return { allowed: false, reason: 'No KYC attestation found' }; } // 2. Verify attester is trusted if (!TRUSTED_PROVIDERS.includes(attestation.attester)) { return { allowed: false, reason: 'Untrusted KYC provider' }; } // 3. Check not revoked if (attestation.revoked) { return { allowed: false, reason: 'KYC revoked' }; } // 4. Check not expired if (attestation.expirationTime && attestation.expirationTime < Date.now()) { return { allowed: false, reason: 'KYC expired' }; } // 5. Decode and validate data const encoder = new SorobanSchemaEncoder(KYC_SCHEMA); const data = await encoder.decodeData(attestation.value); if (!data.verified) { return { allowed: false, reason: 'KYC not verified' }; } return { allowed: true, level: data.level, provider: data.provider, subject: attestation.subject }; } ``` *** ## Issuer Workflow Issue KYC attestations after completing verification. ### 1. Register Schema (One-time) ```typescript theme={null} async function registerKYCSchema(client: StellarAttestationClient, signer: any) { const result = await client.createSchema({ definition: 'struct KYC { bool verified; string level; string provider; u64 timestamp; }', revocable: true, options: { signer } }); return result.schemaUid.toString('hex'); } ``` ### 2. Issue KYC Attestation ```typescript theme={null} async function issueKYC( client: StellarAttestationClient, schemaUid: string, subject: string, level: 'basic' | 'enhanced' | 'premium', provider: string, signer: any, expiresInDays: number = 365 ) { const encoder = new SorobanSchemaEncoder(KYC_SCHEMA); const payload = await encoder.encodeData({ verified: true, level, provider, timestamp: Date.now() }); const expirationTime = Math.floor(Date.now() / 1000) + (expiresInDays * 86400); const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), subject, value: payload.encodedData, expirationTime, options: { signer } }); return { attestationUid: result.attestationUid?.toString('hex'), txHash: result.hash }; } ``` ### 3. Revoke KYC ```typescript theme={null} async function revokeKYC( client: StellarAttestationClient, attestationUid: string, signer: any ) { const result = await client.revoke({ attestationUid: Buffer.from(attestationUid, 'hex'), options: { signer } }); return { txHash: result.hash }; } ``` ### Complete Issuer Example ```typescript theme={null} import { StellarAttestationClient, SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; const KYC_SCHEMA_UID = 'abc123...'; // Your registered schema async function onKYCComplete( userAddress: string, verificationLevel: 'basic' | 'enhanced' | 'premium', providerName: string ) { const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: 'GISSUER...' }); const encoder = new SorobanSchemaEncoder({ name: 'KYC', fields: [ { name: 'verified', type: 'bool' }, { name: 'level', type: 'string' }, { name: 'provider', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }); const payload = await encoder.encodeData({ verified: true, level: verificationLevel, provider: providerName, timestamp: Date.now() }); const result = await client.attest({ schemaUid: Buffer.from(KYC_SCHEMA_UID, 'hex'), subject: userAddress, value: payload.encodedData, expirationTime: Math.floor(Date.now() / 1000) + (365 * 86400), // 1 year options: { signer } }); console.log('KYC attestation issued:', result.attestationUid?.toString('hex')); } ``` *** ## Next Steps Attendance verification and badges Complete API documentation # React Integration Source: https://docs.attest.so/examples/react-integration Set up AttestProtocol SDK in your React application ## Overview This guide shows how to integrate the Stellar SDK into a React application with wallet connection and attestation verification. ## Installation ```bash theme={null} npm install @attestprotocol/stellar-sdk @stellar/stellar-sdk ``` ## SDK Context Create a context to share the SDK client across your app. ```typescript theme={null} // contexts/AttestContext.tsx import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; interface AttestContextType { client: StellarAttestationClient | null; isReady: boolean; error: string | null; } const AttestContext = createContext(undefined); interface Props { children: ReactNode; publicKey: string | null; network?: 'testnet' | 'mainnet'; } export function AttestProvider({ children, publicKey, network = 'testnet' }: Props) { const [client, setClient] = useState(null); const [error, setError] = useState(null); useEffect(() => { if (!publicKey) { setClient(null); return; } try { const rpcUrl = network === 'mainnet' ? 'https://soroban.stellar.org' : 'https://soroban-testnet.stellar.org'; const newClient = new StellarAttestationClient({ rpcUrl, network, publicKey, }); setClient(newClient); setError(null); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to initialize SDK'); setClient(null); } }, [publicKey, network]); return ( {children} ); } export function useAttest() { const context = useContext(AttestContext); if (!context) { throw new Error('useAttest must be used within AttestProvider'); } return context; } ``` ## App Setup Wrap your app with the provider. ```tsx theme={null} // App.tsx import { AttestProvider } from './contexts/AttestContext'; import { useWallet } from './hooks/useWallet'; // Your wallet hook function App() { const { publicKey } = useWallet(); return ( ); } ``` ## Verification Hook Create a hook for verifying attestations. ```typescript theme={null} // hooks/useVerifyAttestation.ts import { useState, useCallback } from 'react'; import { useAttest } from '../contexts/AttestContext'; import { getAttestationByUid, SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; interface VerificationResult { isValid: boolean; data: Record | null; error: string | null; attestation: any | null; } export function useVerifyAttestation() { const { client } = useAttest(); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const verify = useCallback(async ( attestationUid: string, schemaDefinition: { name: string; fields: Array<{ name: string; type: string }> } ) => { if (!client) { setResult({ isValid: false, data: null, error: 'SDK not initialized', attestation: null }); return; } setLoading(true); try { // Fetch attestation const attestation = await getAttestationByUid(attestationUid); if (!attestation) { setResult({ isValid: false, data: null, error: 'Attestation not found', attestation: null }); return; } // Check revocation if (attestation.revoked) { setResult({ isValid: false, data: null, error: 'Attestation revoked', attestation }); return; } // Check expiration if (attestation.expirationTime && attestation.expirationTime < Date.now()) { setResult({ isValid: false, data: null, error: 'Attestation expired', attestation }); return; } // Decode data const encoder = new SorobanSchemaEncoder(schemaDefinition); const decoded = await encoder.decodeData(attestation.value); setResult({ isValid: true, data: decoded, error: null, attestation }); } catch (err) { setResult({ isValid: false, data: null, error: err instanceof Error ? err.message : 'Verification failed', attestation: null }); } finally { setLoading(false); } }, [client]); return { verify, loading, result }; } ``` ## Verification Component ```tsx theme={null} // components/AttestationVerifier.tsx import { useState } from 'react'; import { useVerifyAttestation } from '../hooks/useVerifyAttestation'; const KYC_SCHEMA = { name: 'KYC', fields: [ { name: 'verified', type: 'bool' }, { name: 'level', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }; export function AttestationVerifier() { const [uid, setUid] = useState(''); const { verify, loading, result } = useVerifyAttestation(); const handleVerify = () => { verify(uid, KYC_SCHEMA); }; return (
setUid(e.target.value)} placeholder="Attestation UID" /> {result && (
{result.isValid ? (

Valid attestation

{JSON.stringify(result.data, null, 2)}
) : (

Invalid: {result.error}

)}
)}
); } ``` ## Wallet Signer Create a signer from your wallet kit. ```typescript theme={null} // utils/createSigner.ts import { TransactionSigner } from '@attestprotocol/stellar-sdk'; export function createSigner(walletKit: any): TransactionSigner { return { signTransaction: async (xdr: string) => { const { signedTxXdr } = await walletKit.signTransaction(xdr); return signedTxXdr; } }; } ``` ## Issuing Attestations Hook for creating attestations (issuer workflow). ```typescript theme={null} // hooks/useCreateAttestation.ts import { useState, useCallback } from 'react'; import { useAttest } from '../contexts/AttestContext'; import { SorobanSchemaEncoder, getSchemaByUid } from '@attestprotocol/stellar-sdk'; import { createSigner } from '../utils/createSigner'; export function useCreateAttestation() { const { client } = useAttest(); const [loading, setLoading] = useState(false); const create = useCallback(async ( schemaUid: string, data: Record, subject: string, walletKit: any ) => { if (!client) throw new Error('SDK not initialized'); setLoading(true); try { // Fetch schema const schema = await getSchemaByUid(schemaUid); if (!schema) throw new Error('Schema not found'); const definition = JSON.parse(schema.definition); // Encode data const encoder = new SorobanSchemaEncoder(definition); const payload = await encoder.encodeData(data); // Create attestation const result = await client.attest({ schemaUid: Buffer.from(schemaUid, 'hex'), value: payload.encodedData, subject, options: { signer: createSigner(walletKit) } }); return { txHash: result.hash, attestationUid: result.attestationUid?.toString('hex') }; } finally { setLoading(false); } }, [client]); return { create, loading }; } ``` ## Next Steps Verify and issue KYC attestations Check and issue attendance badges # Introduction Source: https://docs.attest.so/introduction Add proof to anything, on-chain without writing a line of code ## Whenever you need to prove something onchain — *identity, action, ownership, or permission* — you don't need to write a new smart contract or spin up a ZK system. Attest provides a proof infrastructure to build anything from **network states, DAOs, DePIN, and RWA**. With our **Unified Trust Framework,** developers on Stellar can integrate reputation and trust mechanisms in their dApps. ## Add proof to anything, on-chain without writing a line of code. AttestProtocol is a Soroban-based framework that lets you attach attestations to any wallet, asset, or event. Whether that's *proof of identity, contribution, credential, or ownership.* No contracts, no infrastructure, no ZK black magic required. Think of it like SSL for blockchains — a lightweight trust signal you can apply anywhere. ## An Open Standard for Trust Using our **Attestation Service** you get an open standard for verifying *statements, transactions, and authorities* — identity proofs **on the blockchain**. Our **Soroban framework** enhances the reliability of digital interactions on the Stellar Network with a composable blockchain-based **Attestation Service.** ## Use Cases Issue "user is verified" attestations that any dApp can check Prove someone attended a conference, hackathon, or workshop Verify membership status and roles across applications Issue certifications that users can share and verifiers can trust ## Who is this for? ### Attestation Issuers KYC providers, event organizers, DAOs, and certification bodies who want to: * Issue verifiable credentials on-chain * Enable other apps to verify your attestations * Build reputation without siloed databases ### Verifiers Applications that need to: * Check if a user has valid KYC from any trusted provider * Gate access based on credentials without vendor lock-in * Query attestations from multiple issuers in one place ### Developers Teams building: * Reputation systems using aggregated attestation data * Credential verification into existing dApps * Multi-source identity checks ## Key Features | Feature | Description | | -------------------- | ------------------------------------------------------------------ | | **Authorities** | Register as a trusted issuer and create custom attestation schemas | | **Resolvers** | Define rules about who can attest — fees, permissions, rewards | | **BLS Delegation** | Sign attestations off-chain, submit on-chain. Submitter pays gas. | | **Flexible Schemas** | Define custom attestation structures for any use case | | **Multi-Chain** | Stellar (production) + Solana (beta) | | **Revocable** | Invalidate attestations when credentials expire or are revoked | ## Contract Addresses | Network | Version | Protocol Contract | | ------- | ------------ | ----------------- | | Testnet | v2 (current) | | | Testnet | v1 (legacy) | | | Mainnet | v2 (current) | | | Mainnet | v1 (legacy) | | Programmatic access: `getContractId(network, version?)` from `@attestprotocol/stellar-sdk`, or `GET /api/contracts` on horizon. ## Next Steps Create your first attestation in 5 minutes Register as a trusted attestation issuer Define custom validation rules for attestations Understand the architecture # Quickstart Source: https://docs.attest.so/quickstart Create your first attestation in 5 minutes ## Install ```bash theme={null} npm install @attestprotocol/stellar-sdk @stellar/stellar-sdk ``` ## Initialize Client ```typescript theme={null} import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; import { Keypair, Networks, Transaction } from '@stellar/stellar-sdk'; // Your Stellar keypair const keypair = Keypair.fromSecret('SXXXX...'); // Create signer const signer = { signTransaction: async (xdr: string) => { const tx = new Transaction(xdr, Networks.TESTNET); tx.sign(keypair); return tx.toXDR(); } }; // Initialize client const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: keypair.publicKey() }); ``` ## Create Schema ```typescript theme={null} const schemaResult = await client.createSchema({ definition: 'struct KYC { bool verified; string level; }', revocable: true, options: { signer } }); ``` ## Create Attestation ```typescript theme={null} // Generate schema UID const schemaUid = client.generateSchemaUid({ definition: 'struct KYC { bool verified; string level; }', authority: keypair.publicKey() }); // Attest await client.attest({ schemaUid, value: JSON.stringify({ verified: true, level: 'basic' }), options: { signer } }); ``` ## Verify Attestation ```typescript theme={null} const attestation = await client.getAttestation(attestationUid); console.log(attestation.result); ``` ## Next Steps Understand the architecture Complete walkthrough with all features # Getting Started with Stellar Source: https://docs.attest.so/stellar/getting-started Complete guide to using AttestProtocol on Stellar ## Prerequisites * Node.js 18+ * Stellar testnet account with XLM ([Friendbot](https://friendbot.stellar.org)) * Basic TypeScript knowledge ## Installation ```bash theme={null} npm install @attestprotocol/stellar-sdk @stellar/stellar-sdk ``` ## Client Setup ```typescript theme={null} import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; import { Keypair, Networks, Transaction } from '@stellar/stellar-sdk'; const keypair = Keypair.fromSecret(process.env.STELLAR_SECRET_KEY!); const signer = { signTransaction: async (xdr: string) => { const tx = new Transaction(xdr, Networks.TESTNET); tx.sign(keypair); return tx.toXDR(); } }; const client = new StellarAttestationClient({ rpcUrl: 'https://soroban-testnet.stellar.org', network: 'testnet', publicKey: keypair.publicKey() }); ``` ## 1. Create a Schema Schemas define the structure of your attestations. ```typescript theme={null} const result = await client.createSchema({ definition: 'struct Identity { string name; u32 age; bool verified; }', revocable: true, options: { signer } }); // Get schema UID for later use const schemaUid = client.generateSchemaUid({ definition: 'struct Identity { string name; u32 age; bool verified; }', authority: keypair.publicKey() }); ``` **Schema Definition Syntax:** * `string` - Text values * `bool` - True/false * `u32`, `u64`, `i32`, `i64` - Integers * `bytes` - Binary data ## 2. Create an Attestation ```typescript theme={null} await client.attest({ schemaUid, value: JSON.stringify({ name: 'Alice', age: 30, verified: true }), expirationTime: Date.now() + 365 * 24 * 60 * 60 * 1000, // 1 year options: { signer } }); ``` ## 3. Query Attestations ```typescript theme={null} // By UID const attestation = await client.getAttestation(attestationUid); // By wallet const { attestations } = await client.fetchAttestationsByWallet({ walletAddress: 'GXXX...', limit: 50 }); // Latest attestations const latest = await client.fetchAttestations(100); ``` ## 4. Revoke an Attestation Only works if schema has `revocable: true`. ```typescript theme={null} await client.revoke({ attestationUid, options: { signer } }); ``` ## BLS Delegation (Gas-less) Allow third parties to submit attestations on your behalf. ```typescript theme={null} // Generate BLS keys const blsKeys = client.generateBlsKeys(); // Register public key on-chain await client.registerBlsKey(Buffer.from(blsKeys.publicKey), { signer }); // Create delegated attestation (submitter pays gas) await client.attestByDelegation({ attester: keypair.publicKey(), schema_uid: schemaUid, subject: 'GSUBJECT...', value: JSON.stringify({ verified: true }), nonce: BigInt(1), deadline: BigInt(Date.now() + 3600000), expiration_time: undefined, signature: signatureBuffer }, { signer: submitterSigner }); ``` ## Error Handling ```typescript theme={null} import { ContractError, NetworkError } from '@attestprotocol/stellar-sdk'; try { await client.attest({ ... }); } catch (error) { if (error instanceof ContractError) { console.log('Contract error:', error.message); } else if (error instanceof NetworkError) { console.log('Network error:', error.message); } } ``` ## Contract Addresses | Network | Version | Protocol Contract | | ------- | ------------ | ----------------- | | Testnet | v2 (current) | | | Testnet | v1 (legacy) | | | Mainnet | v2 (current) | | | Mainnet | v1 (legacy) | | Programmatic access: `getContractId(network, version?)` from `@attestprotocol/stellar-sdk`, or `GET /api/contracts` on horizon. ## Next Steps Best practices for schema design Complete method reference # Stellar SDK Reference Source: https://docs.attest.so/stellar/reference Complete API reference for @attestprotocol/stellar-sdk ## Installation ```bash theme={null} npm install @attestprotocol/stellar-sdk @stellar/stellar-sdk ``` ## Contract Addresses | Network | Version | Protocol Contract | | ------- | ------------ | ----------------- | | Testnet | v2 (current) | | | Testnet | v1 (legacy) | | | Mainnet | v2 (current) | | | Mainnet | v1 (legacy) | | Programmatic access: `getContractId(network, version?)` from `@attestprotocol/stellar-sdk`, or `GET /api/contracts` on horizon. *** ## StellarAttestationClient Main client for interacting with AttestProtocol on Stellar. ```typescript theme={null} import { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; const client = new StellarAttestationClient({ rpcUrl: string, // Soroban RPC URL network: 'testnet' | 'mainnet', publicKey: string, // Your Stellar public key contractId?: string, // Optional: custom contract ID allowHttp?: boolean // Allow HTTP (dev only) }); ``` *** ## Core Methods ### createSchema Register a new schema on-chain. ```typescript theme={null} await client.createSchema({ definition: string, // Schema definition: "name:string,verified:bool" revocable?: boolean, // Default: true resolver?: string, // Optional resolver contract address options?: { signer, simulate } }) ``` **Returns:** Transaction result with schema UID ### attest Create a new attestation. ```typescript theme={null} await client.attest({ schemaUid: Buffer, // 32-byte schema identifier value: string, // JSON-encoded attestation data subject?: string, // Who the attestation is about (defaults to caller) expirationTime?: number, // Unix timestamp when attestation expires options?: { signer, simulate } }) ``` **Returns:** Transaction result with attestation UID ### revoke Revoke an existing attestation. ```typescript theme={null} await client.revoke({ attestationUid: Buffer, // 32-byte attestation UID options?: { signer, simulate } }) ``` **Returns:** Transaction result ### getSchema Retrieve a schema by UID from the blockchain. ```typescript theme={null} const schema = await client.getSchema(uid: Buffer) ``` **Returns:** Schema object or null ### getAttestation Retrieve an attestation by UID from the blockchain. ```typescript theme={null} const attestation = await client.getAttestation(uid: Buffer) ``` **Returns:** Attestation object or null *** ## Query Methods ### fetchSchemas Fetch latest schemas from the registry. ```typescript theme={null} const schemas = await client.fetchSchemas(limit?: number) // Max 100 ``` ### fetchAttestations Fetch latest attestations from the registry. ```typescript theme={null} const attestations = await client.fetchAttestations(limit?: number) // Max 100 ``` ### fetchSchemasByWallet Fetch schemas created by a specific wallet. ```typescript theme={null} const { schemas, total, hasMore } = await client.fetchSchemasByWallet({ walletAddress: string, limit?: number }) ``` ### fetchAttestationsByWallet Fetch attestations created by a specific wallet. ```typescript theme={null} const { attestations, total, hasMore } = await client.fetchAttestationsByWallet({ walletAddress: string, limit?: number }) ``` ### getSchemasByLedger Fetch schemas from a specific ledger number. ```typescript theme={null} const schemas = await client.getSchemasByLedger(ledger: number, limit?: number) ``` ### getAttestationsByLedger Fetch attestations from a specific ledger number. ```typescript theme={null} const attestations = await client.getAttestationsByLedger(ledger: number, limit?: number) ``` *** ## Schema Encoding ### SorobanSchemaEncoder Encode and decode attestation data matching a schema definition. ```typescript theme={null} import { SorobanSchemaEncoder } from '@attestprotocol/stellar-sdk'; const encoder = new SorobanSchemaEncoder({ name: 'KYCVerification', fields: [ { name: 'verified', type: 'bool' }, { name: 'level', type: 'string' }, { name: 'timestamp', type: 'u64' } ] }); // Encode data for attestation const payload = await encoder.encodeData({ verified: true, level: 'premium', timestamp: Date.now() }); // Decode attestation value const decoded = await encoder.decodeData(attestation.value); ``` ### encodeSchema Encode schema to XDR format for blockchain storage. ```typescript theme={null} const xdrString = client.encodeSchema({ name: 'TestSchema', fields: [...] }) ``` ### decodeSchema Decode XDR-encoded schema back to JavaScript object. ```typescript theme={null} const schema = client.decodeSchema('XDR:AAAAB...') ``` *** ## BLS Delegation Delegated attestations allow authorities to sign off-chain while delegates submit on-chain. ### generateBlsKeys Generate a new BLS key pair. ```typescript theme={null} const { publicKey, privateKey } = client.generateBlsKeys() // publicKey: 192 bytes (uncompressed) // privateKey: 32 bytes ``` ### registerBlsKey Register a BLS public key on-chain. ```typescript theme={null} await client.registerBlsKey(publicKey: Buffer, options?: { signer }) ``` ### getBlsKey Get the registered BLS key for an attester. ```typescript theme={null} const blsKey = await client.getBlsKey(attester?: string) ``` ### attestByDelegation Create an attestation using a delegated BLS signature. ```typescript theme={null} await client.attestByDelegation({ attester: string, schema_uid: Buffer, subject: string, value: string, nonce: bigint, deadline: bigint, expiration_time: bigint | undefined, signature: Buffer }, options?: { signer }) ``` ### revokeByDelegation Revoke an attestation using a delegated BLS signature. ```typescript theme={null} await client.revokeByDelegation({ attestation_uid: Buffer, schema_uid: Buffer, subject: string, revoker: string, nonce: bigint, deadline: bigint, signature: Buffer }, options?: { signer }) ``` ### Helper Functions ```typescript theme={null} import { createDelegatedAttestationRequest, createDelegatedRevocationRequest, createAttestMessage, createRevokeMessage, signHashedMessage, verifySignature } from '@attestprotocol/stellar-sdk'; // Create complete delegated attestation request const request = await createDelegatedAttestationRequest({ schemaUid: Buffer.from('abc...', 'hex'), subject: 'GSUBJECT...', data: JSON.stringify({ verified: true }) }, blsPrivateKey, client.getClientInstance()); // Sign a message const dst = await client.getAttestDST(); const messagePoint = createAttestMessage(request, dst); const signature = signHashedMessage(messagePoint, privateKey); // Verify signature const result = verifySignature({ signature: signatureBuffer, expectedMessage: messagePoint, publicKey: publicKeyBuffer }); ``` *** ## UID Generation ### generateSchemaUid Generate deterministic schema UID. ```typescript theme={null} const uid = client.generateSchemaUid({ definition: string, authority: string, resolver?: string }) ``` ### generateAttestationUid Generate deterministic attestation UID. ```typescript theme={null} const uid = client.generateAttestationUid({ schemaUid: Buffer, subject: string, nonce: bigint }) ``` *** ## Indexer API Standalone functions for querying the registry API. ```typescript theme={null} import { getSchemaByUid, getAttestationByUid, fetchRegistryDump } from '@attestprotocol/stellar-sdk'; // Fetch schema by UID const schema = await getSchemaByUid('abc123...', true, 'testnet'); // Fetch attestation by UID const attestation = await getAttestationByUid('def456...', 'testnet'); // Fetch complete registry dump const dump = await fetchRegistryDump('testnet'); ``` *** ## Utility Methods ### submitTransaction Submit a signed transaction to the network. ```typescript theme={null} const result = await client.submitTransaction(signedXdr: string, { skipSimulation?: boolean }) ``` ### getClientInstance Get underlying protocol client for advanced usage. ```typescript theme={null} const protocolClient = client.getClientInstance() ``` ### getServerInstance Get underlying RPC server instance. ```typescript theme={null} const server = client.getServerInstance() ``` *** ## Types ### ClientOptions ```typescript theme={null} interface ClientOptions { rpcUrl: string; network: 'testnet' | 'mainnet'; publicKey: string; contractId?: string; networkPassphrase?: string; allowHttp?: boolean; } ``` ### TxOptions ```typescript theme={null} interface TxOptions { signer?: TransactionSigner; simulate?: boolean; timeoutInSeconds?: number; } ``` ### TransactionSigner ```typescript theme={null} interface TransactionSigner { signTransaction(xdr: string): Promise; } ``` ### ContractSchema ```typescript theme={null} interface ContractSchema { uid: Buffer; definition: string; authority: string; resolver: string; revocable: boolean; timestamp: number; } ``` ### ContractAttestation ```typescript theme={null} interface ContractAttestation { uid: Buffer; schemaUid: Buffer; subject: string; attester: string; value: any; timestamp: number; expirationTime?: number; revocationTime?: number; revoked: boolean; } ``` ### BlsKeyPair ```typescript theme={null} interface BlsKeyPair { privateKey: Buffer; // 32 bytes publicKey: Buffer; // 192 bytes uncompressed } ``` *** ## Exports ```typescript theme={null} // Main client export { StellarAttestationClient } from '@attestprotocol/stellar-sdk'; // Service classes export { StellarSchemaRegistry, AttestProtocolAuthority }; // Schema encoding export { SorobanSchemaEncoder, encodeSchema, decodeSchema, createSimpleSchema }; // UID generation export { generateAttestationUid, generateSchemaUid }; // BLS delegation export { generateBlsKeys, signHashedMessage, verifySignature }; export { createDelegatedAttestationRequest, createDelegatedRevocationRequest }; export { createAttestMessage, createRevokeMessage }; // Indexer API export { getSchemaByUid, getAttestationByUid, fetchRegistryDump }; // Error classes export { ContractError, NetworkError, ConfigurationError }; ``` # Schema Design Source: https://docs.attest.so/stellar/schemas Creating and using schemas on Stellar ## What is a Schema? A schema defines the structure of attestation data. All attestations reference a schema. ## Creating a Schema ```typescript theme={null} await client.createSchema({ definition: 'struct KYC { bool verified; string level; u64 timestamp; }', revocable: true, resolver: undefined, // Optional: validation contract options: { signer } }); ``` ## Schema Syntax ``` struct Name { type field; type field; ... } ``` ### Supported Types | Type | Description | Example | | -------- | --------------- | --------------- | | `bool` | Boolean | `true`, `false` | | `string` | Text | `"hello"` | | `u32` | Unsigned 32-bit | `42` | | `u64` | Unsigned 64-bit | `1234567890` | | `i32` | Signed 32-bit | `-42` | | `i64` | Signed 64-bit | `-1234567890` | | `bytes` | Binary data | `0x1234...` | ## Example Schemas ### KYC Verification ``` struct KYC { bool verified; string level; string provider; u64 expiry; } ``` ### Professional Credential ``` struct Credential { string title; string issuer; u64 issuedAt; u64 expiresAt; } ``` ### Reputation Score ``` struct Reputation { u32 score; string category; u64 updatedAt; } ``` ## Generating Schema UID Schema UIDs are deterministic based on definition + authority: ```typescript theme={null} const schemaUid = client.generateSchemaUid({ definition: 'struct KYC { bool verified; string level; }', authority: keypair.publicKey(), resolver: undefined }); ``` ## Querying Schemas ```typescript theme={null} // By UID const schema = await client.getSchema(schemaUid); // By wallet (schemas you created) const { schemas } = await client.fetchSchemasByWallet({ walletAddress: keypair.publicKey(), limit: 50 }); // Latest schemas const latest = await client.fetchSchemas(100); ``` ## Best Practices 1. **Keep it simple** - Only include necessary fields 2. **Use appropriate types** - `u64` for timestamps, `bool` for flags 3. **Consider revocability** - Set `revocable: true` for credentials that may need invalidation 4. **Version schemas** - Include version in name: `struct KYC_v2 { ... }`