Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

React / viem

Upload to objekt.sh using React + viem. Works with Privy, wagmi, injected providers, or raw private keys.

You can now offer users the ability to choose their durability preference. Use it at https://ensmetadata.app/

Upload tier selector

Free tier (CDN)

Create a wallet client

import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";
import { sha256 } from "viem/utils";
 
const walletClient = createWalletClient({
  account,  // from useWallets(), useAccount(), privateKeyToAccount(), etc.
  chain: mainnet,
  transport: custom(window.ethereum),
});

Read the file and sign

const buffer = await file.arrayBuffer();
const bytes = new Uint8Array(buffer);
const dataURL = `data:${file.type};base64,${btoa(String.fromCharCode(...bytes))}`;
 
const hash = sha256(bytes);
const expiry = String(Date.now() + 60_000);
 
const sig = await walletClient.signTypedData({
  account,
  domain: { name: "Objekt", version: "1" },
  types: {
    Upload: [
      { name: "upload", type: "string" },
      { name: "expiry", type: "string" },
      { name: "name", type: "string" },
      { name: "hash", type: "string" },
    ],
  },
  primaryType: "Upload",
  message: {
    upload: "avatar",
    expiry,
    name: "yourname.eth",
    hash,
  },
});

Upload

const res = await fetch("https://ens.objekt.sh/yourname.eth/avatar", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    expiry,
    dataURL,
    sig,
    unverifiedAddress: account.address,
  }),
});
 
const { name, kind, bytes, permalink } = await res.json();
// permalink → https://ens.objekt.sh/yourname.eth/avatar

No payment, no API keys. That's it for the free tier.

Paid tier (Arweave / IPFS)

For permanent storage, add ?storage=arweave or ?storage=ipfs. The server returns HTTP 402 and the client handles payment automatically.

pnpm add @x402/core @x402/evm @x402/fetch

Create a payment-wrapped fetch

import { x402Client } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { wrapFetchWithPayment } from "@x402/fetch";
 
const signer = {
  address: account.address,
  signTypedData: (msg) =>
    walletClient.signTypedData({ account, ...msg }),
};
 
const client = new x402Client();
client.register("eip155:8453", new ExactEvmScheme(signer));
const paidFetch = wrapFetchWithPayment(fetch, client);

Upload with payment

const res = await paidFetch(
  "https://ens.objekt.sh/yourname.eth/avatar?storage=arweave",
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ expiry, dataURL, sig, unverifiedAddress: account.address }),
  },
);
 
const { permalink, uri, contenthash } = await res.json();
// permalink → https://ar.objekt.sh/{txId}
// uri       → ar://{txId}

Notes

  • Expiry — Signatures are valid for 60 seconds. Generate expiry right before uploading.
  • Hash — SHA-256 of the raw file bytes (not the base64 string).
  • ENS ownership — Server checks that the signer owns the ENS name on-chain. For api.objekt.sh general uploads, no ownership check is performed.
  • CORS — All endpoints allow * origins. PAYMENT-REQUIRED and X-PAYMENT headers are exposed for browser access.