// npm install @noble/curves @noble/hashes
import { secp256k1 } from '@noble/curves/secp256k1';
import { sha256 } from '@noble/hashes/sha256';
import { randomUUID } from 'node:crypto';
class BlooBank {
constructor(accessKey, privateKeyHex) {
this.baseUrl = 'https://txengine.bloobank.com/txengine/v1';
this.accessKey = accessKey;
this.privateKey = privateKeyHex;
}
sign(method, pathname, rawBody) {
const timestamp = Date.now().toString();
const requestId = randomUUID();
const bodyHex = Buffer.from(sha256(rawBody)).toString('hex');
const canonical = `${this.accessKey}:${requestId}:${timestamp}:${method.toUpperCase()}:${pathname}:${bodyHex}`;
const digest = sha256(new TextEncoder().encode(canonical));
const sigBytes = secp256k1.sign(digest, this.privateKey, { lowS: true }).toCompactRawBytes();
const signatureB64 = Buffer.from(sigBytes).toString('base64');
return { timestamp, requestId, signatureB64 };
}
async request(method, pathname, body) {
const rawBody = body ? new TextEncoder().encode(JSON.stringify(body)) : new Uint8Array(0);
const { timestamp, requestId, signatureB64 } = this.sign(method, pathname, rawBody);
const res = await fetch(`${this.baseUrl}${pathname}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-Access-Key': this.accessKey,
'X-Access-Timestamp': timestamp,
'X-Access-Request-Id': requestId,
'X-Access-Signature': signatureB64,
},
body: body ? rawBody : undefined,
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
return res.json();
}
createPaymentOrder(wallet, body) {
return this.request('POST', `/wallets/${wallet}/paymentOrders`, body);
}
}
// ---- usage ----
const client = new BlooBank(
process.env.BLOOBANK_ACCESS_KEY,
process.env.BLOOBANK_PRIVATE_KEY, // 64-char hex
);
const order = await client.createPaymentOrder('production-main', {
direction: 'IN',
network: 'br.gov.bcb.pix',
idempotencyKey: 'invoice-2026-0184',
amount: 25000, // BRL 250.00 in cents
currency: 'BRL',
instrument: { type: 'PIX_CASH_IN_EMV_DYNAMIC', expiresIn: 86400 },
});
console.log(order);