Como criar um protocolo DeFi em Solana

Web3 e Blockchain

Como criar um protocolo DeFi em Solana

Luiz Duarte
Escrito por Luiz Duarte em 21/05/2026
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

As primeiras criptomoedas surgiram, a exemplo de Bitcoin, como um meio de substituir as moedas fiduciárias (fiat) que quebraram a economia mundial em 2008. Mas não foi apenas culpa da gestão dos governos sobre o dólar e outras moedas governamentais, mas também culpa da irresponsabilidade e ganância dos bancos e instituições financeiras. A resposta a esse mercado em torno das fiats foi o surgimento do conceito de DeFi, ou Decentralized Finance, as Finanças Descentralizadas, onde temos serviços financeiros rodando de forma autônoma na blockchain usando criptomoedas como dinheiro e smart contracts como contratos.

Você pode por exemplo tomar um empréstimo, aplicar em um fundo de investimento, comprar bens digitais e físicos, jogar jogos, vender sua arte, trocar moedas e muito mais, tudo através dos chamados protocolos DeFi, bastando ter uma carteira cripto com saldo nela. Ah, e isso tudo sem qualquer fronteira, barreira, longos cadastros e outras burocracias inerentes ao sistema financeiro tradicional, ao mesmo tempo que de forma segura e barata.

Pois é, estamos no início de uma completa revolução nos serviços financeiros e para que ela seja viável tecnicamente precisamos de programadores capazes de construir tais protocolos. No tutorial de hoje eu quero te ensinar a construir um primeiro protocolo DeFi, bem simples, mas que servirá como exemplo para você fazer coisas mais interessantes mais tarde.

Em outra oportunidade eu ensinei como fazer isso para blockchains EVM, que usam Solidity, mas desta vez vamos fazer para Solana. Sendo assim, usaremos a linguagem Rust e este não deve ser o seu primeiro tutorial nesta linguagem. Se nunca programou em Rust para Solana antes, comece por este outro aqui.

Vamos lá!

#1 – Entendendo o Protocolo

O primeiro ponto a entender sobre protocolos financeiros (DeFi ou não) é que TODOS eles fazem algum “malabarismo” com seu dinheiro. Peguemos como exemplo os bancos: eles pegam dinheiro emprestado de investidores. Esse investidor pode ser o governo, pode ser empresas privadas (incluindo outros bancos) ou pode ser até mesmo eu e você (pessoas físicas). Ou seja, eles contraem uma dívida, a um juro X.

Mas porque eles fazem isso?

Porque com esse dinheiro que eles pegaram emprestado, eles emprestam para outras pessoas, governos e empresas, a um juro Y, sendo que o lucro dos bancos está na diferença entre X (juro baixo) e Y (juro alto). Esse seria o protocolo financeiro real mais simples possível de entender e quem dera os bancos operassem apenas dessa forma, pois assim não teríamos as crises financeiras mundiais.

Enfim, como simular comportamento semelhante através de um programa na blockchain, a fim de ter um protocolo DeFi de investimento?

Para simplificar, vamos pensar em só “guardar dinheiro”, ao invés de investir. Você já deve ter ouvido histórias ou até ter você mesmo guardado dinheiro embaixo do colchão, em caixas de sapato ou os famigerados cofrinhos em forma de porco, certo? Essa é uma modalidade de poupança, mas não de investimento, mas já servirá para ilustrar a principal mecânica envolvida: protocolos de depósito e saque.

Via de regra, toda account em um programa Solana é uma conta na blockchain, o que permitiria que uma única conta do nosso protocolo recebesse os fundos depositados, registrasse os saldos e permitisse os saques, certo? No entanto, essa abordagem traria diversas falhas fundamentais de segurança e a principal delas seria o Single Point of Failure em torno dessa super account. O “Solana way” de trabalhar esse tipo de protocolo é sempre descentralizar o armazenamento e registro das informações e moedas, para que somente os donos de cada registro possamo movê-los e sejam responsáveis pelos mesmos. Para isso, cada usuário do protocolo terá sua própria PDA account com a estrutura abaixo.

