An open API service indexing awesome lists of open source software.

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

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.