.7"/>`;
} else if (sym === 6) {
o += ``;
o += ``;
o += ``;
o += ``;
o += ``;
o += ``;
o += ``;
o += ``;
o += ``;
} else if (sym === 7) {
o += ``;
}
// Pattern (scaled with reputation)
if (pat === 1) {
for (let x = 170; x <= 330; x += 36) o += ``;
for (let y = 170; y <= 310; y += 36) o += ``;
} else if (pat === 2) {
for (let i = 0; i < cx * 2 && i < 20; i++) {
o += ``;
}
} else if (pat === 3) {
for (let i = 0; i < cx && i < 10; i++) {
const x = 180 + i * 15;
o += ``;
}
} else if (pat === 4) {
for (let i = 0; i < cx && i < 8; i++) {
const x1 = 200 + (i * 31 % 100), y1 = 200 + (i * 47 % 80);
const x2 = 200 + ((i + 1) * 31 % 100), y2 = 200 + ((i + 1) * 47 % 80);
o += ``;
o += ``;
}
} else if (pat === 5) {
for (let i = 1; i <= cx && i <= 5; i++) {
o += ``;
}
}
// Activity nodes (module dots in orbit)
if (mods > 0) {
for (let i = 0; i < Math.min(mods, 8); i++) {
const angle = (i / Math.min(mods, 8)) * Math.PI * 2 - Math.PI / 2;
const rx = 95, ry = 95;
const nx = 250 + Math.cos(angle) * rx;
const ny = 240 + Math.sin(angle) * ry;
o += ``;
}
}
// Text overlays
o += `EXOSKELETON`;
o += `#${displayId}`;
o += `${escHtml(displayName)}`;
if (genesis) o += 'GENESIS';
// Stats bar
o += ``;
o += `MSG:${msgs}`;
o += `STO:${sto}`;
o += `MOD:${mods}`;
// Reputation glow
if (repScore >= 100) {
const glowIntensity = Math.min(repScore / 10000, 0.5);
o += ``;
}
return o;
}
// ── Parse Visual Config Bytes ──
function parseVisualConfig(configHex) {
if (!configHex || configHex.length < 18) return null;
const h = configHex.replace('0x', '');
return {
shape: parseInt(h.substring(0, 2), 16),
pr: parseInt(h.substring(2, 4), 16),
pg: parseInt(h.substring(4, 6), 16),
pb: parseInt(h.substring(6, 8), 16),
sr: parseInt(h.substring(8, 10), 16),
sg: parseInt(h.substring(10, 12), 16),
sb: parseInt(h.substring(12, 14), 16),
symbol: parseInt(h.substring(14, 16), 16),
pattern: parseInt(h.substring(16, 18), 16),
};
}
// ── Wait for ethers.js to load, then init ──
function onReady(fn) {
if (typeof ethers !== 'undefined') {
initProvider();
fn();
} else {
const check = setInterval(() => {
if (typeof ethers !== 'undefined') {
clearInterval(check);
initProvider();
fn();
}
}, 50);
setTimeout(() => clearInterval(check), 10000);
}
}
// ── Marketplace Helpers ──
let _marketplaceIface = null;
function getMarketplaceIface() {
if (!_marketplaceIface) _marketplaceIface = new ethers.Interface(MARKETPLACE_ABI);
return _marketplaceIface;
}
async function loadMarketplaceStats() {
initProvider();
const stats = await withFallback(() => contracts.marketplace.getStats());
return {
totalModules: Number(stats.totalModules),
totalApproved: Number(stats.totalApproved),
totalActivations: Number(stats.totalActivations),
totalPlatformRevenue: stats.totalPlatformRevenue,
pendingCount: Number(stats.pendingCount),
listingFees: stats.listingFees,
};
}
async function loadAllMarketplaceModules() {
initProvider();
const names = await withFallback(() => contracts.marketplace.getAllModuleNames());
if (names.length === 0) return [];
const mc = new ethers.Contract(MULTICALL3, MULTICALL_ABI, provider);
const mIface = getMarketplaceIface();
const calls = names.map(n => ({
target: CONTRACTS.marketplace,
callData: mIface.encodeFunctionData('getModule', [n]),
}));
const results = await mc.tryAggregate(false, calls);
const modules = [];
for (let i = 0; i < names.length; i++) {
if (results[i].success) {
try {
const d = mIface.decodeFunctionResult('getModule', results[i].returnData);
modules.push({
nameHash: names[i],
builder: d.builder,
name: d.name,
description: d.description,
version: d.version,
price: d.price,
status: Number(d.status),
submittedAt: Number(d.submittedAt),
approvedAt: Number(d.approvedAt),
activations: Number(d.moduleActivations),
revenue: d.moduleRevenue,
});
} catch (e) {}
}
}
return modules;
}
async function loadBuilderProfile(address) {
initProvider();
const b = await withFallback(() => contracts.marketplace.getBuilder(address));
return {
name: b.name,
bio: b.bio,
modulesSubmitted: Number(b.modulesSubmitted),
totalEarnings: b.totalEarnings,
registered: b.registered,
};
}
// ── Exports (attach to window for use in HTML pages) ──
window.ExoCore = {
CONTRACTS,
CHAIN_ID,
CHAIN_ID_HEX,
RPC_URLS,
SHAPES,
SYMBOLS,
PATTERNS,
CORE_ABI,
REGISTRY_ABI,
RENDERER_ABI,
WALLET_ABI,
MARKETPLACE_ABI,
get provider() { return provider; },
get contracts() { return contracts; },
get account() { return walletAccount; },
get signer() { return walletSigner; },
initProvider,
withFallback,
withRetry,
loadTokenBatch,
loadAllTokens,
renderTokenSVG,
connectWallet,
autoConnect,
getSignedContract,
getOwnedTokens,
loadMarketplaceStats,
loadAllMarketplaceModules,
loadBuilderProfile,
getTrustTier,
truncAddr,
formatAge,
formatScore,
formatEth,
escHtml,
renderSVGPreview,
parseVisualConfig,
onReady,
};