https://github.com/nibiruchain/coded-estate-evm
Solidity and EVM interface contracts for Coded Estate rental app
https://github.com/nibiruchain/coded-estate-evm
ethereum ethers evm nibiru smart-contracts solidity
Last synced: 5 months ago
JSON representation
Solidity and EVM interface contracts for Coded Estate rental app
- Host: GitHub
- URL: https://github.com/nibiruchain/coded-estate-evm
- Owner: NibiruChain
- Created: 2026-01-23T01:23:40.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-01-23T18:27:01.000Z (5 months ago)
- Last Synced: 2026-01-24T08:40:22.785Z (5 months ago)
- Topics: ethereum, ethers, evm, nibiru, smart-contracts, solidity
- Language: TypeScript
- Homepage:
- Size: 76.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 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](#building-the-contracts)
- [Repository layout](#repository-layout)
- [Core dependencies (Nibiru precompiles)](#core-dependencies-nibiru-precompiles)
- [Contract: `CodedEstateEvmInterface`](#contract-codedestateevminterface)
- [What the contract stores](#what-the-contract-stores)
- [Timelocked address updates](#timelocked-address-updates)
- [Calling pattern](#calling-pattern)
- [Action catalog (Rust variant -> JSON -> Solidity call)](#action-catalog-rust-variant---json---solidity-call)
- [TypeScript support (`evmImpl.ts`)](#typescript-support-evmimplts)
## Building the Contracts
1 - [Install bun](https://bun.com/docs/installation).
2 - Run `nvm use`.
3 - Install `just` to run project-specific commands.
```bash
cargo install just
```
**Compile Solidity contracts**.
```bash
just build
```
Ref: [github.com/casey/just](https://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:
```ts
import { ethers } from "ethers"
const wasmMsg = { /* ... */ }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(wasmMsg))
```
Or use the helper in `evmImpl.ts`:
```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](#1-mint-nft)
- [2) List property for short-term rental](#2-list-property-for-short-term-rental)
- [3) Unlist property](#3-unlist-property)
- [4) Create reservation (requires funds)](#4-create-reservation-requires-funds)
- [5) Approve reservation](#5-approve-reservation)
- [6) Reject reservation (opaque)](#6-reject-reservation-opaque)
- [7) Cancel reservation (unapproved, opaque)](#7-cancel-reservation-unapproved-opaque)
- [8) Cancel rental (approved, opaque)](#8-cancel-rental-approved-opaque)
- [9) Finalize rental (opaque)](#9-finalize-rental-opaque)
### 1) Mint NFT
**Rust variant (expected)**: `ExecuteMsg::Mint { token_id, owner, token_uri, extension }`
**Wire JSON**
```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**
```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**
```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**
```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:
```ts
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:
```ts
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**
```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.