From 67f620f1f71dd4dd5c231e464a79328eccdf98da Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 11:02:38 -0500 Subject: [PATCH 01/11] fix: Add index.html redirect to latest.json This fixes the 404 when clicking the GitHub Pages URL in the workflow summary. The root URL now redirects to latest.json. --- .github/workflows/deploy.yml | 15 +++++++++++++++ tokens/token-list.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 343f29a..403a510 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -94,6 +94,21 @@ jobs: echo "Copied historical versions from versions/ directory" fi + # Create index.html that redirects to latest.json + cat > dist/index.html << 'EOF' + + + + + Request Network Token List + + + +

Redirecting to latest.json...

+ + + EOF + echo "Created versioned files: v${VERSION}.json, v${MAJOR}.${MINOR}.json, v${MAJOR}.json, latest.json" - name: Deploy to GitHub Pages diff --git a/tokens/token-list.json b/tokens/token-list.json index 0091c17..024a9e1 100644 --- a/tokens/token-list.json +++ b/tokens/token-list.json @@ -1,6 +1,6 @@ { "name": "Request Network Token List", - "timestamp": "2025-12-02T15:43:26.000Z", + "timestamp": "Set automatically during deployment", "version": { "major": 1, "minor": 3, From 0f6b23e427029506ac2861af39936d882ace5fab Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 11:20:37 -0500 Subject: [PATCH 02/11] feat: Add vX.Y.json aliases for all historical versions Creates major.minor aliases (e.g., v1.2.json) for all historical versions in the versions/ directory. The highest patch version for each major.minor combination is used for the alias. --- .github/workflows/deploy.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 403a510..8c07d5d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -88,10 +88,33 @@ jobs: # Copy base file cp tokens/token-list.json dist/token-list.json - # Copy all historical versions + # Copy all historical versions and create major.minor aliases if [ -d "versions" ]; then - cp versions/*.json dist/ 2>/dev/null || true - echo "Copied historical versions from versions/ directory" + # Declare associative array to track highest patch for each major.minor + declare -A highest_patch + + for version_file in versions/*.json; do + if [ -f "$version_file" ]; then + # Copy the full version file (e.g., v1.2.0.json) + cp "$version_file" dist/ + + # Extract version components from the file content + FILE_MAJOR=$(jq -r '.version.major' "$version_file") + FILE_MINOR=$(jq -r '.version.minor' "$version_file") + FILE_PATCH=$(jq -r '.version.patch' "$version_file") + ALIAS_KEY="${FILE_MAJOR}.${FILE_MINOR}" + ALIAS_NAME="v${ALIAS_KEY}.json" + + # Create alias if it doesn't exist or if this patch is higher + CURRENT_PATCH=${highest_patch[$ALIAS_KEY]:-"-1"} + if [ "$FILE_PATCH" -gt "$CURRENT_PATCH" ]; then + cp "$version_file" "dist/${ALIAS_NAME}" + highest_patch[$ALIAS_KEY]=$FILE_PATCH + echo "Created/updated alias ${ALIAS_NAME} from $(basename $version_file)" + fi + fi + done + echo "Copied historical versions and created aliases" fi # Create index.html that redirects to latest.json From ead87d6e8ffcc9aaf0de757ab1b1898406d9eef4 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 11:22:56 -0500 Subject: [PATCH 03/11] fix: Don't overwrite current version's major.minor alias with historical --- .github/workflows/deploy.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8c07d5d..ee9f6e6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -105,6 +105,12 @@ jobs: ALIAS_KEY="${FILE_MAJOR}.${FILE_MINOR}" ALIAS_NAME="v${ALIAS_KEY}.json" + # Skip if this is the current version's major.minor (already created above) + if [ "$FILE_MAJOR" = "$MAJOR" ] && [ "$FILE_MINOR" = "$MINOR" ]; then + echo "Skipping alias ${ALIAS_NAME} - current version takes precedence" + continue + fi + # Create alias if it doesn't exist or if this patch is higher CURRENT_PATCH=${highest_patch[$ALIAS_KEY]:-"-1"} if [ "$FILE_PATCH" -gt "$CURRENT_PATCH" ]; then From ffcc19c452b4227e5801e23ccdfb26877c0eaa3c Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 11:26:27 -0500 Subject: [PATCH 04/11] chore: Bump version to 1.3.1 --- tokens/token-list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokens/token-list.json b/tokens/token-list.json index 024a9e1..d0bb979 100644 --- a/tokens/token-list.json +++ b/tokens/token-list.json @@ -4,7 +4,7 @@ "version": { "major": 1, "minor": 3, - "patch": 0 + "patch": 1 }, "tokens": [ { From 7f82cf9d57a135fd74b531ad780d521234f231fb Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 11:37:09 -0500 Subject: [PATCH 05/11] feat: add major version aliases (vX.json) for historical versions --- .github/workflows/deploy.yml | 47 +++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ee9f6e6..840b730 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -88,10 +88,11 @@ jobs: # Copy base file cp tokens/token-list.json dist/token-list.json - # Copy all historical versions and create major.minor aliases + # Copy all historical versions and create major.minor and major aliases if [ -d "versions" ]; then - # Declare associative array to track highest patch for each major.minor - declare -A highest_patch + # Declare associative arrays to track highest versions + declare -A highest_patch # tracks highest patch for each major.minor + declare -A highest_minor # tracks highest minor.patch for each major for version_file in versions/*.json; do if [ -f "$version_file" ]; then @@ -102,21 +103,39 @@ jobs: FILE_MAJOR=$(jq -r '.version.major' "$version_file") FILE_MINOR=$(jq -r '.version.minor' "$version_file") FILE_PATCH=$(jq -r '.version.patch' "$version_file") - ALIAS_KEY="${FILE_MAJOR}.${FILE_MINOR}" - ALIAS_NAME="v${ALIAS_KEY}.json" - # Skip if this is the current version's major.minor (already created above) - if [ "$FILE_MAJOR" = "$MAJOR" ] && [ "$FILE_MINOR" = "$MINOR" ]; then - echo "Skipping alias ${ALIAS_NAME} - current version takes precedence" - continue + # Skip if this is the current version's major (already created above) + if [ "$FILE_MAJOR" = "$MAJOR" ]; then + echo "Skipping major alias v${FILE_MAJOR}.json - current version takes precedence" + # But still process minor alias if different minor version + if [ "$FILE_MINOR" = "$MINOR" ]; then + echo "Skipping minor alias v${FILE_MAJOR}.${FILE_MINOR}.json - current version takes precedence" + continue + fi fi - # Create alias if it doesn't exist or if this patch is higher - CURRENT_PATCH=${highest_patch[$ALIAS_KEY]:-"-1"} + # Create/update major.minor alias (e.g., v1.2.json) + MINOR_KEY="${FILE_MAJOR}.${FILE_MINOR}" + MINOR_ALIAS="v${MINOR_KEY}.json" + CURRENT_PATCH=${highest_patch[$MINOR_KEY]:-"-1"} if [ "$FILE_PATCH" -gt "$CURRENT_PATCH" ]; then - cp "$version_file" "dist/${ALIAS_NAME}" - highest_patch[$ALIAS_KEY]=$FILE_PATCH - echo "Created/updated alias ${ALIAS_NAME} from $(basename $version_file)" + cp "$version_file" "dist/${MINOR_ALIAS}" + highest_patch[$MINOR_KEY]=$FILE_PATCH + echo "Created/updated alias ${MINOR_ALIAS} from $(basename $version_file)" + fi + + # Create/update major alias (e.g., v1.json) - only for non-current majors + if [ "$FILE_MAJOR" != "$MAJOR" ]; then + MAJOR_ALIAS="v${FILE_MAJOR}.json" + # Compare as "minor.patch" to find highest version within major + CURRENT_MINOR_PATCH=${highest_minor[$FILE_MAJOR]:-"-1.-1"} + CURRENT_M=$(echo $CURRENT_MINOR_PATCH | cut -d. -f1) + CURRENT_P=$(echo $CURRENT_MINOR_PATCH | cut -d. -f2) + if [ "$FILE_MINOR" -gt "$CURRENT_M" ] || ([ "$FILE_MINOR" = "$CURRENT_M" ] && [ "$FILE_PATCH" -gt "$CURRENT_P" ]); then + cp "$version_file" "dist/${MAJOR_ALIAS}" + highest_minor[$FILE_MAJOR]="${FILE_MINOR}.${FILE_PATCH}" + echo "Created/updated alias ${MAJOR_ALIAS} from $(basename $version_file)" + fi fi fi done From 4d3fc0d37d298be1bd5590fd92add55fc8039888 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:11:11 -0500 Subject: [PATCH 06/11] fix: allow timestamp placeholder in schema and validation --- src/schemas/token-list-schema.json | 7 +++++-- src/validation/validate.ts | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/schemas/token-list-schema.json b/src/schemas/token-list-schema.json index 0cc9afc..609c9e2 100644 --- a/src/schemas/token-list-schema.json +++ b/src/schemas/token-list-schema.json @@ -9,8 +9,11 @@ }, "timestamp": { "type": "string", - "format": "date-time", - "description": "The timestamp of when this list was last updated" + "description": "The timestamp of when this list was last updated. Use 'Set automatically during deployment' as a placeholder.", + "oneOf": [ + { "format": "date-time" }, + { "const": "Set automatically during deployment" } + ] }, "version": { "type": "object", diff --git a/src/validation/validate.ts b/src/validation/validate.ts index e60365e..9fde67e 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -113,6 +113,10 @@ function isValidVersion(version: { } function isValidTimestamp(timestamp: string): boolean { + // Allow placeholder value that gets replaced during deployment + if (timestamp === "Set automatically during deployment") { + return true; + } const date = new Date(timestamp); return date.toString() !== "Invalid Date"; } From b2c20798697e13ca35c797a5537deb2c308c3fd4 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:12:12 -0500 Subject: [PATCH 07/11] fix: require timestamp placeholder in source file validation --- src/schemas/token-list-schema.json | 7 ++----- src/validation/validate.ts | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/schemas/token-list-schema.json b/src/schemas/token-list-schema.json index 609c9e2..49b95b3 100644 --- a/src/schemas/token-list-schema.json +++ b/src/schemas/token-list-schema.json @@ -9,11 +9,8 @@ }, "timestamp": { "type": "string", - "description": "The timestamp of when this list was last updated. Use 'Set automatically during deployment' as a placeholder.", - "oneOf": [ - { "format": "date-time" }, - { "const": "Set automatically during deployment" } - ] + "const": "Set automatically during deployment", + "description": "Must be the placeholder value. Actual timestamp is set during deployment." }, "version": { "type": "object", diff --git a/src/validation/validate.ts b/src/validation/validate.ts index 9fde67e..fb4ee79 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -113,12 +113,12 @@ function isValidVersion(version: { } function isValidTimestamp(timestamp: string): boolean { - // Allow placeholder value that gets replaced during deployment - if (timestamp === "Set automatically during deployment") { - return true; + // Source file must use placeholder - actual timestamp is set during deployment + if (timestamp !== "Set automatically during deployment") { + console.error("Timestamp must be 'Set automatically during deployment' - actual timestamp is set during deployment"); + return false; } - const date = new Date(timestamp); - return date.toString() !== "Invalid Date"; + return true; } function isValidDecimals(decimals: number): boolean { From d7cee5579ce7d92ba326a68deed63d03ad74ee12 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:18:46 -0500 Subject: [PATCH 08/11] fix: make schema permissive for timestamp (validation script enforces placeholder) --- src/schemas/token-list-schema.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/schemas/token-list-schema.json b/src/schemas/token-list-schema.json index 49b95b3..751a1db 100644 --- a/src/schemas/token-list-schema.json +++ b/src/schemas/token-list-schema.json @@ -9,8 +9,11 @@ }, "timestamp": { "type": "string", - "const": "Set automatically during deployment", - "description": "Must be the placeholder value. Actual timestamp is set during deployment." + "description": "The timestamp of when this list was last updated (ISO 8601 format)", + "oneOf": [ + { "format": "date-time" }, + { "const": "Set automatically during deployment" } + ] }, "version": { "type": "object", From 7f9e13f4959025891cb4e97e5dba9330e57abbcc Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:20:35 -0500 Subject: [PATCH 09/11] docs: add comment explaining schema vs validation strictness --- src/validation/validate.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/validation/validate.ts b/src/validation/validate.ts index fb4ee79..7c0ab62 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -112,6 +112,15 @@ function isValidVersion(version: { ); } +/** + * Validates the timestamp field. + * + * Note: The JSON schema allows both date-time format and the placeholder string + * so that consumers can use the schema to validate deployed token lists. + * However, THIS validation script enforces the placeholder because it runs + * on the source file (tokens/token-list.json) during CI. The actual timestamp + * is set during deployment by the GitHub Actions workflow. + */ function isValidTimestamp(timestamp: string): boolean { // Source file must use placeholder - actual timestamp is set during deployment if (timestamp !== "Set automatically during deployment") { From f15ff1c5991a433959ea5aa743100d4a0cc4aa64 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:25:06 -0500 Subject: [PATCH 10/11] test: update tests to use timestamp placeholder --- tests/validation.test.ts | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/validation.test.ts b/tests/validation.test.ts index ac727f5..6700da4 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -2,6 +2,9 @@ import { describe, it, expect } from "vitest"; import { validateTokenList } from "../src/validation/validate"; import { NetworkType, TokenList, TokenType, CHAIN_IDS } from "../src/types"; +// The source file validation requires this placeholder - actual timestamp is set during deployment +const TIMESTAMP_PLACEHOLDER = "Set automatically during deployment"; + describe("Token List Validation", () => { const validToken = { id: "test-token", @@ -17,7 +20,7 @@ describe("Token List Validation", () => { it("should validate a correct token list", async () => { const validList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [validToken], }; @@ -28,7 +31,7 @@ describe("Token List Validation", () => { it("should reject invalid token addresses", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { @@ -44,7 +47,7 @@ describe("Token List Validation", () => { it("should reject duplicate token IDs", async () => { const duplicateList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ validToken, @@ -61,7 +64,7 @@ describe("Token List Validation", () => { it("should reject invalid decimals", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { @@ -77,7 +80,7 @@ describe("Token List Validation", () => { it("should validate version format", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: -1, minor: 0, patch: 0 }, tokens: [validToken], }; @@ -88,7 +91,7 @@ describe("Token List Validation", () => { it("should validate network type", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { @@ -104,7 +107,7 @@ describe("Token List Validation", () => { it("should validate token type", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { @@ -117,7 +120,18 @@ describe("Token List Validation", () => { expect(await validateTokenList(invalidList)).toBe(false); }); - it("should validate timestamp format", async () => { + it("should reject real timestamps (must use placeholder)", async () => { + const invalidList: TokenList = { + name: "Test Token List", + timestamp: new Date().toISOString(), + version: { major: 1, minor: 0, patch: 0 }, + tokens: [validToken], + }; + + expect(await validateTokenList(invalidList)).toBe(false); + }); + + it("should reject invalid timestamp format", async () => { const invalidList: TokenList = { name: "Test Token List", timestamp: "invalid-date", @@ -131,7 +145,7 @@ describe("Token List Validation", () => { it("should reject invalid chainId for network", async () => { const invalidList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { @@ -156,7 +170,7 @@ describe("Token List Validation", () => { for (const { network, chainId } of networks) { const validList: TokenList = { name: "Test Token List", - timestamp: new Date().toISOString(), + timestamp: TIMESTAMP_PLACEHOLDER, version: { major: 1, minor: 0, patch: 0 }, tokens: [ { From 60409986b0b84b75c003ce1fd14c98751dc46393 Mon Sep 17 00:00:00 2001 From: MantisClone Date: Tue, 2 Dec 2025 12:37:30 -0500 Subject: [PATCH 11/11] refactor: centralize TIMESTAMP_PLACEHOLDER constant --- src/types/index.ts | 6 ++++++ src/validation/validate.ts | 5 +++-- tests/validation.test.ts | 5 +---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/types/index.ts b/src/types/index.ts index 163b87a..9ad4063 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -54,6 +54,12 @@ export enum NetworkType { FIAT = "fiat", } +/** + * Placeholder value for timestamp in the source token list file. + * The actual timestamp is set during deployment by the GitHub Actions workflow. + */ +export const TIMESTAMP_PLACEHOLDER = "Set automatically during deployment"; + export const CHAIN_IDS: Record = { mainnet: 1, matic: 137, diff --git a/src/validation/validate.ts b/src/validation/validate.ts index 7c0ab62..741d556 100644 --- a/src/validation/validate.ts +++ b/src/validation/validate.ts @@ -8,6 +8,7 @@ import { NetworkType, TokenType, CHAIN_IDS, + TIMESTAMP_PLACEHOLDER, } from "../types"; import schema from "../schemas/token-list-schema.json"; @@ -123,8 +124,8 @@ function isValidVersion(version: { */ function isValidTimestamp(timestamp: string): boolean { // Source file must use placeholder - actual timestamp is set during deployment - if (timestamp !== "Set automatically during deployment") { - console.error("Timestamp must be 'Set automatically during deployment' - actual timestamp is set during deployment"); + if (timestamp !== TIMESTAMP_PLACEHOLDER) { + console.error(`Timestamp must be '${TIMESTAMP_PLACEHOLDER}' - actual timestamp is set during deployment`); return false; } return true; diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 6700da4..50069ba 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -1,9 +1,6 @@ import { describe, it, expect } from "vitest"; import { validateTokenList } from "../src/validation/validate"; -import { NetworkType, TokenList, TokenType, CHAIN_IDS } from "../src/types"; - -// The source file validation requires this placeholder - actual timestamp is set during deployment -const TIMESTAMP_PLACEHOLDER = "Set automatically during deployment"; +import { NetworkType, TokenList, TokenType, CHAIN_IDS, TIMESTAMP_PLACEHOLDER } from "../src/types"; describe("Token List Validation", () => { const validToken = {