Skip to content

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.

Before using the client library, ensure you have a running Quidnug node:

Terminal window
# Local development
SEED_NODES='[]' ./quidnug-node
# The node will be available at http://localhost:8080

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" }

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 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.

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

Quidnug supports three core transaction types:

  1. Trust Transactions: Establish trust between quids
  2. Identity Transactions: Define attributes for quids
  3. Title Transactions: Establish ownership relationships between quids

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 quid
const 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 });

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 domains
const propertyNodes = await quidnugClient.findNodesForDomain('real-estate.travis-county.texas.us');

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 transaction
const txResult = await quidnugClient.submitTransaction(trustTx);
console.log("Transaction ID:", txResult.data?.transaction_id);
// Create an identity transaction
const 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 transaction
const 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);

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 identity
const identity = await quidnugClient.getIdentity('quid_id', 'example.com');
console.log("Identity:", identity);
// Get asset ownership
const ownership = await quidnugClient.getAssetOwnership('asset_quid_id', 'example.com');
console.log("Owners:", ownership.ownershipMap);

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/client JavaScript SDK also provides createEventTransaction(), getEventStream(), and getStreamEvents(). For the Go client (pkg/client), use c.EmitEvent, c.GetEventStream, c.GetStreamEvents.

Record events against a subject (quid or title) via the REST API:

// Record an event for a quid
const 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 signature and publicKey fields)
  • publicKey: Hex-encoded public key of the signing quid

The system auto-assigns a monotonically increasing sequence number if not provided.

Retrieve stream metadata and paginated events:

// Get stream metadata
const 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);
}

For payloads exceeding 64KB, use IPFS to store content externally:

// Pin large content to IPFS
const 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 payload
await 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 CID
const 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.

Audit Trail: Record all changes to identity attributes for compliance:

// Record identity attribute changes
await 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 event
await 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 display
const activity = await fetch(
`http://localhost:8080/api/streams/${userQuid.id}/events?domain=social.example.com&limit=50&offset=0`
).then(r => r.json());
// Render timeline
for (const event of activity.data) {
renderActivityItem({
type: event.eventType,
timestamp: event.timestamp,
data: event.payload
});
}

Trust in Quidnug is relational, not absolute. This is a fundamental design principle:

  1. 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.

  2. 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
  3. Best Path Selection: When multiple paths exist between observer and target, the algorithm returns the path with the maximum trust level.

  4. Depth Limiting: The maxDepth parameter (default 5) limits how far the algorithm searches. Deeper paths have more decay and are less likely to yield high trust.

  5. Special Cases:

    • Same entity: An observer has full trust (1.0) in itself
    • No path: Returns trust level 0.0
// Your application's quid is the observer
const myQuidId = 'a1b2c3d4e5f6g7h8';
// Query trust in a potential business partner
const 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 scenarios
const result = await quidnugClient.queryRelationalTrust({
observer: myQuidId,
target: 'partner_quid_id',
domain: 'business.example.com',
maxDepth: 5
});

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. verifySignature does not exist on QuidnugClient (v1 or v2). To verify a challenge signature, retrieve the public key via getIdentity(quidId) and call crypto.subtle.verify (Web Crypto API) directly. The function below describes the intended interface.

// Authenticate a user with their quid
async 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 };
}

For applications that need to verify credentials. Trust in the issuer is computed relationally from your service’s perspective:

// Verify a credential
async 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 };
}

For applications that track asset ownership:

// Transfer asset ownership
async 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);
}
  1. Private Key Management: Never expose private keys. Use secure storage solutions.
  2. Trust Verification: Always verify trust paths before relying on them.
  3. Multiple Node Connections: Connect to multiple nodes for redundancy.
  4. Signature Verification: Always verify signatures on received data.
  1. Caching: Cache frequently accessed trust relationships and identities.
  2. Batching: Combine multiple related transactions where possible.
  3. Trust Path Optimization: Limit trust path depth for time-sensitive operations.
  1. Trust Bridge Pattern: Create bridge quids between different trust domains.
  2. Delegate Pattern: Allow users to delegate trust to specialized quids.
  3. Trust Threshold Pattern: Require multiple trusted quids to validate important actions.

Not yet in SDK. requireSignatures and requiredSignatureCount are not fields in CreateTitleTransactionParams, 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 requirement
const 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);

Not yet in SDK. createGovernanceProposal and voteOnProposal are not methods on QuidnugClient (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 proposal
const 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 proposal
await quidnugClient.voteOnProposal(proposal.id, true, userQuid);
  1. Decentralized Identity Systems: Self-sovereign identity with verifiable credentials
  2. Supply Chain Tracking: Trace asset provenance through multiple owners
  3. Professional Credential Verification: Verify licenses and certifications
  4. Decentralized Governance: Voting systems based on trust relationships
  5. Access Control Systems: Permission management based on trust levels
  6. Resource Allocation: Distribute resources based on trust scores
  7. Reputation Systems: Build context-specific reputation metrics

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.

Not yet in SDK. includeUnverified is not forwarded to the node by getTrustLevel or queryRelationalTrust, only maxDepth is sent. The response fields confidence, unverifiedHops, and verificationGaps are not in the current TrustResult. 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 levels
switch (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;
}
ScenarioincludeUnverifiedRationale
AuthenticationfalseHigh-stakes decision needs verified edges only
Large transaction approvalfalseFinancial risk requires high assurance
Discovery / explorationtrueFinding potential connections is lower risk
Displaying trust contexttrueUsers benefit from seeing all connections
Initial onboardingtrueNew users have few verified connections
// High-stakes: authentication - verified edges only
async 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 unverified
async 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;
}

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 domain
const 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}`);
}

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 API

Different nodes may have different views of the blockchain based on their trust relationships. This is by design, not a bug.

  1. Don’t assume global consensus: Your view of “valid” blocks depends on your trust relationships

  2. Design for eventual consistency: Transactions may appear at different times on different nodes

  3. Use trust paths for verification: When displaying data, show users the trust path so they understand the provenance

// Display trust context to users
function 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('')}`;
}
}
  1. 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
}

For audit purposes, you can retrieve full provenance for trust edges:

// Get all trust edges for a quid with provenance
const 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()}`);
}
  1. Start with verified-only queries, expand to unverified when needed
  2. Always show users the trust path, not just the trust score
  3. Design UX for “no path found” scenarios
  4. Cache trust results but with short TTLs (trust relationships change)
  5. Monitor tentative blocks for domains you care about
  6. Establish trust proactively with validators in your ecosystem