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/

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/avatarNo 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/fetchCreate 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
expiryright 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.shgeneral uploads, no ownership check is performed. - CORS — All endpoints allow
*origins.PAYMENT-REQUIREDandX-PAYMENTheaders are exposed for browser access.