Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview

AtomicMarket V2.0 is a marketplace smart contract (C++ / Antelope, formerly EOSIO) for trading
and renting [AtomicAssets](https://github.com/pinknetworkx/atomicassets-contract) NFTs. It builds
[AtomicAssets](https://github.com/pinknetworkx/atomicassets-contract) NFTs. It builds
on the AtomicAssets V2.0 contract (developed in the sibling repo `../atomicassets-contract`,
branch `feat/v2-integration`) and extends the upstream AtomicMarket with:

- **Royalty splits**: a collection's market fee can be distributed across weighted founders,
per-template recipients, and attribute-matching rules instead of going entirely to the
collection author
- **Custodial rentals**: per-hour asset rentals using the AtomicAssets V2 `holders` table and
`move` action (the market contract holds ownership, the renter receives holdership)
- **Single-asset listings only**: bundle listings were removed (legacy bundle rows are
auto-cancelled when touched); multiple assets are traded via multiple listings in one
transaction
Expand All @@ -35,9 +33,6 @@ branch `feat/v2-integration`) and extends the upstream AtomicMarket with:

- **Sales / auctions / buyoffers / template buyoffers**: the upstream listing types; every
listing holds exactly ONE asset
- **Rentals**: `announcerent` -> transfer with memo `"rental"` (custody) -> `rentasset`
(pays per-hour price, holdership moves to renter) -> `endrent` after expiry (anyone) ->
`cancelrent` (owner reclaims the asset)
- **Royalty configs** (`royaltyconf` / `royaltytemp` / `royaltyattr` tables): mutations require
the collection AUTHOR's authorization only (financial config; authorized accounts are
deliberately rejected). Category splits renormalize across categories that have payees;
Expand Down Expand Up @@ -88,8 +83,8 @@ npx jest market # run test files matching a pattern

