Aller au contenu principal

Create Triple

[Problem Internal Link] are higher-order structures in the Intuition system, used to define relationships between Atoms. Each Triple consists of three components - Subject, Predicate, and Object - all represented as Atoms, enabling precise, machine-readable representations of data relationships.

Context

The createTriple and batchCreateTriple functions are part of the [Problem Internal Link] contract, which manages the creation and relationships between Atoms through Semantic Triples. When creating Triples, each component must reference existing Atom vault IDs. The Triple structure enables:

  • Many-to-one attestations
  • Positive and negative signaling through triple vaults
  • Flat data structures for optimal scalability

For example, a Triple might represent:

  • Subject: "Bob" (Atom ID: "1")
  • Predicate: "is" (Atom ID: "2")
  • Object: "Trustworthy" (Atom ID: "3")

Step 1: Identify or Create Required Atoms

The createTriple method requires three existing atom IDs:

function createTriple(uint256 subjectId, uint256 predicateId, uint256 objectId) payable returns (uint256)

You'll need to ensure all three atoms exist before creating the triple. If any don't exist, you'll need to create them first using the atom creation process described above.

Example: Find existing atoms using GraphQL API

const API_URL = '<https://dev.base-sepolia.intuition-api.com/v1/graphql>'

async function findAtomByName(name: string): Promise<bigint | null> {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query FindAtom($name: String!) {
atoms(
where: { value: { thing: { name: { _ilike: $name } } } }
limit: 1
order_by: { term: { total_market_cap: desc } }
) {
term_id
value {
thing {
name
description
}
}
}
}
`,
variables: { name },
}),
})

const result = await response.json()
const atoms = result.data.atoms
return atoms.length > 0 ? BigInt(atoms[0].term_id) : null
}

// Usage example
const subjectId = await findAtomByName('Alice')
const predicateId = await findAtomByName('follows')
const objectId = await findAtomByName('Bob')

if (!subjectId || !predicateId || !objectId) {
throw new Error('One or more required atoms do not exist. Create them first.')
}

Example: Get atom ID from a recently created atom

If you just created an atom in a previous transaction, you can use the atom ID directly:

// From a previous atom creation
const subjectId = 12345n // "Alice" atom
const predicateId = 67890n // "follows" atom
const objectId = 54321n // "Bob" atom

Step 2: Get Creation Cost

There is a minimum triple creation cost in our protocol. To retrieve the currently configured value, call getTripleCost() as shown below. It returns a uint256 in wei, which you will need to use as the eth value in your smart contract call. You can also use more than this if you want to increase the initial deposit when creating the triple. The triple creation value goes into the pro-rata vault of the triple.

const tripleCost = await publicClient.readContract({
address: config.multivaultAddress,
abi: MULTIVAULT_ABI,
functionName: 'getTripleCost',
})

Step 3: Check if Triple Already Exists (Optional)

You can check if a triple with the exact same subject, predicate, and object combination already exists using our GraphQL API. The smart contract itself will revert if you try to create a duplicate triple.

async function checkTripleExists(subjectId: bigint, predicateId: bigint, objectId: bigint): Promise<bigint | null> {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query CheckTriple($subjectId: String!, $predicateId: String!, $objectId: String!) {
triples(
where: {
subject_id: { _eq: $subjectId },
predicate_id: { _eq: $predicateId },
object_id: { _eq: $objectId }
}
limit: 1
) {
term_id
subject {
value {
thing {
name
}
}
}
predicate {
value {
thing {
name
}
}
}
object {
value {
thing {
name
}
}
}
}
}
`,
variables: {
subjectId: subjectId.toString(),
predicateId: predicateId.toString(),
objectId: objectId.toString(),
},
}),
})

const result = await response.json()
const triples = result.data.triples
return triples.length > 0 ? BigInt(triples[0].term_id) : null
}

// Usage
const existingTripleId = await checkTripleExists(subjectId, predicateId, objectId)
if (existingTripleId) {
console.log(`Triple already exists with ID: ${existingTripleId}`)
return existingTripleId
}

Step 4: Create the Triple

Once you have confirmed that all three atoms exist and the triple is unique, you can create the triple. Similar to atom creation, you might want your end user to do this (putting the transaction burden and share ownership on them), or you may want to create the triple on your server backend.

Triples are particularly useful for establishing trust relationships, user preferences, content categorization, and other semantic connections in your application. The wallet that creates the triple becomes the initial recipient of any shares minted by the ETH value used in the transaction.

