Skip to content
Merged
35 changes: 34 additions & 1 deletion guides/asset-lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---
scope: Creator flow on the `atomicassets` contract - create a collection, define a schema, optionally a template, mint assets, edit mutable data, transfer, and burn
depends-on: [reference/atomicassets/structure.md, reference/atomicassets/actions.md, reference/wharfkit.md]
key-modules: ["atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp", "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"]
key-modules:
- "atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp"
- "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"
- "@atomichub/atomicassets 2.1.1 (atomicassets-sdk v2.1.1, 5c70c62): src/Actions/Generator.ts"
---

# Create a collection and mint assets
Expand Down Expand Up @@ -201,6 +204,36 @@ Pass `template_id: -1` to mint a templateless asset carrying its own `immutable_

Source: `atomicassets-contract src/atomicassets.cpp:697-788` (`mintasset`, backing guard at `:786-787`), V1 backing behavior in this repo's V1 tree (`contracts/atomicassets-contract/src/atomicassets.cpp`)

### Building the same mint through the SDK

`@atomichub/atomicassets` builds the identical action object and types the attribute map on the way in, so the eight positional arguments stay in ABI order and `createAttributeMap` picks the `ATOMIC_ATTRIBUTE` variant for each field:

```ts
import { ActionBuilder, createAttributeMap } from '@atomichub/atomicassets'

const builder = new ActionBuilder('atomicassets')
const mutable = createAttributeMap({ level: 1 }, { level: 'uint32' })

const mint = builder.mintasset(
session.actor.toString(), // authorized_minter
'mycollectn1', // collection_name
'cards', // schema_name
123456, // template_id, -1 for a templateless asset
'collector.wam', // new_asset_owner
[], // immutable_data
mutable, // mutable_data
[], // tokens_to_back
)

await session.transact({ action: { ...mint, authorization: [session.permissionLevel] } })
```

The builder returns `{ account, name, data }` and signs nothing, so the object above is the same payload the hand-written snippet sends. `tokens_to_back` is `[]` deliberately: the parameter carries a deprecation tag for the abort documented above.

The numeric parameters are the one thing the builder checks, and it throws a `SerializationError` naming the offending field before any transaction is built. `template_id` is validated as an int32, which keeps `-1` available as the no-template sentinel and rejects a `NaN` that a string-to-number conversion produced; `max_supply` on `createtempl` is validated as a uint32, so a fractional or negative supply fails at the call rather than on chain. Without that check a `NaN` reaches the signing library as `null`, because JSON has no form for it, and the mistake is gone before the chain can name it. The full parameter list is in [@atomichub/atomicassets SDK](../reference/sdk/atomicassets.md#numeric-parameters-are-checked-against-their-abi-type-and-throw) ("Numeric parameters are checked against their ABI type and throw").

Source: atomicassets-sdk (v2.1.1, 5c70c62) src/Actions/Generator.ts:373-384 (`mintasset` and its `template_id` check), src/Actions/Generator.ts:126-156 (the numeric guards), src/Actions/Generator.ts:295-303 (`createtempl` `max_supply`), src/Actions/Generator.ts:358-372 (the `tokens_to_back` deprecation), src/Actions/Generator.ts:524-526 (`_action` returning one object)

## Update mutable data: setassetdata

```json
Expand Down
33 changes: 32 additions & 1 deletion guides/auctions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---
scope: AtomicMarket auction lifecycle on the V2 baseline - announce, transfer the asset into escrow, place deposit-backed bids, claim after the end, and cancel
depends-on: [reference/atomicmarket/actions.md, guides/deposits.md]
key-modules: ["atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp", "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"]
key-modules:
- "atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp"
- "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"
- "@atomichub/atomicmarket 2.4.1 (atomicmarket-sdk v2.4.1, 437300b): src/Actions/Generator.ts"
---

# Working with auctions
Expand Down Expand Up @@ -104,6 +107,34 @@ Failure modes asserted in source:

Source: `atomicmarket-contract src/atomicmarket.cpp:1889-1943` (`receive_asset_transfer`)

### Building the announce and escrow pair with the SDK

The order above is the contract's, not a preference: the transfer's notification handler looks up an announced auction by its assets and seller and aborts when it finds none, so a transfer that arrives first fails. `@atomichub/atomicmarket` composes the pair in that order, with the `auction` memo literal filled in:

```ts
import { MarketActionBuilder } from '@atomichub/atomicmarket'

const builder = new MarketActionBuilder('atomicmarket')

const actions = builder.announceAuctionActions({
seller: session.actor.toString(),
asset_ids: ['1099511627887'],
starting_bid: '10.00000000 WAX',
duration: 86400,
maker_marketplace: 'mymarket',
assets_contract: 'atomicassets',
})
// -> [announceauct on atomicmarket, transfer on atomicassets with memo 'auction']

await session.transact({
actions: actions.map((a) => ({ ...a, authorization: [session.permissionLevel] })),
})
```

