import crypto from "crypto";
import { ethers } from "ethers";
const API_URL = "https://api.sandbox.forja.capital";
const API_KEY = "ak_sandbox_...";
const API_SECRET = "sk_sandbox_...";
const TOKEN_ID = "your-token-id";
const MINTER_PRIVATE_KEY = "0x..."; // treasury burn signer
const minter = new ethers.Wallet(MINTER_PRIVATE_KEY);
const amount = "100.00";
const clientReference = crypto.randomUUID();
const receiptTxHash = "0x..."; // on-chain transfer of tokens to treasury
function signRequest(method, path, body = "") {
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomUUID();
const signingString = [method.toUpperCase(), path, body, timestamp, nonce].join("\n");
const signature = crypto.createHmac("sha256", API_SECRET).update(signingString).digest("hex");
return {
...(body ? { "Content-Type": "application/json" } : {}),
"x-api-key": API_KEY,
"x-timestamp": timestamp,
"x-nonce": nonce,
"x-signature": signature,
};
}
async function forja(method, path, payload, extraHeaders = {}) {
const body = payload === undefined ? "" : JSON.stringify(payload);
const response = await fetch(`${API_URL}${path}`, {
method,
headers: { ...signRequest(method, path, body), ...extraHeaders },
body: body || undefined,
});
const data = await response.json();
if (!response.ok) throw new Error(`${method} ${path} ${response.status}: ${JSON.stringify(data)}`);
return data;
}
async function signUnsignedTx(unsigned) {
return minter.signTransaction({
type: 2,
chainId: unsigned.chainId,
nonce: unsigned.nonce,
to: unsigned.to,
data: unsigned.data,
value: BigInt(unsigned.value),
gasLimit: BigInt(unsigned.gasLimit),
maxFeePerGas: BigInt(unsigned.maxFeePerGas),
maxPriorityFeePerGas: BigInt(unsigned.maxPriorityFeePerGas),
});
}
// 1. Create redemption
const redemption = await forja(
"POST",
`/v1/tokens/${TOKEN_ID}/redemptions`,
{
amount,
clientReference,
redeemerAddress: "0xUserWallet",
metadata: { network: "polygon-amoy" },
},
{ "Idempotency-Key": clientReference }
);
console.log("created", redemption.id, redemption.status);
// 2. Confirm treasury received tokens
const withBurn = await forja("POST", `/v1/redemptions/${redemption.id}/receipt`, {
receiptTxHash,
});
console.log("receipt confirmed", withBurn.status);
// 3. Sign burn and submit
const rawSignedTransaction = await signUnsignedTx(withBurn.unsignedTransaction);
const submitted = await forja("POST", `/v1/redemptions/${redemption.id}/signed`, {
rawSignedTransaction,
});
console.log("submitted", submitted.status, submitted.txHash);
// 4. Poll until terminal status
let current = submitted;
while (!["confirmed", "failed", "expired"].includes(current.status)) {
await new Promise((r) => setTimeout(r, 5000));
current = await forja("GET", `/v1/redemptions/${redemption.id}`);
console.log("poll", current.status, current.txHash);
}