Carteiras compatíveis
A API valida apenas a assinatura criptográfica — não importa qual carteira a gerou.
Qualquer carteira que implemente personal_sign (EIP-191) é aceita.
O que muda entre carteiras é apenas o código de conexão no seu site;
o que é enviado para a API é sempre o mesmo: address, message e signature.
Como a API verifica a assinatura
Independente da carteira, o servidor executa sempre a mesma verificação:
// No servidor (Node.js + ethers.js) const recovered = ethers.verifyMessage(message, signature); if (recovered.toLowerCase() !== address.toLowerCase()) { throw new Error('Assinatura inválida'); }
Por isso, qualquer carteira que assine corretamente com a chave privada associada ao endereço é aceita.
Grupo 1 — Extensões de browser (código idêntico para todas)
Essas carteiras injetam window.ethereum no browser. O código de conexão é exatamente igual entre elas — o usuário escolhe qual instalar.
// Verificar se alguma carteira está instalada if (!window.ethereum) { alert('Nenhuma carteira Ethereum encontrada. Instale MetaMask ou Rabby.'); } // Conectar e obter o endereço async function conectar() { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); return accounts[0].toLowerCase(); // endereço do usuário } // Assinar uma mensagem (abre popup na carteira) async function assinar(address, message) { return window.ethereum.request({ method: 'personal_sign', params: [message, address], }); } // Uso completo com a API const address = await conectar(); const { message } = await ( await fetch(`${API}/auth/challenge?address=${address}`) ).json(); const signature = await assinar(address, message); // → agora chame POST /auth/login com { address, message, signature }
Grupo 2 — WalletConnect v2 (carteiras mobile via QR code)
Conecta qualquer carteira mobile (Trust Wallet, Rainbow, Zerion, etc.) via QR code ou deep link. Requer uma instalação de pacote e um Project ID gratuito em cloud.walletconnect.com.
npm install @walletconnect/ethereum-provider ethers
import { EthereumProvider } from '@walletconnect/ethereum-provider'; import { BrowserProvider } from 'ethers'; let wcProvider; async function conectarWalletConnect() { wcProvider = await EthereumProvider.init({ projectId: 'SEU_PROJECT_ID', // cloud.walletconnect.com → grátis chains: [1], // 1 = mainnet | 11155111 = Sepolia showQrModal: true, // exibe QR para scan no celular }); await wcProvider.connect(); const address = wcProvider.accounts[0].toLowerCase(); return address; } async function assinarWalletConnect(address, message) { const signer = await new BrowserProvider(wcProvider).getSigner(); return signer.signMessage(message); // equivalente a personal_sign } // Uso completo com a API — idêntico ao Grupo 1 daqui em diante const address = await conectarWalletConnect(); const { message } = await ( await fetch(`${API}/auth/challenge?address=${address}`) ).json(); const signature = await assinarWalletConnect(address, message); // → agora chame POST /auth/login com { address, message, signature }
Grupo 3 — Web3Modal (modal pronto que lista todas as opções)
Abre um modal com botões para MetaMask, WalletConnect, Coinbase e outras — o usuário escolhe. É a opção mais amigável quando se quer suportar múltiplas carteiras sem escrever o código de cada uma.
npm install @web3modal/ethers ethers
import { createWeb3Modal, defaultConfig } from '@web3modal/ethers'; const modal = createWeb3Modal({ projectId: 'SEU_PROJECT_ID', // cloud.walletconnect.com → grátis ethersConfig: defaultConfig({ metadata: { name: 'Nome do seu site', description: 'Descrição', url: 'https://seusite.com', } }), chains: [{ chainId: 1, name: 'Ethereum', currency: 'ETH', explorerUrl: 'https://etherscan.io', rpcUrl: 'https://cloudflare-eth.com', }], }); // Botão no HTML: <button onclick="modal.open()">Conectar carteira</button> // Após o usuário conectar, pegar provedor e assinar async function getAddressAndSign(message) { const provider = modal.getWalletProvider(); if (!provider) throw new Error('Nenhuma carteira conectada'); const ethersProvider = new ethers.BrowserProvider(provider); const signer = await ethersProvider.getSigner(); const address = (await signer.getAddress()).toLowerCase(); const signature = await signer.signMessage(message); return { address, signature }; }
Grupo 4 — wagmi (React hooks — abstrai tudo acima)
Se o seu site usa React, wagmi oferece hooks prontos para conexão e assinatura. Suporta MetaMask, WalletConnect, Coinbase e outras via conectores.
npm install wagmi viem @tanstack/react-query
// wagmi.config.js import { http, createConfig } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import { injected, walletConnect } from 'wagmi/connectors'; export const config = createConfig({ chains: [mainnet], transports: { [mainnet.id]: http() }, connectors: [ injected(), // MetaMask / Rabby walletConnect({ projectId: 'SEU_PROJECT_ID' }), // carteiras mobile ], }); // Componente de login import { useConnect, useAccount, useSignMessage } from 'wagmi'; function LoginButton() { const { connect, connectors } = useConnect(); const { address } = useAccount(); const { signMessageAsync } = useSignMessage(); async function login() { // Conectar com o primeiro conector disponível await connect({ connector: connectors[0] }); const { message } = await ( await fetch(`${API}/auth/challenge?address=${address}`) ).json(); const signature = await signMessageAsync({ message }); // → POST /auth/login com { address, message, signature } } return <button onClick={login}>Conectar carteira</button>; }
Resumo: o que muda e o que não muda
| Item | Muda conforme a carteira? |
|---|---|
| Código de conexão no site | Sim — cada grupo tem seu próprio código |
| Corpo do POST /auth/login | Não — sempre { address, message, signature } |
| Corpo do POST /decrypt | Não — sempre { dlmBase64, publicKey, signature, message } |
| Validação no servidor da API | Não — sempre ethers.verifyMessage() |
| Formato da assinatura | Não — todas produzem ECDSA secp256k1 (EIP-191) |
O que é a DLM-PDF API
A DLM-PDF API é um serviço de gestão de direitos digitais (DRM) para e-books em PDF baseado em blockchain. Ela protege a autorização de acesso, e não o arquivo em si: a posse de cada exemplar fica registrada numa carteira Ethereum, e a API permite distribuir, transferir e emprestar licenças de forma que apenas o titular atual consiga abrir o conteúdo.
O que a API faz
| Operação | Descrição |
|---|---|
| Criptografar PDF | Recebe um PDF e uma carteira Ethereum; gera um arquivo .dlm que só aquela carteira consegue abrir. |
| Descriptografar .dlm | Recebe o arquivo .dlm e uma assinatura MetaMask; valida a posse e devolve o PDF. |
| Listar livros | Retorna todos os títulos (.dlm) associados a um endereço Ethereum. |
| Transferir posse | Re-encripta o arquivo para um novo dono e invalida o .dlm anterior. |
| Autenticar usuário | Emite um JWT via assinatura MetaMask — sem senha, sem cadastro. |
Formato do arquivo .dlm
O .dlm é um PDF criptografado com cabeçalho proprietário. A versão atual é v3:
┌────────────────────────────────────────────────────────────┐
│ MAGIC 4 bytes "DLM\x03" — identificador de versão │
│ licenseId 8 bytes uint64 big-endian (ID único) │
│ ownerAddr 42 bytes endereço Ethereum ASCII "0x..." │
│ IV 16 bytes vetor de inicialização AES-256-CBC │
│ HMAC 32 bytes HMAC-SHA-256(licId+owner+IV+cipher) │
│ ciphertext N bytes AES-256-CBC(verifyCode[4B] ‖ PDF) │
│ └─ opcionalmente: "DLMm" + metaLen[2B] + JSON(title,author)│
└────────────────────────────────────────────────────────────┘
A chave AES nunca está no arquivo. Ela é derivada no servidor via HKDF-SHA256 usando a chave mestra + licenseId + ownerAddr. O campo verifyCode = SHA256(pdf)[0:4] é um identificador auxiliar: após a decifração, é recalculado e comparado para selecionar a chave correta na cadeia de custódia, sem expor o conteúdo. Ele não é mecanismo de proteção — a segurança vem da chave AES-256 derivada no servidor e do HMAC-SHA-256.
URL base
https://dlm-pdf-server-production.up.railway.app/api/v1
Todas as respostas são JSON. Erros seguem o formato {"error": "mensagem"}.
Configurar no seu site
Inclua o arquivo js/api.js no seu site e defina a URL da API antes de carregá-lo.
Todos os métodos ficam disponíveis via window.DLM.APIDLM.
1. Incluir no HTML
<script> // Defina antes de carregar api.js window.DLM_API_BASE = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; window.DLM_DRM_API_BASE = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; </script> <script src="js/api.js"></script> <!-- PDF.js — para renderizar o PDF descriptografado no browser --> <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
2. Métodos disponíveis via APIDLM
Após o carregamento, acesse os métodos pelo objeto global:
const { APIDLM } = window.DLM;
0xAbc...123)
DLM:decrypt:{licenseId}:{timestamp}
{ title, author, licenseId } — um por arquivo .dlm associado
DLM:transfer:{licenseId}:{timestamp}
3. Exemplo completo: criptografar um PDF
const { APIDLM } = window.DLM; // Converter o File para Base64 async function fileToBase64(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = e => resolve(e.target.result.split(',')[1]); reader.readAsDataURL(file); }); } // Criptografar e baixar o .dlm async function gerarDLM(pdfFile, walletAddress, userName, userCPF, title, author) { const pdfBase64 = await fileToBase64(pdfFile); const result = await APIDLM.encrypt( pdfBase64, walletAddress, userName, userCPF, undefined, // licenseId gerado automaticamente title, author ); // Baixar o arquivo .dlm const dlmBytes = Uint8Array.from(atob(result.dlmBase64), c => c.charCodeAt(0)); const blob = new Blob([dlmBytes], { type: 'application/octet-stream' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `licenca_${result.licenseId}.dlm`; a.click(); }
4. Exemplo completo: abrir um .dlm
const { APIDLM } = window.DLM; async function abrirDLM(dlmFile, walletAddress, container) { // Ler licenseId do cabeçalho (bytes 4–12) const buf = await dlmFile.arrayBuffer(); const view = new DataView(buf); const licenseId = ( BigInt(view.getUint32(4, false)) * BigInt(0x100000000) + BigInt(view.getUint32(8, false)) ).toString(); // Converter .dlm para Base64 const bytes = new Uint8Array(buf); let bin = ''; for (const b of bytes) bin += String.fromCharCode(b); const dlmBase64 = btoa(bin); // Assinar com MetaMask para provar posse const message = `DLM:decrypt:${licenseId}:${Date.now()}`; const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, walletAddress], }); // Descriptografar no servidor const result = await APIDLM.decrypt(dlmBase64, walletAddress, signature, message); const pdfBytes = Uint8Array.from(atob(result.pdfBase64), c => c.charCodeAt(0)); // Renderizar com PDF.js (nunca salvo em disco) const pdf = await pdfjsLib.getDocument({ data: pdfBytes.buffer }).promise; container.innerHTML = ''; for (let n = 1; n <= pdf.numPages; n++) { const page = await pdf.getPage(n); const viewport = page.getViewport({ scale: 1.4 }); const canvas = document.createElement('canvas'); canvas.width = viewport.width; canvas.height = viewport.height; container.appendChild(canvas); await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise; } }
Fluxo de autenticação
A API usa assinatura MetaMask como prova de identidade. Não há senha ou cadastro — o usuário prova que possui a carteira assinando uma mensagem gerada pelo servidor.
Solicite acesso à carteira com eth_requestAccounts. Guarde o endereço retornado.
GET /auth/challenge?address=0x... — retorna uma mensagem única com nonce de 5 minutos.
Use personal_sign no MetaMask. O usuário vê a mensagem antes de assinar — sem risco.
POST /auth/login com endereço + mensagem + assinatura. O servidor valida e emite um JWT (1h de validade).
Inclua o token no cabeçalho: Authorization: Bearer <token>.
Código de autenticação completo
const API = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; async function loginMetaMask() { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); const address = accounts[0].toLowerCase(); const { message } = await ( await fetch(`${API}/auth/challenge?address=${address}`) ).json(); const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, address], }); const { token } = await ( await fetch(`${API}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address, message, signature }), }) ).json(); return { token, address }; }
GET /auth/challenge
Gera um nonce único para um endereço Ethereum. A mensagem retornada deve ser assinada com MetaMask e enviada para /auth/login. Expira em 5 minutos.
Resposta 200
{ "message": "Bem-vindo ao DLM-PDF!\n\nNonce: abc123...\nExpira: ...", "nonce": "abc123...", "expiresAt": 1715000000000 }
POST /auth/login
Verifica a assinatura do desafio e emite um JWT com validade de 1 hora. Use o token no cabeçalho Authorization: Bearer <token> nas rotas protegidas.
Resposta 200
{ "token": "eyJ...", "address": "0x..." }
Erros
401 { "error": "Assinatura inválida: endereço não confere." } 401 { "error": "Desafio expirado. Solicite um novo." }
POST /publisher/encrypt
Criptografa um PDF e gera um arquivo .dlm v3 vinculado a uma carteira Ethereum específica. Somente o dono dessa carteira poderá descriptografar o arquivo.
Resposta 200
{ "dlmBase64": "base64 do arquivo .dlm gerado", "licenseId": "1234567890", "version": 3, "size": 48312 }
POST /decrypt
Descriptografa um arquivo .dlm. O servidor valida a assinatura MetaMask, confirma que o endereço é o dono do arquivo e devolve o PDF em Base64. O PDF nunca é salvo em disco — apenas trafega em memória.
DLM:decrypt:{licenseId}:{timestamp}
Resposta 200
{ "pdfBase64": "base64 do PDF descriptografado", "dlmBase64": "novo .dlm re-cifrado para o dono atual", "licenseId": "1234567890", "decryptedWith": "0x... — endereço usado para encontrar a chave correta", "metadata": { "title": "Dom Casmurro", "author": "Machado de Assis" }, "owner": { "address": "0x...", "name": "João Silva", "cpf": "12345678901" }, "version": 3 }
O campo dlmBase64 contém o arquivo .dlm re-cifrado com as chaves do dono atual — salve-o para substituir o arquivo anterior. O campo pdfBase64 deve ser renderizado em memória com PDF.js e nunca salvo em disco.
Erros
403 { "error": "Acesso negado: você não é o proprietário atual desta licença." } 401 { "error": "Assinatura expirada. Gere uma nova assinatura e tente novamente." } 404 { "error": "Licença 1234567890 não encontrada no registro." }
GET /busca
Lista todos os livros (.dlm) associados a um endereço Ethereum, retornando título, autor e licenseId de cada um. Use para exibir a biblioteca do usuário.
Resposta 200
{ "publicKey": "0x...", "books": [ { "title": "Dom Casmurro", "author": "Machado de Assis", "licenseId": "1234567890" } ] }
POST /transfer
Transfere a posse de um .dlm para outro endereço Ethereum. O servidor re-encripta o arquivo com a chave do novo dono e invalida o .dlm anterior. Requer assinatura MetaMask do dono atual como autorização.
Resposta 200
{ "success": true, "licenseId": "1234567890", "newOwner": "0x...", "dlmBase64": "novo .dlm para entregar ao destinatário" }
Fluxo recomendado antes de transferir
const { APIDLM } = window.DLM; // 1. Verificar quem é o destinatário antes de confirmar const preview = await APIDLM.previewTransfer(toPublicKey, licenseId); // preview.toName → exibir ao usuário para confirmar // 2. Assinar e executar const message = `DLM:transfer:${licenseId}:${Date.now()}`; const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, fromPublicKey], }); const result = await APIDLM.transfer(fromPublicKey, toPublicKey, licenseId, signature, message);
POST /licenses/:id/open
Valida a posse da licença via JWT e retorna a chave de sessão para descriptografar o .dlm localmente. Requer autenticação.
Resposta 200
{ "granted": true, "licenseId": "1", "sessionKey": "64-char hex — use para descriptografar", "expiresAt": 1715003600000 }
Erros
403 { "error": "Acesso negado. Você não é o proprietário desta licença." } 401 { "error": "Token de autenticação ausente." }
GET /licenses/mine
Retorna todas as licenças associadas ao endereço autenticado no JWT. Requer autenticação.
Resposta 200
{ "licenses": [ { "licenseId": "1", "bookId": "1", "owner": "0x...", "isLoanActive": false } ] }
GET /wallet/:address
Consulta pública (sem autenticação). Retorna todas as licenças de um endereço Ethereum com detalhes do livro associado.
Resposta 200
{ "address": "0x...", "totalLicenses": 1, "licenses": [ { "licenseId": "1", "owner": "0x...", "book": { "title": "...", "author": "...", "active": true } } ] }
GET /health
Verifica se o servidor está online. Retorna 200 quando operacional, 503 quando offline ou sem conexão com a blockchain.
Resposta 200 — modo connected
{ "status": "ok", "storage": "postgresql", "blockchain": { "mode": "connected", "chainId": 31337, "connected": true }, "latencyMs": 4, "version": "1.0.0" }
Resposta 503 — modo offline
{ "status": "offline", "troubleshoot": [ "1. Suba o node local: npx hardhat node", "2. Deploy: npx hardhat run scripts/deploy.js --network localhost", "3. Modo demo rápido: CONTRACT_ADDRESS=demo no .env" ] }
POST /encrypt
Versão pública do endpoint de criptografia — não requer JWT. Recebe PDF em Base64, endereço Ethereum, nome e CPF do titular. Gera um arquivo .dlm v3 e registra o usuário na cadeia de custódia. Use este endpoint no fluxo de compra.
Resposta 200
{ "dlmBase64": "base64 do .dlm gerado", "licenseId": "1234567890", "contentHash": "sha256 do PDF original", "size": 48312, "version": 3, "metadata": { "title": "Dom Casmurro", "author": "Machado de Assis" }, "owner": { "address": "0x...", "name": "João Silva", "cpf": "12345678901" } }
Exemplo curl
curl -X POST https://dlm-pdf-server-production.up.railway.app/api/v1/encrypt \
-H "Content-Type: application/json" \
-d '{
"pdfBase64": "'$(base64 -w0 livro.pdf)'",
"publicKey": "0xSeuEnderecoEthereum",
"userName": "João Silva",
"userCPF": "12345678901",
"title": "Dom Casmurro",
"author": "Machado de Assis"
}'
POST /transfer/preview
Consulta o nome e CPF do destinatário antes de confirmar a transferência. Não executa nenhuma modificação — apenas retorna dados para confirmação ao usuário. Sempre chame este endpoint antes de /transfer.
Resposta 200 — destinatário cadastrado
{ "newOwner": { "address": "0x...", "name": "Maria Santos", "cpf": "98765432100" }, "currentOwner": { "address": "0x..." }, "licenseId": "1234567890" }
Erro 404 — destinatário não cadastrado
{ "error": "Destinatário não cadastrado no sistema. O novo dono deve se registrar antes de receber transferências." }
Exemplo curl
curl -X POST https://dlm-pdf-server-production.up.railway.app/api/v1/transfer/preview \
-H "Content-Type: application/json" \
-d '{ "toPublicKey": "0xEnderecoDestinatario", "licenseId": "1234567890" }'
POST /users/register
Cadastra ou atualiza o vínculo entre um endereço Ethereum e os dados do usuário (nome + CPF). O registro é obrigatório para receber transferências de licenças. Não requer autenticação.
Resposta 200
{ "message": "Usuário registrado com sucesso.", "user": { "address": "0x...", "name": "João Silva", "cpf": "12345678901" } }
Exemplo curl
curl -X POST https://dlm-pdf-server-production.up.railway.app/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{ "address": "0xSeuEnderecoEthereum", "name": "João Silva", "cpf": "12345678901" }'
GET /users/:address
Consulta pública — sem autenticação. Retorna o nome e CPF cadastrados para um endereço Ethereum. Use para verificar se um usuário está cadastrado antes de iniciar uma transferência.
Resposta 200
{ "address": "0x...", "name": "João Silva", "cpf": "12345678901" }
Erros
404 { "error": "Usuário não cadastrado." } 400 { "error": "address Ethereum inválido." }
Exemplo curl
curl https://dlm-pdf-server-production.up.railway.app/api/v1/users/0xSeuEnderecoEthereum
GET /licenses/public/:id
Consulta pública do dono atual de uma licença no registro de custódia. Sem autenticação. Útil para verificar externamente a quem pertence uma licença.
1234567890)
Resposta 200
{ "licenseId": "1234567890", "currentOwner": { "address": "0x..." }, "transferCount": 2, "createdAt": "2026-05-18T12:00:00.000Z" }
PATCH /licenses/:id/metadata
Atualiza o título e/ou autor registrados para uma licença. Apenas o dono atual pode atualizar.
Resposta 200
{ "licenseId": "1234567890", "title": "Novo Título", "author": "Novo Autor" }
Exemplo curl
curl -X PATCH https://dlm-pdf-server-production.up.railway.app/api/v1/licenses/1234567890/metadata \
-H "Content-Type: application/json" \
-d '{ "ownerAddress": "0xSeuEndereco", "title": "Novo Título", "author": "Novo Autor" }'
DELETE /licenses/:id
Remove uma licença do registro de custódia. Apenas o dono atual pode excluir. Operação irreversível — o arquivo .dlm ainda existirá, mas o registro de posse será apagado.
Resposta 200
{ "deleted": true, "licenseId": "1234567890" }
Exemplo curl
curl -X DELETE https://dlm-pdf-server-production.up.railway.app/api/v1/licenses/1234567890 \
-H "Content-Type: application/json" \
-d '{ "ownerAddress": "0xSeuEnderecoEthereum" }'
POST /publisher/books
Registra um livro no contrato inteligente. Requer autenticação (JWT). O chamador torna-se o publisher do livro — somente ele pode emitir licenças (mint). Funciona apenas com blockchain conectada.
Resposta 201
{ "message": "Livro registrado.", "bookId": "1", "txHash": "0x..." }
POST /publisher/books/:bookId/mint
Emite um exemplar (licença) de um livro para um endereço Ethereum específico. Requer autenticação (JWT) e que o chamador seja o publisher do livro. Funciona apenas com blockchain conectada.
POST /publisher/books
Resposta 201
{ "message": "Licença emitida.", "licenseId": "42", "owner": "0x...", "txHash": "0x..." }
Livraria DLM — integração completa
A Livraria DLM (GitHub Pages) é o storefront de referência que demonstra o fluxo completo de compra e leitura usando esta API. Código em github.com/matheusmerlim1/trabalho-nilson.
Como a Livraria usa a API
POST /users/register — vincula nome e CPF à carteira Ethereum do comprador. Obrigatório antes da primeira compra.
GET /busca?publicKey=0x... — carrega a lista de livros do usuário para exibir na vitrine.
POST /encrypt — recebe o PDF do livro comprado e gera o .dlm vinculado à carteira do comprador. O arquivo .dlm é entregue via download.
POST /decrypt com assinatura MetaMask — o servidor valida a posse e devolve o PDF. Renderizado em memória com PDF.js.
POST /transfer/preview → confirmação → POST /transfer — transferência com assinatura MetaMask, re-criptografia automática para o novo dono.
Configurar a URL da API no seu site
<!-- js/api.js deve ser incluído após definir a URL --> <script> window.DLM_API_BASE = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; window.DLM_DRM_API_BASE = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; </script> <script src="js/api.js"></script>
Fluxo completo em JavaScript
const API = 'https://dlm-pdf-server-production.up.railway.app/api/v1'; // ── 1. Cadastrar usuário (uma vez) ────────────────────────────────── await fetch(`${API}/users/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: walletAddr, name: 'João Silva', cpf: '12345678901' }), }); // ── 2. Comprar livro: cifrar PDF ──────────────────────────────────── const pdfBase64 = await fileToBase64(pdfFile); const enc = await (await fetch(`${API}/encrypt`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pdfBase64, publicKey: walletAddr, userName: 'João Silva', userCPF: '12345678901', title: 'Dom Casmurro', author: 'Machado de Assis', }), })).json(); // enc.dlmBase64 → arquivo .dlm para download // enc.licenseId → guardar para referência futura // ── 3. Ler livro: descriptografar ─────────────────────────────────── const dlmBase64 = await fileToBase64(dlmFile); const licenseId = parseLicenseId(dlmFile); // bytes 4-12 do cabeçalho const message = `DLM:decrypt:${licenseId}:${Date.now()}`; const signature = await window.ethereum.request({ method: 'personal_sign', params: [message, walletAddr], }); const dec = await (await fetch(`${API}/decrypt`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dlmBase64, publicKey: walletAddr, signature, message }), })).json(); // dec.pdfBase64 → renderizar com PDF.js // ── 4. Transferir livro ───────────────────────────────────────────── // Primeiro verificar o destinatário: const preview = await (await fetch(`${API}/transfer/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ toPublicKey: toAddr, licenseId }), })).json(); // preview.newOwner.name → exibir ao usuário // Depois confirmar e assinar: const tMsg = `DLM:transfer:${licenseId}:${Date.now()}`; const tSig = await window.ethereum.request({ method: 'personal_sign', params: [tMsg, walletAddr], }); await fetch(`${API}/transfer`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fromPublicKey: walletAddr, toPublicKey: toAddr, licenseId, signature: tSig, message: tMsg, }), });
Exemplos curl — referência rápida
Todos os endpoints em um só lugar. Substitua API pela URL de produção ou http://localhost:3000/api/v1 para desenvolvimento local.
Variável de ambiente
export API=https://dlm-pdf-server-production.up.railway.app/api/v1
1. Verificar status
curl $API/health
2. Autenticação
# 1. Obter desafio curl "$API/auth/challenge?address=0xSeuEndereco" # 2. Fazer login (após assinar a mensagem com MetaMask) curl -X POST $API/auth/login \ -H "Content-Type: application/json" \ -d '{ "address": "0xSeuEndereco", "message": "Bem-vindo ao DLM-PDF!\n\nNonce: abc123...", "signature": "0x..." }'
3. Criptografar PDF
curl -X POST $API/encrypt \
-H "Content-Type: application/json" \
-d '{
"pdfBase64": "'$(base64 -w0 livro.pdf)'",
"publicKey": "0xSeuEndereco",
"userName": "João Silva",
"userCPF": "12345678901",
"title": "Dom Casmurro",
"author": "Machado de Assis"
}' | jq -r '.dlmBase64' | base64 -d > licenca.dlm
4. Descriptografar .dlm
# Necessário assinar a mensagem via MetaMask no browser primeiro
curl -X POST $API/decrypt \
-H "Content-Type: application/json" \
-d '{
"dlmBase64": "'$(base64 -w0 licenca.dlm)'",
"publicKey": "0xSeuEndereco",
"message": "DLM:decrypt:1234567890:1716000000000",
"signature": "0x..."
}' | jq -r '.pdfBase64' | base64 -d > livro.pdf
5. Listar livros de um endereço
curl "$API/busca?publicKey=0xSeuEndereco"
6. Verificar posse de licença (público)
curl $API/licenses/public/1234567890
7. Registrar usuário
curl -X POST $API/users/register \
-H "Content-Type: application/json" \
-d '{ "address": "0xEndereco", "name": "João Silva", "cpf": "12345678901" }'
8. Transferir licença
# Preview primeiro curl -X POST $API/transfer/preview \ -H "Content-Type: application/json" \ -d '{ "toPublicKey": "0xDestinatario", "licenseId": "1234567890" }' # Transferência (com assinatura MetaMask) curl -X POST $API/transfer \ -H "Content-Type: application/json" \ -d '{ "fromPublicKey": "0xRemetente", "toPublicKey": "0xDestinatario", "licenseId": "1234567890", "message": "DLM:transfer:1234567890:1716000000000", "signature": "0x..." }'
9. Remover licença
curl -X DELETE $API/licenses/1234567890 \
-H "Content-Type: application/json" \
-d '{ "ownerAddress": "0xSeuEndereco" }'