SAP DOCv0.20.0
SDK Reference

Metaplex Bridge

Link a SAP agent to a Metaplex Core asset through the AgentIdentity external plugin. SDK v0.20.0 aligned.

Metaplex Bridge

SDK Version: v0.20.0
Peer Dependency: @metaplex-foundation/mpl-core >= 1.9.0

client.metaplex connects a SAP agent to a Metaplex Core asset using the AgentIdentity external plugin. The plugin is asset-only and stores one field: a URI pointing to an EIP-8004 registration JSON.


                ┌──────────────────────────────────────┐
                │   MPL Core Asset (transferable NFT)  │
                │   AgentIdentity.uri ──┐              │
                └───────────────────────┼──────────────┘

   https://explorer.oobeprotocol.ai/agents/<sapAgentPda>/eip-8004.json


        SAP indexer ◀── reads ── AgentAccount + VaultDelegate*

One MPL transaction attaches the plugin. After that, every SAP write propagates with zero MPL transactions because the JSON is rendered live from on-chain SAP state.


Decision Matrix

You want toUse
Mint a tradeable NFT identity for an existing SAP agentbuildAttachAgentIdentityIx(...)
Migrate an asset to a new registry hostbuildUpdateAgentIdentityUriIx(...)
Render an explorer page for one agentgetUnifiedProfile({ asset, rpcUrl })
Confirm an asset cryptographically links to a SAP PDAverifyLink({ asset, sapAgentPda, rpcUrl })
Three-layer link audit (mpl-core + EIP-8004 JSON + SAP PDA)tripleCheckLink({ asset, rpcUrl })
Mint MPL Core asset and attach AgentIdentity in one txbuildMintAndAttachIxs(opts)
Register SAP for an existing MPL asset's ownerbuildRegisterSapForMplOwnerIx(opts)
Atomic both-sided register (start fresh)buildRegisterBothIxs(opts)

Linking Flow (Single Transaction)

import { SapClient } from "@oobe-protocol-labs/synapse-sap-sdk";
import { Transaction } from "@solana/web3.js";

const client = SapClient.from(provider);

const ix = await client.metaplex.buildAttachAgentIdentityIx({
  asset:               mplCoreAsset,
  authority:           wallet.publicKey,
  sapAgentOwner:       wallet.publicKey,
  registrationBaseUrl: "https://explorer.oobeprotocol.ai",
  rpcUrl:              process.env.RPC_URL!,
});

await provider.sendAndConfirm(new Transaction().add(ix));

What happens on-chain:

  1. mpl_core adds an AgentIdentity adapter to the asset with URI pointing to the EIP-8004 JSON
  2. SAP state is untouched: no SAP transaction, no SAP fee

What happens off-chain afterwards:

  • Whenever the SAP agent's capabilities, vault delegates, or x402 tiers change, the served JSON updates automatically
  • The MPL plugin never needs to be touched again unless you migrate hosts

Reading a Unified Profile

const profile = await client.metaplex.getUnifiedProfile({
  asset:  mplCoreAsset,
  rpcUrl: process.env.RPC_URL!,
});

if (profile.linked) {
  console.log("Agent name :", profile.mpl?.registration?.name);
  console.log("Capabilities:", profile.sap.identity?.capabilities);
  console.log("Reputation :", profile.sap.stats?.reputationScore);
}

profile.linked is true when:

  1. AgentIdentity.uri ends with /agents/<sapAgentPda>/eip-8004.json, and
  2. The fetched JSON's synapseAgent field equals the SAP PDA

tripleCheckLink runs three independent checks and reports each layer:

const result = await client.metaplex.tripleCheckLink({
  asset:         assetPk,
  expectedOwner: walletPk,
  rpcUrl,
  rpcHeaders,   // required on gated RPCs
});

result.layers; // { mplOnChain, eip8004Json, sapOnChain }
result.linked; // true ⇔ all three layers pass
LayerPasses whenCommon failure cause
mplOnChainAsset readable + AgentIdentity plugin presentRPC blocks fetchAsset (401), asset not found
eip8004JsonURI resolves AND JSON synapseAgent === sapPdaURI points to a foreign registry
sapOnChainAgentAccount PDA exists for the asset's ownerWallet never registered a SAP agent

Three Register Flows

Flow A: SAP exists, mint MPL and attach (2 ixs in 1 tx)

const ixs = await client.metaplex.buildMintAndAttachIxs({
  sapAgentOwner: wallet,
  authority:     wallet,
  payer:         wallet,
  owner:         wallet,
  name:          "My Agent",
  metadataUri:   "https://...metadata.json",
  registrationBaseUrl: "https://explorer.oobeprotocol.ai",
  rpcUrl:        process.env.RPC_URL!,
});

await provider.sendAndConfirm(new Transaction().add(...ixs));

Flow B: MPL asset exists, register SAP for its owner (idempotent)

const ix = await client.metaplex.buildRegisterSapForMplOwnerIx({
  asset:        mplCoreAsset,
  rpcUrl:       process.env.RPC_URL!,
  registration: { name: "My Agent", description: "...", capabilities: [...] },
});

if (ix) await provider.sendAndConfirm(new Transaction().add(ix));

ix is null when the SAP agent already exists.

Flow C: Atomic both-sided (no SAP, no MPL)

const ixs = await client.metaplex.buildRegisterBothIxs({
  payer: wallet,
  owner: wallet,
  authority: wallet,
  sapRegistration:  { name: "My Agent", capabilities: [...] },
  mplAsset:         { name: "My Agent NFT", metadataUri: "https://..." },
  registrationBaseUrl: "https://explorer.oobeprotocol.ai",
  rpcUrl: process.env.RPC_URL!,
});

await provider.sendAndConfirm(new Transaction().add(...ixs));

Efficiency Comparison

OperationNaive design (dual on-chain)This bridge
Initial linking2 tx (SAP + MPL)1 tx (MPL only)
Add a vault delegate2 tx1 tx (SAP only)
Revoke a delegate2 tx1 tx (SAP only)
Capability add / x402 tier change2 tx1 tx (SAP only)
Reads per profile2 RPC + 2 deserializations1 RPC + 1 fetch (cached)
MPL programs touched after initevery changenever (until host migration)
Required on-chain SAP changesnew instructions + new fieldszero

Next Steps


Last Updated: June 2026
SDK Version: 0.20.0