Sign in with OpenDunes from TypeScript — get an API key on your user's wallet with the PKCE flow.
With Sign in with OpenDunes, your app gets an API key on its user's wallet, with the user's approval. The flow is PKCE; the whole of it is on OAuth PKCE. The exchange needs no key of its own.
async function sha256Challenge(verifier: string) {
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
return btoa(String.fromCharCode(...new Uint8Array(hash)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}
const codeVerifier = crypto.randomUUID() + crypto.randomUUID(); // keep it until the exchange
const codeChallenge = await sha256Challenge(codeVerifier);
const authUrl = `https://opendunes.com/auth?${new URLSearchParams({
callback_url: "https://soukbot.example/callback",
code_challenge: codeChallenge,
code_challenge_method: "S256",
key_label: "Souk Bot",
})}`;
window.location.href = authUrl;callback_url is HTTPS, or HTTP on localhost or 127.0.0.1. The user signs in, may set a spending cap on the key, and chooses Authorize or Deny. On Authorize they come back to your callback with ?code=…; on Deny, with error=access_denied.
const code = new URLSearchParams(window.location.search).get("code");
const res = await fetch("https://opendunes.com/v1/auth/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code,
code_verifier: codeVerifier,
code_challenge_method: "S256",
}),
});
const { key, user_id } = await res.json();A code lives ten minutes and trades once. An unknown, expired or used code, or a verifier that does not match, is a 400.
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://opendunes.com/v1", apiKey: key });Every request on this key spends the user's wallet. The user sees the key among their own and can cap, pause or delete it.