diff --git a/guides/asset-lifecycle.md b/guides/asset-lifecycle.md index b1fb068..8165141 100644 --- a/guides/asset-lifecycle.md +++ b/guides/asset-lifecycle.md @@ -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`) diff --git a/guides/auctions.md b/guides/auctions.md index 8d2fa1e..c90d412 100644 --- a/guides/auctions.md +++ b/guides/auctions.md @@ -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`) @@ -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` diff --git a/guides/buyoffers.md b/guides/buyoffers.md index 67f91df..f0beb9f 100644 --- a/guides/buyoffers.md +++ b/guides/buyoffers.md @@ -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 @@ -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" } @@ -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"). @@ -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 diff --git a/guides/deposits.md b/guides/deposits.md index 2e47a10..0f0efaf 100644 --- a/guides/deposits.md +++ b/guides/deposits.md @@ -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. @@ -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` 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` 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 { diff --git a/guides/links.md b/guides/links.md index 7dd109e..209f94b 100644 --- a/guides/links.md +++ b/guides/links.md @@ -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` diff --git a/guides/notification-integration.md b/guides/notification-integration.md index a747c4d..f89363b 100644 --- a/guides/notification-integration.md +++ b/guides/notification-integration.md @@ -37,7 +37,7 @@ void on_transfer(name from, name to, std::vector 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 | | --- | --- | diff --git a/guides/offers.md b/guides/offers.md index 8f39419..dbb7b07 100644 --- a/guides/offers.md +++ b/guides/offers.md @@ -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) @@ -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: )` 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. diff --git a/guides/querying-the-api.md b/guides/querying-the-api.md index 756266a..f498770 100644 --- a/guides/querying-the-api.md +++ b/guides/querying-the-api.md @@ -58,7 +58,7 @@ Always treat a non-2xx response as an error. An HTTP helper that returns undefin ## Filter template buyoffers by state -`/v1/template_buyoffers` applies no state filter by default and rows are never deleted, so an unfiltered query returns LISTED (0), CANCELED (1), and SOLD (2) offers together. Pass `state=0` when you only want active offers: +`/v1/template_buyoffers` applies no state filter by default and rows are never deleted, so an unfiltered query returns LISTED (0), CANCELED (1), and SOLD (2) offers together. When you only want active offers, pass `state=0`: ```sh curl 'https://wax.api.atomicassets.io/atomicmarket/v1/template_buyoffers?state=0&limit=100&page=1' @@ -68,12 +68,12 @@ Do not rely on socket notifications for lifecycle tracking: only new offers are ## Read chain tables with get_table_rows -When reading contract tables directly over `/v1/chain/get_table_rows`, three behaviors matter: +When reading contract tables directly over `/v1/chain/get_table_rows`, four behaviors matter: -- **Numeric keys need `key_type: 'i64'`.** @wharfkit/antelope's typed `client.v1.chain.get_table_rows` infers `key_type` only from typed bound instances; plain string or number bounds fall back to `key_type: 'name'` and are silently misread as account names, returning wrong ranges. Pass `key_type: 'i64'` explicitly (or typed bounds) for tables keyed by numeric ids. See [@wharfkit/antelope client behavior](../reference/wharfkit.md#typed-get_table_rows-is-not-a-drop-in-for-dynamic-reads) ("Typed get_table_rows is not a drop-in for dynamic reads"). -- **`show_payer` shapes differ by transport.** The raw endpoint wraps each row as `{ data, payer }`; the typed WharfKit client unwraps rows and moves payers into an index-aligned `ram_payers` array on the response. Read `response.ram_payers[i]` with the typed client, or call the raw endpoint with `json: true, show_payer: true` to keep the envelope. See [@wharfkit/antelope client behavior](../reference/wharfkit.md#show_payer-rows-are-unwrapped-into-ram_payers) ("show_payer rows are unwrapped into ram_payers"). -- **Large uint64 values arrive as strings.** nodeos serializes uint64 values above 2^32 as JSON strings and smaller values as JSON numbers; current `sale_id`/`auction_id`/`offer_id` values arrive as numbers, but asset ids (around 2^40) arrive as strings. Parse id fields defensively rather than assuming one shape. See [AtomicMarket V2 changes](../reference/atomicmarket/v2-changes.md#large-integers-serialize-as-strings) ("Large integers serialize as strings"). -- **The `assets` table is scoped by owner, with no collection or template index.** Enumerating every asset in a collection or template is an API-only capability: `/atomicassets/v1/assets?collection_name=...` joins across owners, but `get_table_rows` on `assets` takes the owner account as `scope`, so there is no chain-side path from a collection to its asset list without already knowing the owners. To read one specific asset over the chain, use its current owner as `scope` and its `asset_id` as an `i64`-typed bound. +- Numeric keys need `key_type: 'i64'`. @wharfkit/antelope's typed `client.v1.chain.get_table_rows` infers `key_type` only from typed bound instances; plain string or number bounds fall back to `key_type: 'name'` and are silently misread as account names, returning wrong ranges. Pass `key_type: 'i64'` explicitly (or typed bounds) for tables keyed by numeric ids. See [@wharfkit/antelope client behavior](../reference/wharfkit.md#typed-get_table_rows-is-not-a-drop-in-for-dynamic-reads) ("Typed get_table_rows is not a drop-in for dynamic reads"). +- `show_payer` shapes differ by transport. The raw endpoint wraps each row as `{ data, payer }`; the typed WharfKit client unwraps rows and moves payers into an index-aligned `ram_payers` array on the response. Read `response.ram_payers[i]` with the typed client, or call the raw endpoint with `json: true, show_payer: true` to keep the envelope. See [@wharfkit/antelope client behavior](../reference/wharfkit.md#show_payer-rows-are-unwrapped-into-ram_payers) ("show_payer rows are unwrapped into ram_payers"). +- Large uint64 values arrive as strings. nodeos serializes uint64 values above 2^32 as JSON strings and smaller values as JSON numbers; current `sale_id`/`auction_id`/`offer_id` values arrive as numbers, but asset ids (around 2^40) arrive as strings. Parse id fields defensively rather than assuming one shape. See [AtomicMarket V2 changes](../reference/atomicmarket/v2-changes.md#large-integers-serialize-as-strings) ("Large integers serialize as strings"). +- The `assets` table is scoped by owner, with no collection or template index. Enumerating every asset in a collection or template is an API-only capability: `/atomicassets/v1/assets?collection_name=...` joins across owners, but `get_table_rows` on `assets` takes the owner account as `scope`, so there is no chain-side path from a collection to its asset list without already knowing the owners. To read one specific asset over the chain, use its current owner as `scope` and its `asset_id` as an `i64`-typed bound. ## Classify deterministic errors before retrying diff --git a/guides/sales.md b/guides/sales.md index 1618407..bce5574 100644 --- a/guides/sales.md +++ b/guides/sales.md @@ -110,12 +110,12 @@ await session.transact({ Authorization: the seller (the same account that ran `announcesale`). -AtomicMarket's `atomicassets::lognewoffer` handler matches the offer to a sale by hashing `sender_asset_ids` and scanning sale rows with that hash for one whose seller equals the offer's sender, then records the `offer_id` on the sale row and emits `logsalestart`. This is the validated "lazy accept" mechanism: the sale only becomes purchasable once this offer exists, and a seller who changes their mind before this step can simply not create the offer (or cancel it) with no on-chain trace beyond the sale row itself. +AtomicMarket's `atomicassets::lognewoffer` handler matches the offer to a sale by hashing `sender_asset_ids` and scanning sale rows with that hash for one whose seller equals the offer's sender, then records the `offer_id` on the sale row and emits `logsalestart`. This is the validated "lazy accept" mechanism: the sale only becomes purchasable once this offer exists, and a seller who changes their mind before this step can decline to create the offer (or cancel it) with no on-chain trace beyond the sale row itself. Failure modes asserted in source: - `recipient_asset_ids` must be empty: the sale offer cannot ask for anything back. -- `sender_asset_ids` must contain exactly one id: an offer for more than one asset can no longer activate a sale (only a legacy pre-V2 bundle sale row could ever match one, and those can't be purchased; see below). +- `sender_asset_ids` must contain exactly one id: an offer for more than one asset can no longer activate a sale (only a legacy pre-V2 bundle sale row could ever match one, and those can't be purchased; see "Cancel a sale"). - No unclaimed sale row exists with this sender as seller for this exact asset id set. - The matched sale row already has an offer (`offer_id != -1`): a sale can only be activated once. @@ -178,7 +178,7 @@ await session.transact({ Authorization: the buyer. The buyer's AtomicMarket balance must already cover the settlement price (`guides/deposits.md`): `purchasesale` debits the balance, it does not accept a token transfer inline. -`intended_delphi_median` only matters for a Delphi-priced sale (see below); for a same-token sale it must be `0`. `purchasesale` computes the settlement price, debits the buyer, pays out the seller/marketplaces/collection, accepts the AtomicAssets offer, and transfers the asset to the buyer, then erases the sale row, all in one action. +`intended_delphi_median` only matters for a Delphi-priced sale (see "Delphi (oracle) sales"); for a same-token sale it must be `0`. `purchasesale` computes the settlement price, debits the buyer, pays out the seller/marketplaces/collection, accepts the AtomicAssets offer, and transfers the asset to the buyer, then erases the sale row, all in one action. Failure modes asserted in source: @@ -287,7 +287,7 @@ curl -X POST https://wax.greymass.com/v1/chain/get_table_rows \ -d '{"code":"delphioracle","scope":"delphioracle","table":"pairs","json":true,"limit":1,"lower_bound":"waxpusd","upper_bound":"waxpusd"}' ``` -The mainnet deployment queried above currently reports version `1.3.3` (V1); see [AtomicMarket tables](../reference/atomicmarket/tables.md#config) ("config") for the live-version caveat. The `config` and oracle `pairs`/`datapoints` table layouts are unchanged between V1 and V2, so the read above illustrates the shape correctly either way; only the settlement-side behavior documented below (single-asset listings, execution-time fees) differs. +The mainnet deployment queried above reports version `1.3.3` (V1); see [AtomicMarket tables](../reference/atomicmarket/tables.md#config) ("config") for the live-version caveat. The `config` and oracle `pairs`/`datapoints` table layouts are unchanged between V1 and V2, so the read above illustrates the shape correctly either way; only the settlement-side behavior documented below (single-asset listings, execution-time fees) differs. Source: `atomicmarket-contract src/atomicmarket.cpp:120-166` (`adddelphi`), `atomicmarket-contract include/delphioracle-interface.hpp:1-67` diff --git a/guides/testing-with-vert.md b/guides/testing-with-vert.md index cb359b9..867d1a3 100644 --- a/guides/testing-with-vert.md +++ b/guides/testing-with-vert.md @@ -8,7 +8,7 @@ key-modules: ["@atomichub/vert 2.2.0 (commit a8a4160): src/antelope/blockchain.t VeRT runs a compiled Antelope contract's WebAssembly in-process under any JavaScript test runner, with no `nodeos`, no Docker, and no chain connection. It emulates the parts of the Antelope state machine a contract touches at runtime: action dispatch, the multi-index table store with secondary indexes, inline actions, notifications, permissions, and the crypto and print host functions. A test loads a `.wasm` and its `.abi`, calls actions the same way a transaction would, and reads the resulting tables, all synchronously in one process. -This suits fast, deterministic unit and integration tests of contract logic: fee math, guard conditions, table state transitions, and the notification and inline-action wiring between contracts such as AtomicAssets and AtomicMarket. It does not replace testnet validation. VeRT does not model CPU/NET/RAM billing, real signature verification, block production, or deferred transactions, and its permission checks are a simplified subset (see "Authorization model"). Run the suite for logic coverage on every change; validate resource use, ordering, and end-to-end behavior against a real chain (jungle4 or wax-testnet) before deploying. +This suits fast, deterministic unit and integration tests of contract logic: fee math, guard conditions, table state transitions, and the notification and inline-action wiring between contracts such as AtomicAssets and AtomicMarket. It does not replace testnet validation. VeRT does not model CPU/NET/RAM billing, real signature verification, block production, or deferred transactions, and its permission checks are a simplified subset (see "Authorization model"). Run the suite for logic coverage on every change; validate resource use, ordering, and end-to-end behavior against a real chain (jungle4 or WAX testnet) before deploying. `@atomichub/vert` is a fork of `@waxio/vert` that adds a per-chain host-function gate so a suite links only the host functions the target chain provides. Line citations below are against `@atomichub/vert` 2.2.0 at commit `a8a4160`. @@ -114,8 +114,8 @@ Source: `@atomichub/vert src/antelope/blockchain.ts:67` (epoch-0 genesis), `@ato VeRT's permission checks are a deliberate simplification of a real chain's, and in two respects they are stricter, so an action that a live chain would authorize can fail here. -- **Single-name `require_auth` accepts only `active` or `owner`.** `require_auth(name)` is satisfied only if the authorization list carries `name` with permission exactly `active` or `owner`. There is no permission hierarchy and no linkauth resolution, so authorizing with a custom permission (`alice@mycustom`) does not satisfy `require_auth(alice)` even when a live chain would through a linked permission. `has_auth` follows the same rule. -- **`require_auth2` is an exact match.** `require_auth2(name, permission)` is satisfied only by that exact actor-and-permission pair in the authorization list. +- `require_auth` with a single name accepts only `active` or `owner`. `require_auth(name)` is satisfied only if the authorization list carries `name` with permission exactly `active` or `owner`. There is no permission hierarchy and no linkauth resolution, so authorizing with a custom permission (`alice@mycustom`) does not satisfy `require_auth(alice)` even when a live chain would through a linked permission. `has_auth` follows the same rule. +- `require_auth2` is an exact match. `require_auth2(name, permission)` is satisfied only by that exact actor-and-permission pair in the authorization list. A permission named in an action's authorization must already exist on the account, or the action throws `Account has no permission ` before the WebAssembly runs. Accounts are created with `owner` and `active` only; to authorize with any other permission, add it first with `account.setPermissions(...)`. A `check(false, ...)` that fails on missing authority throws `missing required authority `. diff --git a/reference/api.md b/reference/api.md index fc054df..18e1c8c 100644 --- a/reference/api.md +++ b/reference/api.md @@ -17,7 +17,7 @@ Source: `atomicassets-api src/api/server.ts` (`swagger.setup` mounted at `/docs` ## List endpoints cap limit at 100 -The atomicassets-api validates the `limit` query parameter on list endpoints such as `/atomicmarket/v1/buyoffers` and `/atomicmarket/v1/sales` against a maximum that defaults to 100; requests above the cap are rejected with HTTP 400 and `{"success": false, "message": "Invalid value for parameter limit"}` rather than being clamped. The cap is an operator-configurable server setting (`limits` in the API config), so the reference deployment at wax.api.atomicassets.io enforces 100. Pagination code must therefore bound `limit` to 100 and use `page`, and counting code must treat a non-2xx response as an error: an HTTP client helper that returns undefined or empty on failure will silently turn an over-limit request into a zero count. +The atomicassets-api validates the `limit` query parameter on list endpoints such as `/atomicmarket/v1/buyoffers` and `/atomicmarket/v1/sales` against a maximum that defaults to 100; requests above the cap are rejected with HTTP 400 and `{"success": false, "message": "Invalid value for parameter limit"}` rather than being clamped. The cap is an operator-configurable server setting (`limits` in the API config), so the reference deployment at wax.api.atomicassets.io enforces 100. Pagination code must therefore bound `limit` to 100 and use `page`, and counting code must treat a non-2xx response as an error: an HTTP client helper that returns undefined or empty on failure silently turns an over-limit request into a zero count. ## Two sales list routes answer on the hosted deployment @@ -46,7 +46,7 @@ Every AtomicMarket listing endpoint returns a numeric `state`, but the enum diff - **Sales** (`/v1/sales`, `/v1/sales/{id}`): `WAITING=0`, `LISTED=1`, `CANCELED=2`, `SOLD=3`, `INVALID=4`. A completed purchase reads `state=3`. - **Auctions** (`/v1/auctions`): `WAITING=0`, `LISTED=1`, `CANCELED=2`, `SOLD=3`, `INVALID=4` (INVALID = the auction ended with no bid). - **Buyoffers** (`/v1/buyoffers`): `PENDING=0`, `DECLINED=1`, `CANCELED=2`, `ACCEPTED=3`, `INVALID=4`. -- **Template buyoffers** (`/v1/template_buyoffers`): `LISTED=0`, `CANCELED=1`, `SOLD=2` (see the section above). +- **Template buyoffers** (`/v1/template_buyoffers`): `LISTED=0`, `CANCELED=1`, `SOLD=2` (see "Template buyoffers keep all lifecycle states"). Sales, auctions, and buyoffers share the value `0` for a not-yet-active listing (assets/funds not escrowed) and `2` for CANCELED, but the "settled" value is `3` (SOLD / ACCEPTED), not the `2` a template buyoffer uses. Do not carry a `SOLD=2` assumption from the template-buyoffer enum across to the other three endpoints. diff --git a/reference/atomicassets-api.md b/reference/atomicassets-api.md index 4c8bcd9..e10f88b 100644 --- a/reference/atomicassets-api.md +++ b/reference/atomicassets-api.md @@ -31,4 +31,4 @@ The atomicassets-api migration runner keys on a single `dbinfo` table row named ## CI runs only against main -The atomicassets-api CI workflow triggers on pushes to `main` and on pull requests targeting `main` only. A pull request opened against any other branch (for example a long-lived feature integration branch) starts no checks at all, so an all-green PR page does not mean the change was tested. Contributors working on integration branches should verify locally: run a PostgreSQL container, export `POSTGRES_TEST_HOST`, `POSTGRES_TEST_PORT`, `POSTGRES_TEST_USER`, `POSTGRES_TEST_PASSWORD`, and `POSTGRES_TEST_DATABASE` (with `PGSSLMODE=disable` if the container has no TLS), then run `pnpm test:integration:ci` alongside the lint, typecheck, build, and unit-test scripts that the CI workflow runs. +The atomicassets-api CI workflow triggers on pushes to `main` and on pull requests targeting `main` only. A pull request opened against any other branch (for example a long-lived feature integration branch) starts no checks at all, so an all-green PR page does not mean the change was tested. On an integration branch, verify locally: run a PostgreSQL container, export `POSTGRES_TEST_HOST`, `POSTGRES_TEST_PORT`, `POSTGRES_TEST_USER`, `POSTGRES_TEST_PASSWORD`, and `POSTGRES_TEST_DATABASE` (with `PGSSLMODE=disable` if the container has no TLS), then run `pnpm test:integration:ci` alongside the lint, typecheck, build, and unit-test scripts that the CI workflow runs. diff --git a/reference/atomicassets/actions.md b/reference/atomicassets/actions.md index 0eac571..d82c1e6 100644 --- a/reference/atomicassets/actions.md +++ b/reference/atomicassets/actions.md @@ -8,7 +8,7 @@ key-modules: ["atomicassets-contract (v2.0.0): src/atomicassets.cpp, include/ato Complete action reference for the `atomicassets` contract, baselined on tag `v2.0.0` of `atomicassets-contract` (the release pinned for both `wax-testnet` and `jungle4-testnet`). Every entry cites its declaration in `include/atomicassets.hpp` and its body in `src/atomicassets.cpp`. "Changed in V2" notes compare against the V1 `atomicassets-contract` source. -Live-chain status: see [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"). None of the V2-only actions on this page are callable on WAX mainnet yet. +Live-chain status: see [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"). None of the V2-only actions on this page are callable on WAX mainnet. ## Admin @@ -253,7 +253,7 @@ Source: `include/atomicassets.hpp:137-142`, `src/atomicassets.cpp:849-916` Required authorization: `authorized_editor`, checked via `check_has_collection_auth`. -Erases the `templates` row (and its `templates2` row, if any) only while `issued_supply == 0`. Sends no notification at all, unlike every other collection-editing action in this group: an indexer that only watches `notify_collection_accounts` fan-out will not observe a deletion, though it remains visible as the top-level action in the transaction trace. Changed in V2: does not exist in V1 (a V1 template, once created, can never be removed even with zero issued supply). +Erases the `templates` row (and its `templates2` row, if any) only while `issued_supply == 0`. Sends no notification at all, unlike every other collection-editing action in this group: an indexer that only watches `notify_collection_accounts` fan-out does not observe a deletion, though it remains visible as the top-level action in the transaction trace. Changed in V2: does not exist in V1 (a V1 template, once created, can never be removed even with zero issued supply). Source: `include/atomicassets.hpp:144-148`, `src/atomicassets.cpp:603-633` @@ -293,7 +293,7 @@ Source: `include/atomicassets.hpp:156-161`, `src/atomicassets.cpp:664-699` - `new_asset_owner: name`: must be an existing account. - `immutable_data: ATTRIBUTE_MAP` - `mutable_data: ATTRIBUTE_MAP` -- `tokens_to_back: vector`: must be empty (see below). +- `tokens_to_back: vector`: must be empty (see this action's "Changed in V2" note). Required authorization: `authorized_minter`, checked via `check_has_collection_auth`. @@ -568,7 +568,7 @@ Sent by `createtempl2` (when given non-empty mutable data) and `settempldata`. C Source: `include/atomicassets.hpp:295-301`, `src/atomicassets.cpp:1525-1537` -`logrampayer`, sent by `setrampayer` and `setlastpayer`, is documented under "RAM-payer reassignment (replaces descoped custodial rentals)" below rather than in this group. +`logrampayer`, sent by `setrampayer` and `setlastpayer`, is documented under "RAM-payer reassignment (replaces descoped custodial rentals)" rather than in this group. ### logbackasset diff --git a/reference/atomicassets/data-precedence.md b/reference/atomicassets/data-precedence.md index 7bdf5a9..b0d6f3c 100644 --- a/reference/atomicassets/data-precedence.md +++ b/reference/atomicassets/data-precedence.md @@ -36,7 +36,7 @@ For a given attribute name, the effective value a reader should present is chose 1. Template immutable data (if the asset has a `template_id >= 0` and the template defines that attribute) 2. Asset immutable data 3. Asset mutable data -4. Template mutable data (V2), which the reference reader folds in below all three above (see the note below); the contract prescribes no slot for it, so this ranking is the reader's choice, not a chain-level rule. +4. Template mutable data (V2), which the `atomicassets-api` reference reader ranks beneath the other three layers; the contract prescribes no slot for it, so this ranking is the reader's choice, not a chain-level rule. This means the template's immutable value for a given key always wins over anything set at the asset level for that same key, even a value an editor later pushes through `setassetdata`. In practice, collections avoid this collision by design: shared attributes (name, artwork, rarity) live on the template, and only attributes the template doesn't define (a serial number, a leveling stat, a one-off trait) are set at the asset level - but the contract does not enforce that separation, so nothing stops an attribute name from existing in more than one layer, and readers must resolve the collision the same way every time. @@ -51,7 +51,7 @@ To compute an asset's effective attributes from scratch: 1. Fetch the asset row and deserialize `immutable_serialized_data` and `mutable_serialized_data` against the asset's `schema_name` format (the schema as it currently exists - schema extensions are backward compatible with old serialized data since new lines only add higher identifiers). 2. If `template_id >= 0`, fetch the template row and deserialize its `immutable_serialized_data` against the same schema format. 3. If the reader supports V2 template mutable data, fetch the `templates2` row for that `template_id` (it may not exist) and deserialize its `mutable_serialized_data`. -4. Merge in this order, letting each later step override matching keys: template mutable data, then asset mutable data, then asset immutable data, then template immutable data. That is the order the `atomicassets-api` reference reader uses (template mutable at the bottom, template immutable at the top). The contract prescribes no order of its own, so a reader with a different use case is free to place the template mutable layer elsewhere; this order simply matches the reference implementation. +4. Merge in this order, letting each later step override matching keys: template mutable data, then asset mutable data, then asset immutable data, then template immutable data. That is the order the `atomicassets-api` reference reader uses (template mutable at the bottom, template immutable at the top). The contract prescribes no order of its own, so a reader with a different use case is free to place the template mutable layer elsewhere; this order matches the reference implementation. 5. Treat a key absent from all fetched layers as unset, not as an empty string or zero - the serialization format never encodes "no value" as anything other than the key's absence. Source: derived from `src/atomicassets.cpp:700-791`, `src/atomicassets.cpp:799-839`, `src/atomicassets.cpp:849-910`, `src/atomicassets.cpp:1582-1658`, `include/atomicdata.hpp:488-524`, cross-checked against `atomicassets-api src/api/namespaces/atomicassets/format.ts:1-36` diff --git a/reference/atomicassets/notifications.md b/reference/atomicassets/notifications.md index 1bff0fc..17395d7 100644 --- a/reference/atomicassets/notifications.md +++ b/reference/atomicassets/notifications.md @@ -43,7 +43,7 @@ Source: `src/atomicassets.cpp:76-86` (transfer), `src/atomicassets.cpp:1188-1276 ## What a notified contract can and cannot rely on -A notification is delivered inline, in the same transaction as the triggering action: a notified contract's own handler runs as part of that transaction and can abort it by throwing, so being on a `notify_accounts` list is a way to gate or observe an action, not a read-only subscription. What arrives is only the notifying action's own parameters (for example `logsetdata` carries the deserialized old and new mutable data); a notified contract that needs anything beyond that has to query the tables itself. The set of accounts notified for a collection is read fresh at the moment the `log*` action runs, not cached from an earlier point in the transaction. Two things a notified contract cannot assume: it will not hear anything about `canceloffer` or `declineoffer`, since neither sends any notification, and grouping of a multi-asset `transfer` into per-collection `logtransfer` calls follows `std::map` key order, not the order the caller supplied asset ids in. +A notification is delivered inline, in the same transaction as the triggering action: a notified contract's own handler runs as part of that transaction and can abort it by throwing, so being on a `notify_accounts` list is a way to gate or observe an action, not a read-only subscription. What arrives is only the notifying action's own parameters (for example `logsetdata` carries the deserialized old and new mutable data); a notified contract that needs anything beyond that has to query the tables itself. The set of accounts notified for a collection is read fresh at the moment the `log*` action runs, not cached from an earlier point in the transaction. Two things a notified contract cannot assume: it hears nothing about `canceloffer` or `declineoffer`, since neither sends any notification, and grouping of a multi-asset `transfer` into per-collection `logtransfer` calls follows `std::map` key order, not the order the caller supplied asset ids in. Source: `src/atomicassets.cpp:1826-1859` (`partial_read_collection`), `src/atomicassets.cpp:1883-1891` (`notify_collection_accounts`), `src/atomicassets.cpp:1668-1764` (internal_transfer collection grouping) @@ -55,4 +55,4 @@ Source: `src/atomicassets.cpp:917-943` (setrampayer), `src/atomicassets.cpp:946- ## Live-chain status -See [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"). Every V2-only notification point above is source-verified against the `v2.0.0` contract but had not shipped to WAX mainnet at the time of this check. +See [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"). Every V2-only notification point on this page is source-verified against the `v2.0.0` contract and has not shipped to WAX mainnet. diff --git a/reference/atomicassets/structure.md b/reference/atomicassets/structure.md index 2d3ec00..fe22ca6 100644 --- a/reference/atomicassets/structure.md +++ b/reference/atomicassets/structure.md @@ -84,7 +84,7 @@ Three levels of authority operate over a collection and everything scoped under - **Collection author** (`collections.author`) - the only account that can call `setcoldata`, `addcolauth` / `remcolauth`, `addnotifyacc` / `remnotifyacc`, `setmarketfee`, `forbidnotify`, and initiate an author swap. `forbidnotify` is one-way: once `allow_notify` is false, no action sets it back to true, so `notify_accounts` can never be populated again for that collection (it must also already be empty when `forbidnotify` is called). - **Authorized accounts** (`collections.authorized_accounts`, capped at 24) - can create and edit schemas, templates, and assets belonging to the collection. Every action gated this way calls `check_has_collection_auth(account, collection_name)`, which both requires the caller's own authorization and checks list membership; it does not require being the author. -- **Notify accounts** (`collections.notify_accounts`, capped at 24, gated by `allow_notify`) - not authorization at all, but a `require_recipient` notification list: every relevant action for the collection will attempt to notify these accounts' smart contracts, so a notify account with a throwing `on_notify` handler can block the action. The 24-account cap on both lists exists partly to bound the low-level partial-row read the contract uses to check membership without deserializing a collection's full (and potentially multi-kilobyte) `serialized_data`. This cap is new in V2 for `notify_accounts`: V1's `addnotifyacc` enforced no limit on the list's size. +- **Notify accounts** (`collections.notify_accounts`, capped at 24, gated by `allow_notify`) - not authorization at all, but a `require_recipient` notification list: every relevant action for the collection attempts to notify these accounts' smart contracts, so a notify account with a throwing `on_notify` handler can block the action. The 24-account cap on both lists exists partly to bound the low-level partial-row read the contract uses to check membership without deserializing a collection's full (and potentially multi-kilobyte) `serialized_data`. This cap is new in V2 for `notify_accounts`: V1's `addnotifyacc` enforced no limit on the list's size. Source: `src/atomicassets.cpp:167-445` (author-only and auth-gated actions), `src/atomicassets.cpp:1826-1891` (`partial_read_collection`, `check_has_collection_auth`, `notify_collection_accounts`), `src/atomicassets.cpp:127-128` (24-account cap, createcol), `src/atomicassets.cpp:212` (24-account cap, addcolauth), `src/atomicassets.cpp:274` (24-account cap, addnotifyacc) diff --git a/reference/atomicassets/tables.md b/reference/atomicassets/tables.md index 07886ce..81e09cc 100644 --- a/reference/atomicassets/tables.md +++ b/reference/atomicassets/tables.md @@ -8,7 +8,7 @@ key-modules: ["atomicassets-contract (v2.0.0): src/atomicassets.cpp, include/ato Complete table reference for the `atomicassets` contract, baselined on tag `v2.0.0` of `atomicassets-contract` (the release pinned for both `wax-testnet` and `jungle4-testnet`). Struct and typedef citations are to `include/atomicassets.hpp`; behavior citations are to `src/atomicassets.cpp`. "Changed in V2" notes compare against the V1 `atomicassets-contract` source. -Live-chain status: see [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"); the V2-only tables below (`authorswaps`, `schematypes`, `templates2`) do not exist on WAX mainnet yet. An abandoned custodial-rentals design left a `holders` table on unreleased development branches; it ships in no tagged release and is not documented here. See [AtomicAssets actions](actions.md#ram-payer-reassignment-replaces-descoped-custodial-rentals) ("RAM-payer reassignment (replaces descoped custodial rentals)"). +Live-chain status: see [AtomicAssets V2 upgrade](v2-upgrade.md#deployment-status) ("Deployment status"); the V2-only tables on this page (`authorswaps`, `schematypes`, `templates2`) do not exist on WAX mainnet. An abandoned custodial-rentals design left a `holders` table on unreleased development branches; it ships in no tagged release and is not documented here. See [AtomicAssets actions](actions.md#ram-payer-reassignment-replaces-descoped-custodial-rentals) ("RAM-payer reassignment (replaces descoped custodial rentals)"). Every table below is defined in `include/atomicassets.hpp:337-468`, and every scoped (non-self) table is fetched through a `get_*` helper at `include/atomicassets.hpp:476-490`. diff --git a/reference/atomicassets/v2-upgrade.md b/reference/atomicassets/v2-upgrade.md index 31e9fb9..ae61e18 100644 --- a/reference/atomicassets/v2-upgrade.md +++ b/reference/atomicassets/v2-upgrade.md @@ -14,7 +14,7 @@ AtomicAssets V2 is a non-breaking, additive, in-place upgrade of the V1 contract ## Deployment status -WAX mainnet was still running V1 at the time of writing. A live `get_abi` read against `atomicassets` on `wax.greymass.com` (`POST /v1/chain/get_abi {"account_name":"atomicassets"}`, checked 2026-07-06) returns a 35-action ABI matching the V1 action list exactly, and a `get_table_rows` read of `tokenconfigs` on the same account reports `version: "1.2.3"`. None of the V2-only actions (`createauswap`, `setrampayer`, `setlastpayer`, `logrampayer`, `settempldata`, `logsetdatatl`, `setschematyp`) or tables (`authorswaps`, `schematypes`, `templates2`) are present or callable there yet, and `backasset` is still a live, non-deprecated action on that chain rather than the aborting stub V2 ships. +WAX mainnet runs V1. A live `get_abi` read against `atomicassets` on `wax.greymass.com` (`POST /v1/chain/get_abi {"account_name":"atomicassets"}`, checked 2026-07-06) returns a 35-action ABI matching the V1 action list exactly, and a `get_table_rows` read of `tokenconfigs` on the same account reports `version: "1.2.3"`. None of the V2-only actions (`createauswap`, `setrampayer`, `setlastpayer`, `logrampayer`, `settempldata`, `logsetdatatl`, `setschematyp`) or tables (`authorswaps`, `schematypes`, `templates2`) are present or callable there, and `backasset` is still a live, non-deprecated action on that chain rather than the aborting stub V2 ships. Re-check `tokenconfigs.version` (or `get_abi`) on any target chain before relying on V2-only behavior. `version` is only as accurate as the operator's last `setversion` call, so treat `get_abi`'s actual action/table list as the authoritative check when the two could disagree. diff --git a/reference/atomicmarket/actions.md b/reference/atomicmarket/actions.md index e5384ca..293f7c4 100644 --- a/reference/atomicmarket/actions.md +++ b/reference/atomicmarket/actions.md @@ -8,7 +8,7 @@ key-modules: ["atomicmarket-contract (v2.0.0): src/atomicmarket.cpp, include/ato Every entry cites its declaration in `include/atomicmarket.hpp` and its body in `src/atomicmarket.cpp` (paths relative to the atomicmarket-contract repo), baselined on the V2 source. "Changed in V2" notes compare against the V1 `atomicmarket-contract` source. -See [AtomicMarket marketplaces](marketplaces.md#registering-a-marketplace-with-regmarket) ("Registering a marketplace with regmarket") for marketplace attribution, [AtomicMarket fees and royalties](fees-and-royalties.md#the-collection-fee-applies-at-execution-time-not-at-listing-time) ("The collection fee applies at execution time, not at listing time") for execution-time collection fees, ("The royalty log actions are trace-only and dust always reconciles") for trace-only royalty logs, and [AtomicMarket V2 changes](v2-changes.md#defensive-guards-in-the-v2-contract) ("Defensive guards in the V2 contract") for defensive guards. Bundle retirement is covered inline via this page's own "Changed in V2" notes. +See [AtomicMarket marketplaces](marketplaces.md#registering-a-marketplace-with-regmarket) ("Registering a marketplace with regmarket") for marketplace attribution, [AtomicMarket fees and royalties](fees-and-royalties.md#the-collection-fee-applies-at-execution-time-not-at-listing-time) ("The collection fee applies at execution time, not at listing time") for execution-time collection fees, [AtomicMarket fees and royalties](fees-and-royalties.md#the-royalty-log-actions-are-trace-only-and-dust-always-reconciles) ("The royalty log actions are trace-only and dust always reconciles") for trace-only royalty logs, and [AtomicMarket V2 changes](v2-changes.md#defensive-guards-in-the-v2-contract) ("Defensive guards in the V2 contract") for defensive guards. Bundle retirement is covered inline via this page's own "Changed in V2" notes. Custodial rentals do not appear anywhere in this source tree: neither the V1 baseline nor the V2 source declare any rent-prefixed action or table. A custodial rental feature was explored during V2 development and descoped before shipping; this page can only confirm the feature's absence from both source trees, not the history of the descope itself. @@ -435,7 +435,7 @@ Source: `include/atomicmarket.hpp:308-314`, `src/atomicmarket.cpp:1717-1794` ## RAM -Each of these three actions re-homes the RAM payer of an existing row to `payer` by erasing and re-emplacing it under the new payer, with no other field changes and no token movement. See [AtomicMarket RAM](ram.md#sellers-and-buyers-pay-ram-for-their-own-listing-rows-by-default) ("Sellers and buyers pay RAM for their own listing rows by default") for who pays RAM by default, and ("Practical sizing implications for a high-volume marketplace") for the sizing implications for a high-volume marketplace. +Each of these three actions re-homes the RAM payer of an existing row to `payer` by erasing and re-emplacing it under the new payer, with no other field changes and no token movement. See [AtomicMarket RAM](ram.md#sellers-and-buyers-pay-ram-for-their-own-listing-rows-by-default) ("Sellers and buyers pay RAM for their own listing rows by default") for who pays RAM by default, and [AtomicMarket RAM](ram.md#practical-sizing-implications-for-a-high-volume-marketplace) ("Practical sizing implications for a high-volume marketplace") for the sizing implications for a high-volume marketplace. ### paysaleram diff --git a/reference/atomicmarket/fees-and-royalties.md b/reference/atomicmarket/fees-and-royalties.md index 7b7b86d..735cfc7 100644 --- a/reference/atomicmarket/fees-and-royalties.md +++ b/reference/atomicmarket/fees-and-royalties.md @@ -49,7 +49,7 @@ Source: `src/atomicmarket.cpp:2689-2957` (`distribute_collection_fee` category s ## The royalty log actions are trace-only and dust always reconciles -`logroyfound`, `logroytempl`, `logroyattr`, and `logroydust` are inline actions the contract sends to itself (`require_auth(get_self())`, empty bodies) with no `require_recipient`: a payout recipient's contract can never assert inside a notification handler and block someone else's settlement. That also means a plain notification-driven indexer never observes them; only a trace-reading pipeline (state history / an EOS SHiP consumer) captures them. Every recipient's payout inside `distribute_collection_fee` is accumulated in memory and written to the `balances` table exactly once per recipient, and any integer-division remainder from splitting a share across recipients or rules is added to the collection author's payout and reported through `logroydust`, so `sum(all logroy* payout amounts for one settlement) == the collection fee actually applied` always holds exactly, to the unit. Indexers should store these logged amounts rather than recomputing the split from the royalty config tables, which can diverge through rounding or config changes between listing and settlement under the execution-time fee model. A client reads a settled listing's actual payouts from the indexer's per-listing logs endpoint rather than the chain: `/atomicmarket/v1/sales/{sale_id}/logs` returns the `logroyfound`/`logroytempl`/`logroyattr`/`logroydust` entries, each carrying its `payouts` array of `{recipient, amount}`; the same `/logs` suffix serves `/v1/auctions/{auction_id}`, `/v1/buyoffers/{buyoffer_id}`, and `/v1/template_buyoffers/{buyoffer_id}`. There is no aggregate `/royalties` payout endpoint on a listing; the logs are the payout record. The `atomicmarket-contract` repository's own `docs/api-integration.md` documents the full indexer interface, and its VeRT test suite is an executable reference for every flow. +`logroyfound`, `logroytempl`, `logroyattr`, and `logroydust` are inline actions the contract sends to itself (`require_auth(get_self())`, empty bodies) with no `require_recipient`: a payout recipient's contract can never assert inside a notification handler and block someone else's settlement. That also means a plain notification-driven indexer never observes them; only a trace-reading pipeline (state history / an EOS SHiP consumer) captures them. Every recipient's payout inside `distribute_collection_fee` is accumulated in memory and written to the `balances` table exactly once per recipient, and any integer-division remainder from splitting a share across recipients or rules is added to the collection author's payout and reported through `logroydust`, so `sum(all logroy* payout amounts for one settlement) == the collection fee actually applied` always holds exactly, to the unit. Store these logged amounts rather than recomputing the split from the royalty config tables, which can diverge through rounding or config changes between listing and settlement under the execution-time fee model. A client reads a settled listing's actual payouts from the indexer's per-listing logs endpoint rather than the chain: `/atomicmarket/v1/sales/{sale_id}/logs` returns the `logroyfound`/`logroytempl`/`logroyattr`/`logroydust` entries, each carrying its `payouts` array of `{recipient, amount}`; the same `/logs` suffix serves `/v1/auctions/{auction_id}`, `/v1/buyoffers/{buyoffer_id}`, and `/v1/template_buyoffers/{buyoffer_id}`. There is no aggregate `/royalties` payout endpoint on a listing; the logs are the payout record. The `atomicmarket-contract` repository's own `docs/api-integration.md` documents the full indexer interface, and its VeRT test suite is an executable reference for every flow. Source: `src/atomicmarket.cpp:2085-2117` (`logroyfound`/`logroytempl`/`logroyattr`/`logroydust` action bodies), `src/atomicmarket.cpp:2960-2986` (dust accrual and `logroydust` emission) diff --git a/reference/atomicmarket/tables.md b/reference/atomicmarket/tables.md index 1656fe1..7f4fe94 100644 --- a/reference/atomicmarket/tables.md +++ b/reference/atomicmarket/tables.md @@ -8,7 +8,7 @@ key-modules: ["atomicmarket-contract (v2.0.0): src/atomicmarket.cpp, include/ato Every entry cites its declaration in `include/atomicmarket.hpp` (paths relative to the atomicmarket-contract repo), baselined on the V2 source. "Changed in V2" notes compare against the V1 `atomicmarket-contract` source. -See [AtomicMarket marketplaces](marketplaces.md#registering-a-marketplace-with-regmarket) ("Registering a marketplace with regmarket") for marketplace attribution and [AtomicMarket fees and royalties](fees-and-royalties.md#the-collection-fee-applies-at-execution-time-not-at-listing-time) ("The collection fee applies at execution time, not at listing time") for execution-time collection fees and ("The royalty log actions are trace-only and dust always reconciles") for trace-only royalty logs. This page stays brief on those topics and cross-links instead of repeating them. +See [AtomicMarket marketplaces](marketplaces.md#registering-a-marketplace-with-regmarket) ("Registering a marketplace with regmarket") for marketplace attribution and [AtomicMarket fees and royalties](fees-and-royalties.md#the-collection-fee-applies-at-execution-time-not-at-listing-time) ("The collection fee applies at execution time, not at listing time") for execution-time collection fees and [AtomicMarket fees and royalties](fees-and-royalties.md#the-royalty-log-actions-are-trace-only-and-dust-always-reconciles) ("The royalty log actions are trace-only and dust always reconciles") for trace-only royalty logs. This page stays brief on those topics and cross-links instead of repeating them. See [AtomicAssets V2 upgrade](../atomicassets/v2-upgrade.md#deployment-status) ("Deployment status") for the live-chain read confirming the royalty tables below are source-verified but not yet observable on any publicly reachable chain. @@ -215,7 +215,7 @@ Per-collection royalty split configuration: how a collection's fee is divided be | `split_templates` | uint32_t | Relative weight of the template category. | | `split_attributes` | uint32_t | Relative weight of the attributes category. | -The three split weights are relative, not fractions of 1: at settlement, only the categories that actually have a payee for the specific asset are kept, and their weights are renormalized against each other. See [AtomicMarket fees and royalties](fees-and-royalties.md#the-royalty-split-engine-divides-the-collection-fee-among-founders-templates-and-attributes) ("The royalty split engine divides the collection fee among founders, templates, and attributes") for the full settlement-time distribution mechanics, and ("The royalty log actions are trace-only and dust always reconciles") for why the royalty log actions, not this table, are the source of truth for what was actually paid. +The three split weights are relative, not fractions of 1: at settlement, only the categories that actually have a payee for the specific asset are kept, and their weights are renormalized against each other. See [AtomicMarket fees and royalties](fees-and-royalties.md#the-royalty-split-engine-divides-the-collection-fee-among-founders-templates-and-attributes) ("The royalty split engine divides the collection fee among founders, templates, and attributes") for the full settlement-time distribution mechanics, and [AtomicMarket fees and royalties](fees-and-royalties.md#the-royalty-log-actions-are-trace-only-and-dust-always-reconciles) ("The royalty log actions are trace-only and dust always reconciles") for why the royalty log actions, not this table, are the source of truth for what was actually paid. Source: `include/atomicmarket.hpp:477-490` diff --git a/reference/chain.md b/reference/chain.md index 3303365..2055863 100644 --- a/reference/chain.md +++ b/reference/chain.md @@ -8,4 +8,4 @@ key-modules: [] ## Error 3060002 means the account does not exist -When `/v1/chain/get_account` is queried for an account that does not exist, a WAX (Antelope) node responds HTTP 400 with `error.code` 3060002 and `error.name` `account_query_exception`; the detail message reads "unable to retrieve account info (unknown key ...)" from `chain_plugin.cpp`. This error is deterministic (the account has no on-chain presence), not a transient RPC failure, so retrying is pointless and classifying it as a generic error traps callers in retry loops. Services verifying account existence should map 3060002 to a distinct account-not-found response instead of a generic rejection. This case is common on WAX because Cloud Wallet lets users complete signup with a reserved `.wam` name before the on-chain `newaccount` action confirms, leaving a working wallet UX with no chain account. When detecting the error through a client library such as @wharfkit/antelope's APIClient, check both the structured code (3060002, which may arrive as number or string) and the message text `account_query_exception`, since proxies can strip either. +When `/v1/chain/get_account` is queried for an account that does not exist, a WAX (Antelope) node responds HTTP 400 with `error.code` 3060002 and `error.name` `account_query_exception`; the detail message reads "unable to retrieve account info (unknown key ...)" from `chain_plugin.cpp`. This error is deterministic (the account has no on-chain presence), not a transient RPC failure, so retrying is pointless and classifying it as a generic error traps callers in retry loops. To verify account existence, map 3060002 to a distinct account-not-found response instead of a generic rejection. This case is common on WAX because Cloud Wallet lets users complete signup with a reserved `.wam` name before the on-chain `newaccount` action confirms, leaving a working wallet UX with no chain account. When detecting the error through a client library such as @wharfkit/antelope's APIClient, check both the structured code (3060002, which may arrive as number or string) and the message text `account_query_exception`, since proxies can strip either. diff --git a/reference/media.md b/reference/media.md index 54246e6..9ee54c8 100644 --- a/reference/media.md +++ b/reference/media.md @@ -48,7 +48,7 @@ Source: live reads, `https://wax.api.atomicassets.io/atomicassets/v1/schemas/=20`. The package declares `sideEffects: false`, so a bundler may drop what an application does not import: importing only `ActionBuilder` no longer pulls in the base58 coder, the parser table, or the action-name map. Every public type and value is re-exported from the package root, so consumers import from `@atomichub/atomicassets` and never reach into `build/` subpaths. +`@atomichub/atomicassets` declares no runtime `dependencies`; everything it needs (fetch, serialization, the queue) is either built in or supplied by the host runtime's global `fetch`. It publishes dual builds (`build/index.mjs` for `import`, `build/index.cjs` for `require`) with types for both, and requires Node `>=20`. The package declares `sideEffects: false`, so a bundler may drop what an application does not import: importing only `ActionBuilder` does not pull in the base58 coder, the parser table, or the action-name map. Every public type and value is re-exported from the package root, so consumers import from `@atomichub/atomicassets` and never reach into `build/` subpaths. Source: atomicassets-sdk (v2.1.1, 5c70c62) package.json:32 (`sideEffects: false`), package.json:34-35 (`engines.node >=20`), package.json (no `dependencies` key; `main`/`module`/`exports` dual build), src/index.ts:10-61 (flat root re-exports) @@ -191,13 +191,13 @@ Source: atomicassets-sdk (v2.1.1, 5c70c62) src/Actions/Generator.ts:126-157 (`IN ### Native backing is deprecated on the action and on the mint parameter -`backasset` carries a `@deprecated` tag on both the builder and the generator, and `mintasset` carries the same tag on its `tokens_to_back` parameter. AtomicAssets v2 ends `mintasset` with a check that `tokens_to_back` is empty and guards `backasset` the same way, so both abort there. Both still execute on a chain that has not migrated, which means a call that works says the chain has not arrived yet rather than that the path is supported. Pass `[]` and back nothing. The contract-side rule and its abort message are in [Create a collection and mint assets](../../guides/asset-lifecycle.md#mint-an-asset-mintasset) ("Mint an asset: mintasset"). +`backasset` carries a `@deprecated` tag on both the builder and the generator, and `mintasset` carries the same tag on its `tokens_to_back` parameter. AtomicAssets v2 ends `mintasset` with a check that `tokens_to_back` is empty and guards `backasset` the same way, so both abort there. Both still execute on a chain that has not migrated, which means a call that works says the chain has not upgraded rather than that the path is supported. Pass `[]` and back nothing. The contract-side rule and its abort message are in [Create a collection and mint assets](../../guides/asset-lifecycle.md#mint-an-asset-mintasset) ("Mint an asset: mintasset"). Source: atomicassets-sdk (v2.1.1, 5c70c62) src/Actions/Generator.ts:238-240 (`backasset` on the builder), src/Actions/Generator.ts:581-585 (`backasset` on the generator), src/Actions/Generator.ts:358-372 (`mintasset` `tokens_to_back`) ## Network factories carry AtomicHub's public hosts -`explorerApiForNetwork(network, options?)` and `rpcApiForNetwork(network, contract?, options?)` construct a preconfigured client against AtomicHub's public endpoints, and `NETWORK_ENDPOINTS` exposes the host map. The valid `AtomicHubNetwork` keys are `wax`, `wax-testnet`, `vaulta`, `xpr`, `xpr-testnet`, and `jungle4`. Each key currently maps its `api` and `rpc` to the same host (for example `wax` to `https://wax.api.atomicassets.io`), and the split is kept so the shapes survive if the hosts ever diverge. Any compatible deployment can still be passed straight to the `ExplorerApi`/`RpcApi` constructors instead of using a factory. +`explorerApiForNetwork(network, options?)` and `rpcApiForNetwork(network, contract?, options?)` construct a preconfigured client against AtomicHub's public endpoints, and `NETWORK_ENDPOINTS` exposes the host map. The valid `AtomicHubNetwork` keys are `wax`, `wax-testnet`, `vaulta`, `xpr`, `xpr-testnet`, and `jungle4`. Each key maps its `api` and `rpc` to the same host (for example `wax` to `https://wax.api.atomicassets.io`), and the split is kept so the shapes survive if the hosts ever diverge. Any compatible deployment can still be passed straight to the `ExplorerApi`/`RpcApi` constructors instead of using a factory. Source: atomicassets-sdk (v2.1.1, 5c70c62) src/Networks.ts:9-48 (`AtomicHubNetwork`, `NETWORK_ENDPOINTS`, `explorerApiForNetwork`, `rpcApiForNetwork`); `wax` factory verified live diff --git a/reference/validation.md b/reference/validation.md index e6172fa..3dffc5e 100644 --- a/reference/validation.md +++ b/reference/validation.md @@ -36,8 +36,8 @@ WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts (confi | `reference/atomictools/actions.md` | `atomictools-contract` (commit `d89ce79e4`): `src/link.cpp`, `src/auth.cpp`, `include/atomictoolsx.hpp`; live `get_abi` diff against `atomictoolsx` on WAX mainnet | both | Every action cites header and implementation line ranges. The full action/table list was diffed against the deployed ABI and matches the pinned source exactly; `config.version` reads `1.0.0` live. | | `reference/atomictools/tables.md` | `atomictools-contract` (commit `d89ce79e4`): `include/atomictoolsx.hpp`, `src/link.cpp`; live `get_table_rows` against `wax.greymass.com` | both | Two tables (`links`, `config`), each with its own citation. Row shapes and the `assetidshash` secondary index confirmed by live primary- and secondary-index reads. | | `reference/media.md` | Live reads of `wax.api.atomicassets.io` (templates, schemas, collections across alien.worlds, farmersworld, gpk.topps, official.wax, kogsofficial) and a public IPFS gateway (`ipfs.io`); type/layer facts drawn from `reference/atomicassets/serialization.md`, `custom-types.md`, `data-precedence.md` | both | Field-name conventions and value shapes (bare CIDv0/CIDv1, CID-plus-path) are live-observed across five major WAX collections; the media FORMAT-type convention (`image`/`string`, not `ipfs`) is live-read from schema `format`; gateway resolution is confirmed by a live `ipfs.io` fetch returning `image/webp` with WebP magic bytes. No dedicated `Source:` line consolidates the page; each section carries its own live-read citation. | -| `reference/sdk/atomicassets.md` | `atomicassets-sdk` (`v2.1.1`, `5c70c62`): `src/index.ts`, `src/API/Explorer/index.ts`, `src/API/Rpc/index.ts`, `src/Actions/Generator.ts`, `src/Serialization/index.ts`, `src/Schema/index.ts`, `src/Networks.ts`, `package.json`, `test/explorer-url.test.ts`; live reads of `wax.api.atomicassets.io` | both | The getter-and-route table, the serialization split, action shapes, and error types are read from the 2.1.1 source, each section citing file and line. Source-read at 2.1.1: the lazy `action` getter (construction starts no request), percent-encoding of path segments and of both sides of every query pair, the empty-and-dot-segment guard added in 2.1.1 (its two throw messages, the sixteen guarded getters, the plain `Error` rather than an `ApiError`, and that nothing is sent, all read from `encodeSegment` and the paired test), the numeric ABI-type guards and the fields they cover, the `backasset` and `tokens_to_back` deprecation, and the one-object-versus-array asymmetry against the market builder. The `getTemplateStats` row now reads `(collection, id)`, correcting a `name` the 2.1.1 signature renamed. ExplorerApi reads, a serialization round-trip against a live schema format, the 8-arg `mintasset` output, and the network factories were executed against the built SDK at the earlier 2.0.0 pin and the affected signatures re-read at 2.1.1. The zero-runtime-deps and `sideEffects` facts are from `package.json`. | -| `reference/sdk/atomicmarket.md` | `atomicmarket-sdk` (`v2.4.1`, `437300b`): `src/index.ts`, `src/API/Explorer/index.ts`, `src/API/Explorer/Objects.ts`, `src/API/Explorer/Enums.ts`, `src/API/Explorer/Params.ts`, `src/Actions/Generator.ts`, `src/Actions/Delphi.ts`, `src/Actions/Symbols.ts`, `src/Tables.ts`, `src/Networks.ts`, `package.json`, `test/path-segments.test.ts`; live reads of `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io` | both | The read-surface table, the 31-method action surface (26 actions plus five composers), the composer contracts, and the delphi settlement math are read from the 2.4.1 source, each section citing file and line. Source-read at 2.4.1: the empty-and-dot-segment guard (its two throw messages, the thirteen guarded readers, the plain `Error` that travels out of `getRoyaltyConfig` because only a 416 `ApiError` maps to `null`), and the payout filter surfaces `RoyaltyPayoutApiParams` and `RoyaltyAccountApiParams`. Live-chain: `/atomicmarket/v2/sales` and its `_count` answer 200 on WAX mainnet; the mainnet royalty route answers HTTP 416 with `Royalty config not found`, which corrects the 404 this page previously claimed and inverts the guard advice, since `getRoyaltyConfig` maps 416 to `null`; the WAX testnet royalty reads and the testnet `getConfig` sample (contract `version: 2.0.0`, the `waxpusd` pair) were read live, and mainnet `getConfig` reads `1.3.3`. Live-chain for the 2.4.0 payout ledger: the testnet `/royalties/payouts`, `/payouts/_count`, and `/accounts/{account}` routes answer 200 with rows matching `IRoyaltyPayout` and `IRoyaltyAccountTotal` field for field, the `_count` value arrives as the string `"20"`, the sampled rows confirm the category-to-linkage rule, and the same three routes on WAX mainnet answer 200 with an empty list and a zero count, which is the V1-chain case. The added `market_contract`, `collection_name`, timestamp, and `lookup_hash` fields on the config and rule rows are live-read from the testnet royalty routes as well as declared in `Objects.ts`. The worked `deriveSettlementAmount` figures are computed from the pinned formula against that live pair, not observed on chain. | +| `reference/sdk/atomicassets.md` | `atomicassets-sdk` (`v2.1.1`, `5c70c62`): `src/index.ts`, `src/API/Explorer/index.ts`, `src/API/Rpc/index.ts`, `src/Actions/Generator.ts`, `src/Serialization/index.ts`, `src/Schema/index.ts`, `src/Networks.ts`, `package.json`, `test/explorer-url.test.ts`; live reads of `wax.api.atomicassets.io` | both | The getter-and-route table, the serialization split, action shapes, and error types are read from the 2.1.1 source, each section citing file and line. Source-read at 2.1.1: the lazy `action` getter (construction starts no request), percent-encoding of path segments and of both sides of every query pair, the empty-and-dot-segment guard added in 2.1.1 (its two throw messages, the sixteen guarded getters, the plain `Error` rather than an `ApiError`, and that nothing is sent, all read from `encodeSegment` and the paired test), the numeric ABI-type guards and the fields they cover, the `backasset` and `tokens_to_back` deprecation, and the one-object-versus-array asymmetry against the market builder. The `getTemplateStats` row reads `(collection, id)`, matching the 2.1.1 signature. ExplorerApi reads, a serialization round-trip against a live schema format, the 8-arg `mintasset` output, and the network factories were executed against the built SDK at the earlier 2.0.0 pin and the affected signatures re-read at 2.1.1. The zero-runtime-deps and `sideEffects` facts are from `package.json`. | +| `reference/sdk/atomicmarket.md` | `atomicmarket-sdk` (`v2.4.1`, `437300b`): `src/index.ts`, `src/API/Explorer/index.ts`, `src/API/Explorer/Objects.ts`, `src/API/Explorer/Enums.ts`, `src/API/Explorer/Params.ts`, `src/Actions/Generator.ts`, `src/Actions/Delphi.ts`, `src/Actions/Symbols.ts`, `src/Tables.ts`, `src/Networks.ts`, `package.json`, `test/path-segments.test.ts`; live reads of `wax.api.atomicassets.io` and `test.wax.api.atomicassets.io` | both | The read-surface table, the 31-method action surface (26 actions plus five composers), the composer contracts, and the delphi settlement math are read from the 2.4.1 source, each section citing file and line. Source-read at 2.4.1: the empty-and-dot-segment guard (its two throw messages, the thirteen guarded readers, the plain `Error` that travels out of `getRoyaltyConfig` because only a 416 `ApiError` maps to `null`), and the payout filter surfaces `RoyaltyPayoutApiParams` and `RoyaltyAccountApiParams`. Live-chain: `/atomicmarket/v2/sales` and its `_count` answer 200 on WAX mainnet; the mainnet royalty route answers HTTP 416 with `Royalty config not found`, and `getRoyaltyConfig` maps 416 to `null`, so a caller guards on `null` rather than on `ApiError`; the WAX testnet royalty reads and the testnet `getConfig` sample (contract `version: 2.0.0`, the `waxpusd` pair) were read live, and mainnet `getConfig` reads `1.3.3`. Live-chain for the 2.4.0 payout ledger: the testnet `/royalties/payouts`, `/payouts/_count`, and `/accounts/{account}` routes answer 200 with rows matching `IRoyaltyPayout` and `IRoyaltyAccountTotal` field for field, the `_count` value arrives as the string `"20"`, the sampled rows confirm the category-to-linkage rule, and the same three routes on WAX mainnet answer 200 with an empty list and a zero count, which is the V1-chain case. The added `market_contract`, `collection_name`, timestamp, and `lookup_hash` fields on the config and rule rows are live-read from the testnet royalty routes as well as declared in `Objects.ts`. The worked `deriveSettlementAmount` figures are computed from the pinned formula against that live pair, not observed on chain. | | `reference/atomicassets-api.md` | `atomicassets-api`: `package.json`, `src/api/server.ts` | source-read | Cites repo metadata and the documentation-server source; no live probe cited. | | `reference/atomicassets/actions.md` | `atomicassets-contract` (v2.0.0): `src/atomicassets.cpp`, `include/atomicassets.hpp`; `AntelopeIO/leap` (v5.0.3) billing constants; live WAX mainnet `get_transaction` read | both | Every action cites specific header and implementation line ranges. The 112-byte new-scope transfer charge is checked both ways: computed from the pinned leap billing constants and observed as the sender's `account_ram_deltas` entry in a live WAX mainnet transfer. | | `reference/atomicassets/backing-tokens.md` | `atomicassets-contract` (v2.0.0): `src/atomicassets.cpp`, `include/atomicassets.hpp` | source-read | Cites `announcedepo`, `withdraw`, `addconftoken`, `burnasset`, and the V2 `backasset` abort by line range. | diff --git a/skills/atomic-integration/SKILL.md b/skills/atomic-integration/SKILL.md index bc80d20..b5dcf96 100644 --- a/skills/atomic-integration/SKILL.md +++ b/skills/atomic-integration/SKILL.md @@ -25,9 +25,9 @@ Work on WAX testnet first. Every flow below is the same on mainnet, and the mist Switching network swaps both hosts, never one. The `atomicassets` and `atomicmarket` contract accounts carry the same names on both chains, so nothing in an action's data changes and a half-switched integration reads an unrelated chain while every request keeps answering 200. -WAX testnet is where V2 runs. WAX mainnet still runs the V1 contracts, and jungle4 carries the V2 code with its tables unseeded, so any V2-only behavior (the royalty layer, the bundle retirement, the new tables) is exercisable on testnet and absent on mainnet. The contract's own `version` field does not settle which one a chain runs; table presence does. +WAX testnet is where V2 runs. WAX mainnet still runs the V1 contracts, and jungle4 carries the V2 code with its tables unseeded, so any V2-only behavior (the royalty layer, the bundle retirement, the tables V2 adds) is exercisable on testnet and absent on mainnet. The contract's own `version` field does not settle which one a chain runs; table presence does. -Reads need no key, no account, and no registration. A key is needed only to sign. Build the session first, from `guides/signing.md`, which carries the install lines, the chain ids, and the actor and permission pair. +Reads need no key, no account, and no registration. A key is needed only to sign. Build the session first, from [Build a session and sign](../../guides/signing.md), which carries the install lines, the chain ids, and the actor and permission pair. ## Mint an asset @@ -43,7 +43,7 @@ Native token backing is gone in V2. A non-empty `tokens_to_back` aborts the mint Build the mint through `@atomichub/atomicassets` rather than by hand. `ActionBuilder.mintasset()` takes the eight parameters in ABI order and returns one plain `{ account, name, data }` object for the session to sign, and `createAttributeMap` turns a plain object plus a per-field type lookup into the attribute-map shape, so no schema fetch is needed to build one. The builder checks the numeric parameters and throws a `SerializationError` naming the offending field before any transaction exists: `template_id` is checked as an int32, which keeps `-1` usable and rejects the `NaN` a string-to-number conversion produces, and `createtempl`'s `max_supply` is checked as a uint32, so a fractional or negative supply fails at the call. Without that check a `NaN` reaches the signer as `null`, because JSON has no form for it, and the mistake is invisible by the time the chain sees it. -Full detail: `guides/asset-lifecycle.md` for the flow and every failure mode, `reference/sdk/atomicassets.md` for the builder surface. +Full detail: [Create a collection and mint assets](../../guides/asset-lifecycle.md) for the flow and every failure mode, [@atomichub/atomicassets SDK](../../reference/sdk/atomicassets.md) for the builder surface. ## Compose an AtomicMarket flow @@ -80,8 +80,8 @@ Set either flag only against a chain still running AtomicMarket V1, where bundle Both refusals rule out a transaction the chain would take. Depositing more than the sale costs leaves the surplus sitting as balance, and depositing nothing lets a standing balance pay; each is legitimate for a caller who means it and indistinguishable from a wrong amount for one who does not, and the composer cannot see a balance to tell them apart. To do either deliberately, assemble `assertsale`, your own transfer, and `purchasesale` by hand, which assert nothing. -Full detail: `reference/sdk/atomicmarket.md` for the composers and the settlement math, `guides/sales.md`, `guides/auctions.md`, and `guides/buyoffers.md` for the lifecycle each one drives. +Full detail: [@atomichub/atomicmarket SDK](../../reference/sdk/atomicmarket.md) for the composers and the settlement math, [Working with sales](../../guides/sales.md), [Working with auctions](../../guides/auctions.md), and [Buyoffers](../../guides/buyoffers.md) for the lifecycle each one drives. ## Version pins -Re-check a fact that names a version when that dependency moves. This skill is written against `@atomichub/atomicassets` 2.1.1 and `@atomichub/atomicmarket` 2.4.1, and the contract behavior against `atomicassets-contract` v2.0.0-rc4 and `atomicmarket-contract` v2.0.0-rc2. `reference/validation.md` records how each page was validated and against what. +Re-check a fact that names a version when that dependency moves. This skill is written against `@atomichub/atomicassets` 2.1.1 and `@atomichub/atomicmarket` 2.4.1, and the contract behavior against `atomicassets-contract` v2.0.0-rc4 and `atomicmarket-contract` v2.0.0-rc2. [Validation log](../../reference/validation.md) records how each page was validated and against what. diff --git a/tutorials/first-collection.md b/tutorials/first-collection.md index 12d1b78..61f7b83 100644 --- a/tutorials/first-collection.md +++ b/tutorials/first-collection.md @@ -64,7 +64,7 @@ The body now carries a `core_liquid_balance` field reading `"500.00000000 WAX"`. ## Step 3: install the packages and build the session -Make a directory, install two packages, and write the session once. Everything after this imports it. +Make a directory, install three packages, and write the session once. Everything after this imports it. ``` mkdir first-collection && cd first-collection