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.
How the Link Works
┌──────────────────────────────────────┐
│ 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 to | Use |
|---|---|
| Mint a tradeable NFT identity for an existing SAP agent | buildAttachAgentIdentityIx(...) |
| Migrate an asset to a new registry host | buildUpdateAgentIdentityUriIx(...) |
| Render an explorer page for one agent | getUnifiedProfile({ asset, rpcUrl }) |
| Confirm an asset cryptographically links to a SAP PDA | verifyLink({ 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 tx | buildMintAndAttachIxs(opts) |
| Register SAP for an existing MPL asset's owner | buildRegisterSapForMplOwnerIx(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:
mpl_coreadds anAgentIdentityadapter to the asset with URI pointing to the EIP-8004 JSON- 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:
AgentIdentity.uriends with/agents/<sapAgentPda>/eip-8004.json, and- The fetched JSON's
synapseAgentfield equals the SAP PDA
Triple-Check Link Audit
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| Layer | Passes when | Common failure cause |
|---|---|---|
mplOnChain | Asset readable + AgentIdentity plugin present | RPC blocks fetchAsset (401), asset not found |
eip8004Json | URI resolves AND JSON synapseAgent === sapPda | URI points to a foreign registry |
sapOnChain | AgentAccount PDA exists for the asset's owner | Wallet 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
| Operation | Naive design (dual on-chain) | This bridge |
|---|---|---|
| Initial linking | 2 tx (SAP + MPL) | 1 tx (MPL only) |
| Add a vault delegate | 2 tx | 1 tx (SAP only) |
| Revoke a delegate | 2 tx | 1 tx (SAP only) |
| Capability add / x402 tier change | 2 tx | 1 tx (SAP only) |
| Reads per profile | 2 RPC + 2 deserializations | 1 RPC + 1 fetch (cached) |
| MPL programs touched after init | every change | never (until host migration) |
| Required on-chain SAP changes | new instructions + new fields | zero |
Next Steps
- Agent Lifecycle — SAP side of the link
- Diagrams — Visual flow
- Agent Builder — SAP register API
Last Updated: June 2026
SDK Version: 0.20.0