Skip to content

Solidity and EVM interface contracts for Coded Estate rental app

Notifications You must be signed in to change notification settings

NibiruChain/coded-estate-evm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CodedEstate EVM Interface

This repo contains a Solidity "gateway" contract that lets EVM callers execute CodedEstate rental actions implemented as a CosmWasm contract on Nibiru.

At a high level:

  1. The frontend constructs a CosmWasm ExecuteMsg as JSON.
  2. The frontend UTF-8 encodes that JSON to bytes.
  3. The Solidity gateway forwards those bytes to the Nibiru Wasm precompile (IWasm.execute).
  4. For actions that require payment, the gateway can (optionally) convert ERC20 -> bank coins using the FunToken precompile, then attach bank-denom funds to the Wasm execution.

The result is a stable EVM ABI while the Wasm contract evolves. Most functions accept bytes wasmMsgExecute so the schema stays on the Wasm side.

Building the Contracts

1 - Install bun.

2 - Run nvm use.

3 - Install just to run project-specific commands.

cargo install just

Compile Solidity contracts.

just build

Ref: github.com/casey/just

Repository layout

  • contracts/CodedEstateEvmInterface.sol
    • The gateway contract deployed on Nibiru EVM.
  • evmImpl.ts
    • TypeScript types for the known JSON message shapes and a helper for encoding messages.
  • hardhat.config.js, package.json, justfile
    • Build tooling for compiling the Solidity contract and generating artifacts.

Core dependencies (Nibiru precompiles)

This interface relies on Nibiru's precompiled contracts:

  • IWasm (WASM_PRECOMPILE_ADDRESS = 0x...0802)
    • Executes a Wasm contract's ExecuteMsg given JSON bytes and optional funds.
  • IFunToken (FUNTOKEN_PRECOMPILE_ADDRESS = 0x...0800)
    • Resolves EVM <-> bech32 address pairs and converts ERC20 <-> bank-denom balances.

You do not call these directly from the frontend. The gateway delegates to them internally.


Contract: CodedEstateEvmInterface

CodedEstateEvmInterface is a thin wrapper around IWasm.execute, plus a convenience function for "reservation with payment".

What the contract stores

  • rentalContractAddress (string, bech32)
    • The current Wasm contract address that receives ExecuteMsg.
  • Timelocked address update fields:
    • timelockDelay
    • pendingRentalContractAddress
    • changeEffectiveTimestamp

Timelocked address updates

The owner can rotate the underlying Wasm rental contract address with a delay:

  • proposeNewRentalContractAddress(string newAddress)
    • Sets pendingRentalContractAddress and schedules an effective timestamp.
  • executeNewRentalContractAddress()
    • After the timelock expires, makes the new address active.

This pattern mirrors the PerpVault EVM interface pattern.

Calling pattern

Encode a Wasm execute message

Every CodedEstate action is expressed as a JSON object with a single top-level key (the execute message name) and a payload object.

Encode it like this:

import { ethers } from "ethers"

const wasmMsg = { /* ... */ }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(wasmMsg))

Or use the helper in evmImpl.ts:

import { encodeRentalMessage } from "./evmImpl"

const wasmMsgBytes = encodeRentalMessage(wasmMsg)

Execute without funds

Use this for actions that do not require payment:

  • executeRental(bytes wasmMsgExecute)

Execute with funds

Use this when the Wasm contract expects bank-denom funds:

  • executeRentalWithFunds(bytes wasmMsgExecute, BankCoin[] funds)

Reservation with payment (special wrapper)

Reservations require payment to be attached as funds. Use:

  • setReservationForShortTerm( bytes wasmMsgExecute, string paymentDenom, uint256 paymentAmountBank, address paymentErc20, uint256 useErc20Amount )

What it does:

  • Requires paymentAmountBank > 0.
  • If useErc20Amount > 0, it calls sendToBank(paymentErc20, useErc20Amount, callerBech32).
  • If paymentDenom is empty, it derives the bank denom from paymentErc20.
  • Then it calls executeRentalWithFunds with exactly one BankCoin { denom, amount }.

