Overview
SPHRAGIS documentation
SPHRAGIS is a metaprotocol on Solana for programmable non-fungible assets. Every action is a Seal: a normal Solana transaction carrying a JSON memo and a small fee to the protocol treasury. Indexers replay seals under public rules to compute state. There is no custom program, so anything that can sign a Solana transaction can use SPHRAGIS.
| Role | What they do |
|---|---|
| Holders | Create, evolve and trade relics in the app. |
| Scribes | Write seals from games, tools and apps with the SDK or CLI. |
| Circle owners | Define a standard and appoint wardens. |
| Wardens | Attest whether relics meet their circle’s standard. |
| Indexers | Read the ledger and serve state. Anyone can run one. |
Quickstart
- Install Phantom, Solflare or Backpack.
- Get free devnet SOL at faucet.solana.com.
- Open the app, keep the network on
devnet, connect your wallet. - Go to My Relics and use Cast a Relic. Approve the transaction.
- Open your new relic and use Inscribe to change an attribute. Its art redraws and its history grows.
Set your wallet to the same network as the app (in Phantom: Settings → Developer Settings → Testnet Mode → Solana Devnet), so the transaction preview it shows you is accurate. The app sends the transaction through its own RPC; only the signature comes from your wallet.
Using the app
Explore lists every relic on the selected network. My Relics shows what you hold, with Cast a Relic and Seal an NFT. Circles lets you open a circle and manage its wardens. Governance holds proposals and votes. Activity is the raw seal feed, including rejected seals and the reason each was rejected.
Click any relic to see its attributes, owner, circles, attestations and full history. Each history entry links to its transaction on the Solana explorer. Use the RPC button to plug in a private RPC endpoint if the public one is slow.
Envelope
A valid seal transaction contains:
SystemProgram.transferfrom the signer to the treasury, for at least the fee of the op.- An SPL Memo instruction (
MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr) whose UTF-8 data issphragis:followed by a JSON object with"v":1and an"op"field.
The memo is at most 560 bytes. The signer of the transaction (its fee payer) is the actor. The transaction signature is the id of anything it creates.
| Network | Treasury |
|---|---|
| devnet | GopGEYTYngLFakRMLXwkV3XwUxoAheze9w3s6Txh7TvP |
| mainnet-beta | not set yet |
Operations
Cast a Relic · mint
n name (1–48), i image URL (https://, ipfs:// or ar://, optional), d description (≤200, optional), a attributes (≤16 keys; values are strings ≤64, numbers or booleans).
sphragis:{"v":1,"op":"mint","n":"First Light","a":{"level":1,"element":"ember"}}
Inscribe · set
id relic id, with any of n, i, d, a. Attributes merge, and null deletes a key.
sphragis:{"v":1,"op":"set","id":"<asset id>","a":{"level":2,"element":null}}
Join a Circle · link
sphragis:{"v":1,"op":"link","id":"<asset id>","m":"arena-items"}
Pass on · give
sphragis:{"v":1,"op":"give","id":"<asset id>","to":"<wallet address>"}
Seal an NFT · bind
Attach state to an existing NFT mint. If the mint is already bound, this transfers control to the signer and keeps the history.
sphragis:{"v":1,"op":"bind","mint":"<nft mint address>","n":"My bound piece"}
Open a Circle · mod
m slug ([a-z0-9][a-z0-9-]{1,23}), n name, d description.
sphragis:{"v":1,"op":"mod","m":"arena-items","n":"Arena Items","d":"Weapons and armour for Arena seasons"}
Wardens · val
sphragis:{"v":1,"op":"val","m":"arena-items","add":"<wallet address>"}
Ward · att
sphragis:{"v":1,"op":"att","id":"<asset id>","m":"arena-items","ok":true,"note":"balanced stats"}
Propose · prop and Vote · vote
end is a unix timestamp from 1 hour to 31 days after the proposal’s block time. A vote c is y, n or a.
sphragis:{"v":1,"op":"prop","t":"Lower the mint fee","d":"Halve the fee for 90 days to grow usage","end":1800000000}
sphragis:{"v":1,"op":"vote","pid":"<proposal id>","c":"y"}
Validity rules
- Failed transactions are ignored. Indexers use
confirmedcommitment. - Unparseable memos, unknown ops, wrong versions and schema violations are rejected.
- Fee below the op’s fee: rejected.
set,link,give: the signer must be the current owner. Bound relics cannot be given; they follow the NFT.link: the circle must exist and the relic must not already be linked to it.mod: the slug must be unused.val: the signer must own the circle. The owner cannot be removed.att: the signer must be a warden of the circle and the relic must be linked to it. A newer attestation from the same warden replaces the older one.vote: only inside the window. The latest vote per wallet counts.
Fees & treasury
| op | Devnet fee |
|---|---|
mint | 0.001 SOL |
set | 0.0005 SOL |
link | 0.0005 SOL |
give | 0.0005 SOL |
bind | 0.001 SOL |
mod | 0.001 SOL |
val | 0.00025 SOL |
att | 0.00025 SOL |
prop | 0.001 SOL |
vote | 0 SOL |
You also pay Solana’s normal network fee (about 0.000005 SOL).
Fee schedule
Fees live in feeSchedule in assets/config.js. Each entry has a from unix time, and every seal is checked against the entry that was active at its block time. A governance decision to change fees therefore adds a new entry with a future from; it never rewrites history.
feeSchedule: [
{ from: 0, lamports: 1000000, fees: { set: 500000, vote: 0 } },
// after the $SPHRA launch: pay in the token instead of SOL
{ from: 1767225600, lamports: 0, token: { mint: '<$SPHRA mint>', decimals: 6, symbol: '$SPHRA', amount: 10000000, fees: { vote: 0 } } }
]
When an entry has a token, the transaction also creates the treasury’s token account if needed and makes a TransferChecked of the token fee from the signer. The 0-lamport SOL transfer stays in the transaction, because that is how indexers find it.
JavaScript SDK
The whole protocol is one file, assets/protocol.js. It runs in the browser with @solana/web3.js and in Node.
<script src="https://cdn.jsdelivr.net/npm/@solana/web3.js@1.98.4/lib/index.iife.min.js"></script>
<script src="https://sphragis.xyz/assets/config.js"></script>
<script src="https://sphragis.xyz/assets/protocol.js"></script>
<script>
const P = createProtocol(solanaWeb3, CONFIG, { cluster: 'devnet' });
// write: any wallet with signTransaction (Phantom, Solflare, Backpack…)
await window.phantom.solana.connect();
const id = await P.sendWithWallet(window.phantom.solana, { op: 'mint', n: 'Hello', a: { level: 1 } });
// read: replay the ledger
const state = await P.index();
console.log(state.assets[id]);
</script>
// Node
const web3 = require('@solana/web3.js');
const createProtocol = require('./assets/protocol.js');
const CONFIG = require('./assets/config.js');
const P = createProtocol(web3, CONFIG, { cluster: 'devnet' });
const sig = await P.sendWithKeypair(keypair, { op: 'set', id, a: { level: 2 } });
| Method | Purpose |
|---|---|
encode(op) | Validate and return memo text; throws on invalid input. |
buildTransaction(payer, op) | Unsigned transaction with fee + memo. |
sendWithWallet(provider, op) | Sign with a browser wallet, submit, confirm. Returns the signature. |
sendWithKeypair(keypair, op) | Same, for scripts and servers. |
index({ onProgress }) | Fetch new seals incrementally and return { assets, modules, proposals, feed, rejected }. |
replay(records) | Pure rules engine, useful for tests and custom indexers. |
CLI
git clone <repository-url> sphragis && cd sphragis
npm install
node cli/cli.cjs keygen --out ~/.config/solana/id.json
node cli/cli.cjs airdrop 1
node cli/cli.cjs mint --name "First Light" --attr level=1 --attr element=ember
node cli/cli.cjs set <id> --attr level=2 --attr element=
node cli/cli.cjs module arena-items --name "Arena Items"
node cli/cli.cjs link <id> arena-items
node cli/cli.cjs index
Run node cli/cli.cjs help for every command. Add --cluster mainnet-beta or --rpc <url> to change network.
Run an indexer
Snapshots. So that visitors don’t have to read the whole history on their first visit, the site can serve data/records-devnet.json and data/records-mainnet-beta.json. Generate them with node cli/cli.cjs snapshot --cluster devnet. The included GitHub Actions workflow refreshes them every 15 minutes. A snapshot only speeds up loading: the app keeps reading newer seals from Solana, and RPC → Rebuild from chain ignores the snapshot and recomputes everything locally.
Indexing is just getSignaturesForAddress(treasury) → getParsedTransaction → replay(). The SDK caches what it has seen and only fetches new signatures on the next run. To serve state to others, run index() on a schedule and publish the result as JSON. Because the rules are deterministic, anyone can check your output against their own.
Circles
- Pick a slug and open a circle in the app or with
cli module. - Publish your standard: which attributes a relic needs and what quality bar it has to meet.
- Add wardens with
val. They ward linked relics. - Applications filter to attested relics to show only what meets the standard.
Governance
Governance starts in a signal phase (one wallet, one vote). When the $SPHRA mint address is set as tokenMint in assets/config.js, tallies switch to token-weighted automatically. See the Grimoire for the full process.
FAQ
Is SPHRAGIS its own blockchain?
No. It is a set of rules for reading ordinary Solana transactions. Solana does consensus and settlement.
Do I need to run a validator?
No. SPHRAGIS wardens are a social role inside circles. They are unrelated to Solana validators and need no hardware.
Can my relic be deleted or changed by someone else?
No. Only the owner’s seals are valid for it. Anything else is recorded as rejected and has no effect.
Will my relics appear in Phantom’s NFT tab?
Native relics live in the SPHRAGIS state, not the SPL token program, so they appear in SPHRAGIS-aware apps. Use Seal an NFT to add SPHRAGIS state to an NFT that wallets already display.
What does it cost?
See Fees. On devnet it is free test SOL.