Reference

StitchMemoryRegistry

The registry and marketplace behind every pack on Stitch AI. One contract, no proxy, no admin key over user funds. This page documents the deployed bytecode, function by function.

Deployment

Registry
0x2E681c71416E1AA1f16bf7403276882E13Ed66Ad

Robinhood Chain mainnet, chain id 4663. Verified on Blockscout as StitchMemoryRegistry, compiler v0.8.24+commit.e11b9ed9.

Treasury
0xa7c7F04164db3fB97f6D48916F00A8BE52a6C602

Receives 500 basis points of each purchase, withdrawn by pull payment like any creator.

The contract is Solidity ^0.8.24, not upgradeable and not proxied. The owner can move the treasury address and nothing else: it cannot pause packs, reprice them, seize grants, or touch a creator balance. Read the deployed source on Blockscout.

The Pack struct

One struct carries every pack. getPack and listPacks both return it, so decoding it once is enough for the whole read surface.

FieldTypeMeaning
creatoraddressPublisher. The only address that may push versions or change price and status.
priceuint96Listed price in wei. 0 is a valid price and still requires a purchase call.
namestringDisplay name, required at publish time.
categorystringFree-form label used for filtering, for example onchain, defi, oracles, agents, developer.
uristringPointer to the current payload, usually an ipfs:// CID.
versionuint32Starts at 1 and increments on every pushVersion.
purchasesuint32Count of paid grants. The creator grant at publish time is not counted.
createdAtuint64Unix seconds of the publishing block.
updatedAtuint64Unix seconds of the last pushVersion or setPrice.
activeboolFalse hides the pack and blocks new purchases. Existing owners keep access.

Write functions

All seven are external. Six of them are permissioned by the pack creator or the contract owner; only publishPack, purchase, and withdraw are open to any address.

SignatureParamsReturnsCallable byReverts with
publishPack(string name, string category, string uri, uint96 price)name and uri must be non-empty. category is a free-form label. price is wei, 0 makes the pack free to claim.uint256 packIdAnyoneEmptyField
pushVersion(uint256 packId, string uri)packId of an existing pack, uri of the new payload.nothingPack creatorUnknownPack, NotCreator, EmptyField
purchase(uint256 packId) payablepackId to buy. msg.value must equal the listed price exactly, including 0 for free packs.nothingAny address without a grantUnknownPack, PackInactive, AlreadyOwned, WrongPayment
setPrice(uint256 packId, uint96 price)New price in wei. Applies to future purchases only.nothingPack creatorUnknownPack, NotCreator
setActive(uint256 packId, bool active)Delists or relists the pack. Existing grants are untouched.nothingPack creatorUnknownPack, NotCreator
withdraw()nonenothingAny address with a non-zero earnings balanceNothingToWithdraw, TransferFailed
setTreasury(address treasury_)New fee recipient. Already accrued treasury earnings stay with the old address.nothingContract ownerNotOwner, ZeroAddress

View functions

SignatureParamsReturnsCallable byReverts with
getPack(uint256 packId) viewpackId, 1 based.PackAnyoneUnknownPack
listPacks(uint256 offset, uint256 limit) viewoffset from the newest pack, limit page size. An offset past the end or a limit of 0 returns an empty page and the true total.Pack[] page, uint256 totalAnyonenever
versionHistory(uint256 packId) viewpackId, 1 based. Returns every URI the pack has pointed at, oldest first.string[]AnyoneUnknownPack
packsByCreator(address creator) viewPublisher address. Ids come back in publish order, unknown creators get an empty array.uint256[]Anyonenever
stats() viewNo arguments. Loops every pack, so call it off chain only.uint256 packs, uint256 totalPurchases, uint256 totalVolumeAnyonenever

Public state getters

Solidity generates these from public state variables. They are ordinary view calls.

SignatureParamsReturnsCallable byReverts with
FEE_BPS() viewConstant. 500 out of 10000 basis points, the protocol fee.uint16Anyonenever
packCount() viewThe highest issued pack id. Ids run 1 to packCount.uint256Anyonenever
owner() viewDeployer address. Its only power is setTreasury.addressAnyonenever
treasury() viewAddress that accrues the protocol fee.addressAnyonenever
hasAccess(uint256 packId, address account) viewPack id and reader address. Unknown ids read false rather than reverting.boolAnyonenever
earnings(address account) viewCreator or treasury address. Wei currently withdrawable with withdraw().uint256Anyonenever

Events

Seven events cover the full lifecycle. Indexed parameters are marked in the signature, so you can filter on them at the RPC without pulling every log.

