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
2 changes: 1 addition & 1 deletion guides/asset-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ Pass `template_id: -1` to mint a templateless asset carrying its own `immutable_
- RAM payer: `authorized_minter`, not `new_asset_owner`. The new row lives in the owner's scope, but the minter pays for it, so a minter without enough RAM staked blocks its own mint regardless of the recipient's resources.
- Fails when: `template_id` is neither `-1` nor an existing template id; the template belongs to a different schema than `schema_name`; the template's `max_supply` is already reached (`"The template's maxsupply has already been reached"`); `new_asset_owner` isn't a registered account; or `tokens_to_back` is non-empty (`"Native backing has been deprecated on the AtomicAssets Contract"`).

**Changed in V2:** native token backing (the `tokens_to_back` parameter and the `backasset` action) is deprecated: any non-empty `tokens_to_back` aborts the mint, and `backasset` unconditionally fails. Under V1 both were functional and moved real token balances onto the asset.
Changed in V2: native token backing (the `tokens_to_back` parameter and the `backasset` action) is deprecated: any non-empty `tokens_to_back` aborts the mint, and `backasset` unconditionally fails. Under V1 both were functional and moved real token balances onto the asset.

Source: `atomicassets-contract src/atomicassets.cpp:700-791` (`mintasset`, backing guard at `:789-790`), V1 backing behavior in this repo's V1 tree (`contracts/atomicassets-contract/src/atomicassets.cpp`)

Expand Down
4 changes: 2 additions & 2 deletions guides/auctions.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ AtomicMarket's `atomicassets::transfer` notification handler matches the transfe

Failure modes asserted in source:

- `asset_ids` must contain exactly one id: a transfer of more than one asset can no longer activate an auction (only a legacy pre-V2 bundle auction row could ever match one, and those can only be cancelled; see below).
- `asset_ids` must contain exactly one id: a transfer of more than one asset can no longer activate an auction (only a legacy pre-V2 bundle auction row could ever match one, and those can only be cancelled; see "Cancel an auction").
- No non-finished auction announced by this sender exists for this exact asset id set.

Source: `atomicmarket-contract src/atomicmarket.cpp:1889-1943` (`receive_asset_transfer`)
Expand Down Expand Up @@ -207,7 +207,7 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:1183-1265` (`auctionbid`), `

## Auction end and claiming

An auction has no explicit "end" action: `end_time` simply passing is what makes it claimable. From that point, either side may claim in either order.
An auction has no explicit "end" action: `end_time` passing is what makes it claimable. From that point, either side may claim in either order.

### Buyer claim: `auctclaimbuy`

Expand Down
8 changes: 4 additions & 4 deletions guides/buyoffers.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ key-modules:

How to create, accept, decline, and cancel AtomicMarket buyoffers, for both single assets and templates. Baseline is AtomicMarket V2 (`atomicmarket-contract`); "Changed in V2" notes call out where V1 behaved differently. Lifecycle-state facts for the indexer and hosted API (LISTED/CANCELED/SOLD, no row cleanup, `state` filtering) are validated in [atomicassets-api HTTP API](../reference/api.md) and [Query the API and chain tables](querying-the-api.md); this guide cross-links them rather than repeating them. Deposit and balance mechanics are covered in [Balances and deposits](deposits.md).

A buyoffer is a buyer-initiated, escrowed offer: the price is deducted from the buyer's deposited balance immediately at creation (see [Balances and deposits](deposits.md)), before the counterparty has agreed to anything. WAX mainnet currently runs AtomicMarket V1; see [AtomicMarket tables](../reference/atomicmarket/tables.md#config) ("config") for the live-version check. The V1 behavior noted below is the live behavior only for as long as mainnet stays on V1. Each write below runs through a `session` built in [Build a session and sign](signing.md).
A buyoffer is a buyer-initiated, escrowed offer: the price is deducted from the buyer's deposited balance immediately at creation (see [Balances and deposits](deposits.md)), before the counterparty has agreed to anything. WAX mainnet runs AtomicMarket V1; see [AtomicMarket tables](../reference/atomicmarket/tables.md#config) ("config") for the live-version check. The V1 behavior noted below is the live behavior only for as long as mainnet stays on V1. Each write below runs through a `session` built in [Build a session and sign](signing.md).

## Asset buyoffers

Expand Down Expand Up @@ -159,7 +159,7 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:1634-1649` (`declinebuyo`),