`duration` is the one field the builder checks: it must be a whole number inside the uint32 range, or `announceauct` throws before a transaction is built. That is a serialization bound rather than a chain rule, so the config's minimum and maximum auction duration still apply and are still the chain's to enforce. Nothing else is checked, and the composer carries no bundle opt-out, because an auction action is handed an auction id and cannot see how many assets the row holds. See [@atomichub/atomicmarket SDK](../reference/sdk/atomicmarket.md#the-five-composers) ("The five composers").

Source: atomicmarket-sdk (v2.4.1, 437300b) src/Actions/Generator.ts:615-639 (`announceAuctionActions`, the ordering rule and the `auction` memo), src/Actions/Generator.ts:312-323 (`announceauct` and its `duration` check), src/Actions/Generator.ts:713-729 (`_uint32`), src/Actions/Generator.ts:301-311 (the legacy bundle note on the auction family)

## Bid on an auction

```json
Expand Down
55 changes: 54 additions & 1 deletion guides/buyoffers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---
scope: How to create, accept, decline, and cancel AtomicMarket asset and template buyoffers, whose price leaves the buyer's deposited balance at creation
depends-on: [reference/atomicmarket/actions.md, guides/deposits.md, reference/api.md]
key-modules: ["atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp", "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"]
key-modules:
- "atomicmarket-contract (v2.0.0-rc2): src/atomicmarket.cpp"
- "atomicassets-contract (v2.0.0-rc4): src/atomicassets.cpp"
- "@atomichub/atomicmarket 2.4.1 (atomicmarket-sdk v2.4.1, 437300b): src/Actions/Generator.ts"
---

# Buyoffers
Expand Down Expand Up @@ -98,6 +101,36 @@ Changed in V2: see [AtomicMarket V2 changes](../reference/atomicmarket/v2-change

Source: `atomicmarket-contract src/atomicmarket.cpp:1533-1626` (`acceptbuyo`), `atomicmarket-contract include/atomicmarket.hpp:260-265`

#### Building the accept flow with the SDK

Because `acceptbuyo` identifies its offer as the globally last created row rather than by an id, an `acceptbuyo` action built on its own is not safe to send. `@atomichub/atomicmarket` gives it no standalone builder method for that reason; the only way to reach it is `acceptBuyofferActions`, which emits the `createoffer` and the `acceptbuyo` together, in that order, with the `buyoffer` memo filled in:

```ts
import { MarketActionBuilder } from '@atomichub/atomicmarket'

const builder = new MarketActionBuilder('atomicmarket')

const actions = builder.acceptBuyofferActions({
recipient: session.actor.toString(),
buyoffer_id: '42',
asset_ids: ['1099511627776'],
expected_price: '10.00000000 WAX',
taker_marketplace: 'atomichub',
assets_contract: 'atomicassets',
})
// -> [createoffer on atomicassets with memo 'buyoffer', acceptbuyo on atomicmarket]

await session.transact({
actions: actions.map((a) => ({ ...a, authorization: [session.permissionLevel] })),
})
```

The composer fills `expected_asset_ids` from `asset_ids`, because the contract compares that list twice: once against the buyoffer row and once against the contents of the offer it reads. It does not accept the offer itself, since the market contract sends that `acceptoffer` inline and a pre-accepted offer is gone from the table before the contract can find it. Nothing else in the transaction may create an AtomicAssets offer between these two actions; actions appended after `acceptbuyo` are safe.

The composer throws when `asset_ids` carries more than one id, unless `allow_v1_bundle_buyoffer: true` is set. Under V2 `acceptbuyo` refunds the escrowed price and erases a multi-asset row before it ever reads the offers table, so the transaction commits with the buyoffer gone, nothing sold, and the offer this flow created left dangling on the recipient's RAM until they cancel it. Set the flag only against a chain still running AtomicMarket V1, where bundle buyoffers accept correctly. See [@atomichub/atomicmarket SDK](../reference/sdk/atomicmarket.md#the-two-bundle-opt-out-flags) ("The two bundle opt-out flags").

Source: atomicmarket-sdk (v2.4.1, 437300b) src/Actions/Generator.ts:641-688 (`acceptBuyofferActions`, the last-offer placement rule, the bundle throw), src/Actions/Generator.ts:158-175 (`AcceptBuyofferInput` and `allow_v1_bundle_buyoffer`), src/Actions/Generator.ts:208-476 (the builder's action set, which carries no standalone `acceptbuyo`)

### Declining a buyoffer

`declinebuyo` requires the recipient's authorization (not the buyer's) and refunds the escrowed price to the buyer's deposited balance. The buyer must then `withdraw` it, since nothing is transferred out automatically.
Expand Down Expand Up @@ -248,6 +281,26 @@ await session.transact({

Source: `atomicmarket-contract src/atomicmarket.cpp:1717-1794` (`fulfilltbuyo`), `atomicmarket-contract include/atomicmarket.hpp:308-314`

#### Building the fulfill flow with the SDK

`fulfilltbuyo` reads the last offer the same way `acceptbuyo` does, so it has no standalone builder method either. `fulfillTemplateBuyofferActions` emits the pair with the `tbuyoffer` memo filled in:

```ts
const actions = builder.fulfillTemplateBuyofferActions({
seller: session.actor.toString(),
buyoffer_id: '7',
asset_id: '1099511627777',
expected_price: '5.00000000 WAX',
taker_marketplace: 'atomichub',
assets_contract: 'atomicassets',
})
// -> [createoffer on atomicassets with memo 'tbuyoffer', fulfilltbuyo on atomicmarket]
```

It carries no bundle guard, because a template buyoffer names one asset by construction: `fulfilltbuyo` takes a single `asset_id` and the contract checks that the offer holds exactly that one asset. The placement rule from the accept flow above applies unchanged, and it is the SDK-side statement of the marketplace security consideration in this section: keep the offer immediately before the market action and let nothing else create an offer in between.

Source: atomicmarket-sdk (v2.4.1, 437300b) src/Actions/Generator.ts:690-707 (`fulfillTemplateBuyofferActions` and why it carries no bundle guard), src/Actions/Generator.ts:177-188 (`FulfillTemplateBuyofferInput`), src/Actions/Generator.ts:641-657 (the last-offer placement rule shared with the accept flow)

### Cancelling a template buyoffer

`canceltbuyo` requires the buyer's authorization and refunds the escrowed price to the buyer's deposited balance, the same as `cancelbuyo`.
Expand Down
30 changes: 29 additions & 1 deletion guides/querying-the-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,35 @@ Workflow patterns for reading Atomic data, combining facts from the `reference/`

For the full endpoint, parameter, and schema listing, use the deployment's Swagger UI (`https://wax.api.atomicassets.io/docs/` on the WAX reference deployment); see [atomicassets-api HTTP API](../reference/api.md#interactive-reference-swagger-ui) ("Interactive reference (Swagger UI)"), which also covers why no standalone OpenAPI JSON is published.

The examples here use WAX mainnet hosts. The `atomicassets` and `atomicmarket` contract accounts have the same names on WAX testnet, but switching chains means swapping two hosts, not one. Point chain reads (`get_table_rows`) at a testnet node such as `https://waxtestnet.greymass.com`, and point HTTP API reads at the testnet reference deployment `https://test.wax.api.atomicassets.io` (the same atomicassets-api software indexing the testnet chain). The mainnet API host `wax.api.atomicassets.io` has no testnet data, so a testnet integrator that changes only the RPC node and keeps the mainnet API host reads an unrelated chain.
## Switching to testnet means swapping two hosts, not one

The examples here use WAX mainnet hosts. The `atomicassets` and `atomicmarket` contract accounts have the same names on WAX testnet, so nothing in an action's data changes, and that is exactly what makes the mistake easy to miss.

| Read | WAX mainnet | WAX testnet |
| --- | --- | --- |
| Chain tables (`get_table_rows`) | `https://wax.greymass.com` | `https://waxtestnet.greymass.com` |
| HTTP API | `https://wax.api.atomicassets.io` | `https://test.wax.api.atomicassets.io` |

The testnet API host runs the same atomicassets-api software indexing the testnet chain. The mainnet host has no testnet data, so an integrator who changes only the RPC node and keeps the mainnet API host reads an unrelated chain and sees a working request return facts about someone else's assets. Testnet is also where the V2 contracts run, so a V2-only route such as the royalty read layer answers there and returns HTTP 416 everywhere on mainnet; see [atomicassets-api HTTP API](../reference/api.md#the-royalty-routes-answer-416-when-a-collection-has-no-config) ("The royalty routes answer 416 when a collection has no config").

## Percent-encode every caller-supplied URL part

Both SDKs percent-encode caller-supplied path segments and both the key and the value of every query parameter. A hand-rolled URL has to do the same. An asset id, collection, schema, template, or account name that carries `/`, `?`, or `#` escapes its own path segment and sends the request somewhere else; a data-filter key carrying `&` or `=` appends query parameters of its own.

```
// correct: encodeURIComponent (or the equivalent) on each path segment, and on both sides of every query pair
// avoid: pasting a caller-supplied value straight into the URL string
```

Encoding the whole key is safe even where it looks unnecessary. The typed data filters carry a colon (`data:number.level`, `data:bool.foil`, `data:text.rarity`), which encodes to `data%3Anumber.level` on the wire, and the deployment answers both spellings identically:

```sh
curl 'https://wax.api.atomicassets.io/atomicassets/v1/templates?collection_name=alien.worlds&data:text.rarity=Common&limit=2'
curl 'https://wax.api.atomicassets.io/atomicassets/v1/templates?collection_name=alien.worlds&data%3Atext.rarity=Common&limit=2'
# both return the same two templates (906463, 906461)
```

See [@atomichub/atomicassets SDK](../reference/sdk/atomicassets.md#path-segments-and-query-keys-are-percent-encoded) ("Path segments and query keys are percent-encoded") for what the SDKs do on the caller's behalf.

## Paginate list endpoints under the limit cap

Expand Down
Loading
Loading