Structa.finance
On-chain fundraising for Brazilian real estate, settled in USDC.
The problem
Brazilian real-estate development runs on a specific kind of credit friction. A mid-sized builder — say, twelve concurrent projects, a hundred people on payroll — spends months negotiating one of two things:
- an institutional line from Caixa Econômica Federal (slow, cheap, heavy on collateral and documentation), or
- private investors buying claims on a specific project (fast, expensive, and administratively brutal to track).
I spent six years on the second problem. The bookkeeping alone — knowing who owns what claim on which project, distributing partial payouts as units sell, honoring registry updates when apartments trade hands — was easily one full person's job. And that person was frequently me, on Sunday nights.
Structa is a bet that most of that infrastructure can move on-chain, and that USDC is a good enough settlement asset for the payout leg.
What the protocol does
Each project is a distinct on-chain fundraising offering — a PDA that stores the terms of the raise, the underlying real-estate reference, and the caps on subscription. Investors subscribe by depositing USDC; the program mints them a non-transferable claim tied to the offering. As unit sales close and the builder deposits proceeds back into the vault, the program pro-rates payouts to holders.
The interesting part is what the on-chain claim represents. It is not a token you speculate against; it is a right to distribution from a specific building, gated by the same KYC and legal wrapper that a private-placement memorandum would enforce off-chain today. The chain is where the accounting lives — instant, provable, and cheap to audit — but the underlying instrument is a Brazilian legal object.
Architecture
┌─────────────────────────────────┐
│ Offering (PDA) │
│ · project ref │
│ · terms · cap · deadline │
│ · vault (USDC ATA) │
└─────────────────────────────────┘
│
│ subscribe(amount)
▼
┌───────────────────────────────────────────────┐
│ Investor claim (PDA per investor/offering) │
│ · principal · pro-rata weight │
│ · payout cursor │
└───────────────────────────────────────────────┘
│
│ distribute(usdc_amount)
▼
Investor USDC ATATwo account types, one vault, one instruction per real event. Deliberately boring, because the interesting complexity is off-chain — issuer KYC, the legal wrapper, the reconciliation between on-chain claim and the underlying registration.
A representative instruction
The subscribe instruction, roughly (edited for clarity):
#[derive(Accounts)]
#[instruction(amount: u64)]
pub struct Subscribe<'info> {
#[account(mut, seeds = [b"offering", offering.project_ref.as_ref()], bump)]
pub offering: Account<'info, Offering>,
#[account(
init_if_needed,
payer = investor,
space = 8 + InvestorClaim::SIZE,
seeds = [b"claim", offering.key().as_ref(), investor.key().as_ref()],
bump
)]
pub claim: Account<'info, InvestorClaim>,
#[account(mut, associated_token::mint = usdc_mint, associated_token::authority = investor)]
pub investor_usdc: Account<'info, TokenAccount>,
#[account(mut, associated_token::mint = usdc_mint, associated_token::authority = offering)]
pub vault_usdc: Account<'info, TokenAccount>,
pub usdc_mint: Account<'info, Mint>,
#[account(mut)]
pub investor: Signer<'info>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub system_program: Program<'info, System>,
}
pub fn subscribe(ctx: Context<Subscribe>, amount: u64) -> Result<()> {
let offering = &mut ctx.accounts.offering;
require!(!offering.closed, StructaError::OfferingClosed);
require!(
offering.total_raised.saturating_add(amount) <= offering.cap,
StructaError::CapExceeded
);
require!(Clock::get()?.unix_timestamp <= offering.deadline, StructaError::DeadlinePassed);
let cpi = token::Transfer {
from: ctx.accounts.investor_usdc.to_account_info(),
to: ctx.accounts.vault_usdc.to_account_info(),
authority: ctx.accounts.investor.to_account_info(),
};
token::transfer(CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi), amount)?;
let claim = &mut ctx.accounts.claim;
claim.offering = offering.key();
claim.investor = ctx.accounts.investor.key();
claim.principal = claim.principal.saturating_add(amount);
claim.pro_rata_bps = compute_bps(claim.principal, offering.cap);
offering.total_raised = offering.total_raised.saturating_add(amount);
Ok(())
}Nothing exotic. The design decisions I care about are the invariants around
compute_bps (which has to survive integer-rounding across many partial subscriptions), the
close-out logic when a project fully sells out, and the safety rails against a builder
draining the vault before payouts settle.
What’s hard, and what I’m still building
- Issuer KYC. The on-chain claim is trivial. The off-chain envelope — verifying the builder, verifying the project, wrapping the claim in the correct Brazilian instrument — is the entire job. I am designing it as a permissioned issuer flow rather than trying to be jurisdiction-neutral.
- Registry reconciliation. Brazilian apartments have a physical registry
(
matrícula). When one sells, the payout to Structa holders happens against a specific event in that registry. Bridging registry state to a program instruction cleanly is the open question. - Custody. USDC deposited by investors sits in an offering vault owned by the program. I want the withdraw path to require a multisig timelock on the builder side, so a single compromised key cannot drain a vault mid-raise.
Status
- Built at Solana Frontier (Colosseum) as a working demo — subscribe + distribute path end to end on devnet.
- Currently extending the program with proper KYC gating, closeout, and the legal-wrapper design for a first real pilot with a Northeast Brazilian builder I know.
- Code and design notes live on GitHub. If you are working in this space — Superteam, Colosseum, Solana Foundation, or an issuer thinking about on-chain fundraising — I would like to talk.
Construindo algo nesse espaço?
Estou especialmente aberto a conversas sobre ativos do mundo real, liquidação on-chain pra comerciante e crédito contra garantia real.
lucasalb11@gmail.com