### Test Structure
- `tests/market-smoke.test.js` - end-to-end suite: notification dispatch routing, sale payouts
(legacy + royalty splits with exact integer math), execution-time collection fee, bundle-removal behavior
(including legacy rows injected via `tables.X(...).set(...)`), and the full rental lifecycle
(legacy + royalty splits with exact integer math), execution-time collection fee, and
bundle-removal behavior (including legacy rows injected via `tables.X(...).set(...)`)
- `tests/fixtures/eosio.token/` - token contract fixture (wasm + abi)
- `tests/fixtures/atomicassets/` - the AtomicAssets V2 contract the market integrates with.
Rebuild from the sibling repo (`bash ../atomicassets-contract/build.sh`) and re-copy the
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# AtomicMarket V2.0
AtomicMarket is a marketplace to sell, auction and rent out [AtomicAssets](https://github.com/pinknetworkx/atomicassets-contract) NFTs. V2.0 builds on the AtomicAssets V2.0 contract.
AtomicMarket is a marketplace to sell and auction [AtomicAssets](https://github.com/pinknetworkx/atomicassets-contract) NFTs. V2.0 builds on the AtomicAssets V2.0 contract.

### [Documentation can be found here.](docs/wiki/Home.md)

Expand All @@ -18,10 +18,6 @@ AtomicMarket is a marketplace to sell, auction and rent out [AtomicAssets](https

Instead of using transfers, AtomicAssets **offers** are used for sales. These offers are only accepted when someone buys the NFTs for sale. Therefore, sellers keep ownership over their NFTs while they are listed on the AtomicMarket.

- **Custodial rentals**

Assets can be rented out per hour. The owner lists an asset and transfers it into contract custody; renters pay from their deposited balance and receive the AtomicAssets V2 *holdership* of the asset for the rental period, while ownership stays with the contract. Rental payments are distributed like sale payouts, including royalties.

- **Royalty splits**

On top of the collection fee, collection authors can configure how that fee is distributed: globally weighted founder accounts, per-template recipient lists, and attribute-matching rules (e.g. `rarity = legendary`), each with their own weights. The collection fee applied at settlement is always the fee at execution time, so author fee changes — discounts *and* increases — take effect immediately on all existing listings.
Expand Down
120 changes: 23 additions & 97 deletions docs/api-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,31 @@ for the AtomicMarket V2.0 contract.
- **Status**: merged, CI green, **not yet deployed on-chain** — you can develop against a
testnet deployment or the VeRT suite (`yarn install && bash build.sh && npm test`;
`tests/market-smoke.test.js` doubles as an executable spec of every flow described here)
- **Depends on**: AtomicAssets V2.0 (`holders` table, `move` action, `templates2` mutable
template data) — the rentals feature is built on AA holdership
- **Depends on**: AtomicAssets V2.0 (`templates2` mutable template data)

## 1. Interface delta at a glance

**New tables** (section 2): `rentals`, `royaltyconf`, `royaltytemp`, `royaltyattr`.
**New tables** (section 2): `royaltyconf`, `royaltytemp`, `royaltyattr`.
No existing table changed its layout.

**New actions**:

| Group | Actions |
|---|---|
| Rentals | `announcerent`, `cancelrent`, `rentasset`, `endrent`, `payrentram` |
| Rental logs | `lognewrent`, `logrentstart`, `logrental` |
| Royalty config | `setroyalconf`, `delroyalconf`, `settemplroy`, `deltemplroy`, `setattrroy`, `delattrroy` |
| Royalty logs | `logroyfound`, `logroytempl`, `logroyattr`, `logroydust` |

**New transfer memo**: AtomicAssets transfers to the market account with memo `rental`
activate rental listings (existing memos unchanged: `deposit` for tokens, `auction` for
auction transfers, `sale` / `buyoffer` / `tbuyoffer` for offers).

**Behavior changes to existing actions** (section 5 — these WILL break naive state
machines): single-asset listings, legacy-bundle auto-cancellation, execution-time
collection fee (applied at settlement, section 5.3).
collection fee (applied at settlement, section 4.3).
Comment on lines 25 to +27

**Important for trace consumption**: none of the royalty log actions notify any account
(`require_recipient` deliberately absent — a notified recipient contract could abort
settlements). If your reader is notification-driven rather than trace-driven, these
actions are invisible to it. `logrental` notifies the lister and renter; `lognewrent` and
`logrentstart` notify the lister.
actions are invisible to it.

## 2. New tables

### `rentals` — scope: contract account

One row per listed asset (a rental listing holds exactly one asset; the asset id IS the key).

| Field | Type | Notes |
|---|---|---|
| `asset_id` | uint64 | primary key |
| `owner` | name | listing creator; receives rental payouts |
| `holder` | name | current renter; empty name when not rented out |
| `price_per_hour` | asset | in the listing symbol |
| `settlement_symbol` | symbol | what rentals are paid in (delphi pair if ≠ listing symbol) |
| `maximum_rental_duration` | uint32 | seconds; cap per rental incl. extensions |
| `rental_end` | uint32 | sec since epoch; 0 when not rented out |
| `asset_transferred` | bool | true once the asset is in contract custody |
| `maker_marketplace` | name | |
| `collection_name` | name | |
| `collection_fee` | float64 | fee at listing time; informational only — settlement uses the execution-time fee (section 5.3) |

Secondary index `rentalends` (uint64 on `rental_end`) — useful for expiry sweeps.

### `royaltyconf` — scope: contract account

Per-collection royalty split config. PK: `collection`.
Expand Down Expand Up @@ -89,49 +61,9 @@ PK: `template_id` (stored int32, keyed as uint64). Fields: `template_id` (int32)
| `recipients` | `ROYALTYPAIR[]` | |
| `lookup_hash` | checksum256 | sha256(pack(source, field, value)); secondary index `byhash` |

## 3. Rentals — workflow and indexing signals

State machine of a `rentals` row:

```
(none) --announcerent--> LISTED(asset_transferred=false)
LISTED --AA transfer memo "rental"--> ACTIVE(asset_transferred=true, holder="")
ACTIVE --rentasset--> RENTED(holder=renter, rental_end=T)
RENTED --rentasset by same renter, before T--> RENTED(rental_end += hours*3600) [extension]
RENTED, after T --rentasset by anyone--> RENTED(new holder, rental_end = now + hours*3600)
RENTED, after T --endrent (anyone)--> ACTIVE(holder="", rental_end=0)
ACTIVE or expired-RENTED --cancelrent (owner)--> (row erased, asset returned)
LISTED --cancelrent (owner; anyone if owner no longer owns the asset)--> (row erased)
```

Per-action effects:

| Action / signal | Auth | State effects | Log emitted |
|---|---|---|---|
| `announcerent(lister, asset_id, price_per_hour, settlement_symbol, maximum_rental_duration, maker_marketplace)` | lister | rentals row created | `lognewrent(asset_id, lister, price_per_hour, settlement_symbol, maximum_rental_duration, maker_marketplace, collection_name, collection_fee)` |
| AA `transfer(from, to=market, [asset_id], "rental")` | — | `asset_transferred = true`; market becomes AA owner | `logrentstart(asset_id, lister)` per asset (a multi-asset transfer activates each asset's own listing) |
| `rentasset(renter, asset_id, rental_hours, expected_price_per_hour, intended_delphi_median, taker_marketplace)` | renter | renter balance debited; payout distributed (section 4); `holder = renter`, `rental_end` set; inline AA `move` shifts holdership to the renter — skipped when the renter already holds it (extensions, AND an expired rental re-rented by the same renter without an intervening `endrent`). Do not assume every non-extension `logrental` has a sibling AA `move` trace | `logrental(rental_counter_id, asset_id, lister, renter, rental_hours, paid_settlement_price, rental_end, taker_marketplace)` |
| `endrent(asset_id)` | **anyone** | `holder = ""`, `rental_end = 0`; inline AA `move` returns holdership to the market | — |
| `cancelrent(asset_id)` | owner (anyone if listing invalid & not activated) | row erased; if custodied: holdership reclaimed if needed, asset transferred back to owner | — |
| `payrentram(payer, asset_id)` | payer | row erased + re-created with new RAM payer; **contents unchanged** (do not treat as a state change) | — |
## 3. Royalty splits — config and settlement

Indexing recipes:

- **`rental_counter_id`** in `logrental` is a global, monotonically increasing rental event
id (from the `counters` table, name `rental`) — a natural primary key for rental events.
- **Extensions**: trust `logrental.rental_end` directly; it is the new absolute end. An
extension is recognizable as `renter == previous holder && previous rental_end > block_time`.
- **Effective user of an asset**: from the AA `holders` table (`owner = market account,
holder = renter`). After expiry the holders row persists until `endrent` / next rental /
`cancelrent` — treat `rental_end <= now` as "rental over" regardless.
- **Paid price**: `logrental.paid_settlement_price` is the final settled amount (already
delphi-converted if the listing is oracle-priced).
- `rentasset` aborts with "currently rented out" for non-holders during an active rental —
no state to index on failure (failed transactions don't reach the chain).

## 4. Royalty splits — config and settlement

### 4.1 Config CRUD
### 3.1 Config CRUD

All six actions require the **collection author's** authorization (authorized accounts are
rejected — config controls fund routing). The author pays RAM.
Expand All @@ -149,17 +81,17 @@ Validation guarantees you can rely on: recipient lists are 1–64 entries, weigh
duplicate recipients, all recipients exist; rule values are never float/double or vectors;
`source` is 0 in merged mode, 1–4 in granular mode.

### 4.2 Settlement: who gets the collection fee
### 3.2 Settlement: who gets the collection fee

Every settlement — `purchasesale`, `auctclaimsel`, `acceptbuyo`, `fulfilltbuyo`,
`rentasset` — distributes the payment: maker fee, taker fee, **collection fee**, bonus
Every settlement — `purchasesale`, `auctclaimsel`, `acceptbuyo`, `fulfilltbuyo`
distributes the payment: maker fee, taker fee, **collection fee**, bonus
fees, remainder to the seller/lister (transferred out directly; everything else accrues to
the `balances` table, claimed via `withdraw`).

For the collection fee:

- **No `royaltyconf` row** → the full collection fee goes to the author's balance.
**No log is emitted in this case** — compute the author's earnings yourself (see 5.3 for
**No log is emitted in this case** — compute the author's earnings yourself (see 4.3 for
the amount).
- **Legacy bundle payout** (only reachable via `auctclaimsel` on a buyer-claimed pre-V2
bundle auction) → the full collection fee goes to the author, even when a royalty config
Expand All @@ -181,15 +113,15 @@ You do NOT need to re-implement the split math (category renormalization, two-le
weighting, dust) — the logs carry the final per-recipient amounts. The math lives in
`distribute_collection_fee` in `src/atomicmarket.cpp` if you want to cross-check.

## 5. Breaking behavior changes
## 4. Breaking behavior changes

### 5.1 Single-asset listings
### 4.1 Single-asset listings

`announcesale`, `announceauct`, `createbuyo` now reject `asset_ids.size() != 1`. All new
sales/auctions/buyoffers reference exactly one asset. (`rentals` and `tbuyoffers` were
single-asset by design.)
sales/auctions/buyoffers reference exactly one asset. (`tbuyoffers` were single-asset by
design.)

### 5.2 Legacy bundle rows auto-cancel — execution actions no longer always mean a trade
### 4.2 Legacy bundle rows auto-cancel — execution actions no longer always mean a trade

Rows with `asset_ids.length > 1` can only predate V2. When one is touched, the action
**succeeds** but performs a cancellation instead of a trade. Your state machine must branch
Expand All @@ -206,52 +138,46 @@ on the row's asset count (which you already have in your DB):
| `cancelsale` / `cancelauct` | now allowed for **anyone** on bundles (and bundle auctions with bids, refunding the bidder) — EXCEPT partially-claimed bundle auctions, which can't be cancelled | cancelled |
| offer memo `sale` / transfer memo `auction` with >1 assets | transaction aborts (bundles can't activate) | nothing |

### 5.3 Execution-time collection fee
### 4.3 Execution-time collection fee

The applied collection fee is the collection's `market_fee` on AtomicAssets **at execution
time**, read live at settlement — *not* the fee stored in the listing row. The
`collection_fee` field in sales/auctions/buyoffers/rentals rows is therefore informational
`collection_fee` field in sales/auctions/buyoffers rows is therefore informational
only (the fee at listing time); it does not determine the payout.

- With a royalty config: the applied amount = the logged `logroy*` sum (section 4.2).
- With a royalty config: the applied amount = the logged `logroy*` sum (section 3.2).
- Without: applied amount = `floor(current AA market_fee × price)`
— you already track AA `setmarketfee`, so the current fee is in your DB.

This is deliberate product behavior: the collection author has full control, and fee changes
— down *or* up — apply to all existing listings immediately. Expect fee changes mid-listing
to be common, not exceptional.

### 5.4 Royalty config authorization
### 4.4 Royalty config authorization

Unlike most collection-scoped things in the ecosystem, royalty config actions are valid
ONLY with the **author's** auth. If you surface "who may edit", do not show authorized
accounts for these.

## 6. Unchanged
## 5. Unchanged

Deposits/withdrawals and the `balances` table, marketplace registration and maker/taker
fees, bonus fees, the sale/auction/buyoffer/tbuyoffer happy paths and their existing log
actions (`lognewsale`, `lognewauct`, `lognewbuyo`, `lognewtbuyo`, `logsalestart`,
`logauctstart`), assert actions, `paysaleram`/`payauctram`/`paybuyoram` (joined by
`payrentram`), counters, the config singleton layout (version reports `2.0.0`).
`logauctstart`), assert actions, `paysaleram`/`payauctram`/`paybuyoram`, counters, the
config singleton layout (version reports `2.0.0`).

The deployed ABI keeps the legacy spellings your readers already handle (`key`/`value`
pair fields, `uint8[]`) — `make release` post-processes the raw CDT 4.1 ABI exactly like
the AtomicAssets V2 release does.

## 7. Quick reference: everything to add to your action filter
## 6. Quick reference: everything to add to your action filter

```
setroyalconf delroyalconf settemplroy deltemplroy setattrroy delattrroy
announcerent cancelrent rentasset endrent payrentram
lognewrent logrentstart logrental
logroyfound logroytempl logroyattr logroydust
```

Plus: AA `transfer` notifications to the market account with memo `rental`, and the AA
`holders` table / `logmove` if you want real-time holdership (you likely index those for
AA V2 already).

Questions: the VeRT suite (`tests/market-smoke.test.js`) demonstrates every flow above
end-to-end, including the exact balance outcomes — it is the fastest way to answer "what
exactly happens on chain when X".
11 changes: 5 additions & 6 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AtomicMarket V2.0

AtomicMarket is a marketplace smart contract for selling, auctioning and renting out
AtomicMarket is a marketplace smart contract for selling and auctioning
[AtomicAssets](https://github.com/pinknetworkx/atomicassets-contract) NFTs on Antelope chains.
V2.0 builds on the AtomicAssets V2.0 contract.

Expand All @@ -9,14 +9,13 @@ V2.0 builds on the AtomicAssets V2.0 contract.
| Feature | Summary | Details |
|---|---|---|
| **Royalty splits** | The collection fee can be distributed across weighted founders, per-template recipients, and attribute-matching rules instead of going entirely to the collection author | [Royalty Splits](Royalty-Splits) |
| **Custodial rentals** | Assets can be rented out per hour; renters receive the AtomicAssets *holdership* while ownership stays in contract custody | [Rentals](Rentals) |
| **Single-asset listings** | Every sale, auction and buyoffer contains exactly one asset; bundle listings were removed | [V2 Changes](V2-Changes) |
| **Execution-time collection fee** | Settlements apply the collection's fee at execution time, so author fee changes — down *or* up — take effect immediately on all existing listings | [V2 Changes](V2-Changes) |
| **CPU optimizations** | Lazy table construction, per-action config caching, size-capped raw reads of the collections table | [V2 Changes](V2-Changes) |

## Core concepts (unchanged from V1)

- **Deposits**: buyers and renters pay from a deposited balance. Transfer any supported token
- **Deposits**: buyers pay from a deposited balance. Transfer any supported token
to the market account with the memo `deposit`; withdraw any time with the `withdraw` action.
- **Sales**: `announcesale`, then activate by creating an AtomicAssets trade offer to the
market account with the memo `sale`. Purchased with `purchasesale`.
Expand All @@ -27,12 +26,12 @@ V2.0 builds on the AtomicAssets V2.0 contract.
marketplaces of a settlement each receive a configurable share (1% by default).
- **Delphi pricing**: listings can be priced in one symbol (e.g. USD) and settled in another
(e.g. WAX) at the [delphioracle](https://github.com/eostitan/delphioracle) exchange rate at
execution time. This works for sales **and rentals** in V2.
execution time.

## Settlement payout order

Every settlement (sale purchase, auction seller claim, buyoffer acceptance/fulfillment,
rental) distributes the payment as follows:
Every settlement (sale purchase, auction seller claim, buyoffer acceptance/fulfillment)
distributes the payment as follows:

1. Maker marketplace fee (default 1%)
2. Taker marketplace fee (default 1%)
Expand Down
2 changes: 1 addition & 1 deletion docs/wiki/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ cd /tmp/market-wiki
git add -A && git commit -m "Sync wiki from docs/wiki" && git push
```

Page links between the files use wiki-style page names (e.g. `[Rentals](Rentals)`), which
Page links between the files use wiki-style page names (e.g. `[Royalty Splits](Royalty-Splits)`), which
resolve on the GitHub wiki. When viewing these files inside the repo, append `.md` mentally.
Loading
Loading