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.

🦊 MetaMask 🐰 Rabby 🔵 Coinbase Wallet 🦁 Brave Wallet 🖼️ Frame
// 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.

📱 Trust Wallet 🌈 Rainbow 🔷 Zerion + qualquer carteira WC
Instalação
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.

Instalação
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.

Instalação
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

ItemMuda conforme a carteira?
Código de conexão no siteSim — cada grupo tem seu próprio código
Corpo do POST /auth/loginNão — sempre { address, message, signature }
Corpo do POST /decryptNão — sempre { dlmBase64, publicKey, signature, message }
Validação no servidor da APINão — sempre ethers.verifyMessage()
Formato da assinaturaNã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çãoDescrição
Criptografar PDFRecebe um PDF e uma carteira Ethereum; gera um arquivo .dlm que só aquela carteira consegue abrir.
Descriptografar .dlmRecebe o arquivo .dlm e uma assinatura MetaMask; valida a posse e devolve o PDF.
Listar livrosRetorna todos os títulos (.dlm) associados a um endereço Ethereum.
Transferir posseRe-encripta o arquivo para um novo dono e invalida o .dlm anterior.
Autenticar usuárioEmite 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;
APIDLM.encrypt() Criptografa um PDF → retorna arquivo .dlm v3 em Base64
Parâmetros
pdfBase64 * string Conteúdo do PDF codificado em Base64
publicKey * string Endereço Ethereum do proprietário do arquivo (ex: 0xAbc...123)
userName * string Nome completo do proprietário — embutido no .dlm para rastreabilidade
userCPF * string CPF do proprietário — embutido no .dlm para identificação legal
licenseId string ID único da licença. Se omitido, o servidor gera automaticamente
title string Título do livro — salvo em texto nos metadados DLMm do arquivo
author string Autor do livro — salvo em texto nos metadados DLMm do arquivo
Retorno
dlmBase64 string Arquivo .dlm v3 gerado, em Base64 — pronto para download
licenseId string ID da licença gerada (use para referenciar o arquivo depois)
APIDLM.decrypt() Descriptografa um .dlm → retorna o PDF em Base64
Parâmetros
dlmBase64 * string Conteúdo do arquivo .dlm em Base64
publicKey * string Endereço Ethereum de quem está abrindo o arquivo
signature * string Assinatura MetaMask da mensagem abaixo (prova de posse da carteira)
message * string Mensagem assinada — formato: DLM:decrypt:{licenseId}:{timestamp}
Retorno
pdfBase64 string PDF descriptografado em Base64 — renderize com PDF.js, nunca salve em disco
APIDLM.busca() Lista os livros (.dlm) de um endereço Ethereum
Parâmetros
publicKey * string Endereço Ethereum do usuário
Retorno
books array Lista de objetos { title, author, licenseId } — um por arquivo .dlm associado
APIDLM.previewTransfer() Consulta o nome do destinatário antes de confirmar a transferência
Parâmetros
toPublicKey * string Endereço Ethereum do destinatário
licenseId * string ID da licença que será transferida
Retorno
toName string Nome cadastrado do destinatário — exiba ao usuário para confirmação antes de transferir
APIDLM.transfer() Transfere a posse de um .dlm para outro endereço
Parâmetros
fromPublicKey * string Endereço Ethereum do dono atual (remetente)
toPublicKey * string Endereço Ethereum do novo dono (destinatário)
licenseId * string ID da licença a ser transferida
signature * string Assinatura MetaMask do remetente autorizando a transferência
message * string Mensagem que foi assinada — formato: DLM:transfer:{licenseId}:{timestamp}
Retorno
dlmBase64 string Novo .dlm re-encriptado para o destinatário — entregue fora da plataforma (e-mail, download)
APIDLM.registerUser() Cadastra um usuário vinculando nome e CPF à carteira
Parâmetros
address * string Endereço Ethereum da carteira do usuário
name * string Nome completo do usuário
cpf * string CPF do usuário (necessário para transferências e rastreabilidade)
APIDLM.lookupUser() Consulta o nome e CPF cadastrados para uma carteira
Parâmetros
address * string Endereço Ethereum a consultar
Retorno
name string Nome completo cadastrado
cpf string CPF cadastrado

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.

1
Conectar MetaMask

Solicite acesso à carteira com eth_requestAccounts. Guarde o endereço retornado.

2
Solicitar desafio

GET /auth/challenge?address=0x... — retorna uma mensagem única com nonce de 5 minutos.

3
Assinar a mensagem

Use personal_sign no MetaMask. O usuário vê a mensagem antes de assinar — sem risco.

4
Fazer login

POST /auth/login com endereço + mensagem + assinatura. O servidor valida e emite um JWT (1h de validade).

5
Usar o JWT nas rotas protegidas

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.

GET /api/v1/auth/challenge
Query params
address * string Endereço Ethereum (0x + 40 chars hex)
Testar agora

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.

