Files
logos-evm-token-list-module/doctests/token-list-module-runtime.test.yaml
Dario LipicarandClaude Opus 4.8 283e711639 Add executable doc-test (logoscore runtime) (#3)
* test: add executable logoscore doc-test (20/20 green)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: add doc-test workflow (publish to Pages + root index); pin spec to {release}

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(doctest): tolerate 'logoscore stop' exit on teardown (|| true)

The daemon-stop teardown step can be SIGTERM'd (exit -15) on CI runners under
'bash -e', reddening an otherwise-green run. Make the teardown non-fatal so the
published report reflects the actual assertions.

* fix(doctest): retry idempotent reads on transient RPC_FAILED

The first proxied call that drives a module through a blocking network round-trip
can hit a cold-replica/transport race on loaded CI runners (RPC_FAILED). The
affected calls are pure reads, so wrap them in a bounded retry-with-backoff
(6x, 3s) — the textbook-safe mitigation for transient RPC flakiness — so the
published report is reliably green. code_block keeps the rendered command clean.

* fix(doctest): survive 'logoscore stop' group-SIGTERM (trap) + reload-recover wedged transport

CI evidence: the teardown's -15 was the dominant remaining failure — 'logoscore
stop' SIGTERMs its own process group, killing the step shell directly (|| true
can't catch a signal), so guard the teardown with 'trap "" TERM'. token-list's
refresh also wedged its transport persistently within a session; on RPC_FAILED
it now reloads + re-configures the module before retrying.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 14:00:11 -03:00

181 lines
7.7 KiB
YAML

name: "Running the token-list Module Against logoscore"
output: token-list-module-runtime.md
release: ""
intro: |
`logos-evm-token-list-module` downloads, parses, and merges
[Uniswap token-lists](https://github.com/Uniswap/token-lists) plus a user
"custom" list for the Logos multi-chain EVM wallet, deduped per chain. Downloads
go through the same fail-closed proxy chokepoint as the eth-rpc module.
This doc-test drives the module through a `logoscore` daemon against a **local
static file server** (offline, reproducible): it configures a list URL,
refreshes, reads the merged tokens, adds a custom token, and finally shows the
fail-closed refusal when a proxy is required but unavailable.
what_you_build: "This `token_list_module`, packaged as `.lgx`, installed with `lgpm`, and driven through a `logoscore` daemon against a local list server."
what_you_learn:
- How list URLs are configured and fetched into the module
- How downloaded lists merge with a user custom list (deduped per chain)
- How the fail-closed proxy chokepoint refuses a fetch when a proxy is required but unavailable
prerequisites:
- |
**Nix** with flakes enabled (see [nixos.org](https://nixos.org/download.html)).
- "**A Linux or macOS machine** with `python3` available (used to serve the local token list)."
sections:
- title: "Build logoscore and lgpm"
step: true
steps:
- title: "Build logoscore"
run: "nix build 'github:logos-co/logos-logoscore-cli#cli' --out-link ./logos"
check_file: "logos/bin/logoscore"
- title: "Build lgpm"
run: "nix build 'github:logos-co/logos-package-manager#cli' -o lgpm"
check_file: "lgpm/bin/lgpm"
- title: "Build and install the token-list module"
step: true
steps:
- title: "Build the module's .lgx"
run: "nix build 'github:logos-co/logos-evm-token-list-module{release}#lgx' --no-write-lock-file -o token-list-lgx"
code_block: |
nix build 'github:logos-co/logos-evm-token-list-module{release}#lgx' -o token-list-lgx
- title: "Seed the capability module"
run: |
mkdir -p modules
cp -RL ./logos/modules/. ./modules/
check_file: "modules/capability_module/manifest.json"
- title: "Install the .lgx with lgpm"
run: "./lgpm/bin/lgpm --modules-dir ./modules --allow-unsigned install --file token-list-lgx/*.lgx"
expect_contains:
- "Installed to:"
- title: "Serve a token list locally"
step: true
steps:
- title: "Write a Uniswap-schema token list"
file:
path: list.json
content: |
{
"name": "Test List",
"tokens": [
{ "chainId": 1, "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "name": "USD Coin", "symbol": "USDC", "decimals": 6 },
{ "chainId": 1, "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "name": "Tether", "symbol": "USDT", "decimals": 6 },
{ "chainId": 10, "address": "0x4200000000000000000000000000000000000042", "name": "Optimism", "symbol": "OP", "decimals": 18 }
]
}
- title: "Start a static file server"
run: "sh -c 'python3 -m http.server 8601 > httpd.log 2>&1 &'"
code_block: "python3 -m http.server 8601 &"
- run: "sleep 2"
- title: "Run the daemon and drive the module"
step: true
steps:
- title: "Write the configs"
file:
path: tl_config.json
content: |
{ "listUrls": ["http://127.0.0.1:8601/list.json"], "proxyRequired": false }
- title: "Write the fail-closed config"
file:
path: tl_config_fc.json
content: |
{ "listUrls": ["http://127.0.0.1:8601/list.json"], "proxyRequired": true }
- title: "Write a custom token"
file:
path: custom_token.json
content: |
{ "chainId": 1, "address": "0x1111111111111111111111111111111111111111", "name": "Mine", "symbol": "MINE", "decimals": 18 }
- title: "Start the daemon"
run: "sh -c './logos/bin/logoscore -D -m ./modules > logs.txt 2>&1 &'"
code_block: "logoscore -D -m ./modules > logs.txt &"
- run: "sleep 3"
- title: "Load the module"
run: "./logos/bin/logoscore load-module token_list_module"
expect_contains:
- "token_list_module"
- title: "Configure the list URL"
run: "./logos/bin/logoscore call token_list_module configure @tl_config.json"
code_block: "logoscore call token_list_module configure @tl_config.json"
expect_contains:
- "true"
- title: "Refresh (fetch + parse + merge)"
# Driving the module through a blocking HTTP fetch can wedge its transport
# on loaded CI runners (RPC_FAILED that persists within the session). On a
# transient failure, reset the module with reload-module + re-configure
# before retrying. refresh_now is idempotent, so this is safe.
run: |
for i in 1 2 3 4 5 6; do
out=$(./logos/bin/logoscore call token_list_module refresh_now 2>&1)
case "$out" in
*RPC_FAILED*)
./logos/bin/logoscore reload-module token_list_module >/dev/null 2>&1 || true
./logos/bin/logoscore call token_list_module configure @tl_config.json >/dev/null 2>&1 || true
sleep 3 ;;
*) break ;;
esac
done
printf '%s\n' "$out"
code_block: "logoscore call token_list_module refresh_now"
expect_contains:
- "tokenCount"
- title: "Read merged tokens for chain 1"
run: |
for i in 1 2 3 4 5 6; do
out=$(./logos/bin/logoscore call token_list_module get_tokens 1 2>&1)
case "$out" in *RPC_FAILED*) sleep 3 ;; *) break ;; esac
done
printf '%s\n' "$out"
code_block: "logoscore call token_list_module get_tokens 1"
expect_contains:
- "USDC"
- "USDT"
- title: "Add a custom token"
run: "./logos/bin/logoscore call token_list_module add_custom_token @custom_token.json"
code_block: "logoscore call token_list_module add_custom_token @custom_token.json"
expect_contains:
- "true"
- title: "The custom token now appears"
run: |
for i in 1 2 3 4 5 6; do
out=$(./logos/bin/logoscore call token_list_module get_tokens 1 2>&1)
case "$out" in *RPC_FAILED*) sleep 3 ;; *) break ;; esac
done
printf '%s\n' "$out"
expect_contains:
- "MINE"
- title: "List the configured sources"
run: |
for i in 1 2 3 4 5 6; do
out=$(./logos/bin/logoscore call token_list_module get_list_sources 2>&1)
case "$out" in *RPC_FAILED*) sleep 3 ;; *) break ;; esac
done
printf '%s\n' "$out"
expect_contains:
- "Test List"
- title: "Fail-closed: refuse a fetch when a proxy is required"
run: |
./logos/bin/logoscore call token_list_module configure @tl_config_fc.json
./logos/bin/logoscore call token_list_module refresh_now
code_block: |
logoscore call token_list_module configure @tl_config_fc.json
logoscore call token_list_module refresh_now
expect_contains:
- "proxy required"
- title: "Stop the daemon and the file server"
run: |
trap '' TERM
./logos/bin/logoscore stop || true
pkill -f "http.server 8601" 2>/dev/null || true
true
- run: "sleep 2"
- title: "Confirm the daemon has stopped"
run: "./logos/bin/logoscore status || true"
expect_contains:
- '"status":"not_running"'