Lucas.

← trabalho

WIPSolana· pilot design

Structa.finance

On-chain fundraising for Brazilian real estate, settled in USDC.

Anchor · USDC · React · RWAgithub

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 ATA

Two 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