Integration guide
Quidnug Integration Guide
Section titled “Quidnug Integration Guide”This guide serves as a comprehensive resource for developers looking to build applications on top of the Quidnug platform. Quidnug provides a cryptographic identity, trust, and ownership infrastructure that can be leveraged by various applications.
Connecting to a Node
Section titled “Connecting to a Node”Before using the client library, ensure you have a running Quidnug node:
# Local developmentSEED_NODES='[]' ./quidnug-node
# The node will be available at http://localhost:8080Node Health Check
Section titled “Node Health Check”Always verify node connectivity before operations:
const response = await fetch('http://localhost:8080/api/health');const health = await response.json();// { status: "ok", node_id: "...", uptime: 123, version: "1.0.0" }Core Concepts
Section titled “Core Concepts”A quid is a cryptographic identity (similar to a Bitcoin wallet) with:
- A public/private key pair
- A unique identifier derived from the public key
- The ability to sign transactions and messages
- A reputation based on trust from other quids
Every entity in the Quidnug system (people, organizations, assets, documents) is represented as a quid.
Trust Relationships
Section titled “Trust Relationships”Trust is established between quids with these key characteristics:
- Relational, not absolute: Trust is always computed from an observer’s perspective to a target. There is no global “trust score” for any quid-the same quid may have different trust levels when viewed by different observers.
- Explicit trust levels (0.0 to 1.0) define direct relationships
- Domain-specific (e.g.,
medical.credentials,property.texas) - Can have expiration dates
- Transitive with multiplicative decay: Trust propagates through the network. If A trusts B at 0.8 and B trusts C at 0.7, then A’s transitive trust in C is 0.8 × 0.7 = 0.56.
Trust Domains
Section titled “Trust Domains”Domains organize trust hierarchically:
- Similar to DNS (e.g.,
real-estate.travis-county.texas.us) - Each domain has its own validation rules
- Independent authority at each level
- Specialized functionality for different applications
Transactions
Section titled “Transactions”Quidnug supports three core transaction types:
- Trust Transactions: Establish trust between quids
- Identity Transactions: Define attributes for quids
- Title Transactions: Establish ownership relationships between quids
Getting Started with Client Development
Section titled “Getting Started with Client Development”1. Creating a Quid
Section titled “1. Creating a Quid”To interact with Quidnug, your application needs to create or import quids:
// Example in JavaScript (v2 SDK)const quidnugClient = new QuidnugClient({ defaultNode: 'http://localhost:8080' });
// Generate a new quidconst userQuid = await quidnugClient.generateQuid();
// Save the quid securely (NEVER share the private key)localStorage.setItem('userQuidPublicKey', userQuid.publicKey);localStorage.setItem('userQuidPrivateKey', userQuid.privateKey);console.log("Your Quid ID:", userQuid.id);
// Import an existing quid (both private and public keys required)const importedQuid = await quidnugClient.importQuid({ privateKey, publicKey });2. Connecting to Quidnug Nodes
Section titled “2. Connecting to Quidnug Nodes”Your application needs to connect to one or more Quidnug nodes:
// The defaultNode is set in the constructor; use addNode() for additional peers.quidnugClient.addNode('https://quidnug-node2.example.com');quidnugClient.addNode('https://quidnug-node3.example.com');
// Find nodes for specific domainsconst propertyNodes = await quidnugClient.findNodesForDomain('real-estate.travis-county.texas.us');3. Submitting Transactions
Section titled “3. Submitting Transactions”Creating and submitting transactions:
// Create and sign a trust transaction; userQuid becomes the truster.const trustTx = await quidnugClient.createTrustTransaction({ trustee: 'quid_id_to_trust', // Target quid to trust domain: 'example.com', // Trust domain trustLevel: 0.9, // Trust level (0.0 to 1.0) validUntil: 1672531200 // Optional expiration (Unix timestamp in seconds)}, userQuid);
// Submit the signed transactionconst txResult = await quidnugClient.submitTransaction(trustTx);console.log("Transaction ID:", txResult.data?.transaction_id);
// Create an identity transactionconst identityTx = await quidnugClient.createIdentityTransaction({ subjectQuid: 'target_quid_id', domain: 'example.com', name: 'Example Entity', attributes: { type: 'organization', location: 'Austin, TX', website: 'https://example.com' }}, userQuid);
// Create a title transactionconst titleTx = await quidnugClient.createTitleTransaction({ assetQuid: 'asset_quid_id', domain: 'real-estate.travis-county.texas.us', ownershipMap: [ { ownerId: 'owner1_quid_id', percentage: 75.0 }, { ownerId: 'owner2_quid_id', percentage: 25.0 } ]}, userQuid);4. Querying the Quidnug Network
Section titled “4. Querying the Quidnug Network”Retrieving information:
// Get relational trust from observer to target// Trust is always computed from YOUR perspective (the observer)const result = await quidnugClient.getTrustLevel( observerQuidId, // Your quid (who is asking) targetQuidId, // The quid you want to assess 'example.com', { maxDepth: 5 });console.log("Trust level:", result.trustLevel);console.log("Trust path:", result.trustPath);console.log("Path depth:", result.pathDepth);
// Get quid identityconst identity = await quidnugClient.getIdentity('quid_id', 'example.com');console.log("Identity:", identity);
// Get asset ownershipconst ownership = await quidnugClient.getAssetOwnership('asset_quid_id', 'example.com');console.log("Owners:", ownership.ownershipMap);5. Event Streaming
Section titled “5. Event Streaming”Event streams provide an immutable, append-only history for any quid or title. Each event is sequenced and cannot be modified once recorded, making them ideal for audit logs, activity feeds, and state change tracking.
Note: The examples in this section call the REST API directly via
fetch(). The@quidnug/clientJavaScript SDK also providescreateEventTransaction(),getEventStream(), andgetStreamEvents(). For the Go client (pkg/client), usec.EmitEvent,c.GetEventStream,c.GetStreamEvents.
Creating Event Transactions
Section titled “Creating Event Transactions”Record events against a subject (quid or title) via the REST API:
// Record an event for a quidconst result = await fetch('http://localhost:8080/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subjectId: 'target_quid_id', subjectType: 'QUID', eventType: 'profile.updated', trustDomain: 'example.com', payload: { field: 'name', oldValue: 'Alice', newValue: 'Alice Chen' }, signature: '<ECDSA-P256 signature over canonical bytes, see docs/openapi.yaml>', publicKey: userQuid.publicKey })}).then(r => r.json());console.log('Event sequence:', result.sequence);Key request body fields:
- subjectId: The quid or asset ID this event is recorded against
- subjectType: Either
'QUID'or'TITLE' - eventType: A string describing the event (max 64 characters)
- trustDomain: The trust domain for this event
- payload: Event data (max 64KB for inline payloads)
- signature: ECDSA-P256 signature over canonical bytes (excludes
signatureandpublicKeyfields) - publicKey: Hex-encoded public key of the signing quid
The system auto-assigns a monotonically increasing sequence number if not provided.
Querying Event Streams
Section titled “Querying Event Streams”Retrieve stream metadata and paginated events:
// Get stream metadataconst stream = await fetch( `http://localhost:8080/api/streams/${quidId}?domain=example.com`).then(r => r.json());console.log('Total events:', stream.eventCount);console.log('Latest sequence:', stream.latestSequence);console.log('Stream created:', new Date(stream.createdAt * 1000));console.log('Last updated:', new Date(stream.updatedAt * 1000));
// Get paginated events (ordered by sequence ascending)const events = await fetch( `http://localhost:8080/api/streams/${quidId}/events?domain=example.com&limit=20&offset=0`).then(r => r.json());
for (const event of events.data) { console.log(`[${event.sequence}] ${event.eventType}:`, event.payload);}IPFS Integration for Large Payloads
Section titled “IPFS Integration for Large Payloads”For payloads exceeding 64KB, use IPFS to store content externally:
// Pin large content to IPFSconst largeDocument = JSON.stringify(detailedAuditReport);const pinResult = await fetch('http://localhost:8080/api/ipfs/pin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: largeDocument}).then(r => r.json());const cid = pinResult.cid;console.log('Content pinned with CID:', cid);
// Create event with IPFS reference instead of inline payloadawait fetch('http://localhost:8080/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subjectId: assetQuidId, subjectType: 'TITLE', eventType: 'document.attached', trustDomain: 'property.example.com', payloadCid: cid, // Reference to IPFS content signature: '<ECDSA-P256 signature over canonical bytes, see docs/openapi.yaml>', publicKey: userQuid.publicKey })}).then(r => r.json());
// Later, retrieve the content by CIDconst content = await fetch(`http://localhost:8080/api/ipfs/${cid}`).then(r => r.text());const report = JSON.parse(content);Note: Either payload or payloadCid must be provided, but not both empty.
Event Streaming Use Cases
Section titled “Event Streaming Use Cases”Audit Trail: Record all changes to identity attributes for compliance:
// Record identity attribute changesawait fetch('http://localhost:8080/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subjectId: userQuid.id, subjectType: 'QUID', eventType: 'identity.attribute.changed', trustDomain: 'compliance.example.com', payload: { attribute: 'email', previousValue: 'alice@old.com', newValue: 'alice@new.com', changedBy: adminQuid.id, reason: 'User request' }, signature: '<ECDSA-P256 signature over canonical bytes, see docs/openapi.yaml>', publicKey: adminQuid.publicKey })}).then(r => r.json());Asset History: Track ownership transfers and modifications:
// Record ownership transfer eventawait fetch('http://localhost:8080/api/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subjectId: propertyAssetId, subjectType: 'TITLE', eventType: 'ownership.transferred', trustDomain: 'property.example.com', payload: { fromOwner: sellerQuid.id, toOwner: buyerQuid.id, percentage: 100.0, titleTxId: transferTx.txId }, signature: '<ECDSA-P256 signature over canonical bytes, see docs/openapi.yaml>', publicKey: notaryQuid.publicKey })}).then(r => r.json());Activity Feed: Build a timeline of actions for a quid:
// Fetch recent activity for displayconst activity = await fetch( `http://localhost:8080/api/streams/${userQuid.id}/events?domain=social.example.com&limit=50&offset=0`).then(r => r.json());
// Render timelinefor (const event of activity.data) { renderActivityItem({ type: event.eventType, timestamp: event.timestamp, data: event.payload });}Understanding Relational Trust
Section titled “Understanding Relational Trust”Trust in Quidnug is relational, not absolute. This is a fundamental design principle:
Key Concepts
Section titled “Key Concepts”-
Observer Perspective: Every trust query requires an observer (who is asking) and a target (who is being assessed). The same target quid may have different trust levels when queried by different observers.
-
Multiplicative Decay: Trust decays as it propagates through intermediaries:
A → B (0.8) → C (0.7) → D (0.9)A's trust in D = 0.8 × 0.7 × 0.9 = 0.504 -
Best Path Selection: When multiple paths exist between observer and target, the algorithm returns the path with the maximum trust level.
-
Depth Limiting: The
maxDepthparameter (default 5) limits how far the algorithm searches. Deeper paths have more decay and are less likely to yield high trust. -
Special Cases:
- Same entity: An observer has full trust (1.0) in itself
- No path: Returns trust level 0.0
Example: Computing Relational Trust
Section titled “Example: Computing Relational Trust”// Your application's quid is the observerconst myQuidId = 'a1b2c3d4e5f6g7h8';
// Query trust in a potential business partnerconst partnerTrust = await quidnugClient.getTrustLevel( myQuidId, // Observer: your perspective 'partner_quid_id', // Target: who you're assessing 'business.example.com', { maxDepth: 4 });
if (partnerTrust.trustLevel >= 0.7) { console.log("Partner is trusted via:", partnerTrust.trustPath); // e.g., ["a1b2c3d4", "colleague_quid", "partner_quid_id"]} else if (partnerTrust.trustLevel > 0) { console.log("Partner has low trust:", partnerTrust.trustLevel);} else { console.log("No trust path found to partner");}
// Alternative: POST query for more complex scenariosconst result = await quidnugClient.queryRelationalTrust({ observer: myQuidId, target: 'partner_quid_id', domain: 'business.example.com', maxDepth: 5});Building Applications with Quidnug
Section titled “Building Applications with Quidnug”Authentication & Authorization
Section titled “Authentication & Authorization”Quidnug can serve as an identity and authorization system. Note that trust is always relational-your service assesses trust from its own perspective:
Not yet in SDK.
verifySignaturedoes not exist onQuidnugClient(v1 or v2). To verify a challenge signature, retrieve the public key viagetIdentity(quidId)and callcrypto.subtle.verify(Web Crypto API) directly. The function below describes the intended interface.
// Authenticate a user with their quidasync function authenticateUser(quidId, challenge, signature) { // Verify the user signed the challenge with their quid's private key const isValid = await quidnugClient.verifySignature(quidId, challenge, signature);
if (isValid) { // Check if YOUR SERVICE trusts the user's quid // The observer is your service, the target is the user const trustResult = await quidnugClient.getTrustLevel( 'your_service_quid_id', // Observer: your service's perspective quidId, // Target: the user being authenticated 'your-service.com', { maxDepth: 5 } );
if (trustResult.trustLevel >= 0.7) { return { authenticated: true, trustLevel: trustResult.trustLevel, trustPath: trustResult.trustPath // Shows how trust was established }; } }
return { authenticated: false };}Credential Verification
Section titled “Credential Verification”For applications that need to verify credentials. Trust in the issuer is computed relationally from your service’s perspective:
// Verify a credentialasync function verifyCredential(credentialQuidId, issuerQuidId, domain) { // Check if the credential exists and is defined by the expected issuer const credential = await quidnugClient.getIdentity(credentialQuidId, domain);
if (credential && credential.definerQuid === issuerQuidId) { // Check if YOUR SERVICE trusts the issuer // Different services may have different trust levels for the same issuer const trustResult = await quidnugClient.getTrustLevel( 'your_service_quid_id', // Observer: your service issuerQuidId, // Target: the credential issuer domain, { maxDepth: 4 } );
if (trustResult.trustLevel >= 0.8) { return { verified: true, credential, issuerTrust: trustResult.trustLevel, trustPath: trustResult.trustPath }; } }
return { verified: false };}Asset Tracking
Section titled “Asset Tracking”For applications that track asset ownership:
// Transfer asset ownershipasync function transferAssetOwnership(assetQuidId, newOwners, domain, userQuid) { // Get current ownership const currentTitle = await quidnugClient.getAssetOwnership(assetQuidId, domain);
// Create a new title transaction const titleTx = await quidnugClient.createTitleTransaction({ assetQuid: assetQuidId, domain: domain, ownershipMap: newOwners, prevTitleTxID: currentTitle.txId }, userQuid);
// Sign and submit return await quidnugClient.submitTransaction(titleTx);}Best Practices
Section titled “Best Practices”Security Considerations
Section titled “Security Considerations”- Private Key Management: Never expose private keys. Use secure storage solutions.
- Trust Verification: Always verify trust paths before relying on them.
- Multiple Node Connections: Connect to multiple nodes for redundancy.
- Signature Verification: Always verify signatures on received data.
Performance Optimization
Section titled “Performance Optimization”- Caching: Cache frequently accessed trust relationships and identities.
- Batching: Combine multiple related transactions where possible.
- Trust Path Optimization: Limit trust path depth for time-sensitive operations.
Integration Patterns
Section titled “Integration Patterns”- Trust Bridge Pattern: Create bridge quids between different trust domains.
- Delegate Pattern: Allow users to delegate trust to specialized quids.
- Trust Threshold Pattern: Require multiple trusted quids to validate important actions.
Advanced Features
Section titled “Advanced Features”Multi-signature Capabilities
Section titled “Multi-signature Capabilities”Not yet in SDK.
requireSignaturesandrequiredSignatureCountare not fields inCreateTitleTransactionParams, the SDK ignores them silently. Multi-signature title enforcement is not in the current OpenAPI spec or@quidnug/client(v1 or v2). Human review needed before this example is relied on.
For high-security applications:
// Create a multi-signature requirementconst multiSigTitle = await quidnugClient.createTitleTransaction({ assetQuid: 'high_value_asset', domain: 'security.example.com', ownershipMap: [{ ownerId: 'owner_quid', percentage: 100.0 }], requireSignatures: ['trustee1', 'trustee2', 'trustee3'], requiredSignatureCount: 2 // At least 2 of 3 must sign}, userQuid);Trust Domain Governance
Section titled “Trust Domain Governance”Not yet in SDK.
createGovernanceProposalandvoteOnProposalare not methods onQuidnugClient(v1 or v2). Domain governance HTTP endpoints are pending (QDP-0012 Phase 2). The example below describes the intended interface, not current functionality.
For managing domain rules:
// Create a governance proposalconst proposal = await quidnugClient.createGovernanceProposal({ domain: 'example.com', proposalType: 'UPDATE_TRUST_THRESHOLD', changes: { trustThreshold: 0.8 }, votingDeadline: Date.now() + (7 * 24 * 60 * 60 * 1000) // 1 week from now});
// Vote on a proposalawait quidnugClient.voteOnProposal(proposal.id, true, userQuid);Examples of Quidnug Applications
Section titled “Examples of Quidnug Applications”- Decentralized Identity Systems: Self-sovereign identity with verifiable credentials
- Supply Chain Tracking: Trace asset provenance through multiple owners
- Professional Credential Verification: Verify licenses and certifications
- Decentralized Governance: Voting systems based on trust relationships
- Access Control Systems: Permission management based on trust levels
- Resource Allocation: Distribute resources based on trust scores
- Reputation Systems: Build context-specific reputation metrics
Working with Proof of Trust
Section titled “Working with Proof of Trust”Quidnug uses Proof of Trust consensus, where block validation is subjective-each node validates blocks based on its own relational trust in the validator. This has important implications for application design.
Understanding Confidence Levels
Section titled “Understanding Confidence Levels”Not yet in SDK.
includeUnverifiedis not forwarded to the node bygetTrustLevelorqueryRelationalTrust, onlymaxDepthis sent. The response fieldsconfidence,unverifiedHops, andverificationGapsare not in the currentTrustResult. The examples below describe the intended interface.
When querying trust with includeUnverified=true, the response includes a confidence level:
const result = await quidnugClient.queryRelationalTrust({ observer: myQuidId, target: partnerQuidId, domain: 'business.example.com', includeUnverified: true // Get enhanced result with confidence});
console.log("Trust level:", result.trustLevel);console.log("Confidence:", result.confidence);console.log("Unverified hops:", result.unverifiedHops);
// Handle different confidence levelsswitch (result.confidence) { case 'high': // All edges verified - safe for high-stakes decisions allowFullAccess(); break; case 'medium': // Some unverified edges - use caution allowLimitedAccess(); break; case 'low': // Significant unverified data - require additional verification requestAdditionalVerification(); break;}When to Use includeUnverified
Section titled “When to Use includeUnverified”| Scenario | includeUnverified | Rationale |
|---|---|---|
| Authentication | false | High-stakes decision needs verified edges only |
| Large transaction approval | false | Financial risk requires high assurance |
| Discovery / exploration | true | Finding potential connections is lower risk |
| Displaying trust context | true | Users benefit from seeing all connections |
| Initial onboarding | true | New users have few verified connections |
// High-stakes: authentication - verified edges onlyasync function authenticateUser(userQuidId) { const trust = await quidnugClient.getTrustLevel( serviceQuidId, userQuidId, 'auth.example.com', { includeUnverified: false } // Strict verification ); return trust.trustLevel >= 0.8;}
// Discovery: finding potential partners - include unverifiedasync function discoverPartners(category) { const candidates = await searchByCategory(category);
for (const candidate of candidates) { const trust = await quidnugClient.queryRelationalTrust({ observer: myQuidId, target: candidate.quidId, includeUnverified: true // Cast a wider net });
candidate.trustLevel = trust.trustLevel; candidate.confidence = trust.confidence; candidate.verificationGaps = trust.verificationGaps; }
return candidates;}Working with Tentative Blocks
Section titled “Working with Tentative Blocks”Tentative blocks are cryptographically valid blocks from validators your node doesn’t fully trust. They’re stored separately and may be promoted later.
// Get tentative blocks for a domainconst response = await fetch( 'http://localhost:8080/api/blocks/tentative/example.com');const { blocks } = await response.json();
console.log(`${blocks.length} tentative blocks for example.com`);
for (const block of blocks) { console.log(`Block ${block.index} from validator ${block.trustProof.validatorId}`); console.log(` Validator trust: ${block.trustProof.validatorTrustInCreator}`); console.log(` Transactions: ${block.transactions.length}`);}Promoting Tentative Blocks
Section titled “Promoting Tentative Blocks”When you establish trust in a new validator, tentative blocks from that validator may be promoted:
// 1. Establish trust in a validator// submitTransaction requires a fully-signed transaction, use createTrustTransaction first.const newTrustTx = await quidnugClient.createTrustTransaction({ trustee: newValidatorQuid, domain: 'example.com', trustLevel: 0.85}, userQuid); // userQuid from generateQuid() / importQuid()await quidnugClient.submitTransaction(newTrustTx);
// 2. Node will automatically re-evaluate tentative blocks// Or explicitly trigger re-evaluation via node admin APIHandling Subjective Validation
Section titled “Handling Subjective Validation”Different nodes may have different views of the blockchain based on their trust relationships. This is by design, not a bug.
Application Design Implications
Section titled “Application Design Implications”-
Don’t assume global consensus: Your view of “valid” blocks depends on your trust relationships
-
Design for eventual consistency: Transactions may appear at different times on different nodes
-
Use trust paths for verification: When displaying data, show users the trust path so they understand the provenance
// Display trust context to usersfunction displayPartnerInfo(partner, trustResult) { const trustPath = trustResult.trustPath;
if (trustPath.length === 2) { // Direct trust return `You directly trust ${partner.name}`; } else { // Transitive trust - show the chain const intermediaries = trustPath.slice(1, -1); const names = await Promise.all( intermediaries.map(quid => getQuidName(quid)) ); return `You trust ${partner.name} through: ${names.join(' → ')}`; }}- Handle missing trust paths gracefully: If no trust path exists, the entity isn’t necessarily bad-you just don’t have a connection to them
const trust = await quidnugClient.getTrustLevel(myQuid, unknownQuid, domain);
if (trust.trustLevel === 0 && trust.trustPath.length === 0) { // No trust path exists showMessage( "No trust connection found. This entity may be legitimate, " + "but no one in your trust network has vouched for them." );
// Offer options offerDirectVerification(); // Let user establish direct trust offerReferralRequest(); // Ask someone trusted to vouch}Trust Edge Provenance
Section titled “Trust Edge Provenance”For audit purposes, you can retrieve full provenance for trust edges:
// Get all trust edges for a quid with provenanceconst response = await fetch( 'http://localhost:8080/api/trust/edges/a1b2c3d4e5f6g7h8?includeUnverified=true');const { edges, verifiedCount, unverifiedCount } = await response.json();
console.log(`${verifiedCount} verified edges, ${unverifiedCount} unverified`);
for (const edge of edges) { console.log(`${edge.truster} → ${edge.trustee}: ${edge.trustLevel}`); console.log(` Source block: ${edge.sourceBlock}`); console.log(` Validator: ${edge.validatorQuid}`); console.log(` Verified: ${edge.verified}`); console.log(` Recorded: ${new Date(edge.timestamp * 1000).toISOString()}`);}Best Practices for Proof of Trust
Section titled “Best Practices for Proof of Trust”- Start with verified-only queries, expand to unverified when needed
- Always show users the trust path, not just the trust score
- Design UX for “no path found” scenarios
- Cache trust results but with short TTLs (trust relationships change)
- Monitor tentative blocks for domains you care about
- Establish trust proactively with validators in your ecosystem