A Documentação de API do Hamsa Privacy foi criada para dar ao seu time o controle total sobre a integração com nossa solução de privacidade em L2. Ela mostra como operar com provas ZKP, executar contratos, rotear transações e conectar com o L1 — tudo com segurança, escalabilidade e foco em conformidade. É o guia que transforma complexidade técnica em integração fluida e pronta para produção.
Introdução
A Hamsa Privacy é uma solução de Camada 2 (Layer 2) construída sobre o ZKP System da Microsoft, projetada para oferecer privacidade, escalabilidade e conformidade para instituições financeiras. Ao permitir que instituições operem nós dedicados e isolados, ela garante que dados sensíveis permaneçam privados enquanto suporta aplicações baseadas em blockchain, como contratos inteligentes e rollups de transações. A Hamsa Privacy integra-se com frameworks bancários existentes e oferece ferramentas para gerenciar a confidencialidade de dados em ambientes regulados, sendo adequada para casos de uso como CBDCs (Moedas Digitais de Bancos Centrais) e transações financeiras seguras.
Contexto
A Hamsa Privacy, construída sobre o ZKP System da Microsoft, é uma solução única de Camada 2 para a indústria financeira, projetada para resolver os seguintes desafios:
- Suportar privacidade para aplicações e dados bancários
- Suportar aplicações bancárias versáteis em uma plataforma baseada em blockchain
- Suportar SLA (Acordo de Nível de Serviço) de velocidade de transação, throughput e custo
A Hamsa Privacy, construída sobre o ZKP System da Microsoft, está em fase beta. Novos casos de uso, requisitos, programas piloto e parceiros tecnológicos são bem-vindos.
Arquitetura
A arquitetura da Camada 2 da Hamsa Privacy é mostrada na Figura 1.

Figura 1. Arquitetura da plataforma Hamsa Privacy
Na Figura 1, cada instituição financeira (por exemplo, banco) terá seu próprio nó de Camada 2. Quem opera esses nós para as instituições financeiras e quem fornece disponibilidade não está incluído no escopo deste documento. Ao longo deste documento, usaremos os termos "banco" e "instituição financeira" de forma intercambiável.
A Hamsa Privacy é 100% compatível com EVM (Máquina Virtual Ethereum). Bancos clientes executam seus contratos inteligentes em seus próprios nós Hamsa Privacy. Diferentemente de outras redes públicas de Camada 2, os dados armazenados no livro-razão não são sincronizados com outros nós Hamsa Privacy por questões de privacidade.
A Hamsa Privacy utiliza o Microsoft (MSFT) Nova para realizar o rollup de transações e submetê-las à rede de Camada 1 para verificação. As informações críticas do rollup (por exemplo, os valores de hash da árvore de Merkle de transações e saldos) são salvas na rede de Camada 1 para verificação e rastreamento. Cada nó Hamsa Privacy possui seu próprio verificador de rollup de Camada 1, mas todos compartilham o mesmo contrato inteligente Dvp-match.
Arquitetura do Nó Hamsa Privacy
Diagrama do Nó
O diagrama detalhado de um nó Hamsa Privacy é mostrado na Figura 2.

Figura 2. Arquitetura de um nó Hamsa Privacy
Utilizamos o padrão de microsserviços na implementação da Hamsa Privacy. Suas responsabilidades são listadas abaixo:
- Nó: armazena transações recebidas no
tx_pool, gerencia o ciclo de vida das transações, coordena o fluxo de Dvp e Rollup. - Executor: executa transações usando uma EVM.
- Prover: gera prova de rollup para lotes de transações.
O componente ethTxManger na Figura 2 é responsável por chamar contratos inteligentes na Camada 1. Ele tentará novamente transações da Camada 1 que falharem, se necessário.
O componente synchronizer é responsável por buscar eventos da Camada 1 e atualizar o status das transações da Camada 2 (principalmente para a implementação de Dvp).
Controle de Acesso
Para segurança, implementamos um mecanismo de autenticação e autorização simples, porém poderoso.
Cada endereço pode ser atribuído a um papel na tabela statedb.state.address_role. Por padrão, definimos dois papéis:
- Papel (id=0): usuário normal. Usuários normais só podem consultar seus próprios saldos e transações.
- Papel (id=1): usuário administrador. Usuários administradores podem consultar todos os saldos e transações em um nó Hamsa Privacy.
Não é necessário criar uma linha na tabela address_role para usuários normais. Reguladores devem ter um endereço com o papel Id=1 em todos os nós Hamsa Privacy em sua jurisdição.
Utilizamos assinaturas de mensagens Ethereum para autenticação. Embora simples, nosso método de autorização pode ser facilmente estendido para fornecer um controle de acesso mais granular (por exemplo, implementar um sistema de controle de acesso granular em um gateway antes do nó Hamsa Privacy).
O controle de acesso dentro dos contratos inteligentes deve ser fornecido pela implementação do contrato inteligente. A Hamsa está trabalhando para fornecer uma biblioteca Ethereum.
Integração com a Camada 1
Um nó Hamsa Privacy precisa chamar dois contratos inteligentes implantados na Camada 1:
- Dvp-match: apenas uma instância de Dvp-match é implantada na Camada 1.
- Rollup-verifier: uma instância é implantada para cada nó Hamsa Privacy.
Normalmente, as redes de Camada 1 são instáveis. Muitas transações podem falhar por vários motivos; o componente ethTxManager executará a retentativa.
Para melhorar o desempenho e diminuir a chance de conflito de nós, uma lista de chaves privadas Ethereum da Camada 1 deve ser configurada para o ethTxManager submeter transações à rede de Camada 1.
Arquitetura de Rollup
Diagrama de Rollup

Diagrama 3. Arquitetura de Rollup
O objetivo do rollup é verificar se o saldo da conta (e outras informações) é atualizado corretamente de acordo com as transações em um lote. Um lote pode incluir vários blocos. O rollup é executado por lote por considerações de desempenho do circuito ZKP. As informações (ou seja, entradas e saídas públicas do circuito) armazenadas no contrato inteligente verificador da Camada 1 podem ser usadas para verificação e rastreamento.
Utilizamos a biblioteca de circuitos Microsoft Nova para geração e verificação de provas de rollup na Camada 1.
GitHub - microsoft/Nova, Nova: Argumentos recursivos de alta velocidade a partir de esquemas de dobramento
DSL de Rollup
Em vez de implementar um zkEVM para realizar o rollup de todas as transações, a Hamsa Privacy oferece uma solução leve. Definimos um conjunto de eventos de rollup, que definem o propósito e o efeito das funções de contratos inteligentes de gerenciamento de ativos. Executamos o rollup apenas para contratos inteligentes que emitem esses eventos, para os quais manteremos uma árvore de Merkle de saldos de contas no MongoDB. Quaisquer alterações na árvore de Merkle são enviadas ao Prover para geração de prova e posteriormente verificadas por um contrato inteligente de verificação da Camada 1.
event HamsaTransferMade(address indexed from, address indexed to, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaApprovalMade(address indexed owner, address indexed spender, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaMintMade(address indexed account, address indexed minter, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaBurnMade(address indexed account, address indexed burner, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaDepositMade(address indexed depositor, address indexed account, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaWithdrawMade(address indexed withdrawer, address indexed account, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaFrozenMade(address indexed account, address indexed freezer, uint256 value, uint256 fromBalance, uint256 toBalance);
event HamsaUnfrozenMade(address indexed account, address indexed unfreezer, uint256 value, uint256 fromBalance, uint256 toBalance);
Essa lista pode ser estendida no futuro conforme os requisitos de negócios.
Internamente, também usamos um método semelhante para provar o movimento de fundos em nosso contrato inteligente dvp-escrow. Definimos os seguintes eventos para um contrato inteligente do tipo escrow:
event EscrowScheduleTransfer(address indexed from, address indexed to, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowScheduleBurn(address indexed burner, address indexed account, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowScheduleMint(address indexed account, address indexed minter, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowTransfer(address indexed from, address indexed to, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowBurn(address indexed burner, address indexed account, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowMint(address indexed account, address indexed minter, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowReturn(address indexed escrow, address indexed account, address tokenAddress, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowScheduleTransfer1155(address indexed from, address indexed to, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowScheduleBurn1155(address indexed burner, address indexed account, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowScheduleMint1155(address indexed account, address indexed minter, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowTransfer1155(address indexed from, address indexed to, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowBurn1155(address indexed burner, address indexed account, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowMint1155(address indexed account, address indexed minter, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
event EscrowReturn1155(address indexed escrow, address indexed account, address tokenAddress, uint256 tokenType, uint256 value, uint256 fromBalance, uint256 toBalance);
Arquitetura DvP
Diagrama DvP

Diagrama 4. Arquitetura de DvP
O DvP é necessário apenas para transações entre bancos. Para transações dentro de um mesmo banco, os contratos inteligentes no mesmo nó de Camada 2 podem se chamar mutuamente. Para transações entre bancos (ou seja, transações interbancárias), a implementação de DvP ajuda a corroborar as transações em diferentes nós Hamsa Privacy.
No Diagrama 4, quando o executor detecta que uma função "scheduleXXX" do contrato inteligente dvp-escrow é executada, ela será incluída em um trabalho DvP. Ao final da execução do bloco de transações, as informações do trabalho DvP são salvas na tabela monitored_txs do PostgreSQL. Fornecemos 6 funções relacionadas ao DvP no contrato dvp-escrow:
function scheduleBurn(ScheduleRequest request)
function scheduleMint(ScheduleRequest request)
function scheduleTransfer(ScheduleRequest request)
function scheduleTransfer1155(ScheduleRequest request)
function scheduleBurn1155(ScheduleRequest request)
function scheduleMint1155(ScheduleRequest request)
Com o ScheduleRequest definido como:
struct ScheduleRequest {
address tokenAddress;
address to;
uint256 tokenType;
uint256 amount;
uint256 index;
uint256 chunkHash;
uint256 bundleHash;
uint256 expireTime;
}
As 6 funções acima podem ser usadas em diferentes combinações para implementar fluxos de trabalho de negócios.
Para os casos de uso da fase 2 do Drex, prevemos a necessidade de executar várias ações de tokens dentro de uma única solicitação DvP, então definimos uma nova função:
function scheduleBurnMintAndGenerate(ScheduleRequest2 memory request) external returns (bool)
Com o ScheduleRequest2 definido como:
struct ScheduleRequest2 {
uint256 index;
uint256 chunkHash;
uint256 bundleHash;
uint256 expireTime;
BurnRequest[] burnRequests;
MintRequest[] mintRequests;
BurnSettleRequest[] burnSettleRequests;
}
struct BurnRequest {
address tokenScAddress;
uint256 tokenType;
address account;
uint256 amount;
}
struct MintRequest {
address tokenScAddress;
uint256 tokenType;
address account;
uint256 amount;
}
struct BurnSettleRequest {
address tokenScAddress;
uint256 tokenType;
address account;
uint256 amount;
address toBankAddress;
}
A diferença entre BurnRequest e BurnSettleRequest é que o BurnRequest acionará a liquidação no L1-tmsc a cada 1 minuto.
Essa nova função substituirá as outras funções scheduleXXX. Para os casos de uso da fase 2, use apenas essa nova função. O uso dessa função e seus parâmetros são explicados em detalhes em nosso documento de tutorial de desenvolvimento.
Na nossa implementação atual de DvP, cada transação interbancária (ou seja, um bundle DvP) pode ter duas ou três subtransações, identificadas por um hash de chunk. O hash do bundle e os hashes de chunk têm a relação:
bundle\_hash = poseidon\_hash(chunk\_hash1, chunk\_hash2, chunk\_hash3)
Dentro do contrato inteligente dvp-match, implementamos a verificação de bundle e a verificação de recebimento.
No design do DvP, separamos intencionalmente o fluxo de transações de negócios e o fluxo de liquidação bancária, devido ao fato de que os fundos serão bloqueados no contrato inteligente dvp-escrow durante o processo DvP. O objetivo dessa separação é diminuir o impacto na liquidação bancária.
Os passos para o fluxo de trabalho de compra de TPFT no atacado são explicados no Diagrama 5.

Figura 5. Os passos para a compra de tokens TPFT no atacado
No fluxo de trabalho de compra de TPFT no atacado, a liquidação bancária e a transferência de tokens TPFT sempre ocorrem ao mesmo tempo.
Os passos para o fluxo de trabalho de compra de TPFT no varejo são explicados no Diagrama 6.

Diagrama 6. Os passos para a compra de TPFT no varejo
Os passos para o fluxo de trabalho de liquidação de compra de tokens RCDBC são explicados no Diagrama 7.

Diagrama 7. Os passos para a liquidação de compra de tokens RCDBC
APIs Suportadas
A equipe da Hamsa Privacy fornece APIs para permitir que sistemas de terceiros se integrem a um nó Hamsa Privacy. Assinaturas de mensagens Ethereum são usadas para autenticação. Por design, também implementamos apenas uma autorização mínima baseada em papéis nos nós Hamsa Privacy. É responsabilidade do proprietário do negócio do nó implementar uma política de controle de acesso mais granular (por exemplo, apenas usuários KYC podem acessar seu nó, e apenas nós KYB podem participar das transações DvP).
Autenticação
Usamos assinaturas de mensagens Ethereum para autenticação. O código de exemplo a seguir ilustra como gerar uma assinatura de mensagem e usá-la para autenticação em um script Hardhat (JavaScript):
const \[deployer] = await ethers.getSigners();\
let signature = await deployer.signMessage("hello");
let address = await deployer.getAddress();
let balance = await ethers.provider.send("eth\_safeGetBalance", \[address, null, "hello", signature]);
Outro exemplo:
await ethers.provider.send("eth_getTransactionHistory", ["hello", signature]);
Para usuários normais, essa API retornará seu próprio histórico de transações por página. Para usuários administradores, essa API retornará o histórico de transações de todos os usuários por página.
No MVP, apenas as seguintes APIs foram protegidas por autenticação e autorização:
- eth_safeGetBalance
- eth_getTransactionHistory
eth_estimateGas
const transaction = {
to: ethers.ZeroAddress,
value: 0
};
const gasLimit = await ethers.provider.estimateGas(transaction);
console.log("gasLimit:", gasLimit.toString());
Nota: ao testar contratos inteligentes usando Hardhat, o Hardhat chamará automaticamente a API estimateGas.
eth_getNonce
const \[deployer] = await ethers.getSigners();\
let nonce = await deployer.getNonce();
Nota: ao testar contratos inteligentes usando Hardhat, o Hardhat chamará automaticamente a API getNonce.
Submeter Transação
Se chamarmos funções de contrato inteligente usando um script Hardhat, o Hardhat chamará getNonce e estimateGas automaticamente. O exemplo a seguir mostra como implantar um contrato inteligente ERC20:
async function deploySimple() {
const [deployer] = await ethers.getSigners();
const SimpleToken = await ethers.getContractFactory("Simple");
const simple = await SimpleToken.deploy("simple", "$simple");
await simple.waitForDeployment();
console.log("simple is deployed at ", await simple.getAddress());
}
Em alguns casos especiais (por exemplo, testes de estresse), precisaremos criar o corpo da transação e atribuir um nonce:
async function scheduleL2Transfer(escorting, uclWallet, nonce, chunkHash, bundleHash, expire) {
let schduleRequest = {
tokenAddress: simpleAddress,
to: '0x08883F8d938055aed23b0A64dcd7fD140028F648',
tokenType: 0,
amount: 100,
index: 0,
chunkHash: chunkHash,
bundleHash: bundleHash,
expireTime: expire
};
const data = await escorting.interface.encodeFunctionData("scheduleTransfer", [schduleRequest]);
return uclWallet.sendTransaction({
type: 0,
to: escortingAddress,
nonce: nonce,
value: 0,
gasLimit: 700000,
gasPrice: 1000,
data: data
});
}
Por exemplo, em testes de estresse, o gasLimit pode ser atribuído a um valor grande. Para atribuir manualmente o nonce no código, podemos chamar getNonce para obter o nonce antes do teste e, em seguida, incrementá-lo em 1 por iteração.
eth_getTransactionByHash
Após submeter uma transação, podemos usar o seguinte código para obter o hash da transação e o recibo:
let tx = await simple.approve(escortingAddress, 100 * 1000000);
console.log("tx: ", tx.hash);
let receipt = await tx.wait();
Para obter informações da transação posteriormente, podemos usar o seguinte RPC Ethereum:
async function getTransactionProof() {
let info = await ethers.provider.send("eth_getTransactionByHash", ["0x0377c857f08bbd20b448eca3f54c933ba784c582bc6f5ea36a78c8f9e82c717c", false]);
console.log("tx info", info.transactionProof[0]);
}
A prova de transação retornada e as provas de remetente/destinatário podem ser verificadas no contrato inteligente verificador da Camada 1.
async function verifyProof() {
const L1Verifier = await verifier.attach("0x7056D9dc2B210769D4F96e1f36FbB278Be9A922F");
let smart_contract_address = "0x02f7aC504d940bb1f8C84724502745c787d6BaFa";
let balanceRoot = "0x95AF5372518C1C4A0D4B5C65EF25058B382FC166F792CE3EFF787BF6F628970";
let result = await verifier.queryBalanceRootExist(smart_contract_address, balanceRoot);
console.log("balance verification result: ", result);
let transactionRoot = "1108551082811432058010680812812024348758747321620579242231990872088052780371";
result = await verifier.queryTxRootExist(smart_contract_address, transactionRoot);
console.log("transaction verification result: ", result);
}
eth_getBalance
let balance = await ethers.provider.getBalance(address);\
console.log("balance: ", balance);
Essa função não é protegida por autenticação e autorização.
eth_checkTransactionBundle
Verifica informações e status de transações DvP:
async function getBundleInfo() {
let bundleHash = "0x1f774b3c209088a4117a111ec005fc1cf9ab95b970a3e29c966addf77a83f7ee";
let BundleTransaction = await ethers.provider.send("eth_checkTransactionBundle", [bundleHash]);
console.log("bundle", BundleTransaction);
}
eth_blockNumber
Retorna o número do último bloco:
async function lastL2Block() {
let lastBlock = await ethers.provider.send("eth_blockNumber", []);
console.log("last Block: ", lastBlock);
}
eth_call
Permite a execução instantânea de uma nova chamada de mensagem sem exigir a criação de uma transação na blockchain:
async function ethCallSample() {
const [deployer] = await ethers.getSigners();
const SimpleToken = await ethers.getContractFactory("Simple");
const simple = await SimpleToken.attach(simpleAddress);
const data = await simple.interface.encodeFunctionData("balanceOf", ["0x08883F8d938055aed23b0A64dcd7fD140028F648"]);
const params = {
to: simpleAddress,
data: data
};
let tx = await ethers.provider.send('eth_call', [params, 'latest']);
console.log("tx", tx);
}
eth_chainId
Retorna o ID da cadeia de Camada 2:
async function chaiId() {
let chaiId = await ethers.provider.send("eth_chainId", []);
console.log("chainId: ", chaiId);
}
eth_gasPrice
Retorna a taxa base de gás atual da rede de Camada 2:
async function gasPrice() {
let chaiId = await ethers.provider.send("eth_gasPrice", []);
console.log("chainId: ", chaiId);
}
eth_getBlockByHash
Recupera informações sobre um bloco específico de Camada 2 com um hash de bloco específico:
async function blockByHash() {
let blockHash = "0x89ac6ed7fda6be507454991e1faabfbb1ef7e88bc310802605fdeb8442b4c501";
let block = await ethers.provider.send("eth_getBlockByHash", [blockHash, false]);
console.log("block: ", block);
}
eth_getBlockByNumber
async function blockByNumber() {
let block = await ethers.provider.send("eth_getBlockByNumber", [8, false]);
console.log("block: ", block);
}
eth_getBlockTransactionCountByHash
Recupera a contagem de transações de um bloco com um hash de bloco específico:
async function transactionCountByHash() {
let blockHash = "0x579cb518a0528eda691d45edd21ff95e082aa8a6bafa2f7c77c93a389f73d8c4";
let block = await ethers.provider.send("eth_getBlockTransactionCountByHash", [blockHash]);
console.log("block: ", block);
}
eth_getBlockTransactionCountByNumber
Recupera a contagem de transações de um bloco com um número de bloco específico:
async function transactionCountByNumber() {
let block = await ethers.provider.send("eth_getBlockTransactionCountByNumber", [26]);
console.log("block: ", block);
}
eth_getTransactionReceipt
Recupera o recibo de transação com um hash de transação específico:
async function printReceipt() {
const SimpleToken = await ethers.getContractFactory("Simple");
const simple = await SimpleToken.attach(simpleAddress);
let tx = await simple.mint("0x08883F8d938055aed23b0A64dcd7fD140028F648", 10);
let r = await tx.wait();
console.log("receipt: ", r);
}
eth_sendRawTransaction
Permite submeter uma transação assinada à rede. Após uma transação ser assinada, você pode usar o método eth_sendRawTransaction para submeter a transação assinada à rede Ethereum para processamento.
