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
Robinhood Chain mainnet, chain id 4663. Verified on Blockscout as StitchMemoryRegistry, compiler v0.8.24+commit.e11b9ed9.
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.
| Field | Type | Meaning |
|---|---|---|
| creator | address | Publisher. The only address that may push versions or change price and status. |
| price | uint96 | Listed price in wei. 0 is a valid price and still requires a purchase call. |
| name | string | Display name, required at publish time. |
| category | string | Free-form label used for filtering, for example onchain, defi, oracles, agents, developer. |
| uri | string | Pointer to the current payload, usually an ipfs:// CID. |
| version | uint32 | Starts at 1 and increments on every pushVersion. |
| purchases | uint32 | Count of paid grants. The creator grant at publish time is not counted. |
| createdAt | uint64 | Unix seconds of the publishing block. |
| updatedAt | uint64 | Unix seconds of the last pushVersion or setPrice. |
| active | bool | False 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.
| Signature | Params | Returns | Callable by | Reverts 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 packId | Anyone | EmptyField |
| pushVersion(uint256 packId, string uri) | packId of an existing pack, uri of the new payload. | nothing | Pack creator | UnknownPack, NotCreator, EmptyField |
| purchase(uint256 packId) payable | packId to buy. msg.value must equal the listed price exactly, including 0 for free packs. | nothing | Any address without a grant | UnknownPack, PackInactive, AlreadyOwned, WrongPayment |
| setPrice(uint256 packId, uint96 price) | New price in wei. Applies to future purchases only. | nothing | Pack creator | UnknownPack, NotCreator |
| setActive(uint256 packId, bool active) | Delists or relists the pack. Existing grants are untouched. | nothing | Pack creator | UnknownPack, NotCreator |
| withdraw() | none | nothing | Any address with a non-zero earnings balance | NothingToWithdraw, TransferFailed |
| setTreasury(address treasury_) | New fee recipient. Already accrued treasury earnings stay with the old address. | nothing | Contract owner | NotOwner, ZeroAddress |
View functions
| Signature | Params | Returns | Callable by | Reverts with |
|---|---|---|---|---|
| getPack(uint256 packId) view | packId, 1 based. | Pack | Anyone | UnknownPack |
| listPacks(uint256 offset, uint256 limit) view | offset 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 total | Anyone | never |
| versionHistory(uint256 packId) view | packId, 1 based. Returns every URI the pack has pointed at, oldest first. | string[] | Anyone | UnknownPack |
| packsByCreator(address creator) view | Publisher address. Ids come back in publish order, unknown creators get an empty array. | uint256[] | Anyone | never |
| stats() view | No arguments. Loops every pack, so call it off chain only. | uint256 packs, uint256 totalPurchases, uint256 totalVolume | Anyone | never |
Public state getters
Solidity generates these from public state variables. They are ordinary view calls.
| Signature | Params | Returns | Callable by | Reverts with |
|---|---|---|---|---|
| FEE_BPS() view | Constant. 500 out of 10000 basis points, the protocol fee. | uint16 | Anyone | never |
| packCount() view | The highest issued pack id. Ids run 1 to packCount. | uint256 | Anyone | never |
| owner() view | Deployer address. Its only power is setTreasury. | address | Anyone | never |
| treasury() view | Address that accrues the protocol fee. | address | Anyone | never |
| hasAccess(uint256 packId, address account) view | Pack id and reader address. Unknown ids read false rather than reverting. | bool | Anyone | never |
| earnings(address account) view | Creator or treasury address. Wei currently withdrawable with withdraw(). | uint256 | Anyone | never |
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.
| Signature | When 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.
| Error | Triggered by |
|---|---|
| NotCreator | pushVersion, setPrice, or setActive called by an address that does not own the pack. |
| NotOwner | setTreasury called by anyone other than the contract owner. |
| UnknownPack | A pack id of 0, or above packCount, reached a function guarded by packExists. |
| PackInactive | purchase on a pack the creator has set inactive. |
| AlreadyOwned | purchase from an address that already holds a grant, including the creator. |
| WrongPayment | msg.value did not equal the listed price exactly. Over and under both revert. |
| NothingToWithdraw | withdraw called with a zero earnings balance. |
| TransferFailed | The value transfer in withdraw was rejected by the recipient. |
| EmptyField | publishPack with an empty name or uri, or pushVersion with an empty uri. |
| ZeroAddress | The 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
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
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);
}
}
}WrongPaymentFees 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.
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
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.