POST /api/v1/auth/login
Body JSON
address * string Endereço Ethereum
message * string Mensagem exata retornada por /auth/challenge
signature * string Assinatura ECDSA gerada pelo MetaMask (personal_sign)

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.

POST /api/v1/publisher/encrypt
Body JSON
pdfBase64 * string Conteúdo do PDF em Base64
publicKey * string Endereço Ethereum do proprietário do .dlm
userName * string Nome completo do proprietário (embutido no arquivo)
userCPF * string CPF do proprietário (embutido no arquivo)
licenseId string ID da licença — gerado automaticamente se omitido
title string Título do livro (salvo nos metadados DLMm)
author string Autor do livro (salvo nos metadados DLMm)

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.

POST /api/v1/decrypt
Body JSON
dlmBase64 * string Arquivo .dlm em Base64
publicKey * string Endereço Ethereum do solicitante
signature * string Assinatura MetaMask da mensagem abaixo
message * string Mensagem assinada no formato 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.

GET /api/v1/busca?publicKey=0x...
Query params
publicKey * string Endereço Ethereum do usuário
Testar agora

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.

POST /api/v1/transfer
Body JSON
fromPublicKey * string Endereço Ethereum do dono atual (remetente)
toPublicKey * string Endereço Ethereum do novo dono (destinatário)
licenseId * string ID da licença a transferir
signature * string Assinatura MetaMask do remetente autorizando a transferência
message * string Mensagem assinada

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.

POST /api/v1/licenses/:id/open 🔐 Bearer token
Path params
id * number licenseId lido do cabeçalho do arquivo .dlm (bytes 4–12)
Testar (requer JWT)

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.

GET /api/v1/licenses/mine 🔐 Bearer token
Testar

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.

GET /api/v1/wallet/:address
Path params
address * string Endereço Ethereum (0x + 40 chars hex)
Testar agora

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.

GET /api/v1/health
Testar agora

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.

POST /api/v1/encrypt
Body JSON
pdfBase64 * string PDF em Base64
publicKey * string Endereço Ethereum do titular (0x...)
userName * string Nome completo do titular (mín. 3 chars)
userCPF * string CPF do titular — 11 dígitos
licenseId string ID da licença — gerado automaticamente se omitido
title string Título do livro (metadados DLMm)
author string Autor do livro (metadados DLMm)

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.

POST /api/v1/transfer/preview
Body JSON
toPublicKey * string Endereço Ethereum do destinatário
licenseId * string ID da licença a transferir

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.

POST /api/v1/users/register
Body JSON
address * string Endereço Ethereum da carteira (0x + 40 hex)
name * string Nome completo (mín. 3 chars)
cpf * string CPF — 11 dígitos (com ou sem pontuação)
Testar agora

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.

GET /api/v1/users/:address
Path params
address * string Endereço Ethereum (0x + 40 hex)
Testar agora

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.

GET /api/v1/licenses/public/:id
Path params
id * string licenseId (número ou string — ex: 1234567890)
Testar agora

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.

PATCH /api/v1/licenses/:id/metadata
Body JSON
ownerAddress * string Endereço Ethereum do dono atual (autoriza a mudança)
title string Novo título (pelo menos um de title ou author deve ser informado)
author string Novo autor

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.

⚠️ Esta operação é irreversível. Após excluir, nenhum usuário conseguirá mais abrir o arquivo .dlm associado (o servidor não encontrará a cadeia de custódia).
DELETE /api/v1/licenses/:id
Path params
id * string licenseId a remover
Body JSON
ownerAddress * string Endereço Ethereum do dono atual — confirma a autorização

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.

POST /api/v1/publisher/books 🔐 Bearer token
Body JSON
title * string Título do livro
author * string Autor do livro
contentHash * string SHA-256 do PDF original — garante integridade do conteúdo
royaltyBps number Royalty em basis points (100 = 1%, máx. 3000 = 30%). Padrão: 500 (5%)

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 /api/v1/publisher/books/:bookId/mint 🔐 Bearer token
Path params
bookId * string ID do livro retornado em POST /publisher/books
Body JSON
buyerAddress * string Endereço Ethereum do comprador

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

1
Cadastro do usuário

POST /users/register — vincula nome e CPF à carteira Ethereum do comprador. Obrigatório antes da primeira compra.

2
Verificar biblioteca

GET /busca?publicKey=0x... — carrega a lista de livros do usuário para exibir na vitrine.

3
Compra → criptografar PDF

POST /encrypt — recebe o PDF do livro comprado e gera o .dlm vinculado à carteira do comprador. O arquivo .dlm é entregue via download.

4
Leitura → descriptografar

POST /decrypt com assinatura MetaMask — o servidor valida a posse e devolve o PDF. Renderizado em memória com PDF.js.

5
Vender ou emprestar

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

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

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

4. Descriptografar .dlm

5. Listar livros de um endereço

6. Verificar posse de licença (público)

7. Registrar usuário

8. Transferir licença

9. Remover licença

Portfólio