This keeps the Wasm message payload focused on rental logic while the gateway handles payment mechanics.


Action catalog (Rust variant -> JSON -> Solidity call)

Below, "Rust variant" names are the expected ExecuteMsg variants based on the README naming convention (#[serde(rename_all = "snake_case")]). The wire truth is the JSON key.

1) Mint NFT

Rust variant (expected): ExecuteMsg::Mint { token_id, owner, token_uri, extension }

Wire JSON

{
  "mint": {
    "token_id": "prop_123",
    "owner": "nibi1...",
    "token_uri": "ipfs://cid",
    "extension": {}
  }
}

Solidity call

  • executeRental(wasmMsgBytes)

2) List property for short-term rental

Rust variant (expected): ExecuteMsg::SetListForShortTermRental { ... }

Wire JSON

{
  "set_list_for_short_term_rental": {
    "token_id": "prop_123",
    "denom": "uusdc",
    "price_per_day": "2500000",
    "auto_approve": false,
    "available_period": [],
    "minimum_stay": "2",
    "cancellation": [{ "deadline": "86400", "percentage": "50" }]
  }
}

Solidity call

  • executeRental(wasmMsgBytes)

3) Unlist property

Rust variant (expected): ExecuteMsg::SetUnlistForShorttermRental { token_id }

Wire JSON

{ "set_unlist_for_shortterm_rental": { "token_id": "prop_123" } }

Solidity call

  • executeRental(wasmMsgBytes)

4) Create reservation (requires funds)

Rust variant (expected): ExecuteMsg::SetReservationForShortTerm { token_id, renting_period, guests }

Wire JSON

{
  "set_reservation_for_short_term": {
    "token_id": "prop_123",
    "renting_period": ["1706745600", "1707004800"],
    "guests": "2"
  }
}

Solidity call

Use setReservationForShortTerm(...) so the gateway attaches payment funds.

Example:

await codedEstateEvm.setReservationForShortTerm(
  wasmMsgBytes,
  "uusdc",
  5_000_000n,         // bank units sent as funds
  ethers.ZeroAddress,  // paymentErc20 (optional)
  0n,                  // useErc20Amount (optional)
  overrides
)

If paying via ERC20 first:

await codedEstateEvm.setReservationForShortTerm(
  wasmMsgBytes,
  "uusdc",            // or "" to derive from paymentErc20
  5_000_000n,
  usdcErc20Address,
  useErc20Amount,
  overrides
)

5) Approve reservation

Rust variant (expected): ExecuteMsg::SetApproveForShortTerm { token_id, traveler, renting_period }

Wire JSON

{
  "set_approve_for_short_term": {
    "token_id": "prop_123",
    "traveler": "nibi1...",
    "renting_period": ["1706745600", "1707004800"]
  }
}

Solidity call

  • executeRental(wasmMsgBytes)

6) Reject reservation (opaque)

Wire key: reject_reservation_for_shortterm

The README names this message but does not specify field-level JSON. Keep the payload opaque and send it through:

  • executeRental(wasmMsgBytes)

7) Cancel reservation (unapproved, opaque)

Wire key: cancel_reservation_for_shortterm

Same situation as above:

  • executeRental(wasmMsgBytes)

8) Cancel rental (approved, opaque)

Wire key: cancel_rental_for_shortterm

Same situation:

  • executeRental(wasmMsgBytes)

9) Finalize rental (opaque)

Wire key: finalize_short_term_rental

Same situation:

  • executeRental(wasmMsgBytes)

TypeScript support (evmImpl.ts)

evmImpl.ts provides:

  • Primitive types (bech32, ipfs URIs, string-encoded integers).
  • Strict types for message shapes that are explicitly defined in the README.
  • Opaque placeholders for message names that exist but lack field-level schema.
  • encodeRentalMessage(msg) helper: ethers.toUtf8Bytes(JSON.stringify(msg)).

The intent is: type what we know, avoid guessing what we do not.

About

Solidity and EVM interface contracts for Coded Estate rental app

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published