Consider some example triples:

  • [Alice] [follows] [Bob] - Social relationships
  • [Article #123] [has-tag] [Technology] - Content categorization
  • [User X] [is-verified-by] [Your App] - Trust/verification relationships
  • [Product A] [is-similar-to] [Product B] - Recommendation relationships

createTriple

function createTriple(
uint256 subjectId,
uint256 predicateId,
uint256 objectId
) external payable nonReentrant whenNotPaused returns (uint256)

Parameters

  • subjectId: vault ID of the Atom representing the subject
  • predicateId: vault ID of the Atom representing the predicate/relationship
  • objectId: vault ID of the Atom representing the object
  • value : Initial deposit into the Triple’s positive multivault. Must be ≥ to the triple creation cost.
  • Returns: uint256 - Created triple vault ID

Implementation

// useCreateTriple Hook
import { type GetContractReturnType } from 'viem'
import { base } from 'viem/chains'
import { useContractWriteAndWait } from './useContractWriteAndWait'
import { useMultivaultContract } from './useMultivaultContract'

export const useCreateTriple = () => {
const multivault = useMultivaultContract(
base.id
) as GetContractReturnType

return useContractWriteAndWait({
...multivault,
functionName: 'createTriple',
})
}
// Usage Example
const {
writeAsync: writeCreateTriple,
awaitingWalletConfirmation,
awaitingOnChainConfirmation,
} = useCreateTriple()

async function handleCreateTriple(subjectId: string, predicateId: string, objectId: string) {
if (!awaitingOnChainConfirmation && !awaitingWalletConfirmation && writeCreateTriple) {
try {
const tx = await writeCreateTriple({
address: MULTIVAULT_CONTRACT_ADDRESS,
abi: multivaultAbi,
functionName: 'createTriple',
args: [BigInt(subjectId), BigInt(predicateId), BigInt(objectId)],
value: tripleCost, // Must be >= minimum creation cost
})

if (tx?.hash) {
const receipt = await publicClient.waitForTransactionReceipt({
hash: tx.hash,
})
// Handle success
}
} catch (error) {
// Handle error
}
}
}

batchCreateTriple

Similarly, you can create multiple triples in a single transaction using batchCreateTriple. This method takes three arrays (subjects, predicates, objects) of equal length. The cost is the triple creation cost multiplied by the number of triples. Here is a back-end example:

function createTripleBatch(
uint256[] calldata subjectIds,
uint256[] calldata predicateIds,
uint256[] calldata objectIds
) external payable nonReentrant whenNotPaused returns (uint256[] memory)

Parameters

  • subjectIds: vault ID(s) of the Atom representing the subjects
  • predicateIds: vault ID(s) of the Atom representing the predicate/relationships
  • objectIds: vault ID(s) of the Atom representing the objects
  • value : Initial deposit into the triples’ multivaults. Must be ≥ to the atom creation cost * length of triples. This will get distributed evenly across all created triples.
  • Returns uint256[] - Created triples’ vault ID(s)

Implementation

// useBatchCreateTriple Hook
import { type GetContractReturnType } from 'viem'
import { base } from 'viem/chains'
import { useContractWriteAndWait } from './useContractWriteAndWait'
import { useMultivaultContract } from './useMultivaultContract'

export const useBatchCreateTriple = () => {
const multivault = useMultivaultContract(
base.id
) as GetContractReturnType

return useContractWriteAndWait({
...multivault,
functionName: 'createTripleBatch',
})
}
// Usage Example
const {
writeAsync: writeBatchCreateTriple,
awaitingWalletConfirmation,
awaitingOnChainConfirmation,
} = useBatchCreateTriple()

async function handleBatchCreateTriple(
subjectIds: string[],
predicateIds: string[],
objectIds: string[]
) {
const value = BigInt(tripleValue) * BigInt(subjectIds.length)
if (!awaitingOnChainConfirmation && !awaitingWalletConfirmation && writeBatchCreateTriple) {
try {
const tx = await writeBatchCreateTriple({
address: MULTIVAULT_CONTRACT_ADDRESS,
abi: multivaultAbi,
functionName: 'createTripleBatch',
args: [
subjectIds.map(BigInt),
predicateIds.map(BigInt),
objectIds.map(BigInt),
],
value: tripleCost * subjectIds.length, // Must be >= minimum creation cost * number of triples
})

if (tx?.hash) {
const receipt = await publicClient.waitForTransactionReceipt({
hash: tx.hash,
})
// Handle success
}
} catch (error) {
// Handle error
}
}
}

Batch Creation Addendum:

If you are creating very large batches of atoms or triples (50-100 or more) in a single transaction, the smart contract may revert the transaction due to the block limit being exceeded. In cases like this, you will need to chunk the atoms or triples into smaller batches -- and then execute multiple batch transactions. You could simulate the batch transaction to see if it fits in the block, then chunk it down if the simulation reverts -- or you could just hard-code a limit based on your application. The limit does vary depending on the byte length of the parameters. For example, shorter URIs like keywords (follows, likes, etc) take up much less space in the stack than longer URIs like IPFS CIDs (ipfs://bafy4gyudijfdofjuioppszz883834jkskjfjkl;fjeidfdofkkofokd, etc)

Extracting Triple ID from Transaction

You may want the resulting Term ID once the Triple has been created. You can extract this information from the transaction hash / receipt like this:

export function extractTripleIdFromReceipt(receipt: TransactionReceipt): bigint {
const events = parseEventLogs({
logs: receipt.logs,
abi: [
{
type: 'event',
name: 'TripleCreated',
inputs: [
{ type: 'address', name: 'creator', indexed: true },
{ type: 'address', name: 'tripleWallet', indexed: true },
{ type: 'uint256', name: 'subjectId' },
{ type: 'uint256', name: 'predicateId' },
{ type: 'uint256', name: 'objectId' },
{ type: 'uint256', name: 'vaultID' },
],
},
],
eventName: 'TripleCreated',
})

if (events.length === 0) {
throw new Error('No TripleCreated events found in receipt')
}

return (events[0].args as { vaultID: bigint }).vaultID
}

// Usage:
const receipt = await publicClient.waitForTransactionReceipt({ hash })
const tripleId = extractTripleIdFromReceipt(receipt)

Complete Triple Creation Example

Here's a complete end-to-end example that creates a triple representing "Alice follows Bob":

async function createFollowsTriple() {
// Step 1: Find or create the required atoms
let aliceId = await findAtomByName('Alice')
if (!aliceId) {
// Create Alice atom if it doesn't exist
const aliceUri = await pinMetadata({
name: 'Alice',
description: 'A user in our social network',
})
const aliceHash = await createAtom(aliceUri)
const aliceReceipt = await publicClient.waitForTransactionReceipt({ hash: aliceHash })
aliceId = extractAtomIdFromReceipt(aliceReceipt)
}

let followsId = await findAtomByName('follows')
if (!followsId) {
// Create follows predicate if it doesn't exist
const followsUri = await pinMetadata({
name: 'follows',
description: 'Indicates a following relationship between users',
})
const followsHash = await createAtom(followsUri)
const followsReceipt = await publicClient.waitForTransactionReceipt({ hash: followsHash })
followsId = extractAtomIdFromReceipt(followsReceipt)
}

let bobId = await findAtomByName('Bob')
if (!bobId) {
// Create Bob atom if it doesn't exist
const bobUri = await pinMetadata({
name: 'Bob',
description: 'Another user in our social network',
})
const bobHash = await createAtom(bobUri)
const bobReceipt = await publicClient.waitForTransactionReceipt({ hash: bobHash })
bobId = extractAtomIdFromReceipt(bobReceipt)
}

// Step 2: Check if triple already exists
const existingTriple = await checkTripleExists(aliceId, followsId, bobId)
if (existingTriple) {
console.log(`Triple already exists: ${existingTriple}`)
return existingTriple
}

// Step 3: Create the triple
const tripleHash = await createTriple(aliceId, followsId, bobId)
const tripleReceipt = await publicClient.waitForTransactionReceipt({ hash: tripleHash })
const tripleId = extractTripleIdFromReceipt(tripleReceipt)

console.log(`Created triple: Alice follows Bob (ID: ${tripleId})`)
return tripleId
}

Cost Considerations

  1. Creation Cost:
    • Minimum ETH required to create a Triple
    • Retrieved via getTripleCost()
    • Includes protocol fee sent to treasury
    • Must be included in transaction's value parameter
  2. Initial Deposit:
    • Any ETH sent above the creation cost
    • Deposited into the positive vault
    • Subject to entry fees

See also:

  • [Problem Internal Link]
  • [Problem Internal Link]