# Stellar Attestation Contract
Source: https://docs.attest.so/chains/stellar/overview
AttestProtocol Stellar contract implementation using Soroban smart contracts with native Rust architecture
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
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 { ... }`