### Cancelling a buyoffer

`cancelbuyo` requires the buyer's authorization and refunds the escrowed price to the buyer's deposited balance. Unlike `cancelsale`, there is no permissionless path: nobody but the buyer can cancel their own buyoffer, regardless of whether it has become unfulfillable (see below).
`cancelbuyo` requires the buyer's authorization and refunds the escrowed price to the buyer's deposited balance. Unlike `cancelsale`, there is no permissionless path: nobody but the buyer can cancel their own buyoffer, regardless of whether it has become unfulfillable (see "What makes a buyoffer invalid").

```json
{ "buyoffer_id": "42" }
Expand All @@ -182,7 +182,7 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:1501-1513` (`cancelbuyo`), `

### What makes a buyoffer invalid

`createbuyo` checks that the recipient owns every asset id at creation time (`get_collection_and_check_assets` looks each one up in the recipient's own asset scope and throws if any is missing). That check does not run again later. If the recipient transfers the asset away after the buyoffer exists, the row stays on chain but becomes unfulfillable in practice: `acceptbuyo` requires the recipient to create a matching AtomicAssets offer for an asset they no longer own, which AtomicAssets itself rejects. AtomicMarket has no permissionless invalidation action for buyoffers analogous to `cancelsale`'s "anyone can cancel an invalid sale" path; the buyoffer simply sits there until the buyer cancels it.
`createbuyo` checks that the recipient owns every asset id at creation time (`get_collection_and_check_assets` looks each one up in the recipient's own asset scope and throws if any is missing). That check does not run again later. If the recipient transfers the asset away after the buyoffer exists, the row stays on chain but becomes unfulfillable in practice: `acceptbuyo` requires the recipient to create a matching AtomicAssets offer for an asset they no longer own, which AtomicAssets itself rejects. AtomicMarket has no permissionless invalidation action for buyoffers analogous to `cancelsale`'s "anyone can cancel an invalid sale" path; the buyoffer sits there until the buyer cancels it.

A separate, unrelated "invalid" case is legacy V1 bundle buyoffers (more than one asset id, created before V2 removed the capability): calling `acceptbuyo` on one does not trade anything. It cancels the buyoffer and refunds the buyer, exactly like `declinebuyo`. See [AtomicMarket V2 changes](../reference/atomicmarket/v2-changes.md#bundle-listing-retirement) ("Bundle listing retirement").

Expand Down Expand Up @@ -231,7 +231,7 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:1651-1701` (`createtbuyo`),

The seller creates an AtomicAssets `createoffer` to `atomicmarket` offering exactly one asset of the buyoffer's template, asking nothing back, memo exactly `"tbuyoffer"`, then calls `fulfilltbuyo` in the same transaction. `fulfilltbuyo` verifies the offered asset's `template_id` matches, and like `acceptbuyo`, reads the highest-id row in AtomicAssets' `offers` table rather than taking an `offer_id` parameter, then checks that row's sender, recipient, asset ids, and memo.