Em cada “cofre” temos um único dono, representado pelo seu endereço e o saldo do mesmo. O uso dessa struct vai ficar mais claro na prática, então vamos avançar definindo algumas estruturas para os eventos que serão emitidos e também para os erros que podem ser lançados.

Criamos dois erros personalizados (para quantia inválida e falta de saldo) e dois eventos (para avisar de depósitos e saques).

Como vamos precisar fazer trasferências de SOL, adicione o import ao use anchor_lang::system_program; no topo do arquivo lib.rs também.

A seguir, vamos falar do depósito.

Curso Web3 para Iniciantes

#2 – Criando o Protocolo de Depósito

Vamos começar definindo o nosso contexto de depósito, que será usado na respectiva função.

Aqui estou dizendo que, ao depositar uma quantia, devemos criar uma account PDA para o vault dela se ainda não existir (init_if_needed). Essa opção pode ser insegura dependendo do uso, por isso você deve habilitá-la manualmente no seu cargo.toml da seguinte maneira.

Assim, no primeiro depósito, o vault será criado. Nos subsequentes, o usuário usará o mesmo que já tinha. Para garantir que só existirá um por usuário usamos como seeds a palavra “vault” e a chave-pública da carteira do usuário que assinou o depósito (user) e pagou o rent (payer). Já o space foi definido como 48 bytes que é o necessário para o descritor + os campos da struct Vault.

Agora vamos falar da função de depósito.

Aqui começamos com o uso da macro require! para validar a quantia que está sendo depositada, disparando um erro caso seja inválida.

Depois, montamos o objeto de transferência, informando o from (origem) e to (destino) dos fundos. Com esse objeto + o system_program, criamos o contexto para o CPI (Cross-Program Invocation). Isso é necessário porque o nosso programa vai chamar o programa de sistema para que a transferência ocorra. Sempre que você tem programa chamando programa, ocorre o chamado CPI e exige esse tipo de configuração é necessária.

A transferência em si ocorre na linha system_program::transfer(cpi_tx, amount), onde informamos a quantia a ser transferida e a flag “?” indica que se der erro, a execução deve parar por aqui e retornar erro.

Caso não dê erro, pegamos o vault do contexto, atualizamos seu saldo (usando checked_add para aritmética segura) e emitimos o evento de depósito, que mais tarde pode ser usado para outras aplicações que monitorem nosso programa. Note o uso do unwrap aqui, que é similar ao “?” mas causando panic no sistema em caso de ausência de retorno (por overflow por exemplo).

#3 – Testes de Depósito

Agora vamos falar dos testes dessa função, abaixo os imports dos testes, com atenção ao assert, que vamos usar bastante.

Depois, a preparação geral, com as variáveis globais e a instrução before, que vai rodar antes dos testes todos.

Como os vaults serão PDA accounts, estamos guardando o seu endereço calculado em uma variável para poder usar mais facilmente nas demais funções. Ele é gerado com as mesmas constraints de seeds do contexto, com a palavra “vault” e os bytes do endereço do usuário dono do vault.

Agora vamos criar uma função que chama o depósito e que vai ser usada em vários testes.

A chamada à função deposit exige por parâmetro a quantia a ser depositada e que vai ser transferida a partir da carteira de quem assinar esta transação (user). Lembrando que além da quantia a ser depositada, esta carteira vai ter de pagar pelo rent do vault dele e a taxa de transação de depósito.

No primeiro unit test, temos o cenário principal, de sucesso no depósito.

Aqui definimos que vamos depositar 1 SOL (ou 1 bilhão de lamports), chamando a função deposit para isso. Para verificar se deu tudo certo, fazemos um fetch no PDA do vault do usuário e conferimentos se o saldo é igual ao valor que depositamos.

Agora vamos falar do cenário de teste de depósito inválido, que fica como abaixo.