SignatureWhen it fires
PackPublished(uint256 indexed packId, address indexed creator, string name, string category, uint256 price, string uri)A new pack exists. Emitted once, together with VersionPushed for version 1.
VersionPushed(uint256 indexed packId, uint32 indexed version, string uri, address indexed creator)The pack now points at a new payload. Watch this to refresh a running agent.
PackPurchased(uint256 indexed packId, address indexed buyer, uint256 price, uint256 creatorCut)An access grant was written. price is the full amount paid, creatorCut is the amount credited to the creator.
PriceUpdated(uint256 indexed packId, uint256 price)The creator relisted at a new price.
PackStatusChanged(uint256 indexed packId, bool active)The creator delisted or relisted the pack.
Withdrawn(address indexed account, uint256 amount)A creator or the treasury pulled their balance.
TreasuryUpdated(address indexed treasury)The owner moved the fee recipient.

Custom errors

The contract reverts with typed errors, never with strings, so every failure is four bytes on the wire and decodes to a name your client can branch on.

ErrorTriggered by
NotCreatorpushVersion, setPrice, or setActive called by an address that does not own the pack.
NotOwnersetTreasury called by anyone other than the contract owner.
UnknownPackA pack id of 0, or above packCount, reached a function guarded by packExists.
PackInactivepurchase on a pack the creator has set inactive.
AlreadyOwnedpurchase from an address that already holds a grant, including the creator.
WrongPaymentmsg.value did not equal the listed price exactly. Over and under both revert.
NothingToWithdrawwithdraw called with a zero earnings balance.
TransferFailedThe value transfer in withdraw was rejected by the recipient.
EmptyFieldpublishPack with an empty name or uri, or pushVersion with an empty uri.
ZeroAddressThe constructor or setTreasury received the zero address.

Decoding a revert

Add the error fragments to your ABI once, then viem will name the revert for you instead of handing back raw hex.

errors.ts
// errors.ts
import { parseAbi } from "viem";
import { registryAbi } from "./stitch";

export const registryErrors = parseAbi([
  "error NotCreator()",
  "error NotOwner()",
  "error UnknownPack()",
  "error PackInactive()",
  "error AlreadyOwned()",
  "error WrongPayment()",
  "error NothingToWithdraw()",
  "error TransferFailed()",
  "error EmptyField()",
  "error ZeroAddress()",
]);

export const fullAbi = [...registryAbi, ...registryErrors] as const;

Simulate before you send. This call underpays a pack priced at 0.01, so the simulation fails without spending gas:

simulate.ts
// simulate.ts
import { BaseError, ContractFunctionRevertedError } from "viem";
import { client, REGISTRY } from "./stitch";
import { fullAbi } from "./errors";

try {
  await client.simulateContract({
    address: REGISTRY,
    abi: fullAbi,
    functionName: "purchase",
    args: [6n],
    account,
    value: 0n,
  });
} catch (error) {
  if (error instanceof BaseError) {
    const reverted = error.walk((e) => e instanceof ContractFunctionRevertedError);
    if (reverted instanceof ContractFunctionRevertedError) {
      console.log(reverted.data?.errorName);
    }
  }
}
output
WrongPayment

Fees and pull payments

FEE_BPS is a constant 500 out of 10000, so the protocol takes 5 percent of every purchase and the creator keeps 95 percent. The split is computed inside purchase and credited to balances. No value is forwarded during the purchase itself.

StitchMemoryRegistry.sol
uint16 public constant FEE_BPS = 500; // 5%
uint16 private constant BPS = 10_000;

uint256 fee = (msg.value * FEE_BPS) / BPS;
uint256 creatorCut = msg.value - fee;
if (creatorCut != 0) earnings[p.creator] += creatorCut;
if (fee != 0) earnings[treasury] += fee;

On a 0.01 purchase the treasury is credited 0.0005 and the creator 0.0095. Both sides then call withdraw, which zeroes the balance before sending and reverts with TransferFailed if the recipient rejects the transfer.

withdraw.ts
// withdraw.ts
import { formatEther } from "viem";
import { client, REGISTRY, registryAbi, robinhoodChain } from "./stitch";

const balance = await client.readContract({
  address: REGISTRY,
  abi: registryAbi,
  functionName: "earnings",
  args: [account],
});

if (balance > 0n) {
  const hash = await wallet.writeContract({
    account,
    chain: robinhoodChain,
    address: REGISTRY,
    abi: registryAbi,
    functionName: "withdraw",
  });
  await client.waitForTransactionReceipt({ hash });
  console.log("withdrew", formatEther(balance), "ETH");
}

Reading these balances live, along with the catalogue, is what the SDK page covers. The contract is the only source of truth here; this site holds no separate database of grants.