**Security consideration for marketplaces**: this is a liveness and griefing risk, not theft, and it is safe for a normal wallet-signed transaction. `fulfilltbuyo` accepts whichever offer is last in AtomicAssets' `offers` table when it runs, not a specific id, so an offer injected between the seller's `createoffer` and `fulfilltbuyo` can land last instead. Both actions re-check the last offer's sender, recipient, asset ids, and memo, so a foreign offer cannot silently settle the buyoffer: it would need the same `asset_id` from the same seller with memo `"tbuyoffer"`, impossible while that asset is committed to the intended offer; any mismatch reverts the transaction. An externally-owned wallet runs the two actions back to back, so there is no exposure. A marketplace whose seller side is a smart contract must ensure nothing else in the transaction creates an AtomicAssets offer after the intended one, or its settlements will spuriously revert.
Security consideration for marketplaces: this is a liveness and griefing risk, not theft, and it is safe for a normal wallet-signed transaction. `fulfilltbuyo` accepts whichever offer is last in AtomicAssets' `offers` table when it runs, not a specific id, so an offer injected between the seller's `createoffer` and `fulfilltbuyo` can land last instead. Both actions re-check the last offer's sender, recipient, asset ids, and memo, so a foreign offer cannot silently settle the buyoffer: it would need the same `asset_id` from the same seller with memo `"tbuyoffer"`, impossible while that asset is committed to the intended offer; any mismatch reverts the transaction. An externally-owned wallet runs the two actions back to back, so there is no exposure. A marketplace whose seller side is a smart contract must ensure nothing else in the transaction creates an AtomicAssets offer after the intended one, or its settlements revert spuriously.