Aqui não tem muito o que explicar pois não há muita novidade, então vamos em frente.

Curso Beholder
Curso Beholder

#4 – Criando o Protocolo de Saque

Realizar o saque não é muito mais complexo do que fazer um depósito, então vamos começar pelo contexto.

Aqui temos o vault de onde vamos sacar os fundos e a conta do dono dos mesmos (user). Para garantir que a gente acesse o vault correto, as seeds usadas são as mesmas da inicialização (depósito). E para garantir que somente o dono do vault consiga sacar seus fundos, usamos a constraint comparando o vault.owner com o user.

Agora para fazer o saque do vault para o usuário, esbarramos em um problema da Solana. Isso porque não podemos fazer uma CPI como no depósito pois PDAs não podem ser o “from” de transferências. Sim, eles podem receber dinheiro via transferência mas não enviar. Felizmente, como o user é dono do PDA, ele pode simplesmente “pegar” os lamports dele e mover pra onde quiser, usando a função try_borrow_mut_lamports. Para facilitar o processo, vamos criar uma função auxiliar, que deve ficar FORA do escopo do programa (mod).

A função transfer_lamports vai esperar a referência de duas AccountInfo (atenção ao “&” no tipo), isso porque vamos ter de alterar os lamports delas. Com estas referências, podemos usar uma função nativa das accounts que é o try_borrow_mut_lamports. Essa função “tenta” (pode ser que o sistema rejeite, por falta de permissão) pegar “emprestado” o acesso aos lamports de uma account. Como pretendemos alterar eles, o acesso tem de ser mutável.

Como este acesso é uma referência em cima de outra referência que já tínhamos (recebida por parâmetro), na hora de alterar de fato os lamports nós temos de usar ** (um para cada referência), a fim de que a alteração reflita na sua origem. Ao término da função, os lamports terão sido atualizados no from e no to, desde que o cálculo resulte em uma expressão de “soma zero” (a + b = 0).

Agora falando da função de saque em si, que vai usar a transfer_lamports, nós temos.

Aqui começamos garantindo que a quantia seja válida e que o saldo existente no vault seja suficiente, caso contrário, as macros require! vão garantir a interrupção da execução com os erros apropriados. Passando nas validações, por questões de segurança, atualizamos o saldo do vault em questão, subtraindo de maneira segura (checked_sub) e garantindo que caso aconteça underflow, que a transação seja derrubada e revertida (unwrap).

A transferência em si ficou super facilitada, bastando passar a referência do vault e do user convertidos para AccountInfo. Ao término da transferência, o evento de saque é emitido e tudo termina com sucesso.

#5 – Testes de Saque

Agora que temos a última função implementada, vamos escrever o teste de sucesso para ela, lembrando que, para poder sacar, primeiro temos de depositar.

O saque exige a quantia por parâmetro e o PDA do vault, o user como signer e o system_program como contexto. Para a conferência, basta pegar o saldo do vault antes e depois do saque para ver se a quantia bate com o esperado.

Agora, mais um teste com o cenário de falha por falta de saldo suficiente para o saque.

E um último onde falha porque uma conta está tentando sacar dinheiro de outra.

Com isso, temos um protocolo de “guardar dinheiro embaixo do colchão” funcional e que serve para múltiplos poupadores de SOL. Opcionalmente você poderia modificar este protocolo para permitir que ele suporte outros tokens que não a moeda SOL nativa, isso dificulta um pouco mais mas é perfeitamente possível de fazer.

Além disso, querendo evoluir este protocolo para algo mais completo, você poderia fazer ele se integrar com protocolos de staking, a fim de pegar o dinheiro guardado e aplicá-lo para render, compartilhando com os usuários parte desses dividendos, como acontece em contas remuneradas, por exemplo.

Enfim, o céu é o limite em relação às possibilidades do DeFi e estamos apenas começando.

Na próxima lição, eu vou te ensinar como manipular tokens na Solana, para criar protocolos DeFi ainda mais interessantes.

TAGS:

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *