@msquared/etherbase-client
Version:
React hooks for interacting with Etherbase smart contracts
1,915 lines (1,899 loc) • 68.1 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
EtherbaseProvider: () => EtherbaseProvider,
initializeApp: () => initializeApp,
somnia: () => somnia,
useEtherbase: () => useEtherbase,
useEtherbaseContract: () => useEtherbaseContract,
useEtherbaseEvents: () => useEtherbaseEvents,
useEtherbasePermissions: () => useEtherbasePermissions,
useEtherbaseSource: () => useEtherbaseSource,
useEtherstore: () => useEtherstore,
useWebThree: () => useWebThree
});
module.exports = __toCommonJS(index_exports);
// src/EtherbaseProvider.tsx
var import_react = __toESM(require("react"));
// src/config.ts
var config = null;
function initializeApp(userConfig) {
if (!userConfig.wsReaderUrl && !userConfig.wsWriterUrl && !userConfig.httpReaderUrl) {
throw new Error(
"wsReaderUrl or wsWriterUrl or httpReaderUrl must be provided in etherbase config"
);
}
config = userConfig;
}
function getConfig() {
if (!config) {
throw new Error(
"Etherbase must be initialized with initializeApp() before use, or use the EtherbaseProvider"
);
}
return config;
}
// src/EtherbaseProvider.tsx
var EtherbaseContext = (0, import_react.createContext)({
initialized: false
});
function EtherbaseProvider({
config: config2,
children
}) {
const [initialized, setInitialized] = (0, import_react.useState)(false);
(0, import_react.useEffect)(() => {
if (!initialized) {
initializeApp(config2);
setInitialized(true);
}
}, [config2, initialized]);
return /* @__PURE__ */ import_react.default.createElement(EtherbaseContext.Provider, { value: { initialized } }, initialized ? children : null);
}
function useEtherbaseContext() {
const context = (0, import_react.useContext)(EtherbaseContext);
if (context === void 0) {
throw new Error(
"useEtherbaseContext must be used within an EtherbaseProvider"
);
}
return context;
}
// src/hooks/useEtherstore.ts
var import_react4 = require("react");
// src/hooks/WebSocketManager.ts
var WebSocketManager = class _WebSocketManager {
constructor() {
this.ws = null;
this.dead = false;
/** A map from localId -> SubscriptionStatus. */
this.subscriptions = /* @__PURE__ */ new Map();
this.writerWs = null;
this.readerWs = null;
const config2 = getConfig();
this.readerUrl = config2.wsReaderUrl;
this.writerUrl = config2.wsWriterUrl;
this.privateKey = config2.privateKey;
}
static get() {
if (!_WebSocketManager.instance) {
_WebSocketManager.instance = new _WebSocketManager();
}
return _WebSocketManager.instance;
}
/** Connect if not open or connecting */
async connectReader() {
if (this.dead) return;
if (!this.readerUrl) {
console.error("[WebSocketManager] Reader URL not set");
return;
}
if (this.readerWs && (this.readerWs.readyState === WebSocket.OPEN || this.readerWs.readyState === WebSocket.CONNECTING)) {
if (this.readerWs.readyState === WebSocket.CONNECTING) {
await new Promise((resolve) => {
if (!this.readerWs) return;
this.readerWs.addEventListener("open", () => resolve(), {
once: true
});
});
}
return;
}
console.log("[WebSocketManager] Connecting reader...");
const url = new URL(`${this.readerUrl}/read`);
if (this.privateKey) {
url.searchParams.set("privateKey", this.privateKey);
}
this.readerWs = new WebSocket(url.toString());
this.readerWs.onopen = () => {
console.log("[WebSocketManager] Reader connected.");
for (const status of this.subscriptions.values()) {
status.subscriptionId = void 0;
status.error = void 0;
this.sendSubscribe(status);
}
};
this.readerWs.onmessage = (event) => this.handleMessage(event);
this.readerWs.onerror = (err) => {
console.error("[WebSocketManager] Reader WebSocket error:", err);
};
this.readerWs.onclose = () => {
console.warn("[WebSocketManager] Reader WebSocket closed.");
this.readerWs = null;
};
await new Promise((resolve) => {
if (this.readerWs?.readyState === WebSocket.OPEN) {
resolve();
} else {
this.readerWs?.addEventListener("open", () => resolve(), { once: true });
}
});
console.log("[WebSocketManager] Reader connected.");
}
async connectWriter() {
if (this.dead) return;
if (!this.writerUrl) {
console.error("[WebSocketManager] Writer URL not set");
return;
}
if (this.writerWs && (this.writerWs.readyState === WebSocket.OPEN || this.writerWs.readyState === WebSocket.CONNECTING)) {
if (this.writerWs.readyState === WebSocket.CONNECTING) {
await new Promise((resolve) => {
if (!this.writerWs) return;
this.writerWs.addEventListener("open", () => resolve(), {
once: true
});
});
}
return;
}
console.log("[WebSocketManager] Connecting writer...");
const url = new URL(`${this.writerUrl}/write`);
if (this.privateKey) {
url.searchParams.set("privateKey", this.privateKey);
}
this.writerWs = new WebSocket(url.toString());
this.writerWs.onopen = () => {
console.log("[WebSocketManager] Writer connected.");
};
this.writerWs.onmessage = (event) => this.handleWriterMessage(event);
this.writerWs.onerror = (err) => {
console.error("[WebSocketManager] Writer WebSocket error:", err);
};
this.writerWs.onclose = () => {
console.warn("[WebSocketManager] Writer WebSocket closed.");
this.writerWs = null;
};
await new Promise((resolve) => {
if (this.writerWs?.readyState === WebSocket.OPEN) {
resolve();
} else {
this.writerWs?.addEventListener("open", () => resolve(), { once: true });
}
});
console.log("[WebSocketManager] Writer connected.");
}
handleMessage(event) {
try {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "error":
console.error("[WS] Server error:", msg.error);
break;
case "subscription_success": {
const { pendingId, subscriptionId } = msg.data;
const sub = this.subscriptions.get(pendingId);
if (sub) {
sub.subscriptionId = subscriptionId;
sub.error = void 0;
console.log(
`[WS] Subscription success for localId=${pendingId}, subscriptionId=${subscriptionId}`
);
}
break;
}
case "subscription_failed": {
const { pendingId, error } = msg.data;
const sub = this.subscriptions.get(pendingId);
if (sub) {
sub.error = error;
console.warn(
`[WS] Subscription failed for localId=${pendingId}: ${error}`
);
}
break;
}
case "updates": {
console.log("[WS] Got updates:", msg.data);
this.handleUpdates(msg.data);
break;
}
case "initial_state": {
this.handleUpdates(msg.data);
break;
}
case "transaction_complete":
console.log("[WS] transaction_complete:", msg.data);
break;
case "transaction_failed":
console.warn("[WS] transaction_failed:", msg.data);
break;
case "event_submitted":
console.log("[WS] event_submitted");
break;
default:
console.log("[WS] unhandled message:", msg);
break;
}
} catch (err) {
console.error("[WS] Failed to parse message:", err);
}
}
handleWriterMessage(event) {
try {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "error":
console.error("[WS Writer] Server error:", msg.error);
break;
case "transaction_complete":
console.log("[WS Writer] transaction_complete:", msg.data);
break;
case "transaction_failed":
console.warn("[WS Writer] transaction_failed:", msg.data);
break;
case "event_submitted":
console.log("[WS Writer] event_submitted");
break;
default:
console.log("[WS Writer] unhandled message:", msg);
break;
}
} catch (err) {
console.error("[WS Writer] Failed to parse message:", err);
}
}
handleUpdates({ block, updates }) {
for (const update of updates) {
const sub = [...this.subscriptions.values()].find(
(s) => s.subscriptionId === update.subscriptionId
);
if (!sub) {
console.warn(
"[WS] Got update for unknown subscriptionId:",
update.subscriptionId
);
continue;
}
if (update.type === "event" && "onEvent" in sub.subscription) {
sub.subscription.onEvent(update.data);
} else if (update.type === "state" && "onStateUpdate" in sub.subscription) {
sub.subscription.onStateUpdate(update.data);
}
}
}
async addEventSubscription(localId, subscription) {
this.subscriptions.set(localId, {
localId,
subscription
});
await this.connectReader();
const status = this.subscriptions.get(localId);
if (!status) {
console.warn(
"[WS] Tried to add event subscription for unknown localId:",
localId
);
return;
}
this.sendSubscribe(status);
}
async addStateSubscription(localId, subscription) {
this.subscriptions.set(localId, {
localId,
subscription
});
console.log("Adding state subscription", localId, subscription);
await this.connectReader();
const status = this.subscriptions.get(localId);
if (!status) {
console.warn(
"[WS] Tried to add state subscription for unknown localId:",
localId
);
return;
}
this.sendSubscribe(status);
}
removeSubscription(localId) {
const status = this.subscriptions.get(localId);
if (!status) return;
if (status.subscriptionId && this.readerWs?.readyState === WebSocket.OPEN) {
this.readerWs.send(
JSON.stringify({
type: "unsubscribe",
data: {
subscriptionId: status.subscriptionId
}
})
);
}
this.subscriptions.delete(localId);
}
sendSubscribe(status) {
if (!this.readerWs || this.readerWs.readyState !== WebSocket.OPEN) return;
const { localId, subscription } = status;
if ("onEvent" in subscription) {
this.readerWs.send(
JSON.stringify({
type: "subscribe",
data: {
pendingId: localId,
eventSubscription: {
contractAddress: subscription.contractAddress,
events: subscription.events
}
}
})
);
} else {
console.log("Sending state subscription!!!", localId, subscription);
this.readerWs.send(
JSON.stringify({
type: "subscribe",
data: {
pendingId: localId,
stateSubscription: {
contractAddress: subscription.contractAddress,
statePath: subscription.statePath,
options: subscription.options
}
}
})
);
}
}
getSubscriptionError(localId) {
return this.subscriptions.get(localId)?.error;
}
forceClose() {
console.log("[WebSocketManager] Force closing...");
this.dead = true;
if (this.readerWs) {
this.readerWs.close();
this.readerWs = null;
}
if (this.writerWs) {
this.writerWs.close();
this.writerWs = null;
}
}
async setValue({
contractAddress,
state
}) {
if (!this.writerWs || this.writerWs.readyState !== WebSocket.OPEN) {
await this.connectWriter();
}
this.writerWs?.send(
JSON.stringify({
type: "set_value",
data: {
contractAddress,
state
}
})
);
}
async emitEvent({
sourceAddress,
name,
args
}) {
console.log(
"[WebSocketManager] Emitting event...",
this.writerWs?.readyState
);
if (!this.writerWs || this.writerWs.readyState !== WebSocket.OPEN) {
await this.connectWriter();
}
this.writerWs?.send(
JSON.stringify({
type: "emit_event",
data: {
contractAddress: sourceAddress,
name,
args
}
})
);
}
async executeContractMethod({
contractAddress,
methodName,
args
}) {
console.log(
"[WebSocketManager] Executing contract method...",
this.writerWs?.readyState
);
if (!this.writerWs || this.writerWs.readyState !== WebSocket.OPEN) {
await this.connectWriter();
}
this.writerWs?.send(
JSON.stringify({
type: "execute_contract_method",
data: { contractAddress, methodName, args }
})
);
}
};
// src/hooks/useEtherbaseSource.ts
var import_react3 = require("react");
var import_viem2 = require("viem");
// src/abi/EtherbaseSource.ts
var EtherbaseSourceAbi = [
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "address",
"name": "validator",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EventNameAlreadyRegistered",
"type": "error"
},
{
"inputs": [],
"name": "EventNameNotRegistered",
"type": "error"
},
{
"inputs": [],
"name": "IdentityAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "IdentityDoesNotExist",
"type": "error"
},
{
"inputs": [],
"name": "IncorrectNumberOfTopics",
"type": "error"
},
{
"inputs": [
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "dataType",
"type": "uint8"
}
],
"name": "InvalidDataEncoding",
"type": "error"
},
{
"inputs": [
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "expected",
"type": "uint8"
},
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "actual",
"type": "uint8"
}
],
"name": "InvalidDataType",
"type": "error"
},
{
"inputs": [],
"name": "InvalidIdentity",
"type": "error"
},
{
"inputs": [],
"name": "PathNotFound",
"type": "error"
},
{
"inputs": [],
"name": "TooManyIndexedargs",
"type": "error"
},
{
"inputs": [],
"name": "TooManyTopics",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string[]",
"name": "path",
"type": "string[]"
},
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{
"indexed": false,
"internalType": "uint8",
"name": "dataType",
"type": "uint8"
}
],
"name": "EthDBPathUpdate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "wallet",
"type": "address"
}
],
"name": "IdentityCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "wallet",
"type": "address"
}
],
"name": "IdentityDeleted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "segment",
"type": "string"
},
{
"indexed": true,
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "NewSegment",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"indexed": true,
"internalType": "enum RoleControl.Role",
"name": "role",
"type": "uint8"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"indexed": true,
"internalType": "enum RoleControl.Role",
"name": "role",
"type": "uint8"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "string",
"name": "name",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "id",
"type": "string"
}
],
"name": "SchemaRegistered",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "walletAddress",
"type": "address"
},
{
"internalType": "enum RoleControl.Role[]",
"name": "initialRoles",
"type": "uint8[]"
}
],
"name": "createWalletIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "wallet",
"type": "address"
}
],
"name": "deleteIdentity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "bytes32[]",
"name": "ArgumentTopics",
"type": "bytes32[]"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "emitEvent",
"outputs": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "bytes32[]",
"name": "argumentTopics",
"type": "bytes32[]"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"internalType": "struct EtherbaseSource.BatchEmitEvent[]",
"name": "events",
"type": "tuple[]"
}
],
"name": "emitEvents",
"outputs": [
{
"internalType": "bool",
"name": "success",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"name": "eventSchemas",
"outputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "id",
"type": "string"
},
{
"internalType": "bytes32",
"name": "eventTopic",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "numIndexedArgs",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getAllIdentities",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "walletAddress",
"type": "address"
},
{
"internalType": "enum RoleControl.Role[]",
"name": "roles",
"type": "uint8[]"
}
],
"internalType": "struct RoleControl.IdentityView[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getAllSegments",
"outputs": [
{
"internalType": "string[]",
"name": "",
"type": "string[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getAllWallets",
"outputs": [
{
"internalType": "address[]",
"name": "",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getEntries",
"outputs": [
{
"components": [
{
"internalType": "bytes",
"name": "path",
"type": "bytes"
},
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "dataType",
"type": "uint8"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{
"internalType": "bool",
"name": "exists",
"type": "bool"
}
],
"internalType": "struct EtherDatabaseLib.Node[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "id",
"type": "string"
}
],
"name": "getEventSchemaFromId",
"outputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "id",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "argType",
"type": "string"
},
{
"internalType": "bool",
"name": "isIndexed",
"type": "bool"
}
],
"internalType": "struct Argument[]",
"name": "args",
"type": "tuple[]"
},
{
"internalType": "bytes32",
"name": "eventTopic",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "numIndexedArgs",
"type": "uint8"
}
],
"internalType": "struct EventSchema",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
}
],
"name": "getEventSchemaFromName",
"outputs": [
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "id",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "argType",
"type": "string"
},
{
"internalType": "bool",
"name": "isIndexed",
"type": "bool"
}
],
"internalType": "struct Argument[]",
"name": "args",
"type": "tuple[]"
},
{
"internalType": "bytes32",
"name": "eventTopic",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "numIndexedArgs",
"type": "uint8"
}
],
"internalType": "struct EventSchema",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "segment",
"type": "string"
}
],
"name": "getOrCreateSegmentId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getRegisteredEventNames",
"outputs": [
{
"internalType": "string[]",
"name": "",
"type": "string[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "segment",
"type": "string"
}
],
"name": "getSegmentId",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string[]",
"name": "segments",
"type": "string[]"
}
],
"name": "getValue",
"outputs": [
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "",
"type": "uint8"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"internalType": "enum RoleControl.Role",
"name": "role",
"type": "uint8"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string[]",
"name": "segments",
"type": "string[]"
}
],
"name": "hasEntry",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"name": "idToName",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "id",
"type": "string"
},
{
"internalType": "bytes32",
"name": "eventTopic",
"type": "bytes32"
},
{
"components": [
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "argType",
"type": "string"
},
{
"internalType": "bool",
"name": "isIndexed",
"type": "bool"
}
],
"internalType": "struct Argument[]",
"name": "args",
"type": "tuple[]"
}
],
"name": "registerEventSchema",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string[]",
"name": "segments",
"type": "string[]"
}
],
"name": "removeValue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "wallet",
"type": "address"
},
{
"internalType": "enum RoleControl.Role",
"name": "role",
"type": "uint8"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string[]",
"name": "segments",
"type": "string[]"
},
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "dataType",
"type": "uint8"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "setValue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "string[]",
"name": "segments",
"type": "string[]"
},
{
"internalType": "enum EtherDatabaseLib.DataType",
"name": "dataType",
"type": "uint8"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"internalType": "struct EtherDatabaseLib.BatchSetValue[]",
"name": "values",
"type": "tuple[]"
}
],
"name": "setValues",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "validator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
];
// src/etherbaseSource.ts
async function fetchEventDefinitionsData(sourceAddress, config2) {
const { httpReaderUrl } = config2 || getConfig();
const response = await fetch(
`${httpReaderUrl}/event-definitions?sourceAddress=${sourceAddress}`,
{ cache: "no-store" }
);
return response.json();
}
// src/hooks/useWebThree.tsx
var import_react2 = require("react");
var import_viem = require("viem");
function useWebThree() {
const [walletClient, setWalletClient] = (0, import_react2.useState)(null);
const { chain, privateKey } = getConfig();
const publicClient = (0, import_react2.useMemo)(() => {
if (!window.ethereum) {
return null;
}
const pClient = (0, import_viem.createPublicClient)({
chain,
transport: (0, import_viem.custom)(window.ethereum)
});
return pClient;
}, [chain]);
const getWalletClient = (0, import_react2.useCallback)(async () => {
if (walletClient) {
console.log("Already initialized");
return walletClient;
}
if (!chain) {
console.error(
"No chain specified in config but is required for write operations"
);
return;
}
if (!window.ethereum) {
console.error("No Ethereum provider found");
return;
}
const accounts = await window.ethereum.request({
method: "eth_requestAccounts"
});
const account = accounts[0];
const wClient = (0, import_viem.createWalletClient)({
account,
transport: (0, import_viem.custom)(window.ethereum),
chain
});
setWalletClient(wClient);
return wClient;
}, [walletClient, chain]);
return { publicClient, getWalletClient };
}
// src/hooks/useEtherbaseSource.ts
function useEtherbaseSource({
sourceAddress
}) {
useEtherbaseContext();
const { httpReaderUrl, wsReaderUrl, wsWriterUrl, useBackend, privateKey } = getConfig();
const { publicClient, getWalletClient } = useWebThree();
const [eventDefinitions, setEventDefinitions] = (0, import_react3.useState)([]);
const getWalletClientInternal = (0, import_react3.useCallback)(async () => {
const walletClient = await getWalletClient();
if (!walletClient) {
throw new Error("Wallet client not found");
}
return walletClient;
}, [getWalletClient]);
const executeWriteBrowser = (0, import_react3.useCallback)(
async (writeFunctionName, args) => {
if (!publicClient) {
throw new Error("Public client not found");
}
const walletClient = await getWalletClientInternal();
if (!EtherbaseSourceAbi.find(
(f) => f.type === "function" && f.name === writeFunctionName
)) {
throw new Error(`Invalid function name: ${writeFunctionName}`);
}
const hash = await walletClient.writeContract({
address: sourceAddress,
abi: EtherbaseSourceAbi,
// @ts-ignore
functionName: writeFunctionName,
// @ts-ignore
args,
// currently gas estimation is broken in somnia so hardcoding a high value
gas: 7920027n
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("Execute write receipt:", receipt);
},
[sourceAddress, publicClient, getWalletClientInternal]
);
const getEventAbi = (0, import_react3.useCallback)((event) => {
let abi = `event ${event.name}(`;
for (const param of event.args) {
abi += `${param.argType}`;
if (param.isIndexed) {
abi += " indexed";
}
abi += ` ${param.name}`;
abi += ", ";
}
if (event.args.length > 0) {
abi = abi.slice(0, -2);
}
abi += ")";
return abi;
}, []);
const getEventTopic = (0, import_react3.useCallback)((event) => {
return (0, import_viem2.keccak256)(
(0, import_viem2.stringToBytes)(
`${event.name}(${event.args.map((arg) => arg.argType).join(",")})`
)
);
}, []);
const parseEvent = (0, import_react3.useCallback)(
(event) => {
const abi = getEventAbi(event);
const eventTopic = getEventTopic(event);
const eventAbi = (0, import_viem2.parseAbi)([abi]);
const eventName = eventAbi[0].name;
const args = eventAbi[0].inputs.map(
(input) => ({
name: input.name ?? "",
argType: input.type,
isIndexed: !!input?.indexed
})
);
return { eventName, eventTopic, args };
},
[getEventAbi, getEventTopic]
);
const getEventId = (0, import_react3.useCallback)(
({
eventTopic,
numIndexedArgs
}) => {
return `${eventTopic}:${numIndexedArgs}`;
},
[]
);
const registerEventBrowser = (0, import_react3.useCallback)(
async (event) => {
const { eventName, eventTopic, args } = parseEvent(event);
const id = getEventId({
eventTopic,
numIndexedArgs: args.filter((arg) => arg.isIndexed).length
});
console.log("registering event", eventName, id, eventTopic, args);
await executeWriteBrowser("registerEventSchema", [
eventName,
id,
eventTopic,
args
]);
},
[executeWriteBrowser, getEventId, parseEvent]
);
const encodeEvent = (0, import_react3.useCallback)(
async (eventName, args) => {
if (!publicClient) {
throw new Error("Public client not found");
}
const eventSchema = await publicClient.readContract({
address: sourceAddress,
abi: EtherbaseSourceAbi,
functionName: "getEventSchemaFromName",
args: [eventName]
});
if (!eventSchema) {
throw new Error(`Event schema not found for ${eventName}`);
}
const topics = [];
const dataTypes = [];
const dataValues = [];
if (typeof args !== "object" || Array.isArray(args)) {
throw new Error("Parameter values must be an object");
}
const parameterValues = eventSchema.args.map(
(arg) => args[arg.name]
);
if (parameterValues.length !== eventSchema.args.length) {
throw new Error("Event arguments/values length mismatch");
}
eventSchema.args.forEach((arg, index) => {
const value = parameterValues[index];
if (arg.isIndexed) {
if (arg.argType === "string") {
topics.push((0, import_viem2.keccak256)((0, import_viem2.stringToBytes)(value)));
} else if (arg.argType.startsWith("bytes")) {
topics.push((0, import_viem2.keccak256)(value));
} else if (arg.argType === "address") {
topics.push((0, import_viem2.pad)(value, { size: 32 }));
} else if (arg.argType === "uint256" || arg.argType === "int256") {
topics.push((0, import_viem2.pad)((0, import_viem2.toHex)(value), { size: 32 }));
} else if (arg.argType === "bool") {
topics.push((0, import_viem2.pad)(value ? "0x01" : "0x00", { size: 32 }));
} else {
topics.push((0, import_viem2.pad)((0, import_viem2.toHex)(value), { size: 32 }));
}
} else {
dataTypes.push(arg.argType);
dataValues.push(value);
}
});
const data = (0, import_viem2.encodeAbiParameters)(
dataTypes.map((type) => ({ type })),
dataValues
);
return { parameterTopics: topics, parameterData: data };
},
[publicClient, sourceAddress]
);
const emitEventBrowser = (0, import_react3.useCallback)(
async ({ name, args }) => {
const { parameterTopics, parameterData } = await encodeEvent(name, args);
await executeWriteBrowser("emitEvent", [
name,
parameterTopics,
parameterData
]);
},
[encodeEvent, executeWriteBrowser]
);
const emitEventBackend = (0, import_react3.useCallback)(
async ({ name, args }) => {
if (!privateKey) {
throw new Error("No private key configured");
}
if (wsWriterUrl) {
WebSocketManager.get().emitEvent({
sourceAddress,
name,
args
});
} else if (httpReaderUrl) {
const response = await fetch(`${httpReaderUrl}/emit-event`, {
method: "POST",
body: JSON.stringify({ sourceAddress, name, args }),
headers: {
"x-private-key": privateKey
}
});
const data = await response.json();
console.log("Emit event response:", data);
} else {
throw new Error("No backend URL configured");
}
},
[privateKey, httpReaderUrl, wsWriterUrl, sourceAddress]
);
const emitEvent = (0, import_react3.useCallback)(
async ({ name, args }) => {
if (useBackend) {
console.log("emitting event backend", name, args);
await emitEventBackend({ name, args });
} else {
console.log("emitting event browser", name, args);
await emitEventBrowser({ name, args });
}
},
[useBackend, emitEventBackend, emitEventBrowser]
);
let DataType;
((DataType2) => {
DataType2[DataType2["NONE"] = 0] = "NONE";
DataType2[DataType2["STRING"] = 1] = "STRING";
DataType2[DataType2["BOOL"] = 2] = "BOOL";
DataType2[DataType2["UINT256"] = 3] = "UINT256";
DataType2[DataType2["INT256"] = 4] = "INT256";
DataType2[DataType2["BYTES"] = 5] = "BYTES";
})(DataType || (DataType = {}));
const setValueBrowser = (0, import_react3.useCallback)(
async (state) => {
const batchSetValues = [];
const processState = (obj, path = []) => {
for (const [key, value] of Object.entries(obj)) {
const currentPath = path.length === 0 ? [key] : [...path, key];
console.log("currentPath", currentPath);
if (value === null || value === void 0) {
batchSetValues.push({
segments: currentPath,
dataType: 0 /* NONE */,
data: "0x"
});
} else if (typeof value === "object") {
processState(value, currentPath);
} else if (typeof value === "string") {
batchSetValues.push({
segments: currentPath,
dataType: 1 /* STRING */,
data: (0, import_viem2.toHex)(value)
});
} else if (typeof value === "boolean") {
batchSetValues.push({
segments: currentPath,
dataType: 2 /* BOOL */,
data: value ? "0x01" : "0x00"
});
} else if (typeof value === "number") {
if (Number.isInteger(value)) {
const hexValue = (0, import_viem2.toHex)(BigInt(value), { size: 32 });
batchSetValues.push({
segments: currentPath,
dataType: value >= 0 ? 3 /* UINT256 */ : 4 /* INT256 */,
data: hexValue
});
} else {
throw new Error("Only integer numbers are supported");
}
} else {
throw new Error(`Unsupported value type: ${typeof value}`);
}
}
};
console.log("state", state);
processState(state);
console.log("batchSetValues", batchSetValues);
await executeWriteBrowser("setValues", [batchSetValues]);
},
[executeWriteBrowser]
);
const setValueBackend = (0, import_react3.useCallback)(
async (state) => {
if (!privateKey) return;
if (wsWriterUrl) {
WebSocketManager.get().setValue({
contractAddress: sourceAddress,
state
});
} else if (httpReaderUrl) {
const response = await fetch(`${httpReaderUrl}/set-value`, {
method: "POST",
body: JSON.stringify({ state }),
headers: {
"x-private-key": privateKey
}
});
const data = await response.json();
console.log("Set value response:", data);
} else {
throw new Error("No backend URL configured");
}
},
[privateKey, httpReaderUrl, wsWriterUrl, sourceAddress]
);
const setValue = (0, import_react3.useCallback)(
async (state) => {
if (useBackend) {
await setValueBackend(state);
} else {
await setValueBrowser(state);
}
},
[useBackend, setValueBackend, setValueBrowser]
);
const fetchEventDefinitions = (0, import_react3.useCallback)(async () => {
if (!sourceAddress) return;
try {
const definitions = await fetchEventDefinitionsData(sourceAddress);
setEventDefinitions(definitions);
} catch (error) {
console.error("Error fetching event definitions:", error);
}
}, [sourceAddress]);
(0, import_react3.useEffect)(() => {
fetchEventDefinitions();
const intervalId = setInterval(() => {
fetchEventDefinitions();
}, 1e3);
return () => clearInterval(intervalId);
}, [fetchEventDefinitions]);
return {
eventDefinitions,
fetchEventDefinitions,
registerEvent: registerEventBrowser,
emitEvent: useBackend ? emitEventBackend : emitEventBrowser,
setValue: useBackend ? setValueBackend : setValueBrowser
};
}
// src/hooks/useEtherstore.ts
function deepMerge(target, source) {
if (!target) {
return source;
}
if (!source) {
return target || {};
}
const output = { ...target };
for (const key in source) {
console.log("key", key, source[key], target[key]);
if (source[key] === null) {
delete output[key];
} else if (source[key] instanceof Object && key in target) {
output[key] = deepMerge(
target[key],
source[key]
);
} else {
output[key] = source[key];
}
}
return output;
}
function useEtherstore({
contractAddress,
path,
options = {},
onStateChange
}) {
useEtherbaseContext();
const [state, setState] = (0, import_react4.useState)({});
const [loading, setLoading] = (0, import_react4.useState)(true);
const [error, setError] = (0, import_react4.useState)(null);
const keyRef = (0, import_react4.useRef)(Math.random().toString(36).substring(2, 9));
if (!contractAddress) {
throw new Error("Contract address must be provided in path");
}
const statePath = (0, import_react4.useMemo)(() => path, [JSON.stringify(path)]);
const optionsMemo = (0, import_react4.useMemo)(() => options, [JSON.stringify(options)]);
const { useBackend } = getConfig();
const { setValue } = useEtherbaseSource({ sourceAddress: contractAddress });
const update = async (state2, merge = true) => {
if (!useBackend) {
await setValue(state2);
return;
}
console.log("Updating state", contractAddress, state2);
try {
await WebSocketManager.get().setValue({
contractAddress,
state: state2
});
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
throw err;
}
};
(0, import_react4.useEffect)(() => {
async function createSubscription() {
try {
await WebSocketManager.get().addStateSubscription(keyRef.current, {
contractAddress,
statePath,
options: optionsMemo,
onStateUpdate: (update2) => {
setLoading(false);
setState((prev) => {
const mergedState = deepMerge(prev, update2.state);
if (onStateChange) {
onStateChange(mergedState);
}
return mergedState;
});
}
});
} catch (err) {
console.error("Error creating subscription", err);
setError(err instanceof Error ? err.message : String(err));
setLoading(false);
return;
}
}
console.log("Creating subscription", contractAddress, statePath);
createSubscription();
return () => {
WebSocketManager.get().removeSubscription(keyRef.current);
};
}, [contractAddress, statePath, optionsMemo, onStateChange]);
return { state, loading, error, update };
}
// src/hooks/useEtherbaseEvents.ts
var import_react5 = require("react");
function useEtherbaseEvents({
contractAddress,
events,
onEvent
}) {
useEtherbaseContext();
const [error, setError] = (0, import_react5.useState)(null);
const localIdRef = (0, import_react5.useRef)(Math.random().toString(36).substring(2, 15));
const handleEvent = (0, import_react5.useCallback)(
(event) => {
try {
onEvent(event);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
}
},
[onEvent]
);
(0, import_react5.useEffect)(() => {
if (!contractAddress) {
setError("No contract address provided");
return;
}
const localId = localIdRef.current;
WebSocketManager.get().addEventSubscription(localId, {
contractAddress,
events,
onEvent: handleEvent
}).catch((err) => {
setError(err instanceof Error ? err.message : String(err));
});
return () => {
WebSocketManager.get().removeSubscription(localId);
};
}, [contractAddress, JSON.stringify(events), handleEvent]);
return { error };
}
// src/hooks/useEtherbase.ts
var import_react6 = require("react");
// src/abi/Etherbase.ts
var EtherbaseAbi = [
{
"inputs": [
{
"internalType": "address",
"name": "_validator",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ContractAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "ContractNotFound",
"type": "error"
},
{
"inputs": [],
"name": "EmptyContractABI",
"type": "error"
},
{
"inputs": [],
"name": "NotAuthorized",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "contractAddress",
"type": "address"
}
],
"name": "CustomContractAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "contractAddress",
"type": "address"
}
],
"name": "CustomContractDeleted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "contractAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "string",
"name": "eventName",