Toko Feed
Toko Feed is a standalone Internet Computer canister that builds and serves a durable Pokémon set and card-price catalog. It owns its JustTCG HTTPS integration, canonical identities, pagination checkpoint, and embedded IcyDB database. Consumers see only its Candid endpoints; whether and how it stores the data is an internal implementation detail.
The workspace has two deliberately separated packages. toko-feed builds as
an rlib for shared protocol types and native tests plus a cdylib canister.
toko-feed-cli is the host-only operator package and installs the toko-feed
executable. There is no separate canisters/ tree, schema crate, Canic
dependency, or Toko dependency. A future Canic fleet can install many copies
of the compiled Wasm as opaque feed roles.
Current behavior
- A controller configures the canister's JustTCG API key.
toko_feed_ingest_collectionsfetches JustTCG's complete bounded game catalog, interprets those provider-specific game categories as Toko-owned collectible collections, and stores each provider ID as a source mapping.toko_feed_ingest_setsfetches all Pokémon sets in one bounded JustTCG v1 sets request and upserts their complete current metadata before card ingestion. The current provider fields include release date, total set value, card/variant/sealed counts, and 7/30/90-day value changes. Complete raw set objects are retained so newly introduced provider fields are not discarded.- Each
toko_feed_ingestcall fetches and checkpoints at most 50 Pokémon cards. The requests are split into 20, 20, and 10 cards so they remain compatible with JustTCG's documented free-tier page limit. Repeated calls resume from the stored offset; reaching the provider's end resets the next manually triggered pass to offset zero. This release does not run an internal refresh timer. - Raw successful pages and complete provider card records are retained in IcyDB. Public methods return bounded projections rather than raw provider JSON.
- Re-importing a provider card updates its existing row. Each canonical card receives a Toko-owned generated ULID; provider IDs are stored in a separate source mapping rather than used as the public identity.
- Card ingestion requires an initial provider set import. JustTCG can occasionally return a valid card whose set is absent from its sets endpoint; that card is retained with its provider set ID and name instead of failing the entire bounded ingestion job.
- Summary queries expose the lowest current USD price. Detail queries expose condition, printing, language, current price, provider timestamp, and recent percentage changes for each market variant.
Canonical identity
A ULID is an identifier, not a deduplication rule. Canonical collections therefore use two records:
Collectionowns the Toko ULID, versioned canonical key, and display name.CollectionSourcemaps(provider, provider_key)to that ULID and retains the provider's complete category record.
The curated registry assigns a namespace-stable Toko ULID to every supported
collection. The current 18 JustTCG category records map to 17 canonical
collections because pokemon and pokemon-japan both identify Pokémon.
one-piece-card-game maps to the broader One Piece collection while retaining
the provider's name in its source record. Every canonical ID is identical in
every feed canister and is not derived from a provider ID.
There is deliberately no uncurated or locally generated collection state. A
provider category absent from the registry makes collection ingestion fail
before any collection rows are written. Supporting it requires an explicit
canonical name, key, and Toko ULID plus tests. game and game_id remain raw
JustTCG vocabulary; they are not canonical Toko concepts.
Cards likewise keep two related records:
PokemonCardowns the Toko ULID and canonical query projection.PokemonCardSourcemaps(provider, provider_key)to that ULID and retains the complete provider record.
Canonical card ULIDs are generated and remain the public identity. Source and page rows also have locally generated ULID primary keys, while provider-owned keys and cursors are stored separately and uniquely indexed. Re-imports locate the row by its provider key and then update it through its stable local ID.
When a source has not been seen before, the initial conservative match key is the normalized Pokémon set name, collector number, and card name. This lets a future provider attach to an existing printing without pretending that unrelated provider IDs are comparable. The key is explicitly versioned so a future reconciliation migration can improve matching without changing public ULIDs.
Canister interface
The authoritative interface is toko-feed.did.
| Method | Access | Purpose |
|---|---|---|
toko_feed_configure |
Controller update | Store or rotate the JustTCG credential. |
toko_feed_ingest_collections |
Controller update | Refresh canonical collections from JustTCG source mappings. |
toko_feed_ingest_sets |
Controller update | Fetch and upsert every current JustTCG Pokémon set. |
toko_feed_ingest |
Controller update | Fetch, upsert, and checkpoint up to 50 cards. |
toko_feed_status |
Public query | Read credential-free cursor and health state. |
toko_feed_collections |
Public query | Read up to 100 canonical collections using an optional ULID continuation. |
toko_feed_collection |
Public query | Read one canonical collection and all known provider mappings. |
toko_feed_sets |
Public query | Read up to 100 set records using an optional ULID continuation. |
toko_feed_set |
Public query | Read one complete projected set record by its local ULID. |
toko_feed_cards |
Public query | Read up to 100 summaries using an optional ULID continuation. |
toko_feed_card |
Public query | Read one canonical card and its latest price variants. |
The HTTPS transform query is part of the IC management-canister outcall protocol and is not an application endpoint.
Query pagination
Start a listing with toko_feed_collections(null, limit),
toko_feed_sets(null, limit), or toko_feed_cards(null, limit), then pass the
returned next_after value into the corresponding next call. Results are
ordered by ascending canonical ULID and limit must be between 1 and 100.
Missing records are returned as Ok(null) by their singular query; malformed
IDs and invalid limits return a bounded FeedError.
Operating the canister
- Build and install the generated Wasm with the checked-in Candid interface.
The service constructor takes the empty Candid argument
(). - Fund the canister with enough cycles for HTTPS outcalls. One ingestion can make up to three JustTCG requests.
- Have a controller invoke
toko_feed_configurewith the API key through a secure deployment or administration path. - Have a controller invoke
toko_feed_ingest_collectionsto refresh canonical collection identities and provider mappings. - Have a controller invoke
toko_feed_ingest_setsto establish or refresh the Pokémon set catalog. Set results include the mappedcanonical_collection_id. - Have a controller invoke
toko_feed_ingestwhenever a card refresh is required. Its receipt reportsnext_offsetandprovider_complete; when completion is reported, the following invocation begins a new pass at offset zero. - Let consumers use the public status, collection, set, card-list, and card-detail queries.
There is no deployable hub canister or automatic scheduler yet. A future Toko or Canic fleet can coordinate multiple copies through this public interface while the feed canister continues to own its database and provider integration.
Build and installation
Install the pinned Rust toolchain, its Wasm target, ICP CLI, and ic-wasm, then
build the complete canister package:
The installable artifacts are:
target/wasm32-unknown-unknown/release/toko_feed.wasmcrates/toko-feed/toko-feed.did
The full interface gate also expects didc and candid-extractor on PATH so
the checked-in file can be compared structurally with the compiled Wasm.
make canister builds and validates the canister but does not install or
deploy it. The checked-in icp.yaml uses the official pinned Rust recipe for
ICP lifecycle builds, including Candid metadata and Wasm shrinking. Use
make wasm when only the compiler Wasm artifact is needed and interface
validation is not required.
Install or replace the checkout's operator CLI with:
This follows the same package split and local-development pattern as
ic-query/ic-query-cli: Make performs the Cargo installation, then
toko-feed is invoked directly as a normal executable.
On install and post-upgrade, the canister applies its embedded IcyDB schema. Keep the API key out of shell history, logs, committed argument files, and fixtures. The credential is never returned by status, data, debug, or error surfaces.
Local deployment
The checked-in icp.yaml defines a project-local managed network on port 8003
and the standalone canister build. Create an ignored local credential file,
restrict its permissions, then start, deploy, configure, and seed the canister:
# Edit .env.local and set JUST_TCG_API_KEY without committing it.
make local-ready builds and upgrades or installs the canister, configures it
from .env.local, imports the provider category catalog as collections, and
then imports every Pokémon set. If an intentionally clean local database is
required,
make local-reset performs a reinstall first and therefore erases all existing
local Toko Feed data.
The collection and set entities are newer than the 0.1.4 database layout. A
local canister last installed from that layout needs one make local-reset.
Do not upgrade a persistent production database from the pre-catalog layout
until an explicit IcyDB migration artifact has been prepared and exercised.
Local setup and CLI calls default to ICP's anonymous identity, which the
managed development network funds and which becomes the local canister
controller. Override setup with make local-ready LOCAL_IDENTITY=<name> when
testing another identity. Protected identities can be used by the installed
CLI with --identity and --identity-password-file.
Use the installed CLI for every operational and query endpoint. It targets the
local ICP environment and toko-feed canister by default, passes raw typed
arguments to ICP CLI internally, and prints clean typed JSON:
Without --all, a list call returns one page and its next_after continuation,
which can be passed back through --after. With --all, the CLI follows that
cursor, combines the records, and reports its page and record counts. Automatic
pagination stops on a repeated cursor and defaults to at most 1,000 pages; use
--max-pages to choose a lower operational bound.
Run toko-feed --help for the complete syntax. Global options must precede the
command. --environment, --canister, and --identity select another ICP
deployment; --project-root overrides icp.yaml discovery;
--identity-password-file supports protected non-interactive identities; and
--compact emits one-line JSON. For example:
The CLI deliberately has no configuration or API-key argument. Configuration
continues to read the ignored .env.local file so credentials are not copied
into command history.
ICP CLI keeps ephemeral local network state under ignored .icp/cache/ paths.
Connected-network mappings under .icp/data/ are durable deployment records
and may be committed when an IC canister is deployed.
make ic-deploy builds and deploys the same canister to the IC using the active
ICP identity. It intentionally does not copy a local credential or trigger a
paid provider import; configure those production operations through an
appropriately protected controller workflow.
API access does not itself grant permission to cache or redistribute provider data. Review the provider's current subscription, attribution, caching, and redistribution terms before exposing a production feed publicly.
Layout
| Path | Responsibility |
|---|---|
crates/toko-feed/src/api/just_tcg |
JustTCG request construction, wire models, pagination, compatibility, and sanitized tests. |
crates/toko-feed/src/canister |
Candid endpoints, IC HTTPS adapter, bounded ingestion, and IcyDB access. |
crates/toko-feed/src/collection.rs |
Curated collectible collections, stable Toko IDs, provider aliases, and fallback matching policy. |
crates/toko-feed/src/pokemon |
Canonical set/card match keys, set projections, and card price-summary rules. |
crates/toko-feed/src/schema |
Embedded IcyDB canister plus operational, collection, set/card, provider-source, and future hub declarations. No hub actor is built yet. |
crates/toko-feed-cli |
Host-only installed CLI, typed ICP process boundary, lookup commands, and safe manual or automatic pagination. |
icp.yaml |
Official Rust recipe and local/IC project lifecycle definition. |
Future APIs belong under api/<provider>. Their wire types should remain
provider-specific; only deliberately selected Pokémon identity and price fields
belong in the canonical projection.
Development
The repository is pinned to Rust 1.97.1. Run every required native, Candid, and Wasm check with:
Useful individual targets include make fmt, make clippy, make test,
make docs, make wasm, and make candid-check. Tests use sanitized fixtures
and never call the live provider.
Releases and publication
Release notes live in CHANGELOG.md. Prepare its dated version
entry and commit all non-version work before invoking a release target.
cargo-edit is required for version
management.
These guarded targets run the full checks, bump the workspace version, create
an annotated v<version> tag, and atomically push the release commit and tag.
Minor and major releases require typed confirmation.
After the release is pushed, validate and publish the canister package followed by the CLI package with:
License
Toko Feed is licensed under the MIT License.