diff --git a/AGENTS.md b/AGENTS.md index 825cf36..4b0f0c4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ Start here. Find the outcome you are working toward below, read the file on that | Choose an attribute type, or find out why a value was rejected | `reference/atomicassets/custom-types.md` | | Decode an attribute blob read straight from a chain table | `reference/atomicassets/serialization.md` | | Decide which layer an attribute value comes from when template and asset disagree | `reference/atomicassets/data-precedence.md` | +| Tell a JSON number from the quoted decimal the chain prints for the same value | `reference/numeric-values-in-json.md` | | Find out whether a chain runs V2 yet, and what V2 added | `reference/atomicassets/v2-upgrade.md` | | Handle a token-backed asset minted before backing was deprecated | `reference/atomicassets/backing-tokens.md` | | List an asset for sale and settle the purchase, oracle-priced sales included | `guides/sales.md` | diff --git a/CHANGELOG.md b/CHANGELOG.md index f09a0c8..0447985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ What each release of this corpus changed, one release per tag. `Corrected` comes first in every release, because a fact that was wrong is what a returning reader has to see before anything else. The other sections are `Added`, `Revalidated`, and `Removed`, in that order, and a section with nothing in it is left out. +## 2026.08.4 + +### Added + +- `reference/numeric-values-in-json.md` records what JSON type a numeric attribute value takes on each read path: nodeos widens every float to a double on construction and prints it as a quoted seventeen-place decimal, `@wharfkit/antelope` objectifies one to a string in its own form instead (`toFixed(7)` for a float32 at 1.x, `Number.toString` for a float64, and the same shortest-round-trip form for both widths from the 2.x commit that drops the `Float32` override), and a hosted API answers with a number wherever the value was decoded from serialized bytes and with a string in that client-library form wherever it entered through the ABI action path. +- The same page records that `toFixed(7)` keeps seven decimal places rather than seven significant digits at 1.x, so a float32 value that needs more than seven fractional decimals does not survive the client library's string form there. A sample of 20,000 random values per decade found no failures at or above 1, climbing to 99 percent between 0.01 and 0.02 and 100 percent at or below 0.001, rates over a sample rather than a bound on the interval. It also records that nodeos's own seventeen fixed places bounds the string without making it lossless: a double under `5e-18` prints as zero, and a matching per-decade measurement of `toFixed(17)` round trips finds failures climbing from 27 percent to 91 percent as magnitude falls below 0.1. +- The page attributes the string-versus-number split on the ABI action path to each indexer's own decode call site (`atomicassets-api`'s own `Serializer.objectify` call against `@atomichub/antelope-ship-utils`'s, which switches to `objectifyNumericFloats` at 2.0.0) rather than to the live responses alone, and states that the change reaches new writes only, with a repair pass for rows already stored. `reference/sdk/atomicmarket.md` and `reference/atomicassets/serialization.md` change alongside it: a typed table row is a declared shape, not a runtime conversion, and a native ABI float field decodes independently of a serialized attribute's own codec. +- `reference/validation.md` pins `@atomichub/atomicassets` 2.2.0 for attribute decoding, `@atomichub/antelope-ship-utils` 1.0.1 and 2.0.0 for the decode call site that changed between them, `AntelopeIO/spring` v1.2.2 for the nodeos serialization source, and a `@wharfkit/antelope` 2.x commit for the `Float32` string-form change. The two SDK pages keep the `atomicassets-sdk` `v2.1.1` pin. + ## 2026.08.3 ### Corrected diff --git a/README.md b/README.md index 2667531..7cc49ad 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts while | Directory | Contents | | --- | --- | -| `reference/` | Facts: `atomicassets/`, `atomicmarket/`, `atomictools/`, and `sdk/` directories with per-topic pages (structure, actions, tables, fees, serialization, SDK surfaces), plus one file each for the indexer, API, API streaming, media conventions, chain, and client libraries | +| `reference/` | Facts: `atomicassets/`, `atomicmarket/`, `atomictools/`, and `sdk/` directories with per-topic pages (structure, actions, tables, fees, serialization, SDK surfaces), plus one file each for the indexer, API, API streaming, media conventions, numeric JSON types, chain, and client libraries | | `guides/` | End-to-end workflows: asset lifecycle, offers, sales, auctions, buyoffers, deposits, claim links, notification integration, contract testing with VeRT, and querying the API | | `learning/` | The unverified tier: claims that have not been checked yet, and the gate they pass before promotion | | `skills/` | Agent skills. `atomic-integration` routes a coding agent to the reference file its task needs. `report` writes a sanitized report about these docs into the consuming project, and never edits this repository | diff --git a/reference/atomicassets/serialization.md b/reference/atomicassets/serialization.md index f7357a2..2f1802a 100644 --- a/reference/atomicassets/serialization.md +++ b/reference/atomicassets/serialization.md @@ -68,7 +68,7 @@ An off-chain implementation needs three things, in this order: 2. **The varint/zigzag codec.** Implement `toVarintBytes`/`unsignedFromVarintBytes` and `zigzagEncode`/`zigzagDecode` exactly as described above. These are not standard varints with a sign bit baked in; zigzag is a separate transform applied only to signed types before varint-encoding. 3. **The per-type encode/decode table above**, including the array rule (varint count + repeated elements, one level only) and the identifier arithmetic (`position + 4` on encode, `identifier - 4` on decode). -To serialize an `ATTRIBUTE_MAP` for submission in a transaction (`mintasset`, `setassetdata`, `createtempl`, `setcoldata`, and similar actions all take `ATTRIBUTE_MAP` parameters that the contract itself serializes on execution; the caller passes attribute maps, not bytes), an off-chain caller does not need this codec at all: it constructs the `ATTRIBUTE_MAP` as ABI JSON per `reference/atomicassets/custom-types.md` and lets the contract's own `serialize` call do the encoding. This codec is needed off-chain specifically to decode `immutable_serialized_data` / `mutable_serialized_data` / collection `serialized_data` bytes read back from `get_table_rows`, or to reproduce the contract's stored bytes for verification. Decoding each layer with this codec is only the first step; combining template and asset layers into one effective attribute set is a separate concern covered in [Attribute data precedence](data-precedence.md). +To serialize an `ATTRIBUTE_MAP` for submission in a transaction (`mintasset`, `setassetdata`, `createtempl`, `setcoldata`, and similar actions all take `ATTRIBUTE_MAP` parameters that the contract itself serializes on execution; the caller passes attribute maps, not bytes), an off-chain caller does not need this codec at all: it constructs the `ATTRIBUTE_MAP` as ABI JSON per `reference/atomicassets/custom-types.md` and lets the contract's own `serialize` call do the encoding. This codec is needed off-chain specifically to decode `immutable_serialized_data` / `mutable_serialized_data` / collection `serialized_data` bytes read back from `get_table_rows`, or to reproduce the contract's stored bytes for verification. Decoding each layer with this codec is only the first step; combining template and asset layers into one effective attribute set is a separate concern covered in [Attribute data precedence](data-precedence.md). The JSON type a decoded value then takes is a further concern, and this codec does not settle it: an attribute arrives as whatever type its decoder returns, which for the hosted APIs and the decoding SDKs is a number. A native ABI float column and a serialized attribute are not interchangeable at `get_table_rows`, and the two never meet at this codec: `market_fee` is a plain ABI `float64` field on the `collections` row, so `get_table_rows` decodes it itself and answers a quoted decimal string directly, no codec involved. `serialized_data` on that same row (`immutable_serialized_data` / `mutable_serialized_data` on a template or asset row) is this codec's own `vector`, so `get_table_rows` cannot decode it and answers undecoded bytes; a reader gets an attribute's actual value out of it only by running this codec, not by asking nodeos. See [Numeric values in JSON](../numeric-values-in-json.md). Source: `include/atomicassets.hpp:363-421` (`schemas_s`, `templates_s`, `template_mutables_s`, `assets_s` table shapes), `include/atomicassets.hpp:458` (`config_s.collection_format`) diff --git a/reference/numeric-values-in-json.md b/reference/numeric-values-in-json.md new file mode 100644 index 0000000..65f6283 --- /dev/null +++ b/reference/numeric-values-in-json.md @@ -0,0 +1,76 @@ +--- +scope: What JSON type a float, a double, or a 64-bit integer takes on each read path, why nodeos and the hosted APIs disagree, and where seven decimals lose a float32 +depends-on: + - reference/atomicassets/serialization.md + - reference/atomicassets/custom-types.md + - reference/atomicmarket/v2-changes.md +key-modules: + - "atomicassets-contract (v2.0.0): include/atomicdata.hpp (float and double wire types), the ATOMIC_ATTRIBUTE variant alternatives" + - "AntelopeIO/spring (v1.2.2): libraries/libfc/src/variant.cpp (float-to-double widening on construction, s_fc_to_string), libraries/libfc/src/io/json.cpp and include/fc/io/json.hpp (stringify_large_ints_and_doubles)" + - "@wharfkit/antelope (1.2.0): Float32.toString, Float64.toString, Float.toJSON, Serializer.objectify" + - "@wharfkit/antelope (commit f70daddc, 2.x line): src/chain/float.ts (Float32 no longer overrides toString)" + - "atomicassets-api (2.2.0): live reads of the hosted WAX deployment; live nodeos get_table_rows against WAX mainnet; live reads of the separate AtomicHub data API; src/utils/eosio.ts (deserializeEosioType, Serializer.objectify)" + - "@atomichub/atomicassets (2.2.0): attribute deserialization and convertAttributeMapToObject" + - "@atomichub/antelope-ship-utils (1.0.1): src/deserializer/serialization.ts (deserializeEosioType, Serializer.objectify)" + - "@atomichub/antelope-ship-utils (2.0.0): ABI float decoding and objectifyNumericFloats" +--- + +# Numeric values in JSON + +What an attribute value looks like once it leaves the chain as JSON. The contract stores a `float` and a `double` as raw bytes and carries the same values through the ABI as a variant, and neither of those forms is JSON, so the type a reader receives is chosen by whatever serializes the value for that reader: nodeos, the client library, a hosted API, or a decoding SDK. Those components do not choose the same type, and one hosted API answers with both, because the type follows the path a value took into its database. The wire encoding is in [AtomicAssets attribute serialization](atomicassets/serialization.md) and the ABI type vocabulary is in [AtomicAssets attribute type system](atomicassets/custom-types.md); this page restates neither. It covers what the value becomes in JSON, and where that conversion drops information. + +## One value has more than one JSON type + +| Read through | A `float` or a `double` arrives as | +| --- | --- | +| `get_table_rows` on nodeos | a quoted decimal string, seventeen decimal places of the widened double | +| `Serializer.objectify` in `@wharfkit/antelope` | a string, because `Float.toJSON()` returns the string form | +| A hosted API, on a value decoded from serialized bytes | a JSON number | +| A hosted API, on a value taken from the ABI action path | a string in the client library's own form, because the indexer runs the decoded value through `Serializer.objectify` | +| `@atomichub/atomicassets` and `@atomichub/antelope-ship-utils` | a number | + +A reader written against one row and pointed at a path from another reads the right value at the wrong type. Each row is covered in its own section below. + +Source: each row is cited in the section that covers it below; the table itself adds no claim. + +## The contract holds a float in two forms, neither of them JSON + +The `assets`, `templates`, and `collections` tables carry each attribute value in the schema's own binary format, where a `float` is four raw IEEE 754 bytes and a `double` is eight. An action payload carries the same values through the ABI instead, as the `ATOMIC_ATTRIBUTE` variant, whose float alternatives are named `float32` and `float64` rather than the FORMAT type strings `float` and `double`. Both forms are binary, and the JSON a reader sees is produced afterwards by whichever component answers that reader. That is why one stored value has more than one JSON type, and why the type is a property of the read path rather than of the schema. + +Source: [AtomicAssets attribute serialization](atomicassets/serialization.md) for the four-byte and eight-byte wire encodings, [AtomicAssets attribute type system](atomicassets/custom-types.md) for `ATOMIC_ATTRIBUTE` and the `float32`/`float64` alternative names, both against `atomicassets-contract` v2.0.0 + +## nodeos prints every float as a quoted decimal string + +In the raw `/v1/chain/get_table_rows` response body, nodeos widens every float to a double and prints it as a quoted decimal string with seventeen fixed decimal places, whatever width the field declares. The widening happens on construction: `variant::variant(float val)` stores the value into the same eight-byte double slot a `variant(double val)` uses and marks it `double_type`, so the ABI's `float32` alternative carries no JSON form of its own once it reaches this layer. `s_fc_to_string` then formats that double with `std::setprecision(std::numeric_limits::digits10 + 2) << std::fixed`, seventeen places after the point (`digits10` is 15 for a double, plus 2), and `json::to_stream` quotes the result under the default `stringify_large_ints_and_doubles` output mode, the same mode that quotes an `int64`/`uint64` only once its magnitude passes `0xffffffff`. A live read of the WAX `atomicassets` `collections` table returns `"market_fee":"0.05000000000000000"`, and the `atomicmarket` `config` singleton returns `"minimum_bid_increase":"0.10000000000000001"`, where the seventeen places expose the nearest double to 0.1 rather than a value anyone configured. The same convention covers 64-bit integers: nodeos prints them as strings once they pass the 32-bit range, which [AtomicMarket V2 changes](atomicmarket/v2-changes.md#large-integers-serialize-as-strings) ("Large integers serialize as strings") covers for listing and asset ids. + +Seventeen fixed places bounds the string; it does not make it lossless. A double whose shortest round-trip decimal needs more than seventeen fractional digits loses the rest, and a double under `5e-18` in magnitude prints as all zeros (`0.00000000000000000`). Measured in Node by formatting a random double per magnitude decade with `toFixed(17)` and parsing the result back, 20,000 samples each: every value at 1 or above round-tripped, and failures climb as magnitude falls below 1, from 27 percent in [0.01, 0.1) to 91 percent in [0.001, 0.01) to 99 percent in [0.0001, 0.001). All of that describes the response body itself. A numeric field width a client library declares, as the typed table rows in [@atomichub/atomicmarket SDK](sdk/atomicmarket.md) declare one, is a compile-time label rather than a runtime conversion: it names the type a caller's code sees, not the type the wire body actually carries, and a caller who trusts the declared width without parsing still holds the body nodeos sent. + +Source: live `POST /v1/chain/get_table_rows` against `https://wax.greymass.com` for `atomicassets` `collections`, for the `atomicmarket` `config` singleton, and for the `atomicmarket` `sales` table, whose newest row answers `"collection_fee":"0.05000000000000000"` beside `"sale_id":173949337` as a bare number and `"asset_ids":["1099940394201"]` as strings; `AntelopeIO/spring` v1.2.2 (`04bdb089`) `libraries/libfc/src/variant.cpp:82-86` (`variant::variant(float)` widening), `libraries/libfc/src/variant.cpp:469-475,483-484` (`s_fc_to_string`, called from `variant::as_string`), `libraries/libfc/include/fc/io/json.hpp:29-33` (the `output_formatting` enum, `stringify_large_ints_and_doubles = 0`), `:38` (the default parameter on `json::to_string`), `libraries/libfc/src/io/json.cpp:595-623` (the int64/uint64/double quoting rules in `to_stream`); measured round trip of a double through `toFixed(17)`, per magnitude decade (20,000 samples each), run on Node 24 + +## The client library objectifies a float to a string + +`@wharfkit/antelope` answers with a string too, though not nodeos's string. At 1.2.0, `Float32.toString()` returns `value.toFixed(7)`, `Float64.toString()` returns `value.toString()` (JavaScript's own shortest round-trip form), and `Float.toJSON()` returns whichever of those strings applies, so `Serializer.objectify` yields a string for both widths, each in the client library's own format rather than nodeos's fixed seventeen places. Code that decodes a chain response through the library and hands the objectified result to application code is handing it one of those two strings, not the one nodeos sent. At commit `f70daddc` on the 2.x line, `Float32` no longer overrides `toString`, so it inherits the same `value.toString()` form `Float64` already used, and both widths agree on one string convention. + +Source: `@wharfkit/antelope` 1.2.0: `Float32.toString`, `Float64.toString`, `Float.toJSON`, and `Serializer.objectify`; `@wharfkit/antelope` commit `f70daddc` (2.x line): `src/chain/float.ts` drops the `Float32.toString` override, leaving `Float.toString`'s `value.toString()` at lines 62-64 as the only implementation + +## `toFixed(7)` keeps seven decimal places, not seven significant digits + +At 1.x, the float32 half of that convention risks dropping information below 1. A float32 carries about seven significant digits, while `toFixed(7)` keeps seven digits to the right of the decimal point, so a value that needs more than seven fractional decimals cannot be read back from what is printed. A short decimal literal usually can be: the nearest float32 to 0.001 is `0.0010000000474974513`, which prints as `0.0010000` and parses back to that same float32, and 0.0001, 0.0005 and 0.00025 behave the same way. What magnitude changes is how likely an arbitrary value is to need those extra decimals, not whether any one value does. In a sample of 20,000 random float32 values per decade, none at or above 1 failed to round-trip, 41 percent failed between 0.5 and 1, 87 percent failed between 0.1 and 0.2, 99 percent failed between 0.01 and 0.02, and 100 percent of the sample failed at or below 0.001. Those are rates over a sample; never read them as a bound on the whole interval, since 0.001 itself sits in that last, near-total-failure band and still round-trips, as the worked case above shows. The failure itself is plain: the two float32 values one and two units above 0.5 both print as `0.5000001`, so the printed string cannot say which of them was stored. None of this touches the 2.x line: at commit `f70daddc`, `Float32` renders the same shortest-round-trip form `Float64` uses, so a float32 value round-trips at every magnitude sampled above. + +Source: measured round trip of float32 values through the `Float32.toString()` behavior above, at `@wharfkit/antelope` 1.2.0; the per-decade figures are rates over a random sample rather than a cited source or a live probe, and the 0.001 and 0.5 cases are worked directly; the 2.x claim is measured the same way against `value.toString()` instead of `toFixed(7)`, zero round-trip failures across 200,000 random float32 bit patterns and across each decade sampled above + +## A hosted API's type follows the path the value took in + +The JSON number is the type both APIs are built on, and it is what a value decoded from serialized bytes carries: template data, collection data, and the AtomicAssets API's own `market_fee` column. A `float` arrives widened to a double on that path, so a stored float32 of `0.6197762` reads as `0.61977618932724`, where the extra digits are the double's view of the same four bytes and not precision the chain holds. `float[]` and `double[]` arrive as arrays of numbers. `int64`, `uint64`, and `fixed64`, with their vector forms, arrive from both APIs as decimal strings whatever their magnitude, because a JavaScript number cannot safely represent the full 64-bit range: `Number.MAX_SAFE_INTEGER` is `2^53 - 1`, and a `uint64` runs past `2^64`. A raw chain read does not apply that same margin: nodeos answers a 64-bit integer as a JSON number below the 32-bit range (`0xffffffff`) and quotes it only above, a narrower cutoff than the point a JavaScript number actually stops being safe, as the section above says. + +A value that reached the database through the ABI action path rather than through serialized bytes carries the string form instead, because the indexers decode that path with `@wharfkit/antelope` and objectify the result, which is the string form the client-library section above describes. On the AtomicAssets API that is asset-level `immutable_data` and `mutable_data`. On the AtomicHub API it is `collection.extended_attributes.market_fee` and the data on each asset row. One response carries both shapes: an asset read answers a `double`-declared attribute as a quoted `170.7` and the same body answers the collection's `market_fee` as a bare `0.07`. + +The decode call site is what makes the difference, not which indexer answers the read. `atomicassets-api`'s own `deserializeEosioType` decodes ABI action data through `Serializer.objectify` directly, independent of any `@atomichub/antelope-ship-utils` version; that call site is what makes its `immutable_data`/`mutable_data` strings. `@atomichub/antelope-ship-utils` carries its own `deserializeEosioType`, and the same version bump the frontmatter pins changes what that function does internally: 1.0.1 calls `Serializer.objectify`, the same string form, and 2.0.0 calls `objectifyNumericFloats` instead, which returns a number for an ABI float. An indexer built on this package, such as the one behind the AtomicHub data API, gets numbers for new writes by taking the 2.0.0 dependency alone, with no change to its own calling code. Either way the change reaches new writes only: a row already stored with the string form keeps that shape until a repair pass rewrites it, and both `atomicassets-api` and the AtomicHub indexer ship one. + +Source: live `GET https://wax.api.atomicassets.io/atomicassets/v1/assets?collection_name=greenrabbit&schema_name=greenprints&limit=1` (asset `1100000921185` answers `"Strength":"170.7"`, `"Luck":"11.3"`, and `"Speed":"55.7"` for three `double`-declared attributes in `mutable_data`, and `"market_fee":0.07` for the collection in the same response, and `"Series":"1"` for a `uint64`-declared attribute on that asset's template data); live `GET https://wax.api.atomicassets.io/atomicassets/v1/templates?collection_name=cybauthority&schema_name=utility&limit=2` (template `907522` answers `"version":1` for a `double`-declared attribute on template data); live `GET https://nft-data.api.atomichub.io/v1/nfts?blockchain=wax-mainnet&collection=greenrabbit&limit=5` (every row answers `"market_fee":"0.05"` inside `collection.extended_attributes`); `atomicassets-api` 2.2.0 (`cf8bf323`) `src/utils/eosio.ts:46-57` (`deserializeEosioType`, `Serializer.objectify` at line 56); `@atomichub/antelope-ship-utils` 1.0.1 (`7dad574`) `src/deserializer/serialization.ts:16-32` (`deserializeEosioType`, `Serializer.objectify` at line 31) against 2.0.0 (pinned above) `src/deserializer/serialization.ts:17-33` (the same function calling `objectifyNumericFloats` at line 32) and `src/deserializer/objectify.ts:48-82` (`objectifyNumericFloats`); the stored-row repair carried as given for both indexers, not independently re-derived here + +## The decoding packages return numbers + +`@atomichub/atomicassets` decodes serialized attribute bytes to numbers, so a reader that decodes a table row through it holds the type the hosted APIs serve rather than the string the chain printed. From release 2.2.0, `convertAttributeMapToObject` returns numbers for float attributes as well. `@atomichub/antelope-ship-utils` 2.0.0 decodes ABI floats to numbers, and exports `objectifyNumericFloats` for a consumer that objectifies decoded values itself, which is the step that would otherwise put the client library's string form back. + +Source: `@atomichub/atomicassets` 2.2.0, published from tag `v2.2.0` (attribute deserialization, `convertAttributeMapToObject`); `@atomichub/antelope-ship-utils` 2.0.0 (ABI float decoding, `objectifyNumericFloats`) diff --git a/reference/sdk/atomicmarket.md b/reference/sdk/atomicmarket.md index a76543c..472a66b 100644 --- a/reference/sdk/atomicmarket.md +++ b/reference/sdk/atomicmarket.md @@ -308,9 +308,9 @@ Source: atomicmarket-sdk (v2.4.1, 437300b) src/Actions/Delphi.ts:15-20 (`DelphiP ## Typed table rows ship alongside the API types -`src/Tables.ts` exports the `get_table_rows` shapes for the v2 contract tables, so a chain-side read deserializes into a named type instead of `any`. Field widths follow the on-chain ABI: `uint64` and `name` fields arrive as strings, and `int32`/`uint32`/`uint8`/`float64` fields arrive as numbers. Use these when reading the market's tables directly rather than through the indexer. +`src/Tables.ts` exports the `get_table_rows` shapes for the v2 contract tables as TypeScript interfaces, so a chain-side read deserializes into a named type instead of `any`. An interface declares a shape; it converts nothing at runtime, and annotating a raw `get_table_rows` row with one does not change the value the row actually holds. Field widths follow the on-chain ABI in what they declare: `uint64` and `name` fields are typed as strings, and `int32`/`uint32`/`uint8`/`float64` fields are typed as numbers. Neither declaration is a runtime guarantee: a live read of the `sales` table answers `"collection_fee":"0.05000000000000000"`, a quoted decimal string, for the `float64` field this section types as `number`, and nodeos answers a `uint64` at or below `0xffffffff` as a bare JSON number (`"sale_id":173949337` on that same row), not the string this section types it as (see [Numeric values in JSON](../numeric-values-in-json.md)). A caller who trusts either declared type without checking the actual value still holds whatever the wire sent. Use these types for the shape they document, and convert a `uint64` or `float64` field explicitly before treating it as the type declared, when reading the market's tables directly rather than through the indexer. -Source: atomicmarket-sdk (v2.4.1, 437300b) src/Tables.ts:1-46 (row interfaces and the field-width rule), src/index.ts:27-28 (root re-export) +Source: atomicmarket-sdk (v2.4.1, 437300b) src/Tables.ts:1-46 (row interfaces and the field-width rule), src/index.ts:27-28 (root re-export); the `sales` table live read is cited in full on [Numeric values in JSON](../numeric-values-in-json.md) ## Network factory carries AtomicHub's public hosts diff --git a/reference/validation.md b/reference/validation.md index 3dffc5e..170c111 100644 --- a/reference/validation.md +++ b/reference/validation.md @@ -15,11 +15,16 @@ This log traces how every fact in `reference/` and `guides/` was checked before - `atomicassets-api` at tag `2.2.0`, commit `cf8bf323` (both hosted deployments report this version on `/health`) - `atomictools-contract` at commit `d89ce79e4` (the upstream repository has no release tag; the deployed `atomictoolsx` ABI on WAX matches this commit exactly) - `atomicassets-sdk` at tag `v2.1.1`, commit `5c70c62` (published as `@atomichub/atomicassets` 2.1.1) +- `@atomichub/atomicassets` at `2.2.0`, published from tag `v2.2.0`, read for attribute decoding only; the two SDK pages hold the `atomicassets-sdk` `v2.1.1` pin above - `atomicmarket-sdk` at tag `v2.4.1`, commit `437300b` (published as `@atomichub/atomicmarket` 2.4.1) - `@wharfkit/antelope` at `1.2.0` +- `@wharfkit/antelope` at commit `f70daddc`, on the 2.x line and not under a release tag; read for one behavior change only (`Float32` string form), against the `1.2.0` pin above - `@wharfkit/session` at `1.6.1`, with `@wharfkit/common` at `1.5.0` and `@wharfkit/wallet-plugin-privatekey` at `1.1.0` - `@atomichub/vert` at `2.2.0`, commit `a8a4160` +- `@atomichub/antelope-ship-utils` at `2.0.0` +- `@atomichub/antelope-ship-utils` at tag `v1.0.1`, commit `7dad574`, read for the pre-`2.0.0` decode call site only, against the `2.0.0` pin above - `AntelopeIO/leap` at `v5.0.3` (chain RAM billing: the billable-size constants and the call sites that charge them) +- `AntelopeIO/spring` at tag `v1.2.2`, commit `04bdb089` (nodeos JSON serialization: float-to-double widening, the seventeen-place double format, and the large-integer quoting threshold) A page's `key-modules` frontmatter names the specific baseline(s) it draws from; entries below carry the same pin unless noted otherwise. @@ -36,8 +41,9 @@ 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/numeric-values-in-json.md` | Live `get_table_rows` against `wax.greymass.com`; live reads of `wax.api.atomicassets.io` and `nft-data.api.atomichub.io`; `AntelopeIO/spring` (v1.2.2) nodeos variant/JSON source; `@wharfkit/antelope` (1.2.0, and commit `f70daddc` on the 2.x line) float formatting; `atomicassets-api` (2.2.0) and `@atomichub/antelope-ship-utils` (1.0.1 and 2.0.0) decode call sites; `@atomichub/atomicassets` (2.2.0) decoding; contract-side type facts drawn from `reference/atomicassets/serialization.md` and `reference/atomicassets/custom-types.md` | both | Live-chain: both nodeos shapes are read from WAX mainnet (`market_fee` as a quoted `0.05000000000000000` on `atomicassets` `collections`, `minimum_bid_increase` as `0.10000000000000001` on the `atomicmarket` `config` singleton), and the hosted split is read on both hosts. The AtomicAssets API answers a `double`-declared attribute in an asset's `mutable_data` as the quoted `170.7` and the collection's `market_fee` as the bare `0.07` in the same response, and answers a `double`-declared attribute on template data as the bare `1`; the AtomicHub data API answers `market_fee` as the quoted `0.05` inside `collection.extended_attributes`. The decode-site rule (`atomicassets-api`'s own call versus `@atomichub/antelope-ship-utils`'s) is source-read against both repositories, not inferred from these responses. Source-read: the `Float32`/`Float64` string forms in `@wharfkit/antelope` 1.2.0 and the 2.x commit that drops the `Float32` override; `s_fc_to_string`, the variant float-widening constructor, and the `stringify_large_ints_and_doubles` quoting rule in `AntelopeIO/spring` v1.2.2; the two decoding packages at the versions named, `convertAttributeMapToObject` returning numbers from 2.2.0 included. The per-decade round-trip failure rates, float32 and double alike, are rates over a random sample rather than a cited source or a live probe, in the way `reference/contract-releases.md` carries its resource figures, and the page states them as sample results because a short decimal literal such as 0.001 still round-trips inside a decade where the sample mostly fails. `reference/sdk/atomicmarket.md` and this page agree on the shape of one fact: `atomicmarket-sdk` `src/Tables.ts:5-6` and `src/Tables.ts:45` type `collection_fee` as `number`, which is a declared TypeScript shape rather than a runtime conversion, because a live read of the `atomicmarket` `sales` table answers `"collection_fee":"0.05000000000000000"`, a quoted decimal string. Neither page asks the SDK to change what it declares; both instead say plainly that a declared type and the wire value can differ. The contract-side wire and ABI facts carry no citation of their own; each cites the page that owns it. | | `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/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 typed-table-rows section documents `Tables.ts` interfaces as a declared shape rather than a runtime conversion: a live `sales` read answers the `float64` `collection_fee` as a quoted decimal and nodeos answers a small `uint64` as a bare number, neither of which the interface's declared type performs or guarantees; the declared widths themselves are carried as found (`reference/numeric-values-in-json.md`). 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. | @@ -71,7 +77,7 @@ WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts (confi ## Tier distribution -20 source-read, 1 live-chain, 16 both. 37 pages total. +20 source-read, 1 live-chain, 17 both. 38 pages total. ## Pages with an ambiguous tier signal