```
// correct: the seller's transaction contains exactly one AtomicAssets createoffer (the one fulfilltbuyo expects) and nothing else can insert another offer before it
Expand Down
8 changes: 4 additions & 4 deletions guides/deposits.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:1870-1882` (`receive_token_t

These actions deduct from the caller's deposited balance rather than accepting a token transfer inline, because the balance must already be escrowed before the contract can commit to a trade:

- **`createbuyo`** / **`createtbuyo`**: the offered `price` is deducted from the buyer's balance at creation time, before any counterparty has acted. See [Buyoffers](buyoffers.md).
- **`auctionbid`**: the `bid` amount is deducted from the bidder's balance when the bid is placed. If the bid outbids an existing bidder, that bidder's previous bid is credited back to their balance (not transferred to their account); they must `withdraw` it themselves.
- **`purchasesale`**: the settlement price (which can differ from the listed price for stable-priced sales, via `calc_settlement_price`) is deducted from the buyer's balance at purchase time.
- `createbuyo` and `createtbuyo`: the offered `price` is deducted from the buyer's balance at creation time, before any counterparty has acted. See [Buyoffers](buyoffers.md).
- `auctionbid`: the `bid` amount is deducted from the bidder's balance when the bid is placed. If the bid outbids an existing bidder, that bidder's previous bid is credited back to their balance (not transferred to their account); they must `withdraw` it themselves.
- `purchasesale`: the settlement price (which can differ from the listed price for stable-priced sales, via `calc_settlement_price`) is deducted from the buyer's balance at purchase time.

Declining, cancelling, or being outbid always credits the balance back rather than transferring tokens out. Every one of these paths goes through the same `internal_add_balance` / `internal_decrease_balance` pair that deposits and withdrawals use.

Expand Down Expand Up @@ -94,7 +94,7 @@ Source: `atomicmarket-contract src/atomicmarket.cpp:2993-3026` (`internal_add_ba

## Supported tokens

There is no separate `tokenconfigs` table. Supported tokens live as a `vector<TOKEN>` field (`supported_tokens`, each entry a `{token_contract, token_symbol}` pair) inside the `config` singleton, scoped to the contract itself. A live read against WAX mainnet confirms the shape (production is currently on AtomicMarket V1; this part of `config` is unchanged in V2):
There is no separate `tokenconfigs` table. Supported tokens live as a `vector<TOKEN>` field (`supported_tokens`, each entry a `{token_contract, token_symbol}` pair) inside the `config` singleton, scoped to the contract itself. A live read against WAX mainnet confirms the shape (production runs AtomicMarket V1; this part of `config` is unchanged in V2):

```json
{
Expand Down
2 changes: 1 addition & 1 deletion guides/links.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ await session.transact({
```

- Required authorization: the link's `creator`.
- If the link was funded, the assets are returned to `creator` via an inline AtomicAssets `transfer` with memo `"Cancelled link"`; if it was only announced and never funded, the row is simply erased.
- If the link was funded, the assets are returned to `creator` via an inline AtomicAssets `transfer` with memo `"Cancelled link"`; if it was only announced and never funded, the row is erased.
- Fails when: no link with `link_id` exists, or the caller is not its creator.

Source: `src/link.cpp:107-124`
Expand Down
2 changes: 1 addition & 1 deletion guides/notification-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ void on_transfer(name from, name to, std::vector<uint64_t> asset_ids, std::strin

The string is `contract::action`. For the direct path, bind the primary action the party is named on: `atomicassets::transfer`, and `atomicassets::logmint` for mints (the notification is sent from the inline `logmint`, not from `mintasset` itself). For the collection path, bind the inline `log*` action that carries the fan-out: `atomicassets::logtransfer`, `atomicassets::logmint`, `atomicassets::logburnasset`, `atomicassets::logsetdata`, `atomicassets::logsetdatatl`, `atomicassets::logrampayer`.

The trap: the handler's parameter list must match the notifying action's ABI parameters exactly, in order and type. If it does not, the handler is silently never dispatched. There is no error and no failed transaction; the notification simply does not match the handler, so the reaction just never runs. The exact signatures, from the pinned contract, are:
The trap: the handler's parameter list must match the notifying action's ABI parameters exactly, in order and type. If it does not, the handler is silently never dispatched. There is no error and no failed transaction; the notification does not match the handler, so the reaction never runs. The exact signatures, from the pinned contract, are:

| Bind to | Parameters, in order |
| --- | --- |
Expand Down
6 changes: 3 additions & 3 deletions guides/offers.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Accepting re-verifies ownership of every listed asset, then runs both transfers

- Required authorization: the offer's `recipient`.
- RAM payer: split, not uniform. Assets moving from recipient to sender use the offer's current RAM payer as the scope payer for any new scope the sender needs; assets moving from sender to recipient have the recipient cover their own new scope. In practice this means accepting an offer that requires you to hold an asset type for the first time can implicitly charge your own RAM, not the offer creator's.
- Fails when: the sender or recipient no longer owns one of the listed assets (see below).
- Fails when: the sender or recipient no longer owns one of the listed assets (see "What invalidates an offer").

Source: `atomicassets-contract src/atomicassets.cpp:1302-1353`, `atomicassets-contract src/atomicassets.cpp:1668-1764` (`internal_transfer`, shared transfer logic)

Expand Down Expand Up @@ -155,8 +155,8 @@ Source: `atomicassets-contract src/atomicassets.cpp:1380-1398`

`createoffer` checks ownership and transferability once, at creation. Nothing re-checks a pending offer afterward; ownership is only re-verified when someone calls `acceptoffer`. Between those two points, an offer can go stale in two ways:

- **The asset moved.** The owner transferred it away (a plain `transfer`, or by accepting a different offer that touched the same asset id), so it's no longer in the expected owner's scope.
- **The asset was burned.** `burnasset` erases the row outright.
- The asset moved. The owner transferred it away (a plain `transfer`, or by accepting a different offer that touched the same asset id), so it's no longer in the expected owner's scope.
- The asset was burned. `burnasset` erases the row outright.

Either way, `acceptoffer` fails with `"Offer sender doesn't own at least one of the provided assets"` (or the recipient equivalent), and the offer row is left untouched, since the failed transaction makes no state changes. The contract appends the offending id to that string as a ` (ID: <asset_id>)` suffix (the same suffix `createoffer` and `transfer` add to their ownership and transferability errors), so a match on the message should allow for the trailing id. A stale offer is not pruned automatically: it stays in the `offers` table, discoverable by anyone reading it, until its sender calls `canceloffer`, its recipient calls `declineoffer`, or a failed `acceptoffer` prompts one of them to clean it up. Indexers and UIs that show pending offers should not assume a listed offer is still fulfillable.

Expand Down
Loading
Loading