From a15f5c9e6ce1a562c24ab8edf3375010650fc06b Mon Sep 17 00:00:00 2001 From: Moody Salem <7897876+moodysalem@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:42:43 -0400 Subject: [PATCH 01/19] launch list, detail, stats and swaps endpoints (EKU-827) --- openapi.json | 1401 ++++++++++++++++++++++++++++++++ src/contract/cases/launches.ts | 231 ++++++ src/launchQueries.db.test.ts | 456 +++++++++++ src/queries.ts | 354 ++++++++ src/responseSchemas.test.ts | 2 + src/router.test.ts | 6 +- src/router.ts | 10 + src/routes/launches/index.ts | 626 ++++++++++++++ 8 files changed, 3083 insertions(+), 3 deletions(-) create mode 100644 src/contract/cases/launches.ts create mode 100644 src/launchQueries.db.test.ts create mode 100644 src/routes/launches/index.ts diff --git a/openapi.json b/openapi.json index 82b1487..7996e40 100644 --- a/openapi.json +++ b/openapi.json @@ -9911,6 +9911,1407 @@ } } }, + "/launches": { + "get": { + "tags": [ + "Launches" + ], + "summary": "List launches", + "description": "Lists ScheduledLaunch launches from the indexed launchpad tables, newest first", + "operationId": "get_ListLaunches", + "parameters": [ + { + "schema": { + "type": "integer", + "title": "Chain ID", + "description": "Restrict to one chain", + "format": "int64", + "minimum": 1, + "maximum": 9223372036854776000 + }, + "required": false, + "description": "Restrict to one chain", + "name": "chainId", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "upcoming", + "live", + "ended", + "migrated" + ], + "description": "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete" + }, + "required": false, + "description": "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete", + "name": "status", + "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Address", + "description": "Only launches created through LaunchRouter by this address" + }, + "required": false, + "description": "Only launches created through LaunchRouter by this address", + "name": "creator", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50, + "description": "Maximum number of launches per page" + }, + "required": false, + "description": "Maximum number of launches per page", + "name": "pageSize", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "default": 1, + "description": "Page number to fetch (1-indexed)" + }, + "required": false, + "description": "Page number to fetch (1-indexed)", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A page of launches", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_key_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_id": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "pool_key": { + "type": "object", + "properties": { + "core_address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "token0": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "token1": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "fee": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "tick_spacing": { + "type": [ + "integer", + "null" + ] + }, + "extension": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + } + }, + "required": [ + "core_address", + "token0", + "token1", + "fee", + "tick_spacing", + "extension" + ] + }, + "launch_token": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "decimals": { + "type": "integer" + }, + "total_supply": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals", + "total_supply" + ] + }, + "quote_token": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Null when the token is not in the token list" + }, + "symbol": { + "type": [ + "string", + "null" + ] + }, + "decimals": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals" + ] + }, + "launch_token_is_token1": { + "type": "boolean" + }, + "start_time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "end_time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "target_tick": { + "type": "integer", + "description": "Tick in raw quote units per launch token, independent of token order" + }, + "upper_tick": { + "type": "integer" + }, + "tick_spacing": { + "type": "integer" + }, + "initial_fee": { + "type": "string", + "pattern": "^\\d+$", + "description": "0.64 fixed-point fee fraction" + }, + "final_fee": { + "type": "string", + "pattern": "^\\d+$", + "description": "0.64 fixed-point fee fraction" + }, + "migration_tick_lower": { + "type": "integer" + }, + "migration_tick_upper": { + "type": "integer" + }, + "deployed": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "reserve0": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "reserve1": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "complete": { + "type": "boolean" + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "upcoming", + "live", + "ended", + "migrated", + null + ], + "description": "Null only when the chain has no indexed head yet and the launch is not complete" + }, + "status_as_of": { + "type": [ + "integer", + "null" + ], + "description": "The indexed head block time the status was derived from" + }, + "owner": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "Owner of record in LaunchCreated (LaunchRouter for routed launches)" + }, + "creator": { + "type": [ + "string", + "null" + ], + "pattern": "^0x[a-fA-F0-9]+$", + "description": "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter" + }, + "created_block_number": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "created_transaction_hash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "created_time": { + "type": "integer", + "description": "Unix time in seconds" + } + }, + "required": [ + "chain_id", + "pool_key_id", + "pool_id", + "pool_key", + "launch_token", + "quote_token", + "launch_token_is_token1", + "start_time", + "end_time", + "target_tick", + "upper_tick", + "tick_spacing", + "initial_fee", + "final_fee", + "migration_tick_lower", + "migration_tick_upper", + "deployed", + "reserve0", + "reserve1", + "complete", + "status", + "status_as_of", + "owner", + "creator", + "created_block_number", + "created_transaction_hash", + "created_time" + ] + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "integer", + "minimum": 1 + }, + "pageSize": { + "type": "integer", + "minimum": 1 + }, + "totalPages": { + "type": "integer", + "minimum": 0 + }, + "totalItems": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "page", + "pageSize", + "totalPages", + "totalItems" + ] + } + }, + "required": [ + "data", + "pagination" + ] + } + } + } + }, + "400": { + "description": "The request parameters are invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "An unexpected server error occurred", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/launches/{chainId}/{poolId}/stats": { + "get": { + "tags": [ + "Launches" + ], + "summary": "Get launch stats", + "description": "Swap volume by side, swap and locker counts, creator fees accrued and claimed, and funding received for one launch", + "operationId": "get_GetLaunchStats", + "parameters": [ + { + "schema": { + "type": "integer", + "title": "Chain ID", + "description": "The ID of the network that is being fetched", + "format": "int64", + "minimum": 1, + "maximum": 9223372036854776000 + }, + "required": true, + "description": "The ID of the network that is being fetched", + "name": "chainId", + "in": "path" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "The launch pool's ID", + "example": "0x1234" + }, + "required": true, + "description": "The launch pool's ID", + "name": "poolId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Launch statistics", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_id": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "swaps": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "buy_count": { + "type": "integer" + }, + "sell_count": { + "type": "integer" + }, + "distinct_transactions": { + "type": "integer" + }, + "distinct_lockers": { + "type": "integer", + "description": "Distinct LaunchSwapped.locker addresses: the contract that forwarded the swap (usually a router), not the user" + }, + "distinct_transaction_senders": { + "type": "null", + "description": "Not available: the indexer stores no transaction sender, and the API does not resolve transactions to their senders" + } + }, + "required": [ + "count", + "buy_count", + "sell_count", + "distinct_transactions", + "distinct_lockers", + "distinct_transaction_senders" + ] + }, + "volume": { + "type": "object", + "properties": { + "buy": { + "type": "object", + "properties": { + "quote_in": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "launch_token_out": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "quote_in", + "launch_token_out" + ], + "description": "Swaps that took launch tokens out of the pool" + }, + "sell": { + "type": "object", + "properties": { + "launch_token_in": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "quote_out": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "launch_token_in", + "quote_out" + ], + "description": "Swaps that put launch tokens into the pool" + } + }, + "required": [ + "buy", + "sell" + ] + }, + "creator_fees": { + "type": "object", + "properties": { + "accrued": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "quote_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "launch_token", + "quote_token" + ], + "description": "Sum of LaunchSwapped fees, saved for the launch's creator" + }, + "claimed": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "quote_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "launch_token", + "quote_token" + ] + }, + "claim_count": { + "type": "integer" + } + }, + "required": [ + "accrued", + "claimed", + "claim_count" + ] + }, + "funding": { + "type": "object", + "properties": { + "received": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "quote_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "launch_token", + "quote_token" + ], + "description": "PrincipalReceived from lockers that forwarded LAUNCH_FUND" + }, + "count": { + "type": "integer" + }, + "migrated_principal": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "quote_token": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "launch_token", + "quote_token" + ], + "description": "PrincipalReceived from the launch extension at migration" + } + }, + "required": [ + "received", + "count", + "migrated_principal" + ] + }, + "attribution": { + "type": "object", + "properties": { + "counts": { + "type": "string" + }, + "lockers": { + "type": "string" + }, + "senders": { + "type": "string" + } + }, + "required": [ + "counts", + "lockers", + "senders" + ], + "description": "Where each count comes from" + } + }, + "required": [ + "chain_id", + "pool_id", + "swaps", + "volume", + "creator_fees", + "funding", + "attribution" + ] + } + } + } + }, + "400": { + "description": "The request parameters are invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "The requested resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "An unexpected server error occurred", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/launches/{chainId}/{poolId}/swaps": { + "get": { + "tags": [ + "Launches" + ], + "summary": "List launch swaps", + "description": "Returns a launch's LaunchSwapped events in ascending event order", + "operationId": "get_ListLaunchSwaps", + "parameters": [ + { + "schema": { + "type": "integer", + "title": "Chain ID", + "description": "The ID of the network that is being fetched", + "format": "int64", + "minimum": 1, + "maximum": 9223372036854776000 + }, + "required": true, + "description": "The ID of the network that is being fetched", + "name": "chainId", + "in": "path" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "The launch pool's ID", + "example": "0x1234" + }, + "required": true, + "description": "The launch pool's ID", + "name": "poolId", + "in": "path" + }, + { + "schema": { + "type": "integer", + "format": "int64", + "description": "Return swaps strictly after this event_id; use next_cursor to continue" + }, + "required": false, + "description": "Return swaps strictly after this event_id; use next_cursor to continue", + "name": "cursor", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100, + "description": "Maximum number of swaps to return" + }, + "required": false, + "description": "Maximum number of swaps to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A page of launch swaps", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_id": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "swaps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "event_id": { + "type": "string", + "pattern": "^-?\\d+$" + }, + "block_number": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "transaction_index": { + "type": "integer" + }, + "event_index": { + "type": "integer" + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "locker": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "The locker that forwarded the swap, usually a router contract" + }, + "delta0": { + "type": "string", + "pattern": "^-?\\d+$", + "description": "Fee-inclusive pool-perspective delta of token0" + }, + "delta1": { + "type": "string", + "pattern": "^-?\\d+$", + "description": "Fee-inclusive pool-perspective delta of token1" + }, + "fee_amount": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "fee_is_token1": { + "type": "boolean" + } + }, + "required": [ + "event_id", + "block_number", + "transaction_index", + "event_index", + "transaction_hash", + "time", + "locker", + "delta0", + "delta1", + "fee_amount", + "fee_is_token1" + ] + } + }, + "next_cursor": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d+$" + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "chain_id", + "pool_id", + "swaps", + "next_cursor", + "has_more" + ] + } + } + } + }, + "400": { + "description": "The request parameters are invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "The requested resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "An unexpected server error occurred", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/launches/{chainId}/{poolId}": { + "get": { + "tags": [ + "Launches" + ], + "summary": "Get a launch", + "description": "Returns a launch's configuration, saved state, latest advance, migration and claimed creator fees, plus the Core pool state", + "operationId": "get_GetLaunch", + "parameters": [ + { + "schema": { + "type": "integer", + "title": "Chain ID", + "description": "The ID of the network that is being fetched", + "format": "int64", + "minimum": 1, + "maximum": 9223372036854776000 + }, + "required": true, + "description": "The ID of the network that is being fetched", + "name": "chainId", + "in": "path" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "The launch pool's ID", + "example": "0x1234" + }, + "required": true, + "description": "The launch pool's ID", + "name": "poolId", + "in": "path" + } + ], + "responses": { + "200": { + "description": "The launch", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_key_id": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "pool_id": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "pool_key": { + "type": "object", + "properties": { + "core_address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "token0": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "token1": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "fee": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "tick_spacing": { + "type": [ + "integer", + "null" + ] + }, + "extension": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + } + }, + "required": [ + "core_address", + "token0", + "token1", + "fee", + "tick_spacing", + "extension" + ] + }, + "launch_token": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "decimals": { + "type": "integer" + }, + "total_supply": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals", + "total_supply" + ] + }, + "quote_token": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "Null when the token is not in the token list" + }, + "symbol": { + "type": [ + "string", + "null" + ] + }, + "decimals": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals" + ] + }, + "launch_token_is_token1": { + "type": "boolean" + }, + "start_time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "end_time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "target_tick": { + "type": "integer", + "description": "Tick in raw quote units per launch token, independent of token order" + }, + "upper_tick": { + "type": "integer" + }, + "tick_spacing": { + "type": "integer" + }, + "initial_fee": { + "type": "string", + "pattern": "^\\d+$", + "description": "0.64 fixed-point fee fraction" + }, + "final_fee": { + "type": "string", + "pattern": "^\\d+$", + "description": "0.64 fixed-point fee fraction" + }, + "migration_tick_lower": { + "type": "integer" + }, + "migration_tick_upper": { + "type": "integer" + }, + "deployed": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "reserve0": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "reserve1": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "complete": { + "type": "boolean" + }, + "status": { + "type": [ + "string", + "null" + ], + "enum": [ + "upcoming", + "live", + "ended", + "migrated", + null + ], + "description": "Null only when the chain has no indexed head yet and the launch is not complete" + }, + "status_as_of": { + "type": [ + "integer", + "null" + ], + "description": "The indexed head block time the status was derived from" + }, + "owner": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "Owner of record in LaunchCreated (LaunchRouter for routed launches)" + }, + "creator": { + "type": [ + "string", + "null" + ], + "pattern": "^0x[a-fA-F0-9]+$", + "description": "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter" + }, + "created_block_number": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "created_transaction_hash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "created_time": { + "type": "integer", + "description": "Unix time in seconds" + }, + "pool_state": { + "type": [ + "object", + "null" + ], + "properties": { + "sqrt_ratio": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "tick": { + "type": "integer" + }, + "liquidity": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "sqrt_ratio", + "tick", + "liquidity" + ], + "description": "Core pool state of the launch pool" + }, + "latest_advance": { + "type": [ + "object", + "null" + ], + "properties": { + "block_number": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "time": { + "type": "integer", + "description": "Unix time in seconds" + } + }, + "required": [ + "block_number", + "transaction_hash", + "time" + ], + "description": "The LaunchAdvanced event the deployed/reserve/complete values come from; null before the first advance" + }, + "terminal": { + "type": [ + "object", + "null" + ], + "properties": { + "pool_id": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + "locked_liquidity": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "pool_id", + "locked_liquidity" + ], + "description": "The pool the principal was locked in once migrated" + }, + "creator_fees_claimed": { + "type": "object", + "properties": { + "amount0": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + }, + "amount1": { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + }, + "required": [ + "amount0", + "amount1" + ] + } + }, + "required": [ + "chain_id", + "pool_key_id", + "pool_id", + "pool_key", + "launch_token", + "quote_token", + "launch_token_is_token1", + "start_time", + "end_time", + "target_tick", + "upper_tick", + "tick_spacing", + "initial_fee", + "final_fee", + "migration_tick_lower", + "migration_tick_upper", + "deployed", + "reserve0", + "reserve1", + "complete", + "status", + "status_as_of", + "owner", + "creator", + "created_block_number", + "created_transaction_hash", + "created_time", + "pool_state", + "latest_advance", + "terminal", + "creator_fees_claimed" + ] + } + } + } + }, + "400": { + "description": "The request parameters are invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "The requested resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "An unexpected server error occurred", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, "/governance/{chainId}/proposals": { "get": { "tags": [ diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts new file mode 100644 index 0000000..9f56621 --- /dev/null +++ b/src/contract/cases/launches.ts @@ -0,0 +1,231 @@ +import type { LaunchDetailRow, LaunchRow } from "../../queries"; +import { ContractCase, stubQuery } from "../harness"; +import { OWNER } from "../fixtures"; + +// Base, with the predicted (not deployed) launchpad addresses from EKU-822. +const SCHEDULED_LAUNCH = BigInt( + "0x5184f618B2d6cE625d9fDB9770E151a9B84EdB81", +).toString(); +const LAUNCH_ROUTER = BigInt( + "0x9dae609a75Ac80BB84448a14823199faC514aeDb", +).toString(); +const WETH_BASE = BigInt("0x4200000000000000000000000000000000000006"); +const LAUNCH_TOKEN = BigInt("0x7a1c0ffee0000000000000000000000000000001"); +const CORE = BigInt("0x00000000000014aa86c5d3c41765bb24e11bd701").toString(); +const POOL_ID = + "0x6f1b0c3e9a4d5f2b8c7e6d5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c"; +const TX_HASH = BigInt( + "0x8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d", +).toString(); + +const launchRow = (overrides: Partial = {}): LaunchRow => ({ + chain_id: 8453n, + pool_key_id: 42n, + core_address: CORE, + pool_id: BigInt(POOL_ID).toString(), + token0: WETH_BASE.toString(), + token1: LAUNCH_TOKEN.toString(), + fee: "0", + pool_tick_spacing: 1000, + pool_extension: SCHEDULED_LAUNCH, + token: LAUNCH_TOKEN.toString(), + token_name: "Launch Token", + token_symbol: "LAUNCH", + token_decimals: 18, + total_supply: "1000000000000000000000000000", + quote_token: WETH_BASE.toString(), + quote_token_name: "Wrapped Ether", + quote_token_symbol: "WETH", + quote_token_decimals: 18, + token_is_token1: true, + start_time: 1791331200n, + end_time: 1791417600n, + target_tick: -230000, + upper_tick: -160000, + tick_spacing: 1000, + initial_fee: "184467440737095516", + final_fee: "18446744073709551", + migration_tick_lower: -887000, + migration_tick_upper: 887000, + deployed: "250000000000000000000000000", + reserve0: "12000000000000000000", + reserve1: "750000000000000000000000000", + complete: false, + owner: LAUNCH_ROUTER, + creator: BigInt(OWNER).toString(), + launch_extension: SCHEDULED_LAUNCH, + created_block_number: 36000000n, + created_transaction_hash: TX_HASH, + created_time: new Date("2026-10-06T00:00:00Z"), + created_event_id: -9223217452617613312n, + last_advanced_event_id: -9223217010235916288n, + head_block_time: new Date("2026-10-07T12:00:00Z"), + status: "live", + ...overrides, +}); + +const detailRow = ( + overrides: Partial = {}, +): LaunchDetailRow => ({ + ...launchRow(), + state_sqrt_ratio: "340282366920938463463374607431768211456", + state_tick: 200000, + state_liquidity: "1000000000000000000", + advanced_block_number: 36000100n, + advanced_transaction_hash: TX_HASH, + advanced_time: new Date("2026-10-07T11:59:00Z"), + terminal_pool_id: null, + locked_liquidity: null, + creator_fees_claimed0: "0", + creator_fees_claimed1: "0", + ...overrides, +}); + +const launchPath = `/launches/8453/${POOL_ID}`; + +export const launchesCases: ContractCase[] = [ + { + operation: "/launches", + request: `/launches?chainId=8453&status=live&creator=${OWNER}&page=1&pageSize=2`, + stub: () => + stubQuery("listLaunches", { + rows: [ + launchRow(), + // created directly on the extension, chain head not indexed yet + launchRow({ + pool_key_id: 43n, + creator: null, + quote_token_name: null, + quote_token_symbol: null, + quote_token_decimals: null, + head_block_time: null, + status: null, + last_advanced_event_id: null, + }), + ], + totalCount: 3, + }), + }, + { + operation: "/launches/{chainId}/{poolId}", + request: launchPath, + stub: () => stubQuery("getLaunch", detailRow()), + }, + { + name: `${launchPath} (migrated)`, + operation: "/launches/{chainId}/{poolId}", + request: launchPath, + stub: () => + stubQuery( + "getLaunch", + detailRow({ + complete: true, + status: "migrated", + terminal_pool_id: BigInt(POOL_ID).toString(), + locked_liquidity: "123456789", + creator_fees_claimed0: "5", + creator_fees_claimed1: "6", + }), + ), + }, + { + name: `${launchPath} (before the first advance)`, + operation: "/launches/{chainId}/{poolId}", + request: launchPath, + stub: () => + stubQuery( + "getLaunch", + detailRow({ + status: "upcoming", + advanced_block_number: null, + advanced_transaction_hash: null, + advanced_time: null, + last_advanced_event_id: null, + }), + ), + }, + { + operation: "/launches/{chainId}/{poolId}/stats", + request: `${launchPath}/stats`, + stub: () => + stubQuery("getLaunchStats", { + swap_count: 3, + buy_count: 2, + sell_count: 1, + buy_quote_in: "2000000000000000000", + buy_token_out: "40000000000000000000000000", + sell_token_in: "1000000000000000000000000", + sell_quote_out: "40000000000000000", + distinct_lockers: 1, + distinct_transactions: 3, + fees_accrued_token: "100", + fees_accrued_quote: "200", + fees_claimed_token: "0", + fees_claimed_quote: "150", + fee_claim_count: 1, + funding_token: "0", + funding_quote: "0", + funding_count: 0, + migrated_principal_token: "0", + migrated_principal_quote: "0", + }), + }, + { + operation: "/launches/{chainId}/{poolId}/swaps", + request: `${launchPath}/swaps?limit=1&cursor=-9223217452617613312`, + stub: () => + stubQuery("listLaunchSwaps", [ + { + event_id: -9223217010235916287n, + block_number: 36000100n, + transaction_index: 4, + event_index: 7, + transaction_hash: TX_HASH, + block_time: new Date("2026-10-07T11:59:00Z"), + locker: BigInt( + "0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748", + ).toString(), + delta0: "1000000000000000000", + delta1: "-20000000000000000000000000", + fee_amount: "100", + fee_is_token1: true, + }, + { + event_id: -9223217010235916280n, + block_number: 36000101n, + transaction_index: 0, + event_index: 2, + transaction_hash: TX_HASH, + block_time: new Date("2026-10-07T11:59:02Z"), + locker: BigInt( + "0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748", + ).toString(), + delta0: "-40000000000000000", + delta1: "1000000000000000000000000", + fee_amount: "200", + fee_is_token1: false, + }, + ]), + }, + { + name: `${launchPath}/swaps (launch with no swaps)`, + operation: "/launches/{chainId}/{poolId}/swaps", + request: `${launchPath}/swaps`, + stub: () => + stubQuery("listLaunchSwaps", [ + { + event_id: null, + block_number: null, + transaction_index: null, + event_index: null, + transaction_hash: null, + block_time: null, + locker: null, + delta0: null, + delta1: null, + fee_amount: null, + fee_is_token1: null, + }, + ]), + }, +]; diff --git a/src/launchQueries.db.test.ts b/src/launchQueries.db.test.ts new file mode 100644 index 0000000..9924619 --- /dev/null +++ b/src/launchQueries.db.test.ts @@ -0,0 +1,456 @@ +// Runs the launch queries against a real indexer schema seeded with a fixture +// launch. It needs a throwaway Postgres with every indexer migration applied +// (00134_scheduled_launch_pool_states or later), so it is skipped unless +// LAUNCH_QUERIES_PG points at one: +// +// cd indexer && PG_CONNECTION_STRING=$URL bun scripts/migrate.ts +// LAUNCH_QUERIES_PG=$URL bun test src/launchQueries.db.test.ts +// +// Every test runs inside a transaction that is rolled back. +import { describe, expect, test } from "bun:test"; +import postgres, { type Sql } from "postgres"; +import { Queries } from "./queries"; + +const URL = process.env.LAUNCH_QUERIES_PG; + +const CHAIN = 8453n; +const SL = BigInt("0x5184f618B2d6cE625d9fDB9770E151a9B84EdB81"); +const LLL = BigInt("0x4B4e88581110396a09E3Bc313199b96Ab1D8ADEA"); +const ROUTER = BigInt("0x9dae609a75Ac80BB84448a14823199faC514aeDb"); +const TWAMM = BigInt("0x00000000000000000000000000000000000077aa"); +const CORE = BigInt("0x00000000000014aa86c5d3c41765bb24e11bd701"); +const WETH = BigInt("0x4200000000000000000000000000000000000006"); +const TOKEN = BigInt("0x7a1c0ffee0000000000000000000000000000001"); +const CREATOR = BigInt("0x00000000000000000000000000000000000000cc"); +const YUL_ROUTER = BigInt("0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748"); +const OTHER_ROUTER = BigInt("0x00000000000000000000000000000000000055aa"); +const POOL_ID = 0x6f1bn; +const TERMINAL_POOL_ID = 0x7e2cn; +const OTHER_POOL_ID = 0x8f3dn; +const T0 = 1791331200; +const DAY = 86400; +const E24 = 10n ** 24n; +const E18 = 10n ** 18n; + +class Rollback extends Error {} + +async function withFixture(run: (q: Queries, tx: Sql) => Promise) { + const sql = postgres(URL!, { + max: 1, + types: { bigint: postgres.BigInt }, + onnotice: () => {}, + }); + try { + await sql.begin(async (tx) => { + await seed(tx as unknown as Sql); + await run( + new Queries(tx as unknown as Sql<{ bigint: bigint }>), + tx as unknown as Sql, + ); + throw new Rollback(); + }); + } catch (error) { + if (!(error instanceof Rollback)) throw error; + } finally { + await sql.end(); + } +} + +const n = (value: bigint) => value.toString(); + +async function block(tx: Sql, number: number, time: number) { + await tx` + INSERT INTO blocks (chain_id, block_number, block_hash, block_time, num_events, fork_counter) + VALUES (${n(CHAIN)}, ${number}, ${number * 7919}, to_timestamp(${time}), 1, 0) + `; +} + +async function setHead(tx: Sql, time: number) { + await tx` + INSERT INTO indexer_cursor (chain_id, order_key, last_updated, head_block_number, head_block_hash, head_block_time) + VALUES (${n(CHAIN)}, 200, now(), 200, 1, to_timestamp(${time})) + ON CONFLICT (chain_id) DO UPDATE SET head_block_time = EXCLUDED.head_block_time + `; +} + +const ev = (blockNumber: number, eventIndex = 0, txIndex = 0) => ({ + chain_id: n(CHAIN), + block_number: blockNumber, + transaction_index: txIndex, + event_index: eventIndex, + transaction_hash: n(BigInt(blockNumber * 1000 + txIndex)), +}); + +async function poolKey(tx: Sql, poolId: bigint, extension: bigint) { + const [{ pool_key_id }] = await tx<{ pool_key_id: bigint }[]>` + INSERT INTO pool_keys (chain_id, core_address, pool_id, token0, token1, fee, fee_denominator, tick_spacing, pool_extension) + VALUES (${n(CHAIN)}, ${n(CORE)}, ${n(poolId)}, ${n(WETH)}, ${n(TOKEN)}, 0, ${n(1n << 64n)}, 1000, ${n(extension)}) + RETURNING pool_key_id + `; + return pool_key_id; +} + +async function created( + tx: Sql, + poolKeyId: bigint, + poolId: bigint, + blockNumber: number, + start: number, +) { + await tx` + INSERT INTO scheduled_launch_created ${tx({ + ...ev(blockNumber), + emitter: n(SL), + pool_key_id: n(poolKeyId), + pool_id: n(poolId), + token: n(TOKEN), + owner: n(ROUTER), + quote_token: n(WETH), + name: "Launch Token", + symbol: "LAUNCH", + decimals: 18, + total_supply: n(1000n * E24), + start_time: start, + end_time: start + DAY, + target_tick: -230000, + upper_tick: -160000, + tick_spacing: 1000, + initial_fee: n(1n << 60n), + final_fee: n(1n << 56n), + migration_tick_lower: -887000, + migration_tick_upper: 887000, + })} + `; +} + +async function swap( + tx: Sql, + poolKeyId: bigint, + blockNumber: number, + locker: bigint, + delta0: bigint, + delta1: bigint, + fee: bigint, + feeIsToken1: boolean, +) { + await tx` + INSERT INTO scheduled_launch_swapped ${tx({ + ...ev(blockNumber, 2), + emitter: n(SL), + pool_key_id: n(poolKeyId), + pool_id: n(POOL_ID), + locker: n(locker), + delta0: n(delta0), + delta1: n(delta1), + fee_amount: n(fee), + fee_is_token1: feeIsToken1, + })} + `; +} + +async function advanced( + tx: Sql, + poolKeyId: bigint, + blockNumber: number, + complete: boolean, +) { + await tx` + INSERT INTO scheduled_launch_advanced ${tx({ + ...ev(blockNumber, 0), + emitter: n(SL), + pool_key_id: n(poolKeyId), + pool_id: n(POOL_ID), + deployed: n(250n * E24), + reserve0: n(2n * E18), + reserve1: n(750n * E24), + complete, + })} + `; +} + +let launchKey = 0n; + +// One routed launch on Base starting at T0 with two buys, one sell, a creator +// fee claim, a LAUNCH_FUND top-up and a migration; plus a second, later launch +// created directly on the extension. +async function seed(tx: Sql) { + for (const [number, offset] of [ + [100, -3600], + [101, 60], + [102, 120], + [103, 180], + [104, 240], + [105, 300], + [110, 7200], + ]) { + await block(tx, number, T0 + offset); + } + await setHead(tx, T0 - 10); + await tx` + INSERT INTO erc20_tokens (chain_id, token_address, token_symbol, token_name, token_decimals, visibility_priority, sort_order) + VALUES (${n(CHAIN)}, ${n(WETH)}, 'WETH', 'Wrapped Ether', 18, 0, 0) + ON CONFLICT DO NOTHING + `; + + launchKey = await poolKey(tx, POOL_ID, SL); + const terminalKey = await poolKey(tx, TERMINAL_POOL_ID, TWAMM); + const otherKey = await poolKey(tx, OTHER_POOL_ID, SL); + await tx` + INSERT INTO pool_states (pool_key_id, sqrt_ratio, tick, liquidity, last_event_id) + VALUES (${n(launchKey)}, ${n(1n << 128n)}, 0, 5000, 1) + `; + + await created(tx, launchKey, POOL_ID, 100, T0); + await tx` + INSERT INTO launch_created_by ${tx({ + ...ev(100, 1), + emitter: n(ROUTER), + pool_key_id: n(launchKey), + launch_id: n(POOL_ID), + creator: n(CREATOR), + })} + `; + await created(tx, otherKey, OTHER_POOL_ID, 110, T0 + 2 * DAY); + + // token1 is the launch token: a buy moves quote (token0) in and launch + // tokens out + await advanced(tx, launchKey, 101, false); + await swap(tx, launchKey, 101, YUL_ROUTER, E18, -20n * E24, 100n, true); + await swap(tx, launchKey, 102, YUL_ROUTER, E18, -20n * E24, 100n, true); + await swap( + tx, + launchKey, + 103, + OTHER_ROUTER, + -4n * 10n ** 16n, + E24, + 200n, + false, + ); + + await tx` + INSERT INTO scheduled_launch_creator_fees_claimed ${tx({ + ...ev(104), + emitter: n(SL), + pool_key_id: n(launchKey), + pool_id: n(POOL_ID), + recipient: n(CREATOR), + amount0: "150", + amount1: "0", + })} + `; + await tx` + INSERT INTO launch_principal_received ${tx({ + ...ev(104, 1), + emitter: n(LLL), + pool_key_id: n(launchKey), + launch_id: n(POOL_ID), + from_address: n(ROUTER), + amount0: "5", + amount1: "0", + })} + `; + await tx` + INSERT INTO launch_principal_received ${tx({ + ...ev(105, 1), + emitter: n(LLL), + pool_key_id: n(launchKey), + launch_id: n(POOL_ID), + from_address: n(SL), + amount0: n(2n * E18), + amount1: n(700n * E24), + })} + `; + await tx` + INSERT INTO launch_liquidity_locked ${tx({ + ...ev(105, 2), + emitter: n(LLL), + pool_key_id: n(launchKey), + launch_id: n(POOL_ID), + terminal_pool_key_id: n(terminalKey), + terminal_pool_id: n(TERMINAL_POOL_ID), + liquidity: "777", + })} + `; +} + +async function statuses(q: Queries) { + const { rows } = await q.listLaunches({ + chainId: CHAIN, + status: null, + creator: null, + page: 1, + pageSize: 10, + }); + return Object.fromEntries(rows.map((r) => [r.pool_id, r.status])); +} + +describe.skipIf(!URL)("launch queries against the indexer schema", () => { + test("status follows the indexed head block time", async () => { + await withFixture(async (q, tx) => { + expect(await statuses(q)).toEqual({ + [n(POOL_ID)]: "upcoming", + [n(OTHER_POOL_ID)]: "upcoming", + }); + await setHead(tx, T0); + expect((await statuses(q))[n(POOL_ID)]).toBe("live"); + await setHead(tx, T0 + DAY - 1); + expect((await statuses(q))[n(POOL_ID)]).toBe("live"); + await setHead(tx, T0 + DAY); + expect((await statuses(q))[n(POOL_ID)]).toBe("ended"); + await advanced(tx, launchKey, 105, true); + expect((await statuses(q))[n(POOL_ID)]).toBe("migrated"); + // a migrated launch stays migrated whatever the head says + await setHead(tx, T0 - 10); + expect((await statuses(q))[n(POOL_ID)]).toBe("migrated"); + }); + }); + + test("list filters and pagination", async () => { + await withFixture(async (q, tx) => { + await setHead(tx, T0 + 100); + const all = await q.listLaunches({ + chainId: CHAIN, + status: null, + creator: null, + page: 1, + pageSize: 1, + }); + expect(all.totalCount).toBe(2); + // newest first + expect(all.rows.map((r) => r.pool_id)).toEqual([n(OTHER_POOL_ID)]); + + const live = await q.listLaunches({ + chainId: null, + status: "live", + creator: null, + page: 1, + pageSize: 10, + }); + expect(live.totalCount).toBe(1); + const [row] = live.rows; + expect(row.pool_id).toBe(n(POOL_ID)); + expect(row.creator).toBe(n(CREATOR)); + expect(row.owner).toBe(n(ROUTER)); + expect(row.token_symbol).toBe("LAUNCH"); + expect(row.quote_token_symbol).toBe("WETH"); + expect(row.token_is_token1).toBe(true); + expect(row.start_time).toBe(BigInt(T0)); + expect(row.deployed).toBe(n(250n * E24)); + expect(row.created_block_number).toBe(100n); + expect(row.created_time.getTime()).toBe((T0 - 3600) * 1000); + + const byCreator = await q.listLaunches({ + chainId: CHAIN, + status: null, + creator: CREATOR, + page: 1, + pageSize: 10, + }); + expect(byCreator.rows.map((r) => r.pool_id)).toEqual([n(POOL_ID)]); + + const otherChain = await q.listLaunches({ + chainId: 1n, + status: null, + creator: null, + page: 1, + pageSize: 10, + }); + expect(otherChain).toEqual({ rows: expect.any(Array), totalCount: 0 }); + }); + }); + + test("detail", async () => { + await withFixture(async (q) => { + const row = await q.getLaunch(CHAIN, POOL_ID); + expect(row).not.toBeNull(); + expect(row!.state_sqrt_ratio).toBe(n(1n << 128n)); + expect(row!.state_liquidity).toBe("5000"); + expect(row!.advanced_block_number).toBe(101n); + expect(row!.terminal_pool_id).toBe(n(TERMINAL_POOL_ID)); + expect(row!.locked_liquidity).toBe("777"); + expect(row!.creator_fees_claimed0).toBe("150"); + expect(row!.creator_fees_claimed1).toBe("0"); + + const other = await q.getLaunch(CHAIN, OTHER_POOL_ID); + expect(other!.creator).toBeNull(); + expect(other!.advanced_block_number).toBeNull(); + expect(other!.terminal_pool_id).toBeNull(); + expect(other!.state_sqrt_ratio).toBeNull(); + + expect(await q.getLaunch(CHAIN, TERMINAL_POOL_ID)).toBeNull(); + expect(await q.getLaunch(1n, POOL_ID)).toBeNull(); + }); + }); + + test("stats", async () => { + await withFixture(async (q) => { + expect(await q.getLaunchStats(CHAIN, POOL_ID)).toEqual({ + swap_count: 3, + buy_count: 2, + sell_count: 1, + buy_quote_in: n(2n * E18), + buy_token_out: n(40n * E24), + sell_token_in: n(E24), + sell_quote_out: n(4n * 10n ** 16n), + distinct_lockers: 2, + distinct_transactions: 3, + fees_accrued_token: "200", + fees_accrued_quote: "200", + fees_claimed_token: "0", + fees_claimed_quote: "150", + fee_claim_count: 1, + funding_token: "0", + funding_quote: "5", + funding_count: 1, + migrated_principal_token: n(700n * E24), + migrated_principal_quote: n(2n * E18), + }); + const empty = await q.getLaunchStats(CHAIN, OTHER_POOL_ID); + expect(empty!.swap_count).toBe(0); + expect(empty!.buy_quote_in).toBe("0"); + expect(empty!.fee_claim_count).toBe(0); + expect(await q.getLaunchStats(CHAIN, TERMINAL_POOL_ID)).toBeNull(); + }); + }); + + test("swaps page by event id", async () => { + await withFixture(async (q) => { + const first = await q.listLaunchSwaps({ + chainId: CHAIN, + poolId: POOL_ID, + cursor: null, + limit: 2, + }); + expect(first.map((r) => r.block_number)).toEqual([101n, 102n]); + expect(first[0].delta1).toBe(n(-20n * E24)); + expect(first[0].block_time!.getTime()).toBe((T0 + 60) * 1000); + + const rest = await q.listLaunchSwaps({ + chainId: CHAIN, + poolId: POOL_ID, + cursor: first[1].event_id, + limit: 2, + }); + expect(rest.map((r) => r.locker)).toEqual([n(OTHER_ROUTER)]); + + const none = await q.listLaunchSwaps({ + chainId: CHAIN, + poolId: OTHER_POOL_ID, + cursor: null, + limit: 2, + }); + expect(none).toHaveLength(1); + expect(none[0].event_id).toBeNull(); + + expect( + await q.listLaunchSwaps({ + chainId: CHAIN, + poolId: TERMINAL_POOL_ID, + cursor: null, + limit: 2, + }), + ).toHaveLength(0); + }); + }); +}); diff --git a/src/queries.ts b/src/queries.ts index 7f2d1a5..e430226 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -4268,6 +4268,360 @@ ORDER BY po.token_id DESC totalCount, }; } + + // One row per launch from the indexer's scheduled_launch_pool_states (00134), + // with its LaunchCreated event, LaunchRouter creator and the chain's indexed + // head. Status is derived from the head block time, never the wall clock. + private launchRows(condition: postgres.Fragment) { + return this.sql` + SELECT pk.chain_id, + slps.pool_key_id, + pk.core_address, + pk.pool_id, + pk.token0, + pk.token1, + pk.fee, + pk.tick_spacing AS pool_tick_spacing, + pk.pool_extension, + slps.token, + c.name AS token_name, + c.symbol AS token_symbol, + c.decimals AS token_decimals, + slps.total_supply, + slps.quote_token, + qt.token_name AS quote_token_name, + qt.token_symbol AS quote_token_symbol, + qt.token_decimals AS quote_token_decimals, + slps.token_is_token1, + slps.start_time, + slps.end_time, + slps.target_tick, + slps.upper_tick, + slps.tick_spacing, + slps.initial_fee, + slps.final_fee, + slps.migration_tick_lower, + slps.migration_tick_upper, + slps.deployed, + slps.reserve0, + slps.reserve1, + slps.complete, + slps.owner, + lcb.creator, + c.emitter AS launch_extension, + c.block_number AS created_block_number, + c.transaction_hash AS created_transaction_hash, + b.block_time AS created_time, + slps.created_event_id, + slps.last_advanced_event_id, + ic.head_block_time, + CASE + WHEN slps.complete THEN 'migrated' + WHEN ic.head_block_time IS NULL THEN NULL + WHEN EXTRACT(EPOCH FROM ic.head_block_time) < slps.start_time THEN 'upcoming' + WHEN EXTRACT(EPOCH FROM ic.head_block_time) < slps.end_time THEN 'live' + ELSE 'ended' + END AS status + -- The LATERAL subqueries are point lookups by primary key per launch. + -- As plain joins the planner may instead merge against the chain's + -- whole blocks index when its row estimates are off. + FROM scheduled_launch_pool_states slps + JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id + JOIN LATERAL (SELECT * + FROM scheduled_launch_created sc + WHERE sc.chain_id = pk.chain_id + AND sc.event_id = slps.created_event_id + LIMIT 1) c ON TRUE + JOIN LATERAL (SELECT bl.block_time + FROM blocks bl + WHERE bl.chain_id = c.chain_id + AND bl.block_number = c.block_number + LIMIT 1) b ON TRUE + LEFT JOIN LATERAL (SELECT lc.creator + FROM launch_created_by lc + WHERE lc.pool_key_id = slps.pool_key_id + ORDER BY lc.event_id DESC + LIMIT 1) lcb ON TRUE + LEFT JOIN erc20_tokens qt + ON qt.chain_id = pk.chain_id AND qt.token_address = slps.quote_token + LEFT JOIN indexer_cursor ic ON ic.chain_id = pk.chain_id + WHERE ${condition} + `; + } + + public async listLaunches({ + chainId, + status, + creator, + page, + pageSize, + }: { + chainId: bigint | null; + status: LaunchStatus | null; + creator: bigint | null; + page: number; + pageSize: number; + }) { + const chainCondition = + chainId === null ? this.sql`TRUE` : this.sql`pk.chain_id = ${chainId}`; + const creatorCondition = + creator === null + ? this.sql`TRUE` + : this.sql`lcb.creator = ${creator.toString()}`; + const statusCondition = + status === null ? this.sql`TRUE` : this.sql`l.status = ${status}`; + const base = this.launchRows( + this.sql`${chainCondition} AND ${creatorCondition}`, + ); + + const [rows, [{ total }]] = await Promise.all([ + this.sql` + SELECT l.* + FROM (${base}) l + WHERE ${statusCondition} + ORDER BY l.created_time DESC, l.chain_id, l.pool_key_id + LIMIT ${pageSize} OFFSET ${(page - 1) * pageSize} + `, + this.sql<{ total: number }[]>` + SELECT COUNT(*)::int AS total + FROM (${base}) l + WHERE ${statusCondition} + `, + ]); + return { rows, totalCount: total }; + } + + public async getLaunch(chainId: bigint, poolId: bigint) { + const base = this.launchRows( + this.sql`pk.chain_id = ${chainId} AND pk.pool_id = ${poolId.toString()}`, + ); + const rows = await this.sql` + SELECT l.*, + ps.sqrt_ratio AS state_sqrt_ratio, + ps.tick AS state_tick, + ps.liquidity AS state_liquidity, + a.block_number AS advanced_block_number, + a.transaction_hash AS advanced_transaction_hash, + ab.block_time AS advanced_time, + t.terminal_pool_id, + t.locked_liquidity, + COALESCE(fc.amount0, 0)::text AS creator_fees_claimed0, + COALESCE(fc.amount1, 0)::text AS creator_fees_claimed1 + FROM (${base}) l + LEFT JOIN pool_states ps ON ps.pool_key_id = l.pool_key_id + LEFT JOIN scheduled_launch_advanced a + ON a.chain_id = l.chain_id AND a.event_id = l.last_advanced_event_id + LEFT JOIN blocks ab ON ab.chain_id = a.chain_id AND ab.block_number = a.block_number + LEFT JOIN LATERAL (SELECT stp.terminal_pool_id, stp.locked_liquidity + FROM scheduled_launch_terminal_pools stp + WHERE stp.pool_key_id = l.pool_key_id + ORDER BY stp.last_locked_event_id DESC + LIMIT 1) t ON TRUE + LEFT JOIN LATERAL (SELECT SUM(cfc.amount0) AS amount0, SUM(cfc.amount1) AS amount1 + FROM scheduled_launch_creator_fees_claimed cfc + WHERE cfc.pool_key_id = l.pool_key_id) fc ON TRUE + `; + return rows[0] ?? null; + } + + public async getLaunchStats(chainId: bigint, poolId: bigint) { + const rows = await this.sql` + WITH l AS (SELECT slps.pool_key_id, slps.token_is_token1, c.emitter AS launch_extension + FROM scheduled_launch_pool_states slps + JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id + JOIN scheduled_launch_created c + ON c.chain_id = pk.chain_id AND c.event_id = slps.created_event_id + WHERE pk.chain_id = ${chainId} + AND pk.pool_id = ${poolId.toString()}), + swaps AS (SELECT CASE WHEN l.token_is_token1 THEN s.delta1 ELSE s.delta0 END AS token_delta, + CASE WHEN l.token_is_token1 THEN s.delta0 ELSE s.delta1 END AS quote_delta, + s.fee_amount, + s.fee_is_token1 = l.token_is_token1 AS fee_in_token, + s.locker, + s.transaction_hash + FROM l + JOIN scheduled_launch_swapped s ON s.pool_key_id = l.pool_key_id), + claimed AS (SELECT COALESCE(SUM(CASE WHEN l.token_is_token1 THEN f.amount1 ELSE f.amount0 END), 0) AS token, + COALESCE(SUM(CASE WHEN l.token_is_token1 THEN f.amount0 ELSE f.amount1 END), 0) AS quote, + COUNT(f.*)::int AS claims + FROM l + LEFT JOIN scheduled_launch_creator_fees_claimed f ON f.pool_key_id = l.pool_key_id), + funding AS (SELECT p.from_address = l.launch_extension AS migrated, + CASE WHEN l.token_is_token1 THEN p.amount1 ELSE p.amount0 END AS token, + CASE WHEN l.token_is_token1 THEN p.amount0 ELSE p.amount1 END AS quote + FROM l + JOIN launch_principal_received p ON p.pool_key_id = l.pool_key_id) + SELECT sa.*, + claimed.token::text AS fees_claimed_token, + claimed.quote::text AS fees_claimed_quote, + claimed.claims AS fee_claim_count, + fa.* + FROM l + CROSS JOIN claimed + CROSS JOIN (SELECT COUNT(*)::int AS swap_count, + COUNT(*) FILTER (WHERE token_delta < 0)::int AS buy_count, + COUNT(*) FILTER (WHERE token_delta > 0)::int AS sell_count, + COALESCE(SUM(quote_delta) FILTER (WHERE token_delta < 0), 0)::text AS buy_quote_in, + COALESCE(SUM(-token_delta) FILTER (WHERE token_delta < 0), 0)::text + AS buy_token_out, + COALESCE(SUM(token_delta) FILTER (WHERE token_delta > 0), 0)::text AS sell_token_in, + COALESCE(SUM(-quote_delta) FILTER (WHERE token_delta > 0), 0)::text + AS sell_quote_out, + COUNT(DISTINCT locker)::int AS distinct_lockers, + COUNT(DISTINCT transaction_hash)::int AS distinct_transactions, + COALESCE(SUM(fee_amount) FILTER (WHERE fee_in_token), 0)::text AS fees_accrued_token, + COALESCE(SUM(fee_amount) FILTER (WHERE NOT fee_in_token), 0)::text AS fees_accrued_quote + FROM swaps) sa + CROSS JOIN (SELECT COALESCE(SUM(token) FILTER (WHERE NOT migrated), 0)::text AS funding_token, + COALESCE(SUM(quote) FILTER (WHERE NOT migrated), 0)::text AS funding_quote, + COUNT(*) FILTER (WHERE NOT migrated)::int AS funding_count, + COALESCE(SUM(token) FILTER (WHERE migrated), 0)::text AS migrated_principal_token, + COALESCE(SUM(quote) FILTER (WHERE migrated), 0)::text AS migrated_principal_quote + FROM funding) fa + `; + return rows[0] ?? null; + } + + public async listLaunchSwaps({ + chainId, + poolId, + cursor, + limit, + }: { + chainId: bigint; + poolId: bigint; + cursor: bigint | null; + limit: number; + }) { + const cursorCondition = + cursor === null ? this.sql`TRUE` : this.sql`s.event_id > ${cursor}`; + // The launch row comes back even when it has no swaps (or none after the + // cursor), so the route can tell an unknown launch from an empty page. + return this.sql` + WITH l AS (SELECT slps.pool_key_id, pk.chain_id + FROM scheduled_launch_pool_states slps + JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id + WHERE pk.chain_id = ${chainId} + AND pk.pool_id = ${poolId.toString()}) + SELECT s.event_id, + s.block_number, + s.transaction_index, + s.event_index, + s.transaction_hash, + b.block_time, + s.locker, + s.delta0, + s.delta1, + s.fee_amount, + s.fee_is_token1 + FROM l + LEFT JOIN LATERAL (SELECT * + FROM scheduled_launch_swapped s + WHERE s.pool_key_id = l.pool_key_id + AND ${cursorCondition} + ORDER BY s.event_id + LIMIT ${limit}) s ON TRUE + LEFT JOIN blocks b ON b.chain_id = s.chain_id AND b.block_number = s.block_number + ORDER BY s.event_id + `; + } +} + +export type LaunchStatus = "upcoming" | "live" | "ended" | "migrated"; + +export interface LaunchRow { + chain_id: bigint; + pool_key_id: bigint; + core_address: string; + pool_id: string; + token0: string; + token1: string; + fee: string; + pool_tick_spacing: number | null; + pool_extension: string; + token: string; + token_name: string; + token_symbol: string; + token_decimals: number; + total_supply: string; + quote_token: string; + quote_token_name: string | null; + quote_token_symbol: string | null; + quote_token_decimals: number | null; + token_is_token1: boolean; + start_time: bigint; + end_time: bigint; + target_tick: number; + upper_tick: number; + tick_spacing: number; + initial_fee: string; + final_fee: string; + migration_tick_lower: number; + migration_tick_upper: number; + deployed: string; + reserve0: string; + reserve1: string; + complete: boolean; + owner: string; + creator: string | null; + launch_extension: string; + created_block_number: bigint; + created_transaction_hash: string; + created_time: Date; + created_event_id: bigint; + last_advanced_event_id: bigint | null; + head_block_time: Date | null; + status: LaunchStatus | null; +} + +export interface LaunchDetailRow extends LaunchRow { + state_sqrt_ratio: string | null; + state_tick: number | null; + state_liquidity: string | null; + advanced_block_number: bigint | null; + advanced_transaction_hash: string | null; + advanced_time: Date | null; + terminal_pool_id: string | null; + locked_liquidity: string | null; + creator_fees_claimed0: string; + creator_fees_claimed1: string; +} + +export interface LaunchStatsRow { + swap_count: number; + buy_count: number; + sell_count: number; + buy_quote_in: string; + buy_token_out: string; + sell_token_in: string; + sell_quote_out: string; + distinct_lockers: number; + distinct_transactions: number; + fees_accrued_token: string; + fees_accrued_quote: string; + fees_claimed_token: string; + fees_claimed_quote: string; + fee_claim_count: number; + funding_token: string; + funding_quote: string; + funding_count: number; + migrated_principal_token: string; + migrated_principal_quote: string; +} + +// A launch with no swaps on the page yields one row with every swap column null. +export interface LaunchSwapRow { + event_id: bigint | null; + block_number: bigint | null; + transaction_index: number | null; + event_index: number | null; + transaction_hash: string | null; + block_time: Date | null; + locker: string | null; + delta0: string | null; + delta1: string | null; + fee_amount: string | null; + fee_is_token1: boolean | null; } export async function createQueries(env: Env) { diff --git a/src/responseSchemas.test.ts b/src/responseSchemas.test.ts index b4477ee..5ff9e9c 100644 --- a/src/responseSchemas.test.ts +++ b/src/responseSchemas.test.ts @@ -18,6 +18,7 @@ import { stateCases } from "./contract/cases/state"; import { statsCases } from "./contract/cases/stats"; import { twammCases } from "./contract/cases/twamm"; import { ve33Cases } from "./contract/cases/ve33"; +import { launchesCases } from "./contract/cases/launches"; const cases: ContractCase[] = [ ...metaCases, @@ -30,6 +31,7 @@ const cases: ContractCase[] = [ ...auctionsCases, ...governanceCases, ...ve33Cases, + ...launchesCases, ...incentivesCases, ]; diff --git a/src/router.test.ts b/src/router.test.ts index 9530b0b..115c7cf 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -24,14 +24,14 @@ describe("Hono app integration", () => { ), ); - expect(Object.keys(schema.paths ?? {})).toHaveLength(59); - expect(operations).toHaveLength(59); + expect(Object.keys(schema.paths ?? {})).toHaveLength(63); + expect(operations).toHaveLength(63); expect( operations.reduce( (count, operation) => count + (operation.parameters?.length ?? 0), 0, ), - ).toBe(198); + ).toBe(211); const getToken = operations.find( (operation) => diff --git a/src/router.ts b/src/router.ts index dc57938..baff676 100644 --- a/src/router.ts +++ b/src/router.ts @@ -65,6 +65,12 @@ import { GetAuctionNftState, ListAuctions, } from "./routes/auctions"; +import { + GetLaunch, + GetLaunchStats, + ListLaunches, + ListLaunchSwaps, +} from "./routes/launches"; import { ListVe33Pools, ListVe33TokensByAddress, @@ -123,6 +129,10 @@ const routes: readonly ApiRoute[] = [ GetAuctionNftMetadata, GetAuctionNftState, GetAuctionNftImage, + ListLaunches, + GetLaunchStats, + ListLaunchSwaps, + GetLaunch, ListProposals, ListTopDelegates, ListVotesOnProposal, diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts new file mode 100644 index 0000000..1e20e55 --- /dev/null +++ b/src/routes/launches/index.ts @@ -0,0 +1,626 @@ +import { createRoute, z } from "@hono/zod-openapi"; +import { defineRoute } from "../../shared/context"; +import { + errorResponses, + notFoundResponse, + StatusError, +} from "../../shared/errors"; +import { jsonResponse } from "../../shared/openapi"; +import { + AddressType, + ChainIdType, + DecimalStringType, + HexStringType, + NumericStringType, +} from "../../shared/validation/address"; +import { + createQueries, + LaunchDetailRow, + LaunchRow, + LaunchStatsRow, + LaunchSwapRow, +} from "../../queries"; +import toHex from "../../shared/toHex"; + +// Cache lifetimes. Launch state moves with every swap, so the detail and swap +// pages are short-lived; the list and the stats aggregate tolerate a minute. +export const LAUNCH_LIST_CACHE_CONTROL = "public, max-age=60, must-revalidate"; +export const LAUNCH_DETAIL_CACHE_CONTROL = + "public, max-age=10, must-revalidate"; +export const LAUNCH_STATS_CACHE_CONTROL = "public, max-age=60, must-revalidate"; +export const LAUNCH_SWAPS_CACHE_CONTROL = "public, max-age=10, must-revalidate"; + +const SignedDecimalStringType = z.string().regex(/^-?\d+$/); +const UnixSecondsType = z.number().int().describe("Unix time in seconds"); + +const LaunchStatusType = z + .enum(["upcoming", "live", "ended", "migrated"]) + .describe( + "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete", + ); + +const LaunchTokenType = z.object({ + address: HexStringType, + name: z.string(), + symbol: z.string(), + decimals: z.number().int(), + total_supply: DecimalStringType, +}); + +const QuoteTokenType = z.object({ + address: HexStringType, + name: z + .string() + .nullable() + .describe("Null when the token is not in the token list"), + symbol: z.string().nullable(), + decimals: z.number().int().nullable(), +}); + +const LaunchPoolKeyType = z.object({ + core_address: HexStringType, + token0: HexStringType, + token1: HexStringType, + fee: HexStringType, + tick_spacing: z.number().int().nullable(), + extension: HexStringType, +}); + +const LaunchSummaryShape = { + chain_id: DecimalStringType, + pool_key_id: DecimalStringType, + pool_id: HexStringType, + pool_key: LaunchPoolKeyType, + launch_token: LaunchTokenType, + quote_token: QuoteTokenType, + launch_token_is_token1: z.boolean(), + start_time: UnixSecondsType, + end_time: UnixSecondsType, + target_tick: z + .number() + .int() + .describe( + "Tick in raw quote units per launch token, independent of token order", + ), + upper_tick: z.number().int(), + tick_spacing: z.number().int(), + initial_fee: DecimalStringType.describe("0.64 fixed-point fee fraction"), + final_fee: DecimalStringType.describe("0.64 fixed-point fee fraction"), + migration_tick_lower: z.number().int(), + migration_tick_upper: z.number().int(), + deployed: DecimalStringType, + reserve0: DecimalStringType, + reserve1: DecimalStringType, + complete: z.boolean(), + status: LaunchStatusType.nullable().describe( + "Null only when the chain has no indexed head yet and the launch is not complete", + ), + status_as_of: UnixSecondsType.nullable().describe( + "The indexed head block time the status was derived from", + ), + owner: HexStringType.describe( + "Owner of record in LaunchCreated (LaunchRouter for routed launches)", + ), + creator: HexStringType.nullable().describe( + "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter", + ), + created_block_number: DecimalStringType, + created_transaction_hash: HexStringType, + created_time: UnixSecondsType, +}; + +const LaunchSummaryType = z.object(LaunchSummaryShape); + +const ListLaunchesResponseType = z.object({ + data: z.array(LaunchSummaryType), + pagination: z.object({ + page: z.number().int().min(1), + pageSize: z.number().int().min(1), + totalPages: z.number().int().min(0), + totalItems: z.number().int().min(0), + }), +}); + +const LaunchDetailType = z.object({ + ...LaunchSummaryShape, + pool_state: z + .object({ + sqrt_ratio: DecimalStringType, + tick: z.number().int(), + liquidity: DecimalStringType, + }) + .nullable() + .describe("Core pool state of the launch pool"), + latest_advance: z + .object({ + block_number: DecimalStringType, + transaction_hash: HexStringType, + time: UnixSecondsType, + }) + .nullable() + .describe( + "The LaunchAdvanced event the deployed/reserve/complete values come from; null before the first advance", + ), + terminal: z + .object({ + pool_id: HexStringType, + locked_liquidity: DecimalStringType, + }) + .nullable() + .describe("The pool the principal was locked in once migrated"), + creator_fees_claimed: z.object({ + amount0: DecimalStringType, + amount1: DecimalStringType, + }), +}); + +const TokenQuoteAmountsType = z.object({ + launch_token: DecimalStringType, + quote_token: DecimalStringType, +}); + +const LaunchStatsType = z.object({ + chain_id: DecimalStringType, + pool_id: HexStringType, + swaps: z.object({ + count: z.number().int(), + buy_count: z.number().int(), + sell_count: z.number().int(), + distinct_transactions: z.number().int(), + distinct_lockers: z + .number() + .int() + .describe( + "Distinct LaunchSwapped.locker addresses: the contract that forwarded the swap (usually a router), not the user", + ), + distinct_transaction_senders: z + .null() + .describe( + "Not available: the indexer stores no transaction sender, and the API does not resolve transactions to their senders", + ), + }), + volume: z.object({ + buy: z + .object({ + quote_in: DecimalStringType, + launch_token_out: DecimalStringType, + }) + .describe("Swaps that took launch tokens out of the pool"), + sell: z + .object({ + launch_token_in: DecimalStringType, + quote_out: DecimalStringType, + }) + .describe("Swaps that put launch tokens into the pool"), + }), + creator_fees: z.object({ + accrued: TokenQuoteAmountsType.describe( + "Sum of LaunchSwapped fees, saved for the launch's creator", + ), + claimed: TokenQuoteAmountsType, + claim_count: z.number().int(), + }), + funding: z.object({ + received: TokenQuoteAmountsType.describe( + "PrincipalReceived from lockers that forwarded LAUNCH_FUND", + ), + count: z.number().int(), + migrated_principal: TokenQuoteAmountsType.describe( + "PrincipalReceived from the launch extension at migration", + ), + }), + attribution: z + .object({ + counts: z.string(), + lockers: z.string(), + senders: z.string(), + }) + .describe("Where each count comes from"), +}); + +const LaunchSwapType = z.object({ + event_id: SignedDecimalStringType, + block_number: DecimalStringType, + transaction_index: z.number().int(), + event_index: z.number().int(), + transaction_hash: HexStringType, + time: UnixSecondsType, + locker: HexStringType.describe( + "The locker that forwarded the swap, usually a router contract", + ), + delta0: SignedDecimalStringType.describe( + "Fee-inclusive pool-perspective delta of token0", + ), + delta1: SignedDecimalStringType.describe( + "Fee-inclusive pool-perspective delta of token1", + ), + fee_amount: DecimalStringType, + fee_is_token1: z.boolean(), +}); + +const ListLaunchSwapsResponseType = z.object({ + chain_id: DecimalStringType, + pool_id: HexStringType, + swaps: z.array(LaunchSwapType), + next_cursor: SignedDecimalStringType.nullable(), + has_more: z.boolean(), +}); + +const LaunchParams = z.object({ + chainId: ChainIdType, + poolId: NumericStringType.describe("The launch pool's ID").openapi({ + example: "0x1234", + }), +}); + +const EventCursorType = z.coerce + .bigint() + .min(-(1n << 63n)) + .max((1n << 63n) - 1n) + .openapi({ type: "integer", format: "int64" }); + +const seconds = (value: bigint | Date) => + value instanceof Date ? Math.floor(value.getTime() / 1000) : Number(value); + +const hex = (value: string, width?: number) => toHex(BigInt(value), width); + +function formatQuoteToken(row: LaunchRow) { + return { + address: hex(row.quote_token, 20), + name: row.quote_token_name, + symbol: row.quote_token_symbol, + decimals: row.quote_token_decimals, + }; +} + +function formatLaunch(row: LaunchRow): z.infer { + return { + chain_id: row.chain_id.toString(), + pool_key_id: row.pool_key_id.toString(), + pool_id: hex(row.pool_id, 32), + pool_key: { + core_address: hex(row.core_address, 20), + token0: hex(row.token0, 20), + token1: hex(row.token1, 20), + fee: hex(row.fee), + tick_spacing: row.pool_tick_spacing, + extension: hex(row.pool_extension, 20), + }, + launch_token: { + address: hex(row.token, 20), + name: row.token_name, + symbol: row.token_symbol, + decimals: row.token_decimals, + total_supply: row.total_supply, + }, + quote_token: formatQuoteToken(row), + launch_token_is_token1: row.token_is_token1, + start_time: seconds(row.start_time), + end_time: seconds(row.end_time), + target_tick: row.target_tick, + upper_tick: row.upper_tick, + tick_spacing: row.tick_spacing, + initial_fee: row.initial_fee, + final_fee: row.final_fee, + migration_tick_lower: row.migration_tick_lower, + migration_tick_upper: row.migration_tick_upper, + deployed: row.deployed, + reserve0: row.reserve0, + reserve1: row.reserve1, + complete: row.complete, + status: row.status, + status_as_of: row.head_block_time ? seconds(row.head_block_time) : null, + owner: hex(row.owner, 20), + creator: row.creator === null ? null : hex(row.creator, 20), + created_block_number: row.created_block_number.toString(), + created_transaction_hash: hex(row.created_transaction_hash, 32), + created_time: seconds(row.created_time), + }; +} + +function formatPoolState(row: LaunchDetailRow) { + if (row.state_sqrt_ratio === null || row.state_liquidity === null) { + return null; + } + return { + sqrt_ratio: row.state_sqrt_ratio, + tick: Number(row.state_tick), + liquidity: row.state_liquidity, + }; +} + +function formatLatestAdvance(row: LaunchDetailRow) { + if (row.advanced_block_number === null) return null; + return { + block_number: row.advanced_block_number.toString(), + transaction_hash: hex(row.advanced_transaction_hash!, 32), + time: seconds(row.advanced_time!), + }; +} + +function formatTerminal(row: LaunchDetailRow) { + if (row.terminal_pool_id === null) return null; + return { + pool_id: hex(row.terminal_pool_id, 32), + locked_liquidity: row.locked_liquidity ?? "0", + }; +} + +function formatLaunchDetail( + row: LaunchDetailRow, +): z.infer { + return { + ...formatLaunch(row), + pool_state: formatPoolState(row), + latest_advance: formatLatestAdvance(row), + terminal: formatTerminal(row), + creator_fees_claimed: { + amount0: row.creator_fees_claimed0, + amount1: row.creator_fees_claimed1, + }, + }; +} + +const ATTRIBUTION = { + counts: + "Swap, buy, sell and transaction counts are LaunchSwapped events and their transaction hashes", + lockers: + "distinct_lockers counts LaunchSwapped.locker, the router contract that forwarded each swap", + senders: + "Transaction senders are not indexed and are not resolved by this API", +}; + +function formatStats( + chainId: bigint, + poolId: bigint, + row: LaunchStatsRow, +): z.infer { + return { + chain_id: chainId.toString(), + pool_id: toHex(poolId, 32), + swaps: { + count: row.swap_count, + buy_count: row.buy_count, + sell_count: row.sell_count, + distinct_transactions: row.distinct_transactions, + distinct_lockers: row.distinct_lockers, + distinct_transaction_senders: null, + }, + volume: { + buy: { quote_in: row.buy_quote_in, launch_token_out: row.buy_token_out }, + sell: { + launch_token_in: row.sell_token_in, + quote_out: row.sell_quote_out, + }, + }, + creator_fees: { + accrued: { + launch_token: row.fees_accrued_token, + quote_token: row.fees_accrued_quote, + }, + claimed: { + launch_token: row.fees_claimed_token, + quote_token: row.fees_claimed_quote, + }, + claim_count: row.fee_claim_count, + }, + funding: { + received: { + launch_token: row.funding_token, + quote_token: row.funding_quote, + }, + count: row.funding_count, + migrated_principal: { + launch_token: row.migrated_principal_token, + quote_token: row.migrated_principal_quote, + }, + }, + attribution: ATTRIBUTION, + }; +} + +type LaunchSwap = z.infer; + +function formatSwap(row: LaunchSwapRow): LaunchSwap { + return { + event_id: row.event_id!.toString(), + block_number: row.block_number!.toString(), + transaction_index: row.transaction_index!, + event_index: row.event_index!, + transaction_hash: hex(row.transaction_hash!, 32), + time: seconds(row.block_time!), + locker: hex(row.locker!, 20), + delta0: row.delta0!, + delta1: row.delta1!, + fee_amount: row.fee_amount!, + fee_is_token1: row.fee_is_token1!, + }; +} + +export const ListLaunches = defineRoute( + createRoute({ + method: "get", + path: "/launches", + tags: ["Launches"], + summary: "List launches", + description: + "Lists ScheduledLaunch launches from the indexed launchpad tables, newest first", + operationId: "get_ListLaunches", + request: { + query: z.object({ + chainId: ChainIdType.optional().describe("Restrict to one chain"), + status: LaunchStatusType.optional(), + creator: AddressType.optional().describe( + "Only launches created through LaunchRouter by this address", + ), + pageSize: z.coerce + .number() + .int() + .min(1) + .max(200) + .optional() + .describe("Maximum number of launches per page") + .default(50), + page: z.coerce + .number() + .int() + .min(1) + .optional() + .describe("Page number to fetch (1-indexed)") + .default(1), + }), + }, + responses: { + 200: jsonResponse("A page of launches", ListLaunchesResponseType), + ...errorResponses, + }, + }), + async (c) => { + const { + chainId = null, + status = null, + creator, + page, + pageSize, + } = c.req.valid("query"); + const queries = await createQueries(c.env); + const { rows, totalCount } = await queries.listLaunches({ + chainId, + status, + creator: creator === undefined ? null : BigInt(creator), + page, + pageSize, + }); + + const response = { + data: rows.map(formatLaunch), + pagination: { + page, + pageSize, + totalPages: Math.ceil(totalCount / pageSize), + totalItems: totalCount, + }, + } satisfies z.infer; + + return c.json(response, 200, { + "cache-control": LAUNCH_LIST_CACHE_CONTROL, + }); + }, +); + +export const GetLaunch = defineRoute( + createRoute({ + method: "get", + path: "/launches/{chainId}/{poolId}", + tags: ["Launches"], + summary: "Get a launch", + description: + "Returns a launch's configuration, saved state, latest advance, migration and claimed creator fees, plus the Core pool state", + operationId: "get_GetLaunch", + request: { params: LaunchParams }, + responses: { + 200: jsonResponse("The launch", LaunchDetailType), + ...notFoundResponse, + ...errorResponses, + }, + }), + async (c) => { + const { chainId, poolId } = c.req.valid("param"); + const queries = await createQueries(c.env); + const row = await queries.getLaunch(chainId, BigInt(poolId)); + if (row === null) throw new StatusError(404, "Launch not found"); + + return c.json(formatLaunchDetail(row), 200, { + "cache-control": LAUNCH_DETAIL_CACHE_CONTROL, + }); + }, +); + +export const GetLaunchStats = defineRoute( + createRoute({ + method: "get", + path: "/launches/{chainId}/{poolId}/stats", + tags: ["Launches"], + summary: "Get launch stats", + description: + "Swap volume by side, swap and locker counts, creator fees accrued and claimed, and funding received for one launch", + operationId: "get_GetLaunchStats", + request: { params: LaunchParams }, + responses: { + 200: jsonResponse("Launch statistics", LaunchStatsType), + ...notFoundResponse, + ...errorResponses, + }, + }), + async (c) => { + const { chainId, poolId: poolIdParam } = c.req.valid("param"); + const poolId = BigInt(poolIdParam); + const queries = await createQueries(c.env); + const row = await queries.getLaunchStats(chainId, poolId); + if (row === null) throw new StatusError(404, "Launch not found"); + + return c.json(formatStats(chainId, poolId, row), 200, { + "cache-control": LAUNCH_STATS_CACHE_CONTROL, + }); + }, +); + +export const ListLaunchSwaps = defineRoute( + createRoute({ + method: "get", + path: "/launches/{chainId}/{poolId}/swaps", + tags: ["Launches"], + summary: "List launch swaps", + description: + "Returns a launch's LaunchSwapped events in ascending event order", + operationId: "get_ListLaunchSwaps", + request: { + params: LaunchParams, + query: z.object({ + cursor: EventCursorType.optional().describe( + "Return swaps strictly after this event_id; use next_cursor to continue", + ), + limit: z.coerce + .number() + .int() + .min(1) + .max(1000) + .optional() + .describe("Maximum number of swaps to return") + .default(100), + }), + }, + responses: { + 200: jsonResponse("A page of launch swaps", ListLaunchSwapsResponseType), + ...notFoundResponse, + ...errorResponses, + }, + }), + async (c) => { + const { chainId, poolId: poolIdParam } = c.req.valid("param"); + const { cursor = null, limit } = c.req.valid("query"); + const poolId = BigInt(poolIdParam); + const queries = await createQueries(c.env); + const rows = await queries.listLaunchSwaps({ + chainId, + poolId, + cursor, + limit: limit + 1, + }); + if (rows.length === 0) throw new StatusError(404, "Launch not found"); + + const swaps = rows.filter((row) => row.event_id !== null); + const page = swaps.slice(0, limit).map(formatSwap); + const response = { + chain_id: chainId.toString(), + pool_id: toHex(poolId, 32), + swaps: page, + next_cursor: page.at(-1)?.event_id ?? cursor?.toString() ?? null, + has_more: swaps.length > limit, + } satisfies z.infer; + + return c.json(response, 200, { + "cache-control": LAUNCH_SWAPS_CACHE_CONTROL, + }); + }, +); From da96bb25f46c5d71f2b6e1e997eaa93c7050e907 Mon Sep 17 00:00:00 2001 From: Paperclip Date: Sun, 11 Oct 2026 04:02:38 -0400 Subject: [PATCH 02/19] add frozen zero-seed API schemas and local policy guards EKU-1523 preparation only; route/query integration follows. No handoff or release clearance. Co-Authored-By: Paperclip --- src/routes/launches/policy.test.ts | 94 +++++++++++++++++++++ src/routes/launches/policy.ts | 128 +++++++++++++++++++++++++++++ src/routes/launches/schema.ts | 121 +++++++++++++++++++++++++++ 3 files changed, 343 insertions(+) create mode 100644 src/routes/launches/policy.test.ts create mode 100644 src/routes/launches/policy.ts create mode 100644 src/routes/launches/schema.ts diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts new file mode 100644 index 0000000..2afee8d --- /dev/null +++ b/src/routes/launches/policy.test.ts @@ -0,0 +1,94 @@ +import { expect, test } from "bun:test"; +import { + feeSchedule, + LOCAL_INSTANCES, + supportedInstances, + supportedLaunch, +} from "./policy"; +import { ClaimPage, LaunchDetail, LaunchList, SwapPage } from "./schema"; + +test("local instance manifests bind every constructor/runtime/ABI field", () => { + expect(supportedInstances(LOCAL_INSTANCES, true)).toEqual(LOCAL_INSTANCES); + expect(supportedInstances(LOCAL_INSTANCES, false)).toEqual([]); + expect(supportedInstances([], true)).toEqual([]); + for (const pin of LOCAL_INSTANCES) { + for (const key of Object.keys(pin) as (keyof typeof pin)[]) { + expect( + supportedInstances([{ ...pin, [key]: "unreviewed" }], true), + ).toEqual([]); + } + expect(supportedLaunch(pin, LOCAL_INSTANCES)).toBe(true); + for (const key of ["chainId", "address", "core", "quote"] as const) { + expect( + supportedLaunch({ ...pin, [key]: "unreviewed" }, LOCAL_INSTANCES), + ).toBe(false); + } + expect(supportedLaunch(pin, [])).toBe(false); + } +}); + +const input = { + start: 1000n, + duration: 3n, + initial: 10n, + final: 3n, + indexedTime: 999n, +}; +test("fee phases and floor decrement use indexed time only", () => { + const cases = [ + [999n, "before_trading_start", "10"], + [1000n, "fee_decay", "10"], + [1001n, "fee_decay", "8"], + [1002n, "fee_decay", "6"], + [1003n, "final_fee", "3"], + [1000000000n, "final_fee", "3"], + ] as const; + for (const [indexedTime, phase, rate] of cases) { + const result = feeSchedule({ ...input, indexedTime }); + expect(result.phase).toBe(phase); + expect(result.fee_rate).toBe(rate); + expect(result.evaluated_at).toBe(Number(indexedTime)); + } +}); + +test("invalid indexed domains fail, never coerce or overflow", () => { + for (const change of [ + { start: -1n }, + { duration: 0n }, + { final: 11n }, + { initial: 1n << 64n }, + { indexedTime: -1n }, + { start: (1n << 64n) - 1n }, + { indexedTime: BigInt(Number.MAX_SAFE_INTEGER) + 1n }, + ]) { + expect(() => feeSchedule({ ...input, ...change })).toThrow(); + } +}); + +test("frozen schemas expose no staged status/stats/exhausted fields", () => { + const empty = { + schema_version: "zero-seed-launch-api/v1", + as_of: { + block_number: "1", + block_hash: `0x${"a".repeat(64)}`, + block_time: 1000, + }, + data: [], + next_cursor: null, + }; + expect(LaunchList.safeParse(empty).success).toBe(true); + expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe(false); + for (const schema of [SwapPage, ClaimPage]) { + expect( + schema.safeParse({ + ...empty, + chain_id: "31337", + pool_id: `0x${"b".repeat(64)}`, + }).success, + ).toBe(true); + } + expect(Object.keys(LaunchDetail.shape)).not.toContain("status"); + expect(Object.keys(LaunchDetail.shape.inventory.shape)).not.toContain( + "exhausted", + ); +}); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts new file mode 100644 index 0000000..8572f28 --- /dev/null +++ b/src/routes/launches/policy.ts @@ -0,0 +1,128 @@ +import { z } from "@hono/zod-openapi"; +import { FeeSchedule } from "./schema"; + +export const ABI_SHA256 = + "189ff93c0f6fb64f6e7dd0675872bf6686692f8303e6218b8c2521bfa5ced215"; +export const POLICY_SHA256 = + "7864a2d33a0fa8964922a5225653ed19842c590091f289eb4e29306feac6f6e1"; +const CORE = "0x00000000000014aa86c5d3c41765bb24e11bd701"; +const WETH_RUNTIME = + "0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c"; + +export interface LaunchInstance { + chainId: string; + address: string; + core: string; + quote: string; + runtime: string; + quoteRuntime: string; + abiSha256: string; +} + +// Constructor-executed LOCAL instances in indexer f6af307 / fixture 41d416ce. +// No live instance exists. Never enable these via a hosted configuration. +export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ + { + chainId: "31337", + address: "0x510d7c117adeeb89bb33b9acb53995f3280eaa3a", + core: CORE, + quote: "0xffffffffffffffffffffffffffffffffffffffff", + runtime: + "0x8d8839b97583f275a84aaf26cd6a5fa3ba21a7be04731a65f6140fc5040477c4", + quoteRuntime: WETH_RUNTIME, + abiSha256: ABI_SHA256, + }, + { + chainId: "31337", + address: "0x5195c03d30c11be73485ab76a35eb6f59fc52072", + core: CORE, + quote: "0x0000000000000000000000000000000000010000", + runtime: + "0x8751bab213ba226c4e45f6441b9df2db1f9c8ee9e9335320e180f3b348646014", + quoteRuntime: WETH_RUNTIME, + abiSha256: ABI_SHA256, + }, +]; + +/** An explicit local harness manifest, never an address-only allowlist. */ +export function supportedInstances( + requested: readonly LaunchInstance[], + localHarness: boolean, +): readonly LaunchInstance[] { + if (!localHarness) return []; + return requested.filter((request) => + LOCAL_INSTANCES.some((pin) => + (Object.keys(pin) as (keyof LaunchInstance)[]).every( + (key) => pin[key] === request[key], + ), + ), + ); +} + +export function supportedLaunch( + row: Pick, + enabled: readonly LaunchInstance[], +): boolean { + return enabled.some( + (pin) => + pin.chainId === row.chainId && + pin.address === row.address && + pin.core === row.core && + pin.quote === row.quote, + ); +} + +const UINT64_MAX = (1n << 64n) - 1n; +function uint64(value: bigint) { + if (value < 0n || value > UINT64_MAX) { + throw new Error("Invalid indexed uint64"); + } + return value; +} + +// JSON Unix seconds must be exactly representable; never round indexed time. +function seconds(value: bigint) { + uint64(value); + if (value > BigInt(Number.MAX_SAFE_INTEGER)) { + throw new Error("Indexed time exceeds exact JSON integer domain"); + } + return Number(value); +} + +export function feeSchedule(input: { + start: bigint; + duration: bigint; + initial: bigint; + final: bigint; + indexedTime: bigint; +}): z.infer { + const { start, duration, initial, final, indexedTime } = input; + [start, duration, initial, final, indexedTime].forEach(uint64); + if (duration === 0n || final > initial) { + throw new Error("Invalid indexed fee schedule"); + } + const end = uint64(start + duration); + const phase = feePhase(start, end, indexedTime); + const elapsed = indexedTime > start ? indexedTime - start : 0n; + // Contract floors the decrement (not the remaining fee). + const rate = + indexedTime >= end + ? final + : initial - ((initial - final) * elapsed) / duration; + return { + trading_start: seconds(start), + fee_duration: seconds(duration), + fee_end: seconds(end), + initial_fee: initial.toString(), + final_fee: final.toString(), + phase, + fee_rate: rate.toString(), + evaluated_at: seconds(indexedTime), + }; +} + +function feePhase(start: bigint, end: bigint, time: bigint) { + if (time < start) return "before_trading_start"; + if (time < end) return "fee_decay"; + return "final_fee"; +} diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts new file mode 100644 index 0000000..104a9d6 --- /dev/null +++ b/src/routes/launches/schema.ts @@ -0,0 +1,121 @@ +import { z } from "@hono/zod-openapi"; + +// Frozen API v1 shapes (EKU-1513); behavioral policy is separately v2. +export const SchemaVersion = z.literal("zero-seed-launch-api/v1"); +const U = z.string().regex(/^(0|[1-9][0-9]*)$/); +const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); +const Address = z.string().regex(/^0x[0-9a-f]{40}$/); +const Bytes32 = z.string().regex(/^0x[0-9a-f]{64}$/); +const Seconds = z.number().int().nonnegative(); +export const BlockRef = z.strictObject({ + block_number: U, + block_hash: Bytes32, + block_time: Seconds, +}); +export const EventRef = BlockRef.extend({ + event_id: I, + transaction_index: z.number().int().nonnegative(), + event_index: z.number().int().nonnegative(), + transaction_hash: Bytes32, +}); +const Token = z.strictObject({ + address: Address, + name: z.string().nullable(), + symbol: z.string().nullable(), + decimals: z.number().int().min(0).max(255).nullable(), +}); +const PoolKey = z.strictObject({ + core_address: Address, + token0: Address, + token1: Address, + config: Bytes32, + fee: z.literal("0"), + tick_spacing: z.number().int().positive(), + extension: Address, +}); +export const FeeSchedule = z.strictObject({ + trading_start: Seconds, + fee_duration: z.number().int().positive(), + fee_end: Seconds, + initial_fee: U, + final_fee: U, + phase: z.enum(["before_trading_start", "fee_decay", "final_fee"]), + fee_rate: U, + evaluated_at: Seconds, +}); +export const LaunchSummary = z.strictObject({ + schema_version: SchemaVersion, + chain_id: U, + pool_id: Bytes32, + pool_key: PoolKey, + token: Token, + quote_token: Token, + launch_token_is_token1: z.boolean(), + creator: Address, + ask_tick: z.number().int(), + upper_tick: z.number().int(), + fee_schedule: FeeSchedule, + created: EventRef, + as_of: BlockRef, +}); +const SideAmounts = z.strictObject({ launch_token: U, quote_token: U }); +const LedgerSide = z.strictObject({ accrued: U, claimed: U, unclaimed: U }); +export const LaunchDetail = LaunchSummary.extend({ + principal: z.strictObject({ + position_id: Bytes32, + tick_lower: z.number().int(), + tick_upper: z.number().int(), + liquidity: U, + installed_supply: U, + locked: z.literal(true), + withdrawable: z.literal(false), + pool_reserves: SideAmounts, + reserves_basis: z.literal("core_pool_balance"), + }), + fee_ledger: z.strictObject({ + holder: Address, + transferable: z.literal(false), + launch_token: LedgerSide, + quote_token: LedgerSide, + swap_count: z.number().int().nonnegative(), + claim_count: z.number().int().nonnegative(), + last_event_id: I, + }), + inventory: z.strictObject({ + trading_open: z.boolean(), + no_bid: z.boolean(), + price_tick: z.number().int(), + }), +}); +export const LaunchList = z.strictObject({ + schema_version: SchemaVersion, + data: z.array(LaunchSummary), + next_cursor: z.string().nullable(), + as_of: BlockRef, +}); +export const Swap = z.strictObject({ + event: EventRef, + forwarding_locker: Address, + direction: z.enum(["buy", "sell", "zero_fill"]), + launch_token_delta: I, + quote_token_delta: I, + fee_rate: U, + fee_amount: U, + fee_asset: z.enum(["launch_token", "quote_token"]), +}); +export const Claim = z.strictObject({ + event: EventRef, + creator: Address, + recipient: Address, + launch_token_amount: U, + quote_token_amount: U, +}); +const PageIdentity = z.strictObject({ + schema_version: SchemaVersion, + chain_id: U, + pool_id: Bytes32, + next_cursor: z.string().nullable(), + as_of: BlockRef, +}); +export const SwapPage = PageIdentity.extend({ data: z.array(Swap) }); +export const ClaimPage = PageIdentity.extend({ data: z.array(Claim) }); From 1af28a86cca433db3e56de83eded0af2036a9a6e Mon Sep 17 00:00:00 2001 From: Paperclip Date: Sun, 11 Oct 2026 04:20:21 -0400 Subject: [PATCH 03/19] replace staged launch API with frozen zero-seed indexed reads Use screened local manifests, repeatable-read snapshots and separate Core principal/creator ledgers; hosted support remains disabled. Verify actual handlers against emitted local indexer replay, reorg/repair/idempotency and concurrent snapshot consistency. Co-Authored-By: Paperclip --- openapi.json | 1631 ++++++++++++++-------------- scripts/launchReplayCases.txt | 129 +++ scripts/verifyLaunchReplay.ts | 69 ++ src/contract/cases/launches.ts | 308 ++---- src/launchQueries.db.test.ts | 456 -------- src/queries.ts | 377 +------ src/router.ts | 4 +- src/routes/launches/README.md | 89 ++ src/routes/launches/index.ts | 671 ++---------- src/routes/launches/queries.ts | 420 +++++++ src/routes/launches/routes.test.ts | 66 ++ 11 files changed, 1831 insertions(+), 2389 deletions(-) create mode 100644 scripts/launchReplayCases.txt create mode 100644 scripts/verifyLaunchReplay.ts delete mode 100644 src/launchQueries.db.test.ts create mode 100644 src/routes/launches/README.md create mode 100644 src/routes/launches/queries.ts create mode 100644 src/routes/launches/routes.test.ts diff --git a/openapi.json b/openapi.json index 7996e40..9b22c70 100644 --- a/openapi.json +++ b/openapi.json @@ -9916,60 +9916,23 @@ "tags": [ "Launches" ], - "summary": "List launches", - "description": "Lists ScheduledLaunch launches from the indexed launchpad tables, newest first", "operationId": "get_ListLaunches", + "summary": "List supported zero-seed launches at one indexed head", "parameters": [ { "schema": { - "type": "integer", - "title": "Chain ID", - "description": "Restrict to one chain", - "format": "int64", - "minimum": 1, - "maximum": 9223372036854776000 + "type": "string" }, - "required": false, - "description": "Restrict to one chain", + "required": true, "name": "chainId", "in": "query" }, { "schema": { - "type": "string", - "enum": [ - "upcoming", - "live", - "ended", - "migrated" - ], - "description": "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete" - }, - "required": false, - "description": "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete", - "name": "status", - "in": "query" - }, - { - "schema": { - "anyOf": [ - { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - ], - "title": "Address", - "description": "Only launches created through LaunchRouter by this address" + "type": "string" }, "required": false, - "description": "Only launches created through LaunchRouter by this address", - "name": "creator", + "name": "before_event_id", "in": "query" }, { @@ -9977,144 +9940,148 @@ "type": "integer", "minimum": 1, "maximum": 200, - "default": 50, - "description": "Maximum number of launches per page" + "default": 50 }, "required": false, - "description": "Maximum number of launches per page", - "name": "pageSize", + "name": "limit", "in": "query" }, { "schema": { - "type": "integer", - "minimum": 1, - "default": 1, - "description": "Page number to fetch (1-indexed)" + "type": "string", + "pattern": "^0x[0-9a-fA-F]{40}$" }, "required": false, - "description": "Page number to fetch (1-indexed)", - "name": "page", + "name": "creator", "in": "query" } ], "responses": { "200": { - "description": "A page of launches", + "description": "Indexed zero-seed launch data", "content": { "application/json": { "schema": { "type": "object", "properties": { + "schema_version": { + "type": "string", + "enum": [ + "zero-seed-launch-api/v1" + ] + }, "data": { "type": "array", "items": { "type": "object", "properties": { - "chain_id": { + "schema_version": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "enum": [ + "zero-seed-launch-api/v1" + ] }, - "pool_key_id": { + "chain_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, "pool_id": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" }, "pool_key": { "type": "object", "properties": { "core_address": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "token0": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "token1": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" + }, + "config": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" }, "fee": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "enum": [ + "0" + ] }, "tick_spacing": { - "type": [ - "integer", - "null" - ] + "type": "integer", + "exclusiveMinimum": 0 }, "extension": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" } }, "required": [ "core_address", "token0", "token1", + "config", "fee", "tick_spacing", "extension" - ] + ], + "additionalProperties": false }, - "launch_token": { + "token": { "type": "object", "properties": { "address": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "name": { - "type": "string" + "type": [ + "string", + "null" + ] }, "symbol": { - "type": "string" + "type": [ + "string", + "null" + ] }, "decimals": { - "type": "integer" - }, - "total_supply": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 255 } }, "required": [ "address", "name", "symbol", - "decimals", - "total_supply" - ] + "decimals" + ], + "additionalProperties": false }, "quote_token": { "type": "object", "properties": { "address": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "name": { "type": [ "string", "null" - ], - "description": "Null when the token is not in the token list" + ] }, "symbol": { "type": [ @@ -10126,7 +10093,9 @@ "type": [ "integer", "null" - ] + ], + "minimum": 0, + "maximum": 255 } }, "required": [ @@ -10134,175 +10103,196 @@ "name", "symbol", "decimals" - ] + ], + "additionalProperties": false }, "launch_token_is_token1": { "type": "boolean" }, - "start_time": { - "type": "integer", - "description": "Unix time in seconds" - }, - "end_time": { - "type": "integer", - "description": "Unix time in seconds" - }, - "target_tick": { - "type": "integer", - "description": "Tick in raw quote units per launch token, independent of token order" - }, - "upper_tick": { - "type": "integer" - }, - "tick_spacing": { - "type": "integer" - }, - "initial_fee": { - "type": "string", - "pattern": "^\\d+$", - "description": "0.64 fixed-point fee fraction" - }, - "final_fee": { + "creator": { "type": "string", - "pattern": "^\\d+$", - "description": "0.64 fixed-point fee fraction" + "pattern": "^0x[0-9a-f]{40}$" }, - "migration_tick_lower": { + "ask_tick": { "type": "integer" }, - "migration_tick_upper": { + "upper_tick": { "type": "integer" }, - "deployed": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "reserve0": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "reserve1": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "complete": { - "type": "boolean" - }, - "status": { - "type": [ - "string", - "null" - ], - "enum": [ - "upcoming", - "live", - "ended", - "migrated", - null + "fee_schedule": { + "type": "object", + "properties": { + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "fee_duration": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "fee_end": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "final_fee": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "fee_decay", + "final_fee" + ] + }, + "fee_rate": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "trading_start", + "fee_duration", + "fee_end", + "initial_fee", + "final_fee", + "phase", + "fee_rate", + "evaluated_at" ], - "description": "Null only when the chain has no indexed head yet and the launch is not complete" + "additionalProperties": false }, - "status_as_of": { - "type": [ - "integer", - "null" + "created": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + }, + "event_id": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "transaction_index": { + "type": "integer", + "minimum": 0 + }, + "event_index": { + "type": "integer", + "minimum": 0 + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + } + }, + "required": [ + "block_number", + "block_hash", + "block_time", + "event_id", + "transaction_index", + "event_index", + "transaction_hash" ], - "description": "The indexed head block time the status was derived from" + "additionalProperties": false }, - "owner": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "Owner of record in LaunchCreated (LaunchRouter for routed launches)" - }, - "creator": { - "type": [ - "string", - "null" + "as_of": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "block_number", + "block_hash", + "block_time" ], - "pattern": "^0x[a-fA-F0-9]+$", - "description": "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter" - }, - "created_block_number": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "created_transaction_hash": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - "created_time": { - "type": "integer", - "description": "Unix time in seconds" + "additionalProperties": false } }, "required": [ + "schema_version", "chain_id", - "pool_key_id", "pool_id", "pool_key", - "launch_token", + "token", "quote_token", "launch_token_is_token1", - "start_time", - "end_time", - "target_tick", - "upper_tick", - "tick_spacing", - "initial_fee", - "final_fee", - "migration_tick_lower", - "migration_tick_upper", - "deployed", - "reserve0", - "reserve1", - "complete", - "status", - "status_as_of", - "owner", "creator", - "created_block_number", - "created_transaction_hash", - "created_time" - ] + "ask_tick", + "upper_tick", + "fee_schedule", + "created", + "as_of" + ], + "additionalProperties": false } }, - "pagination": { + "next_cursor": { + "type": [ + "string", + "null" + ] + }, + "as_of": { "type": "object", "properties": { - "page": { - "type": "integer", - "minimum": 1 - }, - "pageSize": { - "type": "integer", - "minimum": 1 + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" }, - "totalPages": { - "type": "integer", - "minimum": 0 + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" }, - "totalItems": { + "block_time": { "type": "integer", "minimum": 0 } }, "required": [ - "page", - "pageSize", - "totalPages", - "totalItems" - ] + "block_number", + "block_hash", + "block_time" + ], + "additionalProperties": false } }, "required": [ + "schema_version", "data", - "pagination" - ] + "next_cursor", + "as_of" + ], + "additionalProperties": false } } } @@ -10317,6 +10307,16 @@ } } }, + "404": { + "description": "The requested resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, "500": { "description": "An unexpected server error occurred", "content": { @@ -10326,292 +10326,201 @@ } } } + }, + "503": { + "description": "The indexed head is missing, inconsistent or stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } } } } }, - "/launches/{chainId}/{poolId}/stats": { + "/launches/{chainId}/{poolId}/claims": { "get": { "tags": [ "Launches" ], - "summary": "Get launch stats", - "description": "Swap volume by side, swap and locker counts, creator fees accrued and claimed, and funding received for one launch", - "operationId": "get_GetLaunchStats", + "operationId": "get_ListLaunchClaims", + "summary": "List atomic full two-asset creator fee claims", "parameters": [ { "schema": { - "type": "integer", - "title": "Chain ID", - "description": "The ID of the network that is being fetched", - "format": "int64", - "minimum": 1, - "maximum": 9223372036854776000 + "type": "string" }, "required": true, - "description": "The ID of the network that is being fetched", "name": "chainId", "in": "path" }, { "schema": { - "anyOf": [ - { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - ], - "title": "Numeric", - "description": "The launch pool's ID", - "example": "0x1234" + "type": "string", + "pattern": "^0x[0-9a-fA-F]{64}$" }, "required": true, - "description": "The launch pool's ID", "name": "poolId", "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "after_event_id", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "required": false, + "name": "limit", + "in": "query" } ], "responses": { "200": { - "description": "Launch statistics", + "description": "Indexed zero-seed launch data", "content": { "application/json": { "schema": { "type": "object", "properties": { + "schema_version": { + "type": "string", + "enum": [ + "zero-seed-launch-api/v1" + ] + }, "chain_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, "pool_id": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" }, - "swaps": { - "type": "object", - "properties": { - "count": { - "type": "integer" - }, - "buy_count": { - "type": "integer" - }, - "sell_count": { - "type": "integer" - }, - "distinct_transactions": { - "type": "integer" - }, - "distinct_lockers": { - "type": "integer", - "description": "Distinct LaunchSwapped.locker addresses: the contract that forwarded the swap (usually a router), not the user" - }, - "distinct_transaction_senders": { - "type": "null", - "description": "Not available: the indexer stores no transaction sender, and the API does not resolve transactions to their senders" - } - }, - "required": [ - "count", - "buy_count", - "sell_count", - "distinct_transactions", - "distinct_lockers", - "distinct_transaction_senders" + "next_cursor": { + "type": [ + "string", + "null" ] }, - "volume": { + "as_of": { "type": "object", "properties": { - "buy": { - "type": "object", - "properties": { - "quote_in": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "launch_token_out": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - }, - "required": [ - "quote_in", - "launch_token_out" - ], - "description": "Swaps that took launch tokens out of the pool" + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" }, - "sell": { - "type": "object", - "properties": { - "launch_token_in": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "quote_out": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - }, - "required": [ - "launch_token_in", - "quote_out" - ], - "description": "Swaps that put launch tokens into the pool" + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 } }, "required": [ - "buy", - "sell" - ] + "block_number", + "block_hash", + "block_time" + ], + "additionalProperties": false }, - "creator_fees": { - "type": "object", - "properties": { - "accrued": { - "type": "object", - "properties": { - "launch_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + }, + "event_id": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "transaction_index": { + "type": "integer", + "minimum": 0 + }, + "event_index": { + "type": "integer", + "minimum": 0 + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + } }, - "quote_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } + "required": [ + "block_number", + "block_hash", + "block_time", + "event_id", + "transaction_index", + "event_index", + "transaction_hash" + ], + "additionalProperties": false }, - "required": [ - "launch_token", - "quote_token" - ], - "description": "Sum of LaunchSwapped fees, saved for the launch's creator" - }, - "claimed": { - "type": "object", - "properties": { - "launch_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "quote_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } + "creator": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" }, - "required": [ - "launch_token", - "quote_token" - ] - }, - "claim_count": { - "type": "integer" - } - }, - "required": [ - "accrued", - "claimed", - "claim_count" - ] - }, - "funding": { - "type": "object", - "properties": { - "received": { - "type": "object", - "properties": { - "launch_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "quote_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } + "recipient": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" }, - "required": [ - "launch_token", - "quote_token" - ], - "description": "PrincipalReceived from lockers that forwarded LAUNCH_FUND" - }, - "count": { - "type": "integer" - }, - "migrated_principal": { - "type": "object", - "properties": { - "launch_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "quote_token": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } + "launch_token_amount": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" }, - "required": [ - "launch_token", - "quote_token" - ], - "description": "PrincipalReceived from the launch extension at migration" - } - }, - "required": [ - "received", - "count", - "migrated_principal" - ] - }, - "attribution": { - "type": "object", - "properties": { - "counts": { - "type": "string" - }, - "lockers": { - "type": "string" + "quote_token_amount": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } }, - "senders": { - "type": "string" - } - }, - "required": [ - "counts", - "lockers", - "senders" - ], - "description": "Where each count comes from" + "required": [ + "event", + "creator", + "recipient", + "launch_token_amount", + "quote_token_amount" + ], + "additionalProperties": false + } } }, "required": [ + "schema_version", "chain_id", "pool_id", - "swaps", - "volume", - "creator_fees", - "funding", - "attribution" - ] + "next_cursor", + "as_of", + "data" + ], + "additionalProperties": false } } } @@ -10645,6 +10554,16 @@ } } } + }, + "503": { + "description": "The indexed head is missing, inconsistent or stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } } } } @@ -10654,176 +10573,204 @@ "tags": [ "Launches" ], - "summary": "List launch swaps", - "description": "Returns a launch's LaunchSwapped events in ascending event order", "operationId": "get_ListLaunchSwaps", + "summary": "List swaps; forwarding lockers are contracts, not users", "parameters": [ { "schema": { - "type": "integer", - "title": "Chain ID", - "description": "The ID of the network that is being fetched", - "format": "int64", - "minimum": 1, - "maximum": 9223372036854776000 + "type": "string" }, "required": true, - "description": "The ID of the network that is being fetched", "name": "chainId", "in": "path" }, { "schema": { - "anyOf": [ - { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - ], - "title": "Numeric", - "description": "The launch pool's ID", - "example": "0x1234" + "type": "string", + "pattern": "^0x[0-9a-fA-F]{64}$" }, "required": true, - "description": "The launch pool's ID", "name": "poolId", "in": "path" }, { "schema": { - "type": "integer", - "format": "int64", - "description": "Return swaps strictly after this event_id; use next_cursor to continue" + "type": "string" }, "required": false, - "description": "Return swaps strictly after this event_id; use next_cursor to continue", - "name": "cursor", + "name": "after_event_id", "in": "query" }, { "schema": { "type": "integer", "minimum": 1, - "maximum": 1000, - "default": 100, - "description": "Maximum number of swaps to return" + "maximum": 200, + "default": 50 }, "required": false, - "description": "Maximum number of swaps to return", "name": "limit", "in": "query" } ], "responses": { "200": { - "description": "A page of launch swaps", + "description": "Indexed zero-seed launch data", "content": { "application/json": { "schema": { "type": "object", "properties": { + "schema_version": { + "type": "string", + "enum": [ + "zero-seed-launch-api/v1" + ] + }, "chain_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, "pool_id": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" + }, + "next_cursor": { + "type": [ + "string", + "null" + ] + }, + "as_of": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "block_number", + "block_hash", + "block_time" + ], + "additionalProperties": false }, - "swaps": { + "data": { "type": "array", "items": { "type": "object", "properties": { - "event_id": { - "type": "string", - "pattern": "^-?\\d+$" + "event": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + }, + "event_id": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "transaction_index": { + "type": "integer", + "minimum": 0 + }, + "event_index": { + "type": "integer", + "minimum": 0 + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + } + }, + "required": [ + "block_number", + "block_hash", + "block_time", + "event_id", + "transaction_index", + "event_index", + "transaction_hash" + ], + "additionalProperties": false }, - "block_number": { + "forwarding_locker": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "transaction_index": { - "type": "integer" - }, - "event_index": { - "type": "integer" + "pattern": "^0x[0-9a-f]{40}$" }, - "transaction_hash": { + "direction": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - "time": { - "type": "integer", - "description": "Unix time in seconds" + "enum": [ + "buy", + "sell", + "zero_fill" + ] }, - "locker": { + "launch_token_delta": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "The locker that forwarded the swap, usually a router contract" + "pattern": "^(0|-?[1-9][0-9]*)$" }, - "delta0": { + "quote_token_delta": { "type": "string", - "pattern": "^-?\\d+$", - "description": "Fee-inclusive pool-perspective delta of token0" + "pattern": "^(0|-?[1-9][0-9]*)$" }, - "delta1": { + "fee_rate": { "type": "string", - "pattern": "^-?\\d+$", - "description": "Fee-inclusive pool-perspective delta of token1" + "pattern": "^(0|[1-9][0-9]*)$" }, "fee_amount": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, - "fee_is_token1": { - "type": "boolean" + "fee_asset": { + "type": "string", + "enum": [ + "launch_token", + "quote_token" + ] } }, "required": [ - "event_id", - "block_number", - "transaction_index", - "event_index", - "transaction_hash", - "time", - "locker", - "delta0", - "delta1", + "event", + "forwarding_locker", + "direction", + "launch_token_delta", + "quote_token_delta", + "fee_rate", "fee_amount", - "fee_is_token1" - ] + "fee_asset" + ], + "additionalProperties": false } - }, - "next_cursor": { - "type": [ - "string", - "null" - ], - "pattern": "^-?\\d+$" - }, - "has_more": { - "type": "boolean" } }, "required": [ + "schema_version", "chain_id", "pool_id", - "swaps", "next_cursor", - "has_more" - ] + "as_of", + "data" + ], + "additionalProperties": false } } } @@ -10857,6 +10804,16 @@ } } } + }, + "503": { + "description": "The indexed head is missing, inconsistent or stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } } } } @@ -10866,160 +10823,142 @@ "tags": [ "Launches" ], - "summary": "Get a launch", - "description": "Returns a launch's configuration, saved state, latest advance, migration and claimed creator fees, plus the Core pool state", "operationId": "get_GetLaunch", + "summary": "Get a supported launch and separate principal/creator ledgers", "parameters": [ { "schema": { - "type": "integer", - "title": "Chain ID", - "description": "The ID of the network that is being fetched", - "format": "int64", - "minimum": 1, - "maximum": 9223372036854776000 + "type": "string" }, "required": true, - "description": "The ID of the network that is being fetched", "name": "chainId", "in": "path" }, { "schema": { - "anyOf": [ - { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - } - ], - "title": "Numeric", - "description": "The launch pool's ID", - "example": "0x1234" + "type": "string", + "pattern": "^0x[0-9a-fA-F]{64}$" }, "required": true, - "description": "The launch pool's ID", "name": "poolId", "in": "path" } ], "responses": { "200": { - "description": "The launch", + "description": "Indexed zero-seed launch data", "content": { "application/json": { "schema": { "type": "object", "properties": { - "chain_id": { + "schema_version": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "enum": [ + "zero-seed-launch-api/v1" + ] }, - "pool_key_id": { + "chain_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, "pool_id": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" }, "pool_key": { "type": "object", "properties": { "core_address": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "token0": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "token1": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" + }, + "config": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" }, "fee": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "enum": [ + "0" + ] }, "tick_spacing": { - "type": [ - "integer", - "null" - ] + "type": "integer", + "exclusiveMinimum": 0 }, "extension": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" } }, "required": [ "core_address", "token0", "token1", + "config", "fee", "tick_spacing", "extension" - ] + ], + "additionalProperties": false }, - "launch_token": { + "token": { "type": "object", "properties": { "address": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" }, "name": { - "type": "string" + "type": [ + "string", + "null" + ] }, "symbol": { - "type": "string" + "type": [ + "string", + "null" + ] }, "decimals": { - "type": "integer" - }, - "total_supply": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 255 } }, "required": [ "address", "name", "symbol", - "decimals", - "total_supply" - ] + "decimals" + ], + "additionalProperties": false }, "quote_token": { "type": "object", "properties": { "address": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{40}$" }, "name": { "type": [ "string", "null" - ], - "description": "Null when the token is not in the token list" + ] }, "symbol": { "type": [ @@ -11031,7 +10970,9 @@ "type": [ "integer", "null" - ] + ], + "minimum": 0, + "maximum": 255 } }, "required": [ @@ -11039,242 +10980,334 @@ "name", "symbol", "decimals" - ] + ], + "additionalProperties": false }, "launch_token_is_token1": { "type": "boolean" }, - "start_time": { - "type": "integer", - "description": "Unix time in seconds" - }, - "end_time": { - "type": "integer", - "description": "Unix time in seconds" - }, - "target_tick": { - "type": "integer", - "description": "Tick in raw quote units per launch token, independent of token order" - }, - "upper_tick": { - "type": "integer" - }, - "tick_spacing": { - "type": "integer" - }, - "initial_fee": { - "type": "string", - "pattern": "^\\d+$", - "description": "0.64 fixed-point fee fraction" - }, - "final_fee": { + "creator": { "type": "string", - "pattern": "^\\d+$", - "description": "0.64 fixed-point fee fraction" + "pattern": "^0x[0-9a-f]{40}$" }, - "migration_tick_lower": { + "ask_tick": { "type": "integer" }, - "migration_tick_upper": { + "upper_tick": { "type": "integer" }, - "deployed": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "reserve0": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "reserve1": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "complete": { - "type": "boolean" - }, - "status": { - "type": [ - "string", - "null" - ], - "enum": [ - "upcoming", - "live", - "ended", - "migrated", - null - ], - "description": "Null only when the chain has no indexed head yet and the launch is not complete" - }, - "status_as_of": { - "type": [ - "integer", - "null" - ], - "description": "The indexed head block time the status was derived from" - }, - "owner": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "Owner of record in LaunchCreated (LaunchRouter for routed launches)" - }, - "creator": { - "type": [ - "string", - "null" + "fee_schedule": { + "type": "object", + "properties": { + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "fee_duration": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "fee_end": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "final_fee": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "fee_decay", + "final_fee" + ] + }, + "fee_rate": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "trading_start", + "fee_duration", + "fee_end", + "initial_fee", + "final_fee", + "phase", + "fee_rate", + "evaluated_at" ], - "pattern": "^0x[a-fA-F0-9]+$", - "description": "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter" - }, - "created_block_number": { - "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" - }, - "created_transaction_hash": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" - }, - "created_time": { - "type": "integer", - "description": "Unix time in seconds" + "additionalProperties": false }, - "pool_state": { - "type": [ - "object", - "null" - ], + "created": { + "type": "object", "properties": { - "sqrt_ratio": { + "block_number": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, - "tick": { - "type": "integer" + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" }, - "liquidity": { + "block_time": { + "type": "integer", + "minimum": 0 + }, + "event_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "transaction_index": { + "type": "integer", + "minimum": 0 + }, + "event_index": { + "type": "integer", + "minimum": 0 + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" } }, "required": [ - "sqrt_ratio", - "tick", - "liquidity" + "block_number", + "block_hash", + "block_time", + "event_id", + "transaction_index", + "event_index", + "transaction_hash" ], - "description": "Core pool state of the launch pool" + "additionalProperties": false }, - "latest_advance": { - "type": [ - "object", - "null" - ], + "as_of": { + "type": "object", "properties": { "block_number": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" }, - "transaction_hash": { + "block_hash": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" }, - "time": { + "block_time": { "type": "integer", - "description": "Unix time in seconds" + "minimum": 0 } }, "required": [ "block_number", - "transaction_hash", - "time" + "block_hash", + "block_time" ], - "description": "The LaunchAdvanced event the deployed/reserve/complete values come from; null before the first advance" + "additionalProperties": false }, - "terminal": { - "type": [ - "object", - "null" - ], + "principal": { + "type": "object", "properties": { - "pool_id": { + "position_id": { "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "A hexadecimal number" + "pattern": "^0x[0-9a-f]{64}$" + }, + "tick_lower": { + "type": "integer" + }, + "tick_upper": { + "type": "integer" }, - "locked_liquidity": { + "liquidity": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|[1-9][0-9]*)$" + }, + "installed_supply": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "locked": { + "type": "boolean", + "enum": [ + true + ] + }, + "withdrawable": { + "type": "boolean", + "enum": [ + false + ] + }, + "pool_reserves": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "quote_token": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "launch_token", + "quote_token" + ], + "additionalProperties": false + }, + "reserves_basis": { + "type": "string", + "enum": [ + "core_pool_balance" + ] } }, "required": [ - "pool_id", - "locked_liquidity" + "position_id", + "tick_lower", + "tick_upper", + "liquidity", + "installed_supply", + "locked", + "withdrawable", + "pool_reserves", + "reserves_basis" ], - "description": "The pool the principal was locked in once migrated" + "additionalProperties": false }, - "creator_fees_claimed": { + "fee_ledger": { "type": "object", "properties": { - "amount0": { + "holder": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^0x[0-9a-f]{40}$" + }, + "transferable": { + "type": "boolean", + "enum": [ + false + ] + }, + "launch_token": { + "type": "object", + "properties": { + "accrued": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "claimed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "unclaimed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "accrued", + "claimed", + "unclaimed" + ], + "additionalProperties": false + }, + "quote_token": { + "type": "object", + "properties": { + "accrued": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "claimed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "unclaimed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "accrued", + "claimed", + "unclaimed" + ], + "additionalProperties": false + }, + "swap_count": { + "type": "integer", + "minimum": 0 + }, + "claim_count": { + "type": "integer", + "minimum": 0 }, - "amount1": { + "last_event_id": { "type": "string", - "pattern": "^\\d+$", - "description": "A decimal number" + "pattern": "^(0|-?[1-9][0-9]*)$" } }, "required": [ - "amount0", - "amount1" - ] + "holder", + "transferable", + "launch_token", + "quote_token", + "swap_count", + "claim_count", + "last_event_id" + ], + "additionalProperties": false + }, + "inventory": { + "type": "object", + "properties": { + "trading_open": { + "type": "boolean" + }, + "no_bid": { + "type": "boolean" + }, + "price_tick": { + "type": "integer" + } + }, + "required": [ + "trading_open", + "no_bid", + "price_tick" + ], + "additionalProperties": false } }, "required": [ + "schema_version", "chain_id", - "pool_key_id", "pool_id", "pool_key", - "launch_token", + "token", "quote_token", "launch_token_is_token1", - "start_time", - "end_time", - "target_tick", - "upper_tick", - "tick_spacing", - "initial_fee", - "final_fee", - "migration_tick_lower", - "migration_tick_upper", - "deployed", - "reserve0", - "reserve1", - "complete", - "status", - "status_as_of", - "owner", "creator", - "created_block_number", - "created_transaction_hash", - "created_time", - "pool_state", - "latest_advance", - "terminal", - "creator_fees_claimed" - ] + "ask_tick", + "upper_tick", + "fee_schedule", + "created", + "as_of", + "principal", + "fee_ledger", + "inventory" + ], + "additionalProperties": false } } } @@ -11308,6 +11341,16 @@ } } } + }, + "503": { + "description": "The indexed head is missing, inconsistent or stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } } } } diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt new file mode 100644 index 0000000..1aa48a0 --- /dev/null +++ b/scripts/launchReplayCases.txt @@ -0,0 +1,129 @@ + async function apiHead(db: Db) { + await db.sql`UPDATE indexer_cursor SET + head_block_number = b.block_number, head_block_hash = b.block_hash, + head_block_time = b.block_time, last_updated = NOW() + FROM (SELECT * FROM blocks WHERE chain_id = ${CHAIN_ID} ORDER BY block_number DESC LIMIT 1) b + WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; + } + + test("API actual handlers: emitted-state golden, screening, pages, ledgers, reorg and repair", async () => { + const db = await freshDb(); + const processors = arithmeticReplayProcessors(); + const reader = new LaunchQueries(db.sql as never, { + localHarness: true, instances: LOCAL_INSTANCES, maxIndexAgeSeconds: 60, + }); + const spies = [ + spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), + spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), + spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), + spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), + ]; + const get = async (path: string, status = 200) => { + const response = await app.fetch(new Request(`http://localhost${path}`), { PG_CONNECTION_STRING: urlFor(db.name) }); + const body = await response.json() as any; + expect(response.status).toBe(status); + if (response.status !== status) throw new Error(JSON.stringify(body)); + return body; + }; + const fullFixture = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); + const base = `/launches/${CHAIN_ID}/${fixture.launches.l1!.poolId}`; + try { + await replay(db, prefix, processors, async (state) => { + await expectChainParity(db, state); + await apiHead(db); + if (!state.pools.some((p) => p.label === "l1")) return; + const body = await get(base); + const launchState = state.pools.find((p) => p.label === "l1")!; + expect(body.principal.installed_supply).toBe(fullFixture.installations.l1.actualInstalledSupply); + expect(body.principal.liquidity).toBe(fullFixture.installations.l1.actualLiquidity); + expect(body.fee_ledger.launch_token.unclaimed).toBe(launchState.creatorFees[0]); + expect(body.fee_ledger.quote_token.unclaimed).toBe(launchState.creatorFees[1]); + expect(body.inventory.price_tick).toBe(decodePoolState(launchState.poolStateWord).tick); + expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); + expect(body).not.toHaveProperty("status"); + expect(body.inventory).not.toHaveProperty("exhausted"); + }); + await replay(db, alternate, processors); + await apiHead(db); + const list = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); + expect(list.data).toHaveLength(1); + expect(list.data[0].pool_id).toBe(fixture.launches.l3!.poolId); + const second = await get(`/launches?chainId=${CHAIN_ID}&limit=1&before_event_id=${list.next_cursor}`); + expect(second.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + expect(second.next_cursor).toBeNull(); + const lowWeth = await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`); + expect(lowWeth.launch_token_is_token1).toBe(true); + expect(lowWeth.principal.installed_supply).toBe(fullFixture.installations.l3.actualInstalledSupply); + await replay(db, canonical, processors); + await apiHead(db); + const canonicalList = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); + expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + expect(canonicalList.next_cursor).toBeNull(); + await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`, 404); + for (const label of ["l2", "l4"]) { + await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`, 404); + await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}/swaps`, 404); + await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}/claims`, 404); + } + const detail = await get(base); + const swaps = await get(`${base}/swaps`); + const claims = await get(`${base}/claims`); + expect(swaps.data.length).toBe(detail.fee_ledger.swap_count); + expect(claims.data.length).toBe(detail.fee_ledger.claim_count); + const firstSwap = await get(`${base}/swaps?limit=1`); + const later = await get(`${base}/swaps?after_event_id=${firstSwap.next_cursor}`); + expect([...firstSwap.data, ...later.data]).toEqual(swaps.data); + const firstClaim = await get(`${base}/claims?limit=1`); + const laterClaims = await get(`${base}/claims?after_event_id=${firstClaim.next_cursor}`); + expect([...firstClaim.data, ...laterClaims.data]).toEqual(claims.data); + for (const row of [...swaps.data, ...claims.data]) { + const [block] = await db.sql`SELECT block_hash, block_time FROM blocks WHERE chain_id=${CHAIN_ID} AND block_number=${row.event.block_number}`; + expect(row.event.block_hash).toBe(`0x${block!.block_hash.toString(16).padStart(64, "0")}`); + expect(row.event.block_time).toBe(Math.floor(block!.block_time.getTime() / 1000)); + } + if (process.env.LAUNCH_GOLDEN_DIR) { + await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ list, second, lowWeth, canonicalList, detail, swaps, claims }, null, 2)); + } + const before = await get(base); + await replay(db, canonical, processors); + await apiHead(db); + expect(await get(base)).toEqual(before); + await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; + expect(await get(base)).toEqual(before); + await db.sql`UPDATE zero_seed_launch_states SET claimed0 = accrued0 + 1 WHERE pool_key_id = (SELECT pool_key_id FROM pool_keys WHERE chain_id=${CHAIN_ID} AND pool_id=${BigInt(fixture.launches.l1!.poolId).toString()}::numeric)`; + await get(base, 500); + await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; + expect(await get(base)).toEqual(before); + await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes' WHERE chain_id=${CHAIN_ID}`; + await get(base, 503); + await apiHead(db); + await db.sql`UPDATE indexer_cursor SET head_block_hash = 0 WHERE chain_id=${CHAIN_ID}`; + await get(base, 503); + await apiHead(db); + const writerSql = postgres(urlFor(db.name), { max: 1, onnotice: () => {}, types: { bigint: postgres.BigInt, numeric: NumericIntegerType } }) as unknown as Db["sql"]; + const writer = { sql: writerSql, dao: Reflect.construct(DAO, [writerSql, CHAIN_ID]) as DAO, name: db.name }; + try { + await Reflect.apply(Reflect.get(reader, "snapshot"), reader, [CHAIN_ID, async (tx: any, asOf: any) => { + const [old] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; + await invalidateFrom(writer.dao, 9); + await apiHead(writer); + const [same] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; + const [sameHead] = await tx`SELECT head_block_number FROM indexer_cursor WHERE chain_id=${CHAIN_ID}`; + expect(same.n).toBe(old.n); + expect(sameHead.head_block_number.toString()).toBe(asOf.block_number); + }]); + } finally { + await writerSql.end(); + } + await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`, 404); + expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + await invalidateFrom(db.dao, Number(before.created.block_number)); + await apiHead(db); + expect((await get(`/launches?chainId=${CHAIN_ID}`)).data).toEqual([]); + await get(base, 404); + expect((await db.sql`SELECT COUNT(*)::int AS n FROM zero_seed_launch_states`)[0]!.n).toBe(0); + } finally { + spies.forEach((s) => s.mockRestore()); + await db.sql.end(); + } + }, 300_000); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts new file mode 100644 index 0000000..631227a --- /dev/null +++ b/scripts/verifyLaunchReplay.ts @@ -0,0 +1,69 @@ +// Reuse the exact reviewed indexer replay harness without editing its checkout. +// No RPC/provider call or production database is involved. Generated test files +// belong to the run's scratch directory and are evidence, not source overrides. +import { resolve, dirname } from "node:path"; +import { mkdir } from "node:fs/promises"; + +const root = process.env.ZERO_SEED_INDEXER_WORKTREE; +const scratch = + process.env.PAPERCLIP_RUN_SCRATCH_DIR ?? process.env.PAPERCLIP_SCRATCH_DIR; +const pg = process.env.TEST_PG_CONNECTION_STRING; +if (!root || !scratch || !pg) + throw new Error( + "Indexer worktree, run scratch and local Postgres are required", + ); +const url = new URL(pg); +if (!["127.0.0.1", "localhost"].includes(url.hostname)) + throw new Error("Only loopback test Postgres is permitted"); +const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); +if ( + head.stdout.toString().trim() !== "f6af3070036dda06c885a91351277044330c03fd" +) + throw new Error("Indexer source pin mismatch"); +const clean = Bun.spawnSync(["git", "-C", root, "diff", "--quiet", "HEAD", "--"]); +if (clean.exitCode !== 0) throw new Error("Pinned indexer tracked source is modified"); +const fixturePath = resolve( + root, + "tests/fixtures/zero-seed-launch/local-chain.json", +); +const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); +if ( + new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== + "41d416cebdc78845f022f504ed72c86789890f3009f37e2dccf62f2d0f9ca9d4" +) + throw new Error("Emitted fixture pin mismatch"); +const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); +let source = await Bun.file(original).text(); +source = source.replace(/from "([^"]+)"/g, (match, name: string) => { + if (name.startsWith("node:") || name === "bun:test") return match; + const target = name.startsWith(".") + ? resolve(dirname(original), name) + : Bun.resolveSync(name, root); + return `from ${JSON.stringify(target)}`; +}); +source = source.replace( + 'const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");', + `const ROOT = ${JSON.stringify(root)};`, +); +const api = resolve(import.meta.dir, "../src"); +const extra = await Bun.file( + resolve(import.meta.dir, "launchReplayCases.txt"), +).text(); +source = `import { spyOn } from "bun:test"; +import { LaunchQueries } from ${JSON.stringify(resolve(api, "routes/launches/queries"))}; +import { LOCAL_INSTANCES } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; +import { Queries } from ${JSON.stringify(resolve(api, "queries"))}; +import { app } from ${JSON.stringify(resolve(api, "router"))}; +${source}`; +const ending = source.lastIndexOf("});"); +if (ending < 0) throw new Error("Unrecognized pinned indexer harness"); +source = source.slice(0, ending) + extra + source.slice(ending); +await mkdir(scratch, { recursive: true }); +const target = resolve(scratch, "api-emitted-replay.test.ts"); +await Bun.write(target, source); +const run = Bun.spawn(["bun", "test", target, "--timeout", "300000"], { + stdout: "inherit", + stderr: "inherit", + env: process.env, +}); +process.exit(await run.exited); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 9f56621..6a0bb06 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -1,231 +1,115 @@ -import type { LaunchDetailRow, LaunchRow } from "../../queries"; import { ContractCase, stubQuery } from "../harness"; -import { OWNER } from "../fixtures"; +import { LOCAL_INSTANCES } from "../../routes/launches/policy"; -// Base, with the predicted (not deployed) launchpad addresses from EKU-822. -const SCHEDULED_LAUNCH = BigInt( - "0x5184f618B2d6cE625d9fDB9770E151a9B84EdB81", -).toString(); -const LAUNCH_ROUTER = BigInt( - "0x9dae609a75Ac80BB84448a14823199faC514aeDb", -).toString(); -const WETH_BASE = BigInt("0x4200000000000000000000000000000000000006"); -const LAUNCH_TOKEN = BigInt("0x7a1c0ffee0000000000000000000000000000001"); -const CORE = BigInt("0x00000000000014aa86c5d3c41765bb24e11bd701").toString(); -const POOL_ID = - "0x6f1b0c3e9a4d5f2b8c7e6d5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c"; -const TX_HASH = BigInt( - "0x8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d", -).toString(); - -const launchRow = (overrides: Partial = {}): LaunchRow => ({ - chain_id: 8453n, - pool_key_id: 42n, - core_address: CORE, - pool_id: BigInt(POOL_ID).toString(), - token0: WETH_BASE.toString(), - token1: LAUNCH_TOKEN.toString(), - fee: "0", - pool_tick_spacing: 1000, - pool_extension: SCHEDULED_LAUNCH, - token: LAUNCH_TOKEN.toString(), - token_name: "Launch Token", - token_symbol: "LAUNCH", - token_decimals: 18, - total_supply: "1000000000000000000000000000", - quote_token: WETH_BASE.toString(), - quote_token_name: "Wrapped Ether", - quote_token_symbol: "WETH", - quote_token_decimals: 18, - token_is_token1: true, - start_time: 1791331200n, - end_time: 1791417600n, - target_tick: -230000, - upper_tick: -160000, - tick_spacing: 1000, - initial_fee: "184467440737095516", - final_fee: "18446744073709551", - migration_tick_lower: -887000, - migration_tick_upper: 887000, - deployed: "250000000000000000000000000", - reserve0: "12000000000000000000", - reserve1: "750000000000000000000000000", - complete: false, - owner: LAUNCH_ROUTER, - creator: BigInt(OWNER).toString(), - launch_extension: SCHEDULED_LAUNCH, - created_block_number: 36000000n, - created_transaction_hash: TX_HASH, - created_time: new Date("2026-10-06T00:00:00Z"), - created_event_id: -9223217452617613312n, - last_advanced_event_id: -9223217010235916288n, - head_block_time: new Date("2026-10-07T12:00:00Z"), - status: "live", - ...overrides, -}); - -const detailRow = ( - overrides: Partial = {}, -): LaunchDetailRow => ({ - ...launchRow(), - state_sqrt_ratio: "340282366920938463463374607431768211456", - state_tick: 200000, - state_liquidity: "1000000000000000000", - advanced_block_number: 36000100n, - advanced_transaction_hash: TX_HASH, - advanced_time: new Date("2026-10-07T11:59:00Z"), - terminal_pool_id: null, - locked_liquidity: null, - creator_fees_claimed0: "0", - creator_fees_claimed1: "0", - ...overrides, -}); - -const launchPath = `/launches/8453/${POOL_ID}`; - -export const launchesCases: ContractCase[] = [ - { - operation: "/launches", - request: `/launches?chainId=8453&status=live&creator=${OWNER}&page=1&pageSize=2`, - stub: () => - stubQuery("listLaunches", { - rows: [ - launchRow(), - // created directly on the extension, chain head not indexed yet - launchRow({ - pool_key_id: 43n, - creator: null, - quote_token_name: null, - quote_token_symbol: null, - quote_token_decimals: null, - head_block_time: null, - status: null, - last_advanced_event_id: null, - }), - ], - totalCount: 3, - }), +const pin = LOCAL_INSTANCES[0]!; +const zero = `0x${"0".repeat(64)}`; +const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; +const summary = { + schema_version: "zero-seed-launch-api/v1" as const, + chain_id: "31337", + pool_id: zero, + pool_key: { + core_address: pin.core, + token0: `0x${"1".repeat(40)}`, + token1: pin.quote, + config: zero, + fee: "0" as const, + tick_spacing: 100, + extension: pin.address, }, - { - operation: "/launches/{chainId}/{poolId}", - request: launchPath, - stub: () => stubQuery("getLaunch", detailRow()), + token: { + address: `0x${"1".repeat(40)}`, + name: "Local", + symbol: "LOCAL", + decimals: 18, }, - { - name: `${launchPath} (migrated)`, - operation: "/launches/{chainId}/{poolId}", - request: launchPath, - stub: () => - stubQuery( - "getLaunch", - detailRow({ - complete: true, - status: "migrated", - terminal_pool_id: BigInt(POOL_ID).toString(), - locked_liquidity: "123456789", - creator_fees_claimed0: "5", - creator_fees_claimed1: "6", - }), - ), + quote_token: { + address: pin.quote, + name: "Wrapped Ether", + symbol: "WETH", + decimals: 18, }, - { - name: `${launchPath} (before the first advance)`, - operation: "/launches/{chainId}/{poolId}", - request: launchPath, - stub: () => - stubQuery( - "getLaunch", - detailRow({ - status: "upcoming", - advanced_block_number: null, - advanced_transaction_hash: null, - advanced_time: null, - last_advanced_event_id: null, - }), - ), + launch_token_is_token1: false, + creator: `0x${"2".repeat(40)}`, + ask_tick: 0, + upper_tick: 100, + fee_schedule: { + trading_start: 1000, + fee_duration: 3600, + fee_end: 4600, + initial_fee: "10", + final_fee: "3", + phase: "fee_decay" as const, + fee_rate: "10", + evaluated_at: 1000, + }, + created: { + ...as_of, + event_id: "-1", + transaction_index: 0, + event_index: 0, + transaction_hash: zero, }, + as_of, +}; +const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; +const identity = { + schema_version: "zero-seed-launch-api/v1" as const, + chain_id: "31337", + pool_id: zero, + as_of, + next_cursor: null, +}; +const path = `/launches/31337/${zero}`; +export const launchesCases: ContractCase[] = [ { - operation: "/launches/{chainId}/{poolId}/stats", - request: `${launchPath}/stats`, + operation: "/launches", + request: "/launches?chainId=31337", stub: () => - stubQuery("getLaunchStats", { - swap_count: 3, - buy_count: 2, - sell_count: 1, - buy_quote_in: "2000000000000000000", - buy_token_out: "40000000000000000000000000", - sell_token_in: "1000000000000000000000000", - sell_quote_out: "40000000000000000", - distinct_lockers: 1, - distinct_transactions: 3, - fees_accrued_token: "100", - fees_accrued_quote: "200", - fees_claimed_token: "0", - fees_claimed_quote: "150", - fee_claim_count: 1, - funding_token: "0", - funding_quote: "0", - funding_count: 0, - migrated_principal_token: "0", - migrated_principal_quote: "0", + stubQuery("readZeroSeedList", { + schema_version: "zero-seed-launch-api/v1", + data: [summary], + as_of, + next_cursor: null, }), }, { - operation: "/launches/{chainId}/{poolId}/swaps", - request: `${launchPath}/swaps?limit=1&cursor=-9223217452617613312`, + operation: "/launches/{chainId}/{poolId}", + request: path, stub: () => - stubQuery("listLaunchSwaps", [ - { - event_id: -9223217010235916287n, - block_number: 36000100n, - transaction_index: 4, - event_index: 7, - transaction_hash: TX_HASH, - block_time: new Date("2026-10-07T11:59:00Z"), - locker: BigInt( - "0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748", - ).toString(), - delta0: "1000000000000000000", - delta1: "-20000000000000000000000000", - fee_amount: "100", - fee_is_token1: true, + stubQuery("readZeroSeedDetail", { + ...summary, + principal: { + position_id: zero, + tick_lower: 0, + tick_upper: 100, + liquidity: "100", + installed_supply: "1000", + locked: true, + withdrawable: false, + pool_reserves: { launch_token: "1000", quote_token: "0" }, + reserves_basis: "core_pool_balance", }, - { - event_id: -9223217010235916280n, - block_number: 36000101n, - transaction_index: 0, - event_index: 2, - transaction_hash: TX_HASH, - block_time: new Date("2026-10-07T11:59:02Z"), - locker: BigInt( - "0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748", - ).toString(), - delta0: "-40000000000000000", - delta1: "1000000000000000000000000", - fee_amount: "200", - fee_is_token1: false, + fee_ledger: { + holder: summary.creator, + transferable: false, + launch_token: ledger, + quote_token: ledger, + swap_count: 0, + claim_count: 0, + last_event_id: "-1", }, - ]), + inventory: { trading_open: true, no_bid: true, price_tick: 0 }, + }), }, { - name: `${launchPath}/swaps (launch with no swaps)`, operation: "/launches/{chainId}/{poolId}/swaps", - request: `${launchPath}/swaps`, - stub: () => - stubQuery("listLaunchSwaps", [ - { - event_id: null, - block_number: null, - transaction_index: null, - event_index: null, - transaction_hash: null, - block_time: null, - locker: null, - delta0: null, - delta1: null, - fee_amount: null, - fee_is_token1: null, - }, - ]), + request: `${path}/swaps`, + stub: () => stubQuery("readZeroSeedSwaps", { ...identity, data: [] }), + }, + { + operation: "/launches/{chainId}/{poolId}/claims", + request: `${path}/claims`, + stub: () => stubQuery("readZeroSeedClaims", { ...identity, data: [] }), }, ]; diff --git a/src/launchQueries.db.test.ts b/src/launchQueries.db.test.ts deleted file mode 100644 index 9924619..0000000 --- a/src/launchQueries.db.test.ts +++ /dev/null @@ -1,456 +0,0 @@ -// Runs the launch queries against a real indexer schema seeded with a fixture -// launch. It needs a throwaway Postgres with every indexer migration applied -// (00134_scheduled_launch_pool_states or later), so it is skipped unless -// LAUNCH_QUERIES_PG points at one: -// -// cd indexer && PG_CONNECTION_STRING=$URL bun scripts/migrate.ts -// LAUNCH_QUERIES_PG=$URL bun test src/launchQueries.db.test.ts -// -// Every test runs inside a transaction that is rolled back. -import { describe, expect, test } from "bun:test"; -import postgres, { type Sql } from "postgres"; -import { Queries } from "./queries"; - -const URL = process.env.LAUNCH_QUERIES_PG; - -const CHAIN = 8453n; -const SL = BigInt("0x5184f618B2d6cE625d9fDB9770E151a9B84EdB81"); -const LLL = BigInt("0x4B4e88581110396a09E3Bc313199b96Ab1D8ADEA"); -const ROUTER = BigInt("0x9dae609a75Ac80BB84448a14823199faC514aeDb"); -const TWAMM = BigInt("0x00000000000000000000000000000000000077aa"); -const CORE = BigInt("0x00000000000014aa86c5d3c41765bb24e11bd701"); -const WETH = BigInt("0x4200000000000000000000000000000000000006"); -const TOKEN = BigInt("0x7a1c0ffee0000000000000000000000000000001"); -const CREATOR = BigInt("0x00000000000000000000000000000000000000cc"); -const YUL_ROUTER = BigInt("0x7B2aA7Ecc0B5936b7C52E6259A19C3BA557d0748"); -const OTHER_ROUTER = BigInt("0x00000000000000000000000000000000000055aa"); -const POOL_ID = 0x6f1bn; -const TERMINAL_POOL_ID = 0x7e2cn; -const OTHER_POOL_ID = 0x8f3dn; -const T0 = 1791331200; -const DAY = 86400; -const E24 = 10n ** 24n; -const E18 = 10n ** 18n; - -class Rollback extends Error {} - -async function withFixture(run: (q: Queries, tx: Sql) => Promise) { - const sql = postgres(URL!, { - max: 1, - types: { bigint: postgres.BigInt }, - onnotice: () => {}, - }); - try { - await sql.begin(async (tx) => { - await seed(tx as unknown as Sql); - await run( - new Queries(tx as unknown as Sql<{ bigint: bigint }>), - tx as unknown as Sql, - ); - throw new Rollback(); - }); - } catch (error) { - if (!(error instanceof Rollback)) throw error; - } finally { - await sql.end(); - } -} - -const n = (value: bigint) => value.toString(); - -async function block(tx: Sql, number: number, time: number) { - await tx` - INSERT INTO blocks (chain_id, block_number, block_hash, block_time, num_events, fork_counter) - VALUES (${n(CHAIN)}, ${number}, ${number * 7919}, to_timestamp(${time}), 1, 0) - `; -} - -async function setHead(tx: Sql, time: number) { - await tx` - INSERT INTO indexer_cursor (chain_id, order_key, last_updated, head_block_number, head_block_hash, head_block_time) - VALUES (${n(CHAIN)}, 200, now(), 200, 1, to_timestamp(${time})) - ON CONFLICT (chain_id) DO UPDATE SET head_block_time = EXCLUDED.head_block_time - `; -} - -const ev = (blockNumber: number, eventIndex = 0, txIndex = 0) => ({ - chain_id: n(CHAIN), - block_number: blockNumber, - transaction_index: txIndex, - event_index: eventIndex, - transaction_hash: n(BigInt(blockNumber * 1000 + txIndex)), -}); - -async function poolKey(tx: Sql, poolId: bigint, extension: bigint) { - const [{ pool_key_id }] = await tx<{ pool_key_id: bigint }[]>` - INSERT INTO pool_keys (chain_id, core_address, pool_id, token0, token1, fee, fee_denominator, tick_spacing, pool_extension) - VALUES (${n(CHAIN)}, ${n(CORE)}, ${n(poolId)}, ${n(WETH)}, ${n(TOKEN)}, 0, ${n(1n << 64n)}, 1000, ${n(extension)}) - RETURNING pool_key_id - `; - return pool_key_id; -} - -async function created( - tx: Sql, - poolKeyId: bigint, - poolId: bigint, - blockNumber: number, - start: number, -) { - await tx` - INSERT INTO scheduled_launch_created ${tx({ - ...ev(blockNumber), - emitter: n(SL), - pool_key_id: n(poolKeyId), - pool_id: n(poolId), - token: n(TOKEN), - owner: n(ROUTER), - quote_token: n(WETH), - name: "Launch Token", - symbol: "LAUNCH", - decimals: 18, - total_supply: n(1000n * E24), - start_time: start, - end_time: start + DAY, - target_tick: -230000, - upper_tick: -160000, - tick_spacing: 1000, - initial_fee: n(1n << 60n), - final_fee: n(1n << 56n), - migration_tick_lower: -887000, - migration_tick_upper: 887000, - })} - `; -} - -async function swap( - tx: Sql, - poolKeyId: bigint, - blockNumber: number, - locker: bigint, - delta0: bigint, - delta1: bigint, - fee: bigint, - feeIsToken1: boolean, -) { - await tx` - INSERT INTO scheduled_launch_swapped ${tx({ - ...ev(blockNumber, 2), - emitter: n(SL), - pool_key_id: n(poolKeyId), - pool_id: n(POOL_ID), - locker: n(locker), - delta0: n(delta0), - delta1: n(delta1), - fee_amount: n(fee), - fee_is_token1: feeIsToken1, - })} - `; -} - -async function advanced( - tx: Sql, - poolKeyId: bigint, - blockNumber: number, - complete: boolean, -) { - await tx` - INSERT INTO scheduled_launch_advanced ${tx({ - ...ev(blockNumber, 0), - emitter: n(SL), - pool_key_id: n(poolKeyId), - pool_id: n(POOL_ID), - deployed: n(250n * E24), - reserve0: n(2n * E18), - reserve1: n(750n * E24), - complete, - })} - `; -} - -let launchKey = 0n; - -// One routed launch on Base starting at T0 with two buys, one sell, a creator -// fee claim, a LAUNCH_FUND top-up and a migration; plus a second, later launch -// created directly on the extension. -async function seed(tx: Sql) { - for (const [number, offset] of [ - [100, -3600], - [101, 60], - [102, 120], - [103, 180], - [104, 240], - [105, 300], - [110, 7200], - ]) { - await block(tx, number, T0 + offset); - } - await setHead(tx, T0 - 10); - await tx` - INSERT INTO erc20_tokens (chain_id, token_address, token_symbol, token_name, token_decimals, visibility_priority, sort_order) - VALUES (${n(CHAIN)}, ${n(WETH)}, 'WETH', 'Wrapped Ether', 18, 0, 0) - ON CONFLICT DO NOTHING - `; - - launchKey = await poolKey(tx, POOL_ID, SL); - const terminalKey = await poolKey(tx, TERMINAL_POOL_ID, TWAMM); - const otherKey = await poolKey(tx, OTHER_POOL_ID, SL); - await tx` - INSERT INTO pool_states (pool_key_id, sqrt_ratio, tick, liquidity, last_event_id) - VALUES (${n(launchKey)}, ${n(1n << 128n)}, 0, 5000, 1) - `; - - await created(tx, launchKey, POOL_ID, 100, T0); - await tx` - INSERT INTO launch_created_by ${tx({ - ...ev(100, 1), - emitter: n(ROUTER), - pool_key_id: n(launchKey), - launch_id: n(POOL_ID), - creator: n(CREATOR), - })} - `; - await created(tx, otherKey, OTHER_POOL_ID, 110, T0 + 2 * DAY); - - // token1 is the launch token: a buy moves quote (token0) in and launch - // tokens out - await advanced(tx, launchKey, 101, false); - await swap(tx, launchKey, 101, YUL_ROUTER, E18, -20n * E24, 100n, true); - await swap(tx, launchKey, 102, YUL_ROUTER, E18, -20n * E24, 100n, true); - await swap( - tx, - launchKey, - 103, - OTHER_ROUTER, - -4n * 10n ** 16n, - E24, - 200n, - false, - ); - - await tx` - INSERT INTO scheduled_launch_creator_fees_claimed ${tx({ - ...ev(104), - emitter: n(SL), - pool_key_id: n(launchKey), - pool_id: n(POOL_ID), - recipient: n(CREATOR), - amount0: "150", - amount1: "0", - })} - `; - await tx` - INSERT INTO launch_principal_received ${tx({ - ...ev(104, 1), - emitter: n(LLL), - pool_key_id: n(launchKey), - launch_id: n(POOL_ID), - from_address: n(ROUTER), - amount0: "5", - amount1: "0", - })} - `; - await tx` - INSERT INTO launch_principal_received ${tx({ - ...ev(105, 1), - emitter: n(LLL), - pool_key_id: n(launchKey), - launch_id: n(POOL_ID), - from_address: n(SL), - amount0: n(2n * E18), - amount1: n(700n * E24), - })} - `; - await tx` - INSERT INTO launch_liquidity_locked ${tx({ - ...ev(105, 2), - emitter: n(LLL), - pool_key_id: n(launchKey), - launch_id: n(POOL_ID), - terminal_pool_key_id: n(terminalKey), - terminal_pool_id: n(TERMINAL_POOL_ID), - liquidity: "777", - })} - `; -} - -async function statuses(q: Queries) { - const { rows } = await q.listLaunches({ - chainId: CHAIN, - status: null, - creator: null, - page: 1, - pageSize: 10, - }); - return Object.fromEntries(rows.map((r) => [r.pool_id, r.status])); -} - -describe.skipIf(!URL)("launch queries against the indexer schema", () => { - test("status follows the indexed head block time", async () => { - await withFixture(async (q, tx) => { - expect(await statuses(q)).toEqual({ - [n(POOL_ID)]: "upcoming", - [n(OTHER_POOL_ID)]: "upcoming", - }); - await setHead(tx, T0); - expect((await statuses(q))[n(POOL_ID)]).toBe("live"); - await setHead(tx, T0 + DAY - 1); - expect((await statuses(q))[n(POOL_ID)]).toBe("live"); - await setHead(tx, T0 + DAY); - expect((await statuses(q))[n(POOL_ID)]).toBe("ended"); - await advanced(tx, launchKey, 105, true); - expect((await statuses(q))[n(POOL_ID)]).toBe("migrated"); - // a migrated launch stays migrated whatever the head says - await setHead(tx, T0 - 10); - expect((await statuses(q))[n(POOL_ID)]).toBe("migrated"); - }); - }); - - test("list filters and pagination", async () => { - await withFixture(async (q, tx) => { - await setHead(tx, T0 + 100); - const all = await q.listLaunches({ - chainId: CHAIN, - status: null, - creator: null, - page: 1, - pageSize: 1, - }); - expect(all.totalCount).toBe(2); - // newest first - expect(all.rows.map((r) => r.pool_id)).toEqual([n(OTHER_POOL_ID)]); - - const live = await q.listLaunches({ - chainId: null, - status: "live", - creator: null, - page: 1, - pageSize: 10, - }); - expect(live.totalCount).toBe(1); - const [row] = live.rows; - expect(row.pool_id).toBe(n(POOL_ID)); - expect(row.creator).toBe(n(CREATOR)); - expect(row.owner).toBe(n(ROUTER)); - expect(row.token_symbol).toBe("LAUNCH"); - expect(row.quote_token_symbol).toBe("WETH"); - expect(row.token_is_token1).toBe(true); - expect(row.start_time).toBe(BigInt(T0)); - expect(row.deployed).toBe(n(250n * E24)); - expect(row.created_block_number).toBe(100n); - expect(row.created_time.getTime()).toBe((T0 - 3600) * 1000); - - const byCreator = await q.listLaunches({ - chainId: CHAIN, - status: null, - creator: CREATOR, - page: 1, - pageSize: 10, - }); - expect(byCreator.rows.map((r) => r.pool_id)).toEqual([n(POOL_ID)]); - - const otherChain = await q.listLaunches({ - chainId: 1n, - status: null, - creator: null, - page: 1, - pageSize: 10, - }); - expect(otherChain).toEqual({ rows: expect.any(Array), totalCount: 0 }); - }); - }); - - test("detail", async () => { - await withFixture(async (q) => { - const row = await q.getLaunch(CHAIN, POOL_ID); - expect(row).not.toBeNull(); - expect(row!.state_sqrt_ratio).toBe(n(1n << 128n)); - expect(row!.state_liquidity).toBe("5000"); - expect(row!.advanced_block_number).toBe(101n); - expect(row!.terminal_pool_id).toBe(n(TERMINAL_POOL_ID)); - expect(row!.locked_liquidity).toBe("777"); - expect(row!.creator_fees_claimed0).toBe("150"); - expect(row!.creator_fees_claimed1).toBe("0"); - - const other = await q.getLaunch(CHAIN, OTHER_POOL_ID); - expect(other!.creator).toBeNull(); - expect(other!.advanced_block_number).toBeNull(); - expect(other!.terminal_pool_id).toBeNull(); - expect(other!.state_sqrt_ratio).toBeNull(); - - expect(await q.getLaunch(CHAIN, TERMINAL_POOL_ID)).toBeNull(); - expect(await q.getLaunch(1n, POOL_ID)).toBeNull(); - }); - }); - - test("stats", async () => { - await withFixture(async (q) => { - expect(await q.getLaunchStats(CHAIN, POOL_ID)).toEqual({ - swap_count: 3, - buy_count: 2, - sell_count: 1, - buy_quote_in: n(2n * E18), - buy_token_out: n(40n * E24), - sell_token_in: n(E24), - sell_quote_out: n(4n * 10n ** 16n), - distinct_lockers: 2, - distinct_transactions: 3, - fees_accrued_token: "200", - fees_accrued_quote: "200", - fees_claimed_token: "0", - fees_claimed_quote: "150", - fee_claim_count: 1, - funding_token: "0", - funding_quote: "5", - funding_count: 1, - migrated_principal_token: n(700n * E24), - migrated_principal_quote: n(2n * E18), - }); - const empty = await q.getLaunchStats(CHAIN, OTHER_POOL_ID); - expect(empty!.swap_count).toBe(0); - expect(empty!.buy_quote_in).toBe("0"); - expect(empty!.fee_claim_count).toBe(0); - expect(await q.getLaunchStats(CHAIN, TERMINAL_POOL_ID)).toBeNull(); - }); - }); - - test("swaps page by event id", async () => { - await withFixture(async (q) => { - const first = await q.listLaunchSwaps({ - chainId: CHAIN, - poolId: POOL_ID, - cursor: null, - limit: 2, - }); - expect(first.map((r) => r.block_number)).toEqual([101n, 102n]); - expect(first[0].delta1).toBe(n(-20n * E24)); - expect(first[0].block_time!.getTime()).toBe((T0 + 60) * 1000); - - const rest = await q.listLaunchSwaps({ - chainId: CHAIN, - poolId: POOL_ID, - cursor: first[1].event_id, - limit: 2, - }); - expect(rest.map((r) => r.locker)).toEqual([n(OTHER_ROUTER)]); - - const none = await q.listLaunchSwaps({ - chainId: CHAIN, - poolId: OTHER_POOL_ID, - cursor: null, - limit: 2, - }); - expect(none).toHaveLength(1); - expect(none[0].event_id).toBeNull(); - - expect( - await q.listLaunchSwaps({ - chainId: CHAIN, - poolId: TERMINAL_POOL_ID, - cursor: null, - limit: 2, - }), - ).toHaveLength(0); - }); - }); -}); diff --git a/src/queries.ts b/src/queries.ts index e430226..303a94c 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -1,5 +1,9 @@ import postgres, { type Sql } from "postgres"; import { Env } from "./env"; +import { + LaunchQueries, + type LaunchReadOptions, +} from "./routes/launches/queries"; export type StateFilter = "opened" | "closed"; @@ -197,6 +201,22 @@ export class Queries { this.sql = sql; } + public readZeroSeedList(input: LaunchReadOptions & { creator?: bigint }) { + return new LaunchQueries(this.sql).list(input); + } + + public readZeroSeedDetail(chainId: bigint, poolId: bigint) { + return new LaunchQueries(this.sql).detail(chainId, poolId); + } + + public readZeroSeedSwaps(input: LaunchReadOptions & { poolId: bigint }) { + return new LaunchQueries(this.sql).swaps(input); + } + + public readZeroSeedClaims(input: LaunchReadOptions & { poolId: bigint }) { + return new LaunchQueries(this.sql).claims(input); + } + public async listErc20Tokens({ minVisibilityPriority, pageSize, @@ -3081,8 +3101,7 @@ pt.last_transfer_event_id DESC NULLS LAST }; type NullableVe33PoolRow = { [K in keyof Omit]: - | Ve33PoolRow[K] - | null; + Ve33PoolRow[K] | null; } & Pick; const rows = await this.sql` @@ -4268,360 +4287,6 @@ ORDER BY po.token_id DESC totalCount, }; } - - // One row per launch from the indexer's scheduled_launch_pool_states (00134), - // with its LaunchCreated event, LaunchRouter creator and the chain's indexed - // head. Status is derived from the head block time, never the wall clock. - private launchRows(condition: postgres.Fragment) { - return this.sql` - SELECT pk.chain_id, - slps.pool_key_id, - pk.core_address, - pk.pool_id, - pk.token0, - pk.token1, - pk.fee, - pk.tick_spacing AS pool_tick_spacing, - pk.pool_extension, - slps.token, - c.name AS token_name, - c.symbol AS token_symbol, - c.decimals AS token_decimals, - slps.total_supply, - slps.quote_token, - qt.token_name AS quote_token_name, - qt.token_symbol AS quote_token_symbol, - qt.token_decimals AS quote_token_decimals, - slps.token_is_token1, - slps.start_time, - slps.end_time, - slps.target_tick, - slps.upper_tick, - slps.tick_spacing, - slps.initial_fee, - slps.final_fee, - slps.migration_tick_lower, - slps.migration_tick_upper, - slps.deployed, - slps.reserve0, - slps.reserve1, - slps.complete, - slps.owner, - lcb.creator, - c.emitter AS launch_extension, - c.block_number AS created_block_number, - c.transaction_hash AS created_transaction_hash, - b.block_time AS created_time, - slps.created_event_id, - slps.last_advanced_event_id, - ic.head_block_time, - CASE - WHEN slps.complete THEN 'migrated' - WHEN ic.head_block_time IS NULL THEN NULL - WHEN EXTRACT(EPOCH FROM ic.head_block_time) < slps.start_time THEN 'upcoming' - WHEN EXTRACT(EPOCH FROM ic.head_block_time) < slps.end_time THEN 'live' - ELSE 'ended' - END AS status - -- The LATERAL subqueries are point lookups by primary key per launch. - -- As plain joins the planner may instead merge against the chain's - -- whole blocks index when its row estimates are off. - FROM scheduled_launch_pool_states slps - JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id - JOIN LATERAL (SELECT * - FROM scheduled_launch_created sc - WHERE sc.chain_id = pk.chain_id - AND sc.event_id = slps.created_event_id - LIMIT 1) c ON TRUE - JOIN LATERAL (SELECT bl.block_time - FROM blocks bl - WHERE bl.chain_id = c.chain_id - AND bl.block_number = c.block_number - LIMIT 1) b ON TRUE - LEFT JOIN LATERAL (SELECT lc.creator - FROM launch_created_by lc - WHERE lc.pool_key_id = slps.pool_key_id - ORDER BY lc.event_id DESC - LIMIT 1) lcb ON TRUE - LEFT JOIN erc20_tokens qt - ON qt.chain_id = pk.chain_id AND qt.token_address = slps.quote_token - LEFT JOIN indexer_cursor ic ON ic.chain_id = pk.chain_id - WHERE ${condition} - `; - } - - public async listLaunches({ - chainId, - status, - creator, - page, - pageSize, - }: { - chainId: bigint | null; - status: LaunchStatus | null; - creator: bigint | null; - page: number; - pageSize: number; - }) { - const chainCondition = - chainId === null ? this.sql`TRUE` : this.sql`pk.chain_id = ${chainId}`; - const creatorCondition = - creator === null - ? this.sql`TRUE` - : this.sql`lcb.creator = ${creator.toString()}`; - const statusCondition = - status === null ? this.sql`TRUE` : this.sql`l.status = ${status}`; - const base = this.launchRows( - this.sql`${chainCondition} AND ${creatorCondition}`, - ); - - const [rows, [{ total }]] = await Promise.all([ - this.sql` - SELECT l.* - FROM (${base}) l - WHERE ${statusCondition} - ORDER BY l.created_time DESC, l.chain_id, l.pool_key_id - LIMIT ${pageSize} OFFSET ${(page - 1) * pageSize} - `, - this.sql<{ total: number }[]>` - SELECT COUNT(*)::int AS total - FROM (${base}) l - WHERE ${statusCondition} - `, - ]); - return { rows, totalCount: total }; - } - - public async getLaunch(chainId: bigint, poolId: bigint) { - const base = this.launchRows( - this.sql`pk.chain_id = ${chainId} AND pk.pool_id = ${poolId.toString()}`, - ); - const rows = await this.sql` - SELECT l.*, - ps.sqrt_ratio AS state_sqrt_ratio, - ps.tick AS state_tick, - ps.liquidity AS state_liquidity, - a.block_number AS advanced_block_number, - a.transaction_hash AS advanced_transaction_hash, - ab.block_time AS advanced_time, - t.terminal_pool_id, - t.locked_liquidity, - COALESCE(fc.amount0, 0)::text AS creator_fees_claimed0, - COALESCE(fc.amount1, 0)::text AS creator_fees_claimed1 - FROM (${base}) l - LEFT JOIN pool_states ps ON ps.pool_key_id = l.pool_key_id - LEFT JOIN scheduled_launch_advanced a - ON a.chain_id = l.chain_id AND a.event_id = l.last_advanced_event_id - LEFT JOIN blocks ab ON ab.chain_id = a.chain_id AND ab.block_number = a.block_number - LEFT JOIN LATERAL (SELECT stp.terminal_pool_id, stp.locked_liquidity - FROM scheduled_launch_terminal_pools stp - WHERE stp.pool_key_id = l.pool_key_id - ORDER BY stp.last_locked_event_id DESC - LIMIT 1) t ON TRUE - LEFT JOIN LATERAL (SELECT SUM(cfc.amount0) AS amount0, SUM(cfc.amount1) AS amount1 - FROM scheduled_launch_creator_fees_claimed cfc - WHERE cfc.pool_key_id = l.pool_key_id) fc ON TRUE - `; - return rows[0] ?? null; - } - - public async getLaunchStats(chainId: bigint, poolId: bigint) { - const rows = await this.sql` - WITH l AS (SELECT slps.pool_key_id, slps.token_is_token1, c.emitter AS launch_extension - FROM scheduled_launch_pool_states slps - JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id - JOIN scheduled_launch_created c - ON c.chain_id = pk.chain_id AND c.event_id = slps.created_event_id - WHERE pk.chain_id = ${chainId} - AND pk.pool_id = ${poolId.toString()}), - swaps AS (SELECT CASE WHEN l.token_is_token1 THEN s.delta1 ELSE s.delta0 END AS token_delta, - CASE WHEN l.token_is_token1 THEN s.delta0 ELSE s.delta1 END AS quote_delta, - s.fee_amount, - s.fee_is_token1 = l.token_is_token1 AS fee_in_token, - s.locker, - s.transaction_hash - FROM l - JOIN scheduled_launch_swapped s ON s.pool_key_id = l.pool_key_id), - claimed AS (SELECT COALESCE(SUM(CASE WHEN l.token_is_token1 THEN f.amount1 ELSE f.amount0 END), 0) AS token, - COALESCE(SUM(CASE WHEN l.token_is_token1 THEN f.amount0 ELSE f.amount1 END), 0) AS quote, - COUNT(f.*)::int AS claims - FROM l - LEFT JOIN scheduled_launch_creator_fees_claimed f ON f.pool_key_id = l.pool_key_id), - funding AS (SELECT p.from_address = l.launch_extension AS migrated, - CASE WHEN l.token_is_token1 THEN p.amount1 ELSE p.amount0 END AS token, - CASE WHEN l.token_is_token1 THEN p.amount0 ELSE p.amount1 END AS quote - FROM l - JOIN launch_principal_received p ON p.pool_key_id = l.pool_key_id) - SELECT sa.*, - claimed.token::text AS fees_claimed_token, - claimed.quote::text AS fees_claimed_quote, - claimed.claims AS fee_claim_count, - fa.* - FROM l - CROSS JOIN claimed - CROSS JOIN (SELECT COUNT(*)::int AS swap_count, - COUNT(*) FILTER (WHERE token_delta < 0)::int AS buy_count, - COUNT(*) FILTER (WHERE token_delta > 0)::int AS sell_count, - COALESCE(SUM(quote_delta) FILTER (WHERE token_delta < 0), 0)::text AS buy_quote_in, - COALESCE(SUM(-token_delta) FILTER (WHERE token_delta < 0), 0)::text - AS buy_token_out, - COALESCE(SUM(token_delta) FILTER (WHERE token_delta > 0), 0)::text AS sell_token_in, - COALESCE(SUM(-quote_delta) FILTER (WHERE token_delta > 0), 0)::text - AS sell_quote_out, - COUNT(DISTINCT locker)::int AS distinct_lockers, - COUNT(DISTINCT transaction_hash)::int AS distinct_transactions, - COALESCE(SUM(fee_amount) FILTER (WHERE fee_in_token), 0)::text AS fees_accrued_token, - COALESCE(SUM(fee_amount) FILTER (WHERE NOT fee_in_token), 0)::text AS fees_accrued_quote - FROM swaps) sa - CROSS JOIN (SELECT COALESCE(SUM(token) FILTER (WHERE NOT migrated), 0)::text AS funding_token, - COALESCE(SUM(quote) FILTER (WHERE NOT migrated), 0)::text AS funding_quote, - COUNT(*) FILTER (WHERE NOT migrated)::int AS funding_count, - COALESCE(SUM(token) FILTER (WHERE migrated), 0)::text AS migrated_principal_token, - COALESCE(SUM(quote) FILTER (WHERE migrated), 0)::text AS migrated_principal_quote - FROM funding) fa - `; - return rows[0] ?? null; - } - - public async listLaunchSwaps({ - chainId, - poolId, - cursor, - limit, - }: { - chainId: bigint; - poolId: bigint; - cursor: bigint | null; - limit: number; - }) { - const cursorCondition = - cursor === null ? this.sql`TRUE` : this.sql`s.event_id > ${cursor}`; - // The launch row comes back even when it has no swaps (or none after the - // cursor), so the route can tell an unknown launch from an empty page. - return this.sql` - WITH l AS (SELECT slps.pool_key_id, pk.chain_id - FROM scheduled_launch_pool_states slps - JOIN pool_keys pk ON pk.pool_key_id = slps.pool_key_id - WHERE pk.chain_id = ${chainId} - AND pk.pool_id = ${poolId.toString()}) - SELECT s.event_id, - s.block_number, - s.transaction_index, - s.event_index, - s.transaction_hash, - b.block_time, - s.locker, - s.delta0, - s.delta1, - s.fee_amount, - s.fee_is_token1 - FROM l - LEFT JOIN LATERAL (SELECT * - FROM scheduled_launch_swapped s - WHERE s.pool_key_id = l.pool_key_id - AND ${cursorCondition} - ORDER BY s.event_id - LIMIT ${limit}) s ON TRUE - LEFT JOIN blocks b ON b.chain_id = s.chain_id AND b.block_number = s.block_number - ORDER BY s.event_id - `; - } -} - -export type LaunchStatus = "upcoming" | "live" | "ended" | "migrated"; - -export interface LaunchRow { - chain_id: bigint; - pool_key_id: bigint; - core_address: string; - pool_id: string; - token0: string; - token1: string; - fee: string; - pool_tick_spacing: number | null; - pool_extension: string; - token: string; - token_name: string; - token_symbol: string; - token_decimals: number; - total_supply: string; - quote_token: string; - quote_token_name: string | null; - quote_token_symbol: string | null; - quote_token_decimals: number | null; - token_is_token1: boolean; - start_time: bigint; - end_time: bigint; - target_tick: number; - upper_tick: number; - tick_spacing: number; - initial_fee: string; - final_fee: string; - migration_tick_lower: number; - migration_tick_upper: number; - deployed: string; - reserve0: string; - reserve1: string; - complete: boolean; - owner: string; - creator: string | null; - launch_extension: string; - created_block_number: bigint; - created_transaction_hash: string; - created_time: Date; - created_event_id: bigint; - last_advanced_event_id: bigint | null; - head_block_time: Date | null; - status: LaunchStatus | null; -} - -export interface LaunchDetailRow extends LaunchRow { - state_sqrt_ratio: string | null; - state_tick: number | null; - state_liquidity: string | null; - advanced_block_number: bigint | null; - advanced_transaction_hash: string | null; - advanced_time: Date | null; - terminal_pool_id: string | null; - locked_liquidity: string | null; - creator_fees_claimed0: string; - creator_fees_claimed1: string; -} - -export interface LaunchStatsRow { - swap_count: number; - buy_count: number; - sell_count: number; - buy_quote_in: string; - buy_token_out: string; - sell_token_in: string; - sell_quote_out: string; - distinct_lockers: number; - distinct_transactions: number; - fees_accrued_token: string; - fees_accrued_quote: string; - fees_claimed_token: string; - fees_claimed_quote: string; - fee_claim_count: number; - funding_token: string; - funding_quote: string; - funding_count: number; - migrated_principal_token: string; - migrated_principal_quote: string; -} - -// A launch with no swaps on the page yields one row with every swap column null. -export interface LaunchSwapRow { - event_id: bigint | null; - block_number: bigint | null; - transaction_index: number | null; - event_index: number | null; - transaction_hash: string | null; - block_time: Date | null; - locker: string | null; - delta0: string | null; - delta1: string | null; - fee_amount: string | null; - fee_is_token1: boolean | null; } export async function createQueries(env: Env) { diff --git a/src/router.ts b/src/router.ts index baff676..efa0360 100644 --- a/src/router.ts +++ b/src/router.ts @@ -67,7 +67,7 @@ import { } from "./routes/auctions"; import { GetLaunch, - GetLaunchStats, + ListLaunchClaims, ListLaunches, ListLaunchSwaps, } from "./routes/launches"; @@ -130,7 +130,7 @@ const routes: readonly ApiRoute[] = [ GetAuctionNftState, GetAuctionNftImage, ListLaunches, - GetLaunchStats, + ListLaunchClaims, ListLaunchSwaps, GetLaunch, ListProposals, diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md new file mode 100644 index 0000000..56603da --- /dev/null +++ b/src/routes/launches/README.md @@ -0,0 +1,89 @@ +# Zero-seed launch reads (draft/local) + +Frozen response contract: `zero-seed-launch-api/v1`. Behavioral policy: +`zero-seed-launch-draft-policy/v2`, CTO freeze revision +`ceebe872-8deb-4858-a192-8c013516def1`. Indexer source: +`f6af3070036dda06c885a91351277044330c03fd`. Live instance lists are empty. + +## Requests + +- `GET /launches?chainId=31337&limit=50&before_event_id=...&creator=0x...` +- `GET /launches/{chainId}/{poolId}` +- `GET /launches/{chainId}/{poolId}/swaps?limit=50&after_event_id=...` +- `GET /launches/{chainId}/{poolId}/claims?limit=50&after_event_id=...` + +One chain per list request gives an unambiguous indexed `as_of`. Pool IDs are +32-byte hex. Cursors are signed decimal event IDs: newest-first creations use +`before_event_id`; oldest-first swaps/claims use `after_event_id`. Limits are +1–200. A non-null `next_cursor` means another row was observed in that snapshot. +Cursor continuation is not a frozen historical snapshot: reorgs may change pages. + +No stats, lifecycle status, staged funding, advances, terminal migration, user +counts or connected-wallet analytics exist. Forwarding lockers are contracts, +not traders. Fee phases are schedule labels, not completion/exhaustion states. + +## Supported instances and consistency + +All hosted query delegates construct a reader with **no enabled instances**. +They refuse launch reads with 404 without querying Postgres; missing database +configuration also returns 404. There is no environment-binding enable switch. +Only the explicit local test harness can pass the full reviewed instance +manifest. Constructor quote, Core, executed runtime, WETH runtime and ABI must +match together; synthetic 6-decimal fixtures and template runtimes are refused. + +List SQL screens the extension, quote, Core, ordered pair, zero Core fee and +spacing before pagination. Detail and both histories use that same selection. +Missing/unsupported launches return 404. A missing, mismatched or stale cursor +head returns 503. Parameters return 400; malformed internal indexed output +fails as 500, never a success or client-validation fallback. + +Each read uses one read-only repeatable-read transaction for the coherent head, +configuration, current Core balance, creator ledger and event page. Responses +are `no-store`. Local test freshness is 60 seconds from cursor `last_updated`; +this is not an approved live/hosted parameter. Fee rates and phases depend only +on the indexed block timestamp. HTTP does not accept a wall-clock fee override. + +Principal is the immutable installed supply/L pair and Core event-accounted pool +reserves (including rounding dust). Creator accrued/claimed/unclaimed ledgers +are separate, per asset. Upper-bound leftover token residue and Core unowned +token/quote dust are different quantities; no shared <=90 bound or fabricated +claimable-funds estimate is exposed. Both assets are claimed atomically in full. + +Requested supply and build/expiry times are not all emitted onchain. The API +must not fabricate them. Producer/owner surfaces separately bind a single +preview's requested→actual supply/L, 31 UTF-8 byte metadata, all calldata and +exact sink/start/expiry constraints. This read API does not close L-3 end-to-end. + +## Local verification and stop/reset + +Run `bun test src/routes/launches src/responseSchemas.test.ts +src/openapiSnapshot.test.ts`, `bun run lint`, and `bunx tsc --noEmit`. + +For actual emitted-state/Postgres/handler replay, provide the exact pinned +indexer checkout, a disposable loopback Postgres 18, and the run scratch path: + +```sh +ZERO_SEED_INDEXER_WORKTREE=/path/to/pinned/indexer \ +TEST_PG_CONNECTION_STRING=postgres://fixture:fixture@127.0.0.1:55453/postgres \ +PAPERCLIP_RUN_SCRATCH_DIR=/path/to/run-scratch \ +LAUNCH_GOLDEN_DIR=/path/to/run-scratch \ +bun scripts/verifyLaunchReplay.ts +``` + +The runner checks source/fixture pins, creates a scratch copy of the reviewed +indexer test harness, appends API tests, and executes actual stream/DAO/SQL +replay without editing the indexer checkout. Test-only query-method spies wire +the actual HTTP handlers to real local SQL with the reviewed local manifest; +they do not stub response rows. All temporary databases are dropped by the +harness. No RPC, fork URL, provider/secret read or production migration is used. + +Safe stop is launch support OFF. A pre-issuance source rollback to the PR base +without launch routes is safe; the old staged or arbitrary-quote drafts are +not. Reset only the dedicated disposable local test databases/container. +Application rollback cannot refund, withdraw, rebalance or migrate immutable +principal. No merge/deploy/live issuance or authority change is authorized. + +Live provenance/registration/state, production query performance/capacity/costs, +served artifacts, mined UserOps, full WETH metadata reproduction, downstream +whole-stack QA/Sage, external audit and legal/provider/wallet/board/release +controls remain NOT PROVEN/HOLD. diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts index 1e20e55..c53fd5d 100644 --- a/src/routes/launches/index.ts +++ b/src/routes/launches/index.ts @@ -1,440 +1,53 @@ import { createRoute, z } from "@hono/zod-openapi"; +import { createQueries } from "../../queries"; +import type { Env } from "../../env"; import { defineRoute } from "../../shared/context"; import { errorResponses, notFoundResponse, + ErrorResponseType, StatusError, } from "../../shared/errors"; -import { jsonResponse } from "../../shared/openapi"; -import { - AddressType, - ChainIdType, - DecimalStringType, - HexStringType, - NumericStringType, -} from "../../shared/validation/address"; -import { - createQueries, - LaunchDetailRow, - LaunchRow, - LaunchStatsRow, - LaunchSwapRow, -} from "../../queries"; -import toHex from "../../shared/toHex"; - -// Cache lifetimes. Launch state moves with every swap, so the detail and swap -// pages are short-lived; the list and the stats aggregate tolerate a minute. -export const LAUNCH_LIST_CACHE_CONTROL = "public, max-age=60, must-revalidate"; -export const LAUNCH_DETAIL_CACHE_CONTROL = - "public, max-age=10, must-revalidate"; -export const LAUNCH_STATS_CACHE_CONTROL = "public, max-age=60, must-revalidate"; -export const LAUNCH_SWAPS_CACHE_CONTROL = "public, max-age=10, must-revalidate"; - -const SignedDecimalStringType = z.string().regex(/^-?\d+$/); -const UnixSecondsType = z.number().int().describe("Unix time in seconds"); - -const LaunchStatusType = z - .enum(["upcoming", "live", "ended", "migrated"]) - .describe( - "Derived from the chain's indexed head block time, not the wall clock: upcoming before start_time, live in [start_time, end_time) while not complete, ended at or after end_time while not complete, migrated once complete", - ); - -const LaunchTokenType = z.object({ - address: HexStringType, - name: z.string(), - symbol: z.string(), - decimals: z.number().int(), - total_supply: DecimalStringType, -}); - -const QuoteTokenType = z.object({ - address: HexStringType, - name: z - .string() - .nullable() - .describe("Null when the token is not in the token list"), - symbol: z.string().nullable(), - decimals: z.number().int().nullable(), -}); - -const LaunchPoolKeyType = z.object({ - core_address: HexStringType, - token0: HexStringType, - token1: HexStringType, - fee: HexStringType, - tick_spacing: z.number().int().nullable(), - extension: HexStringType, -}); - -const LaunchSummaryShape = { - chain_id: DecimalStringType, - pool_key_id: DecimalStringType, - pool_id: HexStringType, - pool_key: LaunchPoolKeyType, - launch_token: LaunchTokenType, - quote_token: QuoteTokenType, - launch_token_is_token1: z.boolean(), - start_time: UnixSecondsType, - end_time: UnixSecondsType, - target_tick: z - .number() - .int() - .describe( - "Tick in raw quote units per launch token, independent of token order", - ), - upper_tick: z.number().int(), - tick_spacing: z.number().int(), - initial_fee: DecimalStringType.describe("0.64 fixed-point fee fraction"), - final_fee: DecimalStringType.describe("0.64 fixed-point fee fraction"), - migration_tick_lower: z.number().int(), - migration_tick_upper: z.number().int(), - deployed: DecimalStringType, - reserve0: DecimalStringType, - reserve1: DecimalStringType, - complete: z.boolean(), - status: LaunchStatusType.nullable().describe( - "Null only when the chain has no indexed head yet and the launch is not complete", - ), - status_as_of: UnixSecondsType.nullable().describe( - "The indexed head block time the status was derived from", - ), - owner: HexStringType.describe( - "Owner of record in LaunchCreated (LaunchRouter for routed launches)", - ), - creator: HexStringType.nullable().describe( - "LaunchRouter.LaunchCreatedBy creator, the account that may claim creator fees; null when the launch was not created through LaunchRouter", - ), - created_block_number: DecimalStringType, - created_transaction_hash: HexStringType, - created_time: UnixSecondsType, -}; - -const LaunchSummaryType = z.object(LaunchSummaryShape); - -const ListLaunchesResponseType = z.object({ - data: z.array(LaunchSummaryType), - pagination: z.object({ - page: z.number().int().min(1), - pageSize: z.number().int().min(1), - totalPages: z.number().int().min(0), - totalItems: z.number().int().min(0), - }), -}); - -const LaunchDetailType = z.object({ - ...LaunchSummaryShape, - pool_state: z - .object({ - sqrt_ratio: DecimalStringType, - tick: z.number().int(), - liquidity: DecimalStringType, - }) - .nullable() - .describe("Core pool state of the launch pool"), - latest_advance: z - .object({ - block_number: DecimalStringType, - transaction_hash: HexStringType, - time: UnixSecondsType, - }) - .nullable() - .describe( - "The LaunchAdvanced event the deployed/reserve/complete values come from; null before the first advance", - ), - terminal: z - .object({ - pool_id: HexStringType, - locked_liquidity: DecimalStringType, - }) - .nullable() - .describe("The pool the principal was locked in once migrated"), - creator_fees_claimed: z.object({ - amount0: DecimalStringType, - amount1: DecimalStringType, - }), -}); - -const TokenQuoteAmountsType = z.object({ - launch_token: DecimalStringType, - quote_token: DecimalStringType, -}); - -const LaunchStatsType = z.object({ - chain_id: DecimalStringType, - pool_id: HexStringType, - swaps: z.object({ - count: z.number().int(), - buy_count: z.number().int(), - sell_count: z.number().int(), - distinct_transactions: z.number().int(), - distinct_lockers: z - .number() - .int() - .describe( - "Distinct LaunchSwapped.locker addresses: the contract that forwarded the swap (usually a router), not the user", - ), - distinct_transaction_senders: z - .null() - .describe( - "Not available: the indexer stores no transaction sender, and the API does not resolve transactions to their senders", - ), - }), - volume: z.object({ - buy: z - .object({ - quote_in: DecimalStringType, - launch_token_out: DecimalStringType, - }) - .describe("Swaps that took launch tokens out of the pool"), - sell: z - .object({ - launch_token_in: DecimalStringType, - quote_out: DecimalStringType, - }) - .describe("Swaps that put launch tokens into the pool"), - }), - creator_fees: z.object({ - accrued: TokenQuoteAmountsType.describe( - "Sum of LaunchSwapped fees, saved for the launch's creator", - ), - claimed: TokenQuoteAmountsType, - claim_count: z.number().int(), - }), - funding: z.object({ - received: TokenQuoteAmountsType.describe( - "PrincipalReceived from lockers that forwarded LAUNCH_FUND", - ), - count: z.number().int(), - migrated_principal: TokenQuoteAmountsType.describe( - "PrincipalReceived from the launch extension at migration", - ), - }), - attribution: z - .object({ - counts: z.string(), - lockers: z.string(), - senders: z.string(), - }) - .describe("Where each count comes from"), -}); - -const LaunchSwapType = z.object({ - event_id: SignedDecimalStringType, - block_number: DecimalStringType, - transaction_index: z.number().int(), - event_index: z.number().int(), - transaction_hash: HexStringType, - time: UnixSecondsType, - locker: HexStringType.describe( - "The locker that forwarded the swap, usually a router contract", - ), - delta0: SignedDecimalStringType.describe( - "Fee-inclusive pool-perspective delta of token0", - ), - delta1: SignedDecimalStringType.describe( - "Fee-inclusive pool-perspective delta of token1", - ), - fee_amount: DecimalStringType, - fee_is_token1: z.boolean(), -}); +import { LaunchList, LaunchDetail, SwapPage, ClaimPage } from "./schema"; -const ListLaunchSwapsResponseType = z.object({ - chain_id: DecimalStringType, - pool_id: HexStringType, - swaps: z.array(LaunchSwapType), - next_cursor: SignedDecimalStringType.nullable(), - has_more: z.boolean(), -}); - -const LaunchParams = z.object({ - chainId: ChainIdType, - poolId: NumericStringType.describe("The launch pool's ID").openapi({ - example: "0x1234", - }), -}); - -const EventCursorType = z.coerce +const chainId = z.coerce .bigint() - .min(-(1n << 63n)) + .min(0n) .max((1n << 63n) - 1n) - .openapi({ type: "integer", format: "int64" }); - -const seconds = (value: bigint | Date) => - value instanceof Date ? Math.floor(value.getTime() / 1000) : Number(value); - -const hex = (value: string, width?: number) => toHex(BigInt(value), width); - -function formatQuoteToken(row: LaunchRow) { - return { - address: hex(row.quote_token, 20), - name: row.quote_token_name, - symbol: row.quote_token_symbol, - decimals: row.quote_token_decimals, - }; -} - -function formatLaunch(row: LaunchRow): z.infer { - return { - chain_id: row.chain_id.toString(), - pool_key_id: row.pool_key_id.toString(), - pool_id: hex(row.pool_id, 32), - pool_key: { - core_address: hex(row.core_address, 20), - token0: hex(row.token0, 20), - token1: hex(row.token1, 20), - fee: hex(row.fee), - tick_spacing: row.pool_tick_spacing, - extension: hex(row.pool_extension, 20), - }, - launch_token: { - address: hex(row.token, 20), - name: row.token_name, - symbol: row.token_symbol, - decimals: row.token_decimals, - total_supply: row.total_supply, - }, - quote_token: formatQuoteToken(row), - launch_token_is_token1: row.token_is_token1, - start_time: seconds(row.start_time), - end_time: seconds(row.end_time), - target_tick: row.target_tick, - upper_tick: row.upper_tick, - tick_spacing: row.tick_spacing, - initial_fee: row.initial_fee, - final_fee: row.final_fee, - migration_tick_lower: row.migration_tick_lower, - migration_tick_upper: row.migration_tick_upper, - deployed: row.deployed, - reserve0: row.reserve0, - reserve1: row.reserve1, - complete: row.complete, - status: row.status, - status_as_of: row.head_block_time ? seconds(row.head_block_time) : null, - owner: hex(row.owner, 20), - creator: row.creator === null ? null : hex(row.creator, 20), - created_block_number: row.created_block_number.toString(), - created_transaction_hash: hex(row.created_transaction_hash, 32), - created_time: seconds(row.created_time), - }; -} - -function formatPoolState(row: LaunchDetailRow) { - if (row.state_sqrt_ratio === null || row.state_liquidity === null) { - return null; - } - return { - sqrt_ratio: row.state_sqrt_ratio, - tick: Number(row.state_tick), - liquidity: row.state_liquidity, - }; -} - -function formatLatestAdvance(row: LaunchDetailRow) { - if (row.advanced_block_number === null) return null; - return { - block_number: row.advanced_block_number.toString(), - transaction_hash: hex(row.advanced_transaction_hash!, 32), - time: seconds(row.advanced_time!), - }; -} - -function formatTerminal(row: LaunchDetailRow) { - if (row.terminal_pool_id === null) return null; - return { - pool_id: hex(row.terminal_pool_id, 32), - locked_liquidity: row.locked_liquidity ?? "0", - }; -} - -function formatLaunchDetail( - row: LaunchDetailRow, -): z.infer { - return { - ...formatLaunch(row), - pool_state: formatPoolState(row), - latest_advance: formatLatestAdvance(row), - terminal: formatTerminal(row), - creator_fees_claimed: { - amount0: row.creator_fees_claimed0, - amount1: row.creator_fees_claimed1, - }, - }; -} - -const ATTRIBUTION = { - counts: - "Swap, buy, sell and transaction counts are LaunchSwapped events and their transaction hashes", - lockers: - "distinct_lockers counts LaunchSwapped.locker, the router contract that forwarded each swap", - senders: - "Transaction senders are not indexed and are not resolved by this API", + .openapi({ type: "string" }); +const poolId = z.string().regex(/^0x[0-9a-fA-F]{64}$/); +const cursor = z + .string() + .regex(/^(0|-?[1-9][0-9]*)$/) + .transform(BigInt) + .pipe( + z + .bigint() + .min(-(1n << 63n)) + .max((1n << 63n) - 1n), + ) + .openapi({ type: "string" }); +const limit = z.coerce.number().int().min(1).max(200).default(50); +const params = z.object({ chainId, poolId }); +const page = z.object({ after_event_id: cursor.optional(), limit }); +const errors = { + ...errorResponses, + ...notFoundResponse, + 503: { + description: "The indexed head is missing, inconsistent or stale", + content: { "application/json": { schema: ErrorResponseType } }, + }, }; - -function formatStats( - chainId: bigint, - poolId: bigint, - row: LaunchStatsRow, -): z.infer { - return { - chain_id: chainId.toString(), - pool_id: toHex(poolId, 32), - swaps: { - count: row.swap_count, - buy_count: row.buy_count, - sell_count: row.sell_count, - distinct_transactions: row.distinct_transactions, - distinct_lockers: row.distinct_lockers, - distinct_transaction_senders: null, - }, - volume: { - buy: { quote_in: row.buy_quote_in, launch_token_out: row.buy_token_out }, - sell: { - launch_token_in: row.sell_token_in, - quote_out: row.sell_quote_out, - }, - }, - creator_fees: { - accrued: { - launch_token: row.fees_accrued_token, - quote_token: row.fees_accrued_quote, - }, - claimed: { - launch_token: row.fees_claimed_token, - quote_token: row.fees_claimed_quote, - }, - claim_count: row.fee_claim_count, - }, - funding: { - received: { - launch_token: row.funding_token, - quote_token: row.funding_quote, - }, - count: row.funding_count, - migrated_principal: { - launch_token: row.migrated_principal_token, - quote_token: row.migrated_principal_quote, - }, - }, - attribution: ATTRIBUTION, - }; -} - -type LaunchSwap = z.infer; - -function formatSwap(row: LaunchSwapRow): LaunchSwap { - return { - event_id: row.event_id!.toString(), - block_number: row.block_number!.toString(), - transaction_index: row.transaction_index!, - event_index: row.event_index!, - transaction_hash: hex(row.transaction_hash!, 32), - time: seconds(row.block_time!), - locker: hex(row.locker!, 20), - delta0: row.delta0!, - delta1: row.delta1!, - fee_amount: row.fee_amount!, - fee_is_token1: row.fee_is_token1!, - }; +const response = (schema: z.ZodType) => ({ + description: "Indexed zero-seed launch data", + content: { "application/json": { schema } }, +}); +const headers = { "cache-control": "no-store" }; +function launchQueries(env: Env) { + if (!env.PG_CONNECTION_STRING && !env.HYPERDRIVE) { + throw new StatusError(404, "Launch support is disabled"); + } + return createQueries(env); } export const ListLaunches = defineRoute( @@ -442,69 +55,31 @@ export const ListLaunches = defineRoute( method: "get", path: "/launches", tags: ["Launches"], - summary: "List launches", - description: - "Lists ScheduledLaunch launches from the indexed launchpad tables, newest first", operationId: "get_ListLaunches", + summary: "List supported zero-seed launches at one indexed head", request: { query: z.object({ - chainId: ChainIdType.optional().describe("Restrict to one chain"), - status: LaunchStatusType.optional(), - creator: AddressType.optional().describe( - "Only launches created through LaunchRouter by this address", - ), - pageSize: z.coerce - .number() - .int() - .min(1) - .max(200) - .optional() - .describe("Maximum number of launches per page") - .default(50), - page: z.coerce - .number() - .int() - .min(1) - .optional() - .describe("Page number to fetch (1-indexed)") - .default(1), + chainId, + before_event_id: cursor.optional(), + limit, + creator: z + .string() + .regex(/^0x[0-9a-fA-F]{40}$/) + .optional(), }), }, - responses: { - 200: jsonResponse("A page of launches", ListLaunchesResponseType), - ...errorResponses, - }, + responses: { 200: response(LaunchList), ...errors }, }), async (c) => { - const { - chainId = null, - status = null, - creator, - page, - pageSize, - } = c.req.valid("query"); - const queries = await createQueries(c.env); - const { rows, totalCount } = await queries.listLaunches({ + const { chainId, before_event_id, limit, creator } = c.req.valid("query"); + const q = await launchQueries(c.env); + const data = await q.readZeroSeedList({ chainId, - status, - creator: creator === undefined ? null : BigInt(creator), - page, - pageSize, - }); - - const response = { - data: rows.map(formatLaunch), - pagination: { - page, - pageSize, - totalPages: Math.ceil(totalCount / pageSize), - totalItems: totalCount, - }, - } satisfies z.infer; - - return c.json(response, 200, { - "cache-control": LAUNCH_LIST_CACHE_CONTROL, + cursor: before_event_id, + limit, + creator: creator === undefined ? undefined : BigInt(creator), }); + return c.json(data, 200, headers); }, ); @@ -513,114 +88,72 @@ export const GetLaunch = defineRoute( method: "get", path: "/launches/{chainId}/{poolId}", tags: ["Launches"], - summary: "Get a launch", - description: - "Returns a launch's configuration, saved state, latest advance, migration and claimed creator fees, plus the Core pool state", operationId: "get_GetLaunch", - request: { params: LaunchParams }, - responses: { - 200: jsonResponse("The launch", LaunchDetailType), - ...notFoundResponse, - ...errorResponses, - }, + summary: "Get a supported launch and separate principal/creator ledgers", + request: { params }, + responses: { 200: response(LaunchDetail), ...errors }, }), async (c) => { const { chainId, poolId } = c.req.valid("param"); - const queries = await createQueries(c.env); - const row = await queries.getLaunch(chainId, BigInt(poolId)); - if (row === null) throw new StatusError(404, "Launch not found"); - - return c.json(formatLaunchDetail(row), 200, { - "cache-control": LAUNCH_DETAIL_CACHE_CONTROL, - }); + const q = await launchQueries(c.env); + return c.json( + await q.readZeroSeedDetail(chainId, BigInt(poolId)), + 200, + headers, + ); }, ); -export const GetLaunchStats = defineRoute( +export const ListLaunchSwaps = defineRoute( createRoute({ method: "get", - path: "/launches/{chainId}/{poolId}/stats", + path: "/launches/{chainId}/{poolId}/swaps", tags: ["Launches"], - summary: "Get launch stats", - description: - "Swap volume by side, swap and locker counts, creator fees accrued and claimed, and funding received for one launch", - operationId: "get_GetLaunchStats", - request: { params: LaunchParams }, - responses: { - 200: jsonResponse("Launch statistics", LaunchStatsType), - ...notFoundResponse, - ...errorResponses, - }, + operationId: "get_ListLaunchSwaps", + summary: "List swaps; forwarding lockers are contracts, not users", + request: { params, query: page }, + responses: { 200: response(SwapPage), ...errors }, }), async (c) => { - const { chainId, poolId: poolIdParam } = c.req.valid("param"); - const poolId = BigInt(poolIdParam); - const queries = await createQueries(c.env); - const row = await queries.getLaunchStats(chainId, poolId); - if (row === null) throw new StatusError(404, "Launch not found"); - - return c.json(formatStats(chainId, poolId, row), 200, { - "cache-control": LAUNCH_STATS_CACHE_CONTROL, - }); + const { chainId, poolId } = c.req.valid("param"); + const { after_event_id, limit } = c.req.valid("query"); + const q = await launchQueries(c.env); + return c.json( + await q.readZeroSeedSwaps({ + chainId, + poolId: BigInt(poolId), + cursor: after_event_id, + limit, + }), + 200, + headers, + ); }, ); -export const ListLaunchSwaps = defineRoute( +export const ListLaunchClaims = defineRoute( createRoute({ method: "get", - path: "/launches/{chainId}/{poolId}/swaps", + path: "/launches/{chainId}/{poolId}/claims", tags: ["Launches"], - summary: "List launch swaps", - description: - "Returns a launch's LaunchSwapped events in ascending event order", - operationId: "get_ListLaunchSwaps", - request: { - params: LaunchParams, - query: z.object({ - cursor: EventCursorType.optional().describe( - "Return swaps strictly after this event_id; use next_cursor to continue", - ), - limit: z.coerce - .number() - .int() - .min(1) - .max(1000) - .optional() - .describe("Maximum number of swaps to return") - .default(100), - }), - }, - responses: { - 200: jsonResponse("A page of launch swaps", ListLaunchSwapsResponseType), - ...notFoundResponse, - ...errorResponses, - }, + operationId: "get_ListLaunchClaims", + summary: "List atomic full two-asset creator fee claims", + request: { params, query: page }, + responses: { 200: response(ClaimPage), ...errors }, }), async (c) => { - const { chainId, poolId: poolIdParam } = c.req.valid("param"); - const { cursor = null, limit } = c.req.valid("query"); - const poolId = BigInt(poolIdParam); - const queries = await createQueries(c.env); - const rows = await queries.listLaunchSwaps({ - chainId, - poolId, - cursor, - limit: limit + 1, - }); - if (rows.length === 0) throw new StatusError(404, "Launch not found"); - - const swaps = rows.filter((row) => row.event_id !== null); - const page = swaps.slice(0, limit).map(formatSwap); - const response = { - chain_id: chainId.toString(), - pool_id: toHex(poolId, 32), - swaps: page, - next_cursor: page.at(-1)?.event_id ?? cursor?.toString() ?? null, - has_more: swaps.length > limit, - } satisfies z.infer; - - return c.json(response, 200, { - "cache-control": LAUNCH_SWAPS_CACHE_CONTROL, - }); + const { chainId, poolId } = c.req.valid("param"); + const { after_event_id, limit } = c.req.valid("query"); + const q = await launchQueries(c.env); + return c.json( + await q.readZeroSeedClaims({ + chainId, + poolId: BigInt(poolId), + cursor: after_event_id, + limit, + }), + 200, + headers, + ); }, ); diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts new file mode 100644 index 0000000..73ff291 --- /dev/null +++ b/src/routes/launches/queries.ts @@ -0,0 +1,420 @@ +import type postgres from "postgres"; +import type { Sql } from "postgres"; +import { z } from "@hono/zod-openapi"; +import { StatusError } from "../../shared/errors"; +import { feeSchedule, supportedInstances, type LaunchInstance } from "./policy"; +import { + BlockRef, + ClaimPage, + LaunchDetail, + LaunchList, + SwapPage, +} from "./schema"; + +type DB = Sql<{ bigint: bigint }>; +type Numeric = string | bigint; +interface EventRow { + event_id: bigint; + block_number: bigint; + block_hash: Numeric; + block_time: Date; + transaction_index: number; + event_index: number; + transaction_hash: Numeric; +} +interface LaunchRow extends EventRow { + chain_id: bigint; + pool_id: Numeric; + pool_key_id: bigint; + core_address: Numeric; + emitter: Numeric; + token0: Numeric; + token1: Numeric; + token: Numeric; + quote_token: Numeric; + creator: Numeric; + name: string; + symbol: string; + decimals: number; + quote_name: string | null; + quote_symbol: string | null; + quote_decimals: number | null; + token_is_token1: boolean; + ask_tick: number; + upper_tick: number; + tick_spacing: bigint; + trading_start: bigint; + fee_duration: bigint; + initial_fee: Numeric; + final_fee: Numeric; + position_id: Numeric; + tick_lower: number; + tick_upper: number; + liquidity: Numeric; + supply: Numeric; + balance0: Numeric; + balance1: Numeric; + tick: number; + accrued0: Numeric; + accrued1: Numeric; + claimed0: Numeric; + claimed1: Numeric; + swap_count: bigint; + claim_count: bigint; + last_event_id: bigint; +} +const decimal = (n: Numeric) => n.toString(); +const hex = (n: Numeric, width: number) => + `0x${BigInt(n) + .toString(16) + .padStart(width * 2, "0")}`; +const seconds = (date: Date) => Math.floor(date.getTime() / 1000); +function parseIndexed( + schema: T, + value: unknown, +): z.output { + const result = schema.safeParse(value); + if (!result.success) throw new Error("Invalid indexed launch data"); + return result.data; +} +function event(row: EventRow) { + return { + event_id: decimal(row.event_id), + block_number: decimal(row.block_number), + block_hash: hex(row.block_hash, 32), + block_time: seconds(row.block_time), + transaction_index: row.transaction_index, + event_index: row.event_index, + transaction_hash: hex(row.transaction_hash, 32), + }; +} +function summary(row: LaunchRow, asOf: z.infer) { + return { + schema_version: "zero-seed-launch-api/v1" as const, + chain_id: decimal(row.chain_id), + pool_id: hex(row.pool_id, 32), + pool_key: { + core_address: hex(row.core_address, 20), + token0: hex(row.token0, 20), + token1: hex(row.token1, 20), + config: hex( + (BigInt(row.emitter) << 96n) | (1n << 31n) | row.tick_spacing, + 32, + ), + fee: "0" as const, + tick_spacing: Number(row.tick_spacing), + extension: hex(row.emitter, 20), + }, + token: { + address: hex(row.token, 20), + name: row.name, + symbol: row.symbol, + decimals: row.decimals, + }, + quote_token: { + address: hex(row.quote_token, 20), + name: row.quote_name, + symbol: row.quote_symbol, + decimals: row.quote_decimals, + }, + launch_token_is_token1: row.token_is_token1, + creator: hex(row.creator, 20), + ask_tick: row.ask_tick, + upper_tick: row.upper_tick, + fee_schedule: feeSchedule({ + start: row.trading_start, + duration: row.fee_duration, + initial: BigInt(row.initial_fee), + final: BigInt(row.final_fee), + indexedTime: BigInt(asOf.block_time), + }), + created: event(row), + as_of: asOf, + }; +} +function ledger(accrued: Numeric, claimed: Numeric) { + return { + accrued: decimal(accrued), + claimed: decimal(claimed), + unclaimed: (BigInt(accrued) - BigInt(claimed)).toString(), + }; +} +function detail(row: LaunchRow, asOf: z.infer) { + const launch = summary(row, asOf); + const [tokenBalance, quoteBalance] = row.token_is_token1 + ? [row.balance1, row.balance0] + : [row.balance0, row.balance1]; + return parseIndexed(LaunchDetail, { + ...launch, + principal: { + position_id: hex(row.position_id, 32), + tick_lower: row.tick_lower, + tick_upper: row.tick_upper, + liquidity: decimal(row.liquidity), + installed_supply: decimal(row.supply), + locked: true, + withdrawable: false, + pool_reserves: { + launch_token: decimal(tokenBalance), + quote_token: decimal(quoteBalance), + }, + reserves_basis: "core_pool_balance", + }, + fee_ledger: { + holder: launch.creator, + transferable: false, + launch_token: row.token_is_token1 + ? ledger(row.accrued1, row.claimed1) + : ledger(row.accrued0, row.claimed0), + quote_token: row.token_is_token1 + ? ledger(row.accrued0, row.claimed0) + : ledger(row.accrued1, row.claimed1), + swap_count: Number(row.swap_count), + claim_count: Number(row.claim_count), + last_event_id: decimal(row.last_event_id), + }, + inventory: { + trading_open: asOf.block_time >= Number(row.trading_start), + no_bid: BigInt(quoteBalance) === 0n, + price_tick: row.token_is_token1 ? -row.tick : row.tick, + }, + }); +} + +export interface LaunchReadOptions { + chainId: bigint; + cursor?: bigint; + limit: number; +} + +/** All constituent reads share a read-only repeatable-read snapshot. */ +export class LaunchQueries { + private readonly enabled: readonly LaunchInstance[]; + constructor( + private readonly sql: DB, + options: { + localHarness: boolean; + instances: readonly LaunchInstance[]; + maxIndexAgeSeconds: number; + } = { localHarness: false, instances: [], maxIndexAgeSeconds: 60 }, + ) { + this.enabled = supportedInstances(options.instances, options.localHarness); + this.maxAge = options.maxIndexAgeSeconds; + if (!Number.isFinite(this.maxAge) || this.maxAge <= 0) { + throw new Error("Invalid local index freshness bound"); + } + } + private readonly maxAge: number; + + private async snapshot( + chainId: bigint, + read: (tx: DB, asOf: z.infer) => Promise, + ): Promise { + if (!this.enabled.some((p) => p.chainId === chainId.toString())) { + throw new StatusError(404, "Launch support is disabled"); + } + return this.sql.begin( + "isolation level repeatable read read only", + async (tx) => { + const db = tx as unknown as DB; + const [head] = await db< + { + block_number: bigint; + block_hash: Numeric; + block_time: Date; + last_updated: Date; + }[] + >` + SELECT b.block_number, b.block_hash, b.block_time, ic.last_updated + FROM indexer_cursor ic JOIN blocks b + ON b.chain_id = ic.chain_id AND b.block_number = ic.head_block_number + AND b.block_hash = ic.head_block_hash + WHERE ic.chain_id = ${chainId} + `; + if ( + !head || + Date.now() - head.last_updated.getTime() > this.maxAge * 1000 + ) { + throw new StatusError( + 503, + "Launch index is stale or has no coherent head", + ); + } + const asOf = parseIndexed(BlockRef, { + block_number: decimal(head.block_number), + block_hash: hex(head.block_hash, 32), + block_time: seconds(head.block_time), + }); + return read(db, asOf); + }, + ) as Promise; + } + + private rows(tx: DB, chainId: bigint, filter: postgres.Fragment) { + // Screening occurs in SQL BEFORE cursor/limit; no arbitrary quote can + // occupy a page or be served by detail/history. + const pins = this.enabled.filter((p) => p.chainId === chainId.toString()); + const checks = pins.map( + (p) => tx` + (c.emitter = ${BigInt(p.address).toString()}::numeric + AND c.quote_token = ${BigInt(p.quote).toString()}::numeric + AND pk.core_address = ${BigInt(p.core).toString()}::numeric) + `, + ); + const supported = checks.reduce((a, b) => tx`${a} OR ${b}`); + return tx` + SELECT c.*, b.block_hash, b.block_time, pk.core_address, pk.token0, pk.token1, + qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, + qt.token_decimals AS quote_decimals, s.accrued0, s.accrued1, + s.claimed0, s.claimed1, s.swap_count, s.claim_count, s.last_event_id, + pt.balance0, pt.balance1, ps.tick + FROM zero_seed_launch_created c + JOIN zero_seed_launch_pool_keys registration USING (pool_key_id) + JOIN pool_keys pk USING (pool_key_id) + JOIN blocks b ON b.chain_id = c.chain_id AND b.block_number = c.block_number + JOIN zero_seed_launch_states s USING (pool_key_id) + JOIN pool_states ps USING (pool_key_id) + JOIN pool_tvl pt USING (pool_key_id) + LEFT JOIN erc20_tokens qt ON qt.chain_id = c.chain_id AND qt.token_address = c.quote_token + WHERE c.chain_id = ${chainId} AND pk.chain_id = c.chain_id + AND pk.pool_extension = c.emitter AND pk.fee = 0 + AND pk.token0 = LEAST(c.token, c.quote_token) + AND pk.token1 = GREATEST(c.token, c.quote_token) + AND pk.tick_spacing = c.tick_spacing + AND (${supported}) AND ${filter} + `; + } + + async list(input: LaunchReadOptions & { creator?: bigint }) { + return this.snapshot(input.chainId, async (tx, asOf) => { + const filter = tx` + (${input.cursor ?? null}::int8 IS NULL OR c.event_id < ${input.cursor ?? null}) + AND (${input.creator?.toString() ?? null}::numeric IS NULL OR c.creator = ${input.creator?.toString() ?? null}) + `; + const rows = await tx` + SELECT * FROM (${this.rows(tx, input.chainId, filter)}) l + ORDER BY event_id DESC LIMIT ${input.limit + 1} + `; + const page = rows.slice(0, input.limit); + return parseIndexed(LaunchList, { + schema_version: "zero-seed-launch-api/v1", + as_of: asOf, + data: page.map((r) => summary(r, asOf)), + next_cursor: + rows.length > input.limit ? page.at(-1)!.event_id.toString() : null, + }); + }); + } + + private async launch(tx: DB, chainId: bigint, poolId: bigint) { + const [row] = await this.rows( + tx, + chainId, + tx`c.pool_id = ${poolId.toString()}::numeric`, + ); + if (!row) throw new StatusError(404, "Launch not found"); + return row; + } + + async detail(chainId: bigint, poolId: bigint) { + return this.snapshot(chainId, async (tx, asOf) => + detail(await this.launch(tx, chainId, poolId), asOf), + ); + } + + async swaps(input: LaunchReadOptions & { poolId: bigint }) { + return this.snapshot(input.chainId, async (tx, asOf) => { + const launch = await this.launch(tx, input.chainId, input.poolId); + const rows = await tx< + (EventRow & { + locker: Numeric; + delta0: Numeric; + delta1: Numeric; + fee_rate: Numeric; + fee_amount: Numeric; + fee_is_token1: boolean; + })[] + >` + SELECT s.*, b.block_hash, b.block_time FROM zero_seed_launch_swapped s + JOIN blocks b ON b.chain_id = s.chain_id AND b.block_number = s.block_number + WHERE s.pool_key_id = ${launch.pool_key_id} + AND (${input.cursor ?? null}::int8 IS NULL OR s.event_id > ${input.cursor ?? null}) + ORDER BY s.event_id LIMIT ${input.limit + 1} + `; + const data = rows.slice(0, input.limit).map((row) => { + const [token, quote] = launch.token_is_token1 + ? [row.delta1, row.delta0] + : [row.delta0, row.delta1]; + return { + event: event(row), + forwarding_locker: hex(row.locker, 20), + direction: swapDirection(BigInt(token), BigInt(quote)), + launch_token_delta: decimal(token), + quote_token_delta: decimal(quote), + fee_rate: decimal(row.fee_rate), + fee_amount: decimal(row.fee_amount), + fee_asset: + row.fee_is_token1 === launch.token_is_token1 + ? "launch_token" + : "quote_token", + }; + }); + return parseIndexed(SwapPage, { + schema_version: "zero-seed-launch-api/v1", + chain_id: input.chainId.toString(), + pool_id: hex(input.poolId, 32), + as_of: asOf, + data, + next_cursor: + rows.length > input.limit ? data.at(-1)!.event.event_id : null, + }); + }); + } + + async claims(input: LaunchReadOptions & { poolId: bigint }) { + return this.snapshot(input.chainId, async (tx, asOf) => { + const launch = await this.launch(tx, input.chainId, input.poolId); + const rows = await tx< + (EventRow & { + creator: Numeric; + recipient: Numeric; + amount0: Numeric; + amount1: Numeric; + })[] + >` + SELECT cl.*, b.block_hash, b.block_time FROM zero_seed_launch_fees_claimed cl + JOIN blocks b ON b.chain_id = cl.chain_id AND b.block_number = cl.block_number + WHERE cl.pool_key_id = ${launch.pool_key_id} + AND (${input.cursor ?? null}::int8 IS NULL OR cl.event_id > ${input.cursor ?? null}) + ORDER BY cl.event_id LIMIT ${input.limit + 1} + `; + const data = rows.slice(0, input.limit).map((row) => ({ + event: event(row), + creator: hex(row.creator, 20), + recipient: hex(row.recipient, 20), + launch_token_amount: decimal( + launch.token_is_token1 ? row.amount1 : row.amount0, + ), + quote_token_amount: decimal( + launch.token_is_token1 ? row.amount0 : row.amount1, + ), + })); + return parseIndexed(ClaimPage, { + schema_version: "zero-seed-launch-api/v1", + chain_id: input.chainId.toString(), + pool_id: hex(input.poolId, 32), + as_of: asOf, + data, + next_cursor: + rows.length > input.limit ? data.at(-1)!.event.event_id : null, + }); + }); + } +} + +function swapDirection(token: bigint, quote: bigint) { + if (token === 0n && quote === 0n) return "zero_fill"; + if (token <= 0n && quote >= 0n) return "buy"; + if (token >= 0n && quote <= 0n) return "sell"; + throw new Error("Invalid indexed swap direction"); +} diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts new file mode 100644 index 0000000..b97e2a5 --- /dev/null +++ b/src/routes/launches/routes.test.ts @@ -0,0 +1,66 @@ +import { expect, spyOn, test } from "bun:test"; +import { app } from "../../router"; +import { Queries } from "../../queries"; +import { StatusError } from "../../shared/errors"; + +const pool = `0x${"1".repeat(64)}`; +const base = `/launches/31337/${pool}`; +const localEnv = { + PG_CONNECTION_STRING: "postgres://fixture:fixture@127.0.0.1:1/unused", +}; +const request = (path: string, env = {}) => + app.fetch(new Request(`http://localhost${path}`), env); + +test("hosted launch entrypoints stay disabled without database or live pins", async () => { + for (const path of [ + "/launches?chainId=31337", + base, + `${base}/swaps`, + `${base}/claims`, + ]) { + for (const env of [{}, localEnv]) { + const response = await request(path, env); + expect(response.status).toBe(404); + expect((await response.json()) as unknown).toEqual({ + status: 404, + error: "Launch support is disabled", + }); + } + } +}); + +test("invalid arguments return 400 and the removed stats route returns 404", async () => { + for (const path of [ + "/launches", + "/launches?chainId=-1", + "/launches?chainId=31337&limit=0", + "/launches?chainId=31337&before_event_id=9223372036854775808", + `${base}/swaps?after_event_id=-9223372036854775809`, + "/launches/31337/0x1234", + "/launches/31337/NaN", + ]) { + const response = await request(path); + expect(response.status).toBe(400); + expect(((await response.json()) as { status: number }).status).toBe(400); + } + expect((await request(`${base}/stats`)).status).toBe(404); +}); + +test("stale-index errors retain the established 503 error envelope", async () => { + const stub = spyOn( + Queries.prototype, + "readZeroSeedDetail", + ).mockImplementation(() => { + throw new StatusError(503, "Launch index is stale or has no coherent head"); + }); + try { + const response = await request(base, localEnv); + expect(response.status).toBe(503); + expect((await response.json()) as unknown).toEqual({ + status: 503, + error: "Launch index is stale or has no coherent head", + }); + } finally { + stub.mockRestore(); + } +}); From bd06ee3936b40197fcb899b201c429e4ac34e3ec Mon Sep 17 00:00:00 2001 From: Paperclip Date: Sun, 11 Oct 2026 04:21:28 -0400 Subject: [PATCH 04/19] update route parameter census for cursor-based launch reads Co-Authored-By: Paperclip --- src/router.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/router.test.ts b/src/router.test.ts index 115c7cf..bd4d747 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -31,7 +31,7 @@ describe("Hono app integration", () => { (count, operation) => count + (operation.parameters?.length ?? 0), 0, ), - ).toBe(211); + ).toBe(212); const getToken = operations.find( (operation) => From faf0316c25e6011f450184696e2bc4d13115711c Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 19 Oct 2026 13:06:18 -0400 Subject: [PATCH 05/19] Accept any integer form of launch path parameters The edge cache folds integer-like path segments, so a path parameter must mean the same thing in every hex or decimal spelling. Declare the launch chainId as an int64 integer and accept the pool id as any hex or decimal uint256, which keeps the launch routes inside the cache-key contract. --- openapi.json | 63 +++++++++++++++++++++++++----- src/routes/launches/index.ts | 10 ++++- src/routes/launches/routes.test.ts | 8 ++-- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/openapi.json b/openapi.json index 9b22c70..747972d 100644 --- a/openapi.json +++ b/openapi.json @@ -9921,7 +9921,8 @@ "parameters": [ { "schema": { - "type": "string" + "type": "integer", + "format": "int64" }, "required": true, "name": "chainId", @@ -10350,7 +10351,8 @@ "parameters": [ { "schema": { - "type": "string" + "type": "integer", + "format": "int64" }, "required": true, "name": "chainId", @@ -10358,10 +10360,23 @@ }, { "schema": { - "type": "string", - "pattern": "^0x[0-9a-fA-F]{64}$" + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "A number represented in hexadecimal or decimal" }, "required": true, + "description": "A number represented in hexadecimal or decimal", "name": "poolId", "in": "path" }, @@ -10578,7 +10593,8 @@ "parameters": [ { "schema": { - "type": "string" + "type": "integer", + "format": "int64" }, "required": true, "name": "chainId", @@ -10586,10 +10602,23 @@ }, { "schema": { - "type": "string", - "pattern": "^0x[0-9a-fA-F]{64}$" + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "A number represented in hexadecimal or decimal" }, "required": true, + "description": "A number represented in hexadecimal or decimal", "name": "poolId", "in": "path" }, @@ -10828,7 +10857,8 @@ "parameters": [ { "schema": { - "type": "string" + "type": "integer", + "format": "int64" }, "required": true, "name": "chainId", @@ -10836,10 +10866,23 @@ }, { "schema": { - "type": "string", - "pattern": "^0x[0-9a-fA-F]{64}$" + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "A number represented in hexadecimal or decimal" }, "required": true, + "description": "A number represented in hexadecimal or decimal", "name": "poolId", "in": "path" } diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts index c53fd5d..b9165cd 100644 --- a/src/routes/launches/index.ts +++ b/src/routes/launches/index.ts @@ -9,13 +9,19 @@ import { StatusError, } from "../../shared/errors"; import { LaunchList, LaunchDetail, SwapPage, ClaimPage } from "./schema"; +import { NumericStringType } from "../../shared/validation/address"; const chainId = z.coerce .bigint() .min(0n) .max((1n << 63n) - 1n) - .openapi({ type: "string" }); -const poolId = z.string().regex(/^0x[0-9a-fA-F]{64}$/); + .openapi({ type: "integer", format: "int64" }); +// Path segments are folded as integers in the edge cache key, so the pool id +// accepts any hex or decimal form of the same uint256. +const poolId = NumericStringType.refine( + (value) => BigInt(value) < 1n << 256n, + "Pool ID out of range", +); const cursor = z .string() .regex(/^(0|-?[1-9][0-9]*)$/) diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index b97e2a5..9973508 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -21,7 +21,7 @@ test("hosted launch entrypoints stay disabled without database or live pins", as for (const env of [{}, localEnv]) { const response = await request(path, env); expect(response.status).toBe(404); - expect((await response.json()) as unknown).toEqual({ + expect(await response.json()).toEqual({ status: 404, error: "Launch support is disabled", }); @@ -36,12 +36,12 @@ test("invalid arguments return 400 and the removed stats route returns 404", asy "/launches?chainId=31337&limit=0", "/launches?chainId=31337&before_event_id=9223372036854775808", `${base}/swaps?after_event_id=-9223372036854775809`, - "/launches/31337/0x1234", + `/launches/31337/0x1${"0".repeat(64)}`, "/launches/31337/NaN", ]) { const response = await request(path); expect(response.status).toBe(400); - expect(((await response.json()) as { status: number }).status).toBe(400); + expect(await response.json()).toMatchObject({ status: 400 }); } expect((await request(`${base}/stats`)).status).toBe(404); }); @@ -56,7 +56,7 @@ test("stale-index errors retain the established 503 error envelope", async () => try { const response = await request(base, localEnv); expect(response.status).toBe(503); - expect((await response.json()) as unknown).toEqual({ + expect(await response.json()).toEqual({ status: 503, error: "Launch index is stale or has no coherent head", }); From 661351c98ebcfbbb58ed5e37c78aacb3d00a9559 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 19 Oct 2026 13:06:18 -0400 Subject: [PATCH 06/19] Pass the formatter and linter, and follow the refreshed indexer harness Format the launch sources, drop assertions the linter flags as unnecessary, pin the replay to the rebased indexer head, and insert the API replay cases whether the indexer harness closes its describe as `});` or across lines. --- scripts/verifyLaunchReplay.ts | 21 ++++++++++++++++----- src/contract/cases/launches.ts | 2 +- src/queries.ts | 3 ++- src/routes/launches/README.md | 2 +- src/routes/launches/policy.test.ts | 4 +++- src/routes/launches/policy.ts | 2 +- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index 631227a..df069a8 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -17,11 +17,20 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "f6af3070036dda06c885a91351277044330c03fd" + head.stdout.toString().trim() !== "76c42bfb1c1ce0311b120cd4f6e4c4c561160961" ) throw new Error("Indexer source pin mismatch"); -const clean = Bun.spawnSync(["git", "-C", root, "diff", "--quiet", "HEAD", "--"]); -if (clean.exitCode !== 0) throw new Error("Pinned indexer tracked source is modified"); +const clean = Bun.spawnSync([ + "git", + "-C", + root, + "diff", + "--quiet", + "HEAD", + "--", +]); +if (clean.exitCode !== 0) + throw new Error("Pinned indexer tracked source is modified"); const fixturePath = resolve( root, "tests/fixtures/zero-seed-launch/local-chain.json", @@ -55,9 +64,11 @@ import { LOCAL_INSTANCES } from ${JSON.stringify(resolve(api, "routes/launches/p import { Queries } from ${JSON.stringify(resolve(api, "queries"))}; import { app } from ${JSON.stringify(resolve(api, "router"))}; ${source}`; -const ending = source.lastIndexOf("});"); +// The harness ends by closing its describe callback, as `});` or, formatted +// across lines, ` },\n);`. The API cases go inside that callback. +const ending = source.search(/\n(?:\}\);| \},\n\);)\s*$/); if (ending < 0) throw new Error("Unrecognized pinned indexer harness"); -source = source.slice(0, ending) + extra + source.slice(ending); +source = source.slice(0, ending + 1) + extra + source.slice(ending + 1); await mkdir(scratch, { recursive: true }); const target = resolve(scratch, "api-emitted-replay.test.ts"); await Bun.write(target, source); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 6a0bb06..4834221 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -1,7 +1,7 @@ import { ContractCase, stubQuery } from "../harness"; import { LOCAL_INSTANCES } from "../../routes/launches/policy"; -const pin = LOCAL_INSTANCES[0]!; +const pin = LOCAL_INSTANCES[0]; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { diff --git a/src/queries.ts b/src/queries.ts index 303a94c..46664c2 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -3101,7 +3101,8 @@ pt.last_transfer_event_id DESC NULLS LAST }; type NullableVe33PoolRow = { [K in keyof Omit]: - Ve33PoolRow[K] | null; + | Ve33PoolRow[K] + | null; } & Pick; const rows = await this.sql` diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 56603da..952663b 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -3,7 +3,7 @@ Frozen response contract: `zero-seed-launch-api/v1`. Behavioral policy: `zero-seed-launch-draft-policy/v2`, CTO freeze revision `ceebe872-8deb-4858-a192-8c013516def1`. Indexer source: -`f6af3070036dda06c885a91351277044330c03fd`. Live instance lists are empty. +`76c42bfb1c1ce0311b120cd4f6e4c4c561160961`. Live instance lists are empty. ## Requests diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index 2afee8d..b4a1e1b 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -77,7 +77,9 @@ test("frozen schemas expose no staged status/stats/exhausted fields", () => { next_cursor: null, }; expect(LaunchList.safeParse(empty).success).toBe(true); - expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe(false); + expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe( + false, + ); for (const schema of [SwapPage, ClaimPage]) { expect( schema.safeParse({ diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 8572f28..4ff15a3 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -19,7 +19,7 @@ export interface LaunchInstance { abiSha256: string; } -// Constructor-executed LOCAL instances in indexer f6af307 / fixture 41d416ce. +// Constructor-executed LOCAL instances in indexer 76c42bf / fixture 41d416ce. // No live instance exists. Never enable these via a hosted configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { From 35c22bdcb6ffb3ed6d7ac22d74f1efacedf58272 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 19 Oct 2026 19:41:26 -0400 Subject: [PATCH 07/19] Serve the draft exponential creator-fee decay in launch reads The indexed launch now carries decay_factor, a literal Q0.64 per-second multiplier. fee_schedule.fee_rate follows the contract's normalized exponential: powQ64 square-and-multiply flooring after every Q64 multiply, final + floor((initial - final) * (b^t - b^D) / (2^64 - b^D)). The response gains fee_schedule.decay_factor, so the contract version becomes zero-seed-launch-api/v2. Re-pin the local instances and ABI to evm-contracts b1cbb1c and the indexer replay harness to its matching head. Tests reproduce the contract's fee rows, chord counterexamples and all-second digests at six rates, and the replay checks the served rate against the indexer's SQL fee_at. --- openapi.json | 20 +++- scripts/launchReplayCases.txt | 8 ++ scripts/verifyLaunchReplay.ts | 4 +- src/contract/cases/launches.ts | 7 +- src/routes/launches/README.md | 16 +++- src/routes/launches/policy.test.ts | 148 ++++++++++++++++++++++++++++- src/routes/launches/policy.ts | 67 ++++++++++--- src/routes/launches/queries.ts | 10 +- src/routes/launches/schema.ts | 4 +- 9 files changed, 247 insertions(+), 37 deletions(-) diff --git a/openapi.json b/openapi.json index 747972d..9941858 100644 --- a/openapi.json +++ b/openapi.json @@ -9968,7 +9968,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v1" + "zero-seed-launch-api/v2" ] }, "data": { @@ -9979,7 +9979,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v1" + "zero-seed-launch-api/v2" ] }, "chain_id": { @@ -10143,6 +10143,10 @@ "type": "string", "pattern": "^(0|[1-9][0-9]*)$" }, + "decay_factor": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, "phase": { "type": "string", "enum": [ @@ -10166,6 +10170,7 @@ "fee_end", "initial_fee", "final_fee", + "decay_factor", "phase", "fee_rate", "evaluated_at" @@ -10411,7 +10416,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v1" + "zero-seed-launch-api/v2" ] }, "chain_id": { @@ -10653,7 +10658,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v1" + "zero-seed-launch-api/v2" ] }, "chain_id": { @@ -10898,7 +10903,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v1" + "zero-seed-launch-api/v2" ] }, "chain_id": { @@ -11062,6 +11067,10 @@ "type": "string", "pattern": "^(0|[1-9][0-9]*)$" }, + "decay_factor": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, "phase": { "type": "string", "enum": [ @@ -11085,6 +11094,7 @@ "fee_end", "initial_fee", "final_fee", + "decay_factor", "phase", "fee_rate", "evaluated_at" diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index 1aa48a0..9e63d47 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -40,6 +40,14 @@ expect(body.fee_ledger.quote_token.unclaimed).toBe(launchState.creatorFees[1]); expect(body.inventory.price_tick).toBe(decodePoolState(launchState.poolStateWord).tick); expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); + // The API's TypeScript schedule equals the indexer's SQL feeAt and the stored factor. + const [sql] = await db.sql<{ rate: string; factor: string }[]>` + SELECT zero_seed_launch_fee_at(trading_start, fee_duration, initial_fee, final_fee, decay_factor, + ${body.as_of.block_time}::int8)::text AS rate, decay_factor::text AS factor + FROM zero_seed_launch_created WHERE pool_id = ${BigInt(fixture.launches.l1!.poolId).toString()}::numeric`; + expect(body.fee_schedule.fee_rate).toBe(sql!.rate); + expect(body.fee_schedule.decay_factor).toBe(sql!.factor); + expect(body.schema_version).toBe("zero-seed-launch-api/v2"); expect(body).not.toHaveProperty("status"); expect(body.inventory).not.toHaveProperty("exhausted"); }); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index df069a8..f1c2efb 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -17,7 +17,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "76c42bfb1c1ce0311b120cd4f6e4c4c561160961" + head.stdout.toString().trim() !== "cdf58fcfca963aa3eaaaa5d388578ddf81739459" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -38,7 +38,7 @@ const fixturePath = resolve( const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); if ( new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "41d416cebdc78845f022f504ed72c86789890f3009f37e2dccf62f2d0f9ca9d4" + "b4026beee434641d3420de7db672d9424d79bad3a3ed86f44f50cf2bd5ca16fd" ) throw new Error("Emitted fixture pin mismatch"); const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 4834221..bbddb6b 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -5,7 +5,7 @@ const pin = LOCAL_INSTANCES[0]; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { - schema_version: "zero-seed-launch-api/v1" as const, + schema_version: "zero-seed-launch-api/v2" as const, chain_id: "31337", pool_id: zero, pool_key: { @@ -39,6 +39,7 @@ const summary = { fee_end: 4600, initial_fee: "10", final_fee: "3", + decay_factor: "18425445863997925462", phase: "fee_decay" as const, fee_rate: "10", evaluated_at: 1000, @@ -54,7 +55,7 @@ const summary = { }; const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; const identity = { - schema_version: "zero-seed-launch-api/v1" as const, + schema_version: "zero-seed-launch-api/v2" as const, chain_id: "31337", pool_id: zero, as_of, @@ -67,7 +68,7 @@ export const launchesCases: ContractCase[] = [ request: "/launches?chainId=31337", stub: () => stubQuery("readZeroSeedList", { - schema_version: "zero-seed-launch-api/v1", + schema_version: "zero-seed-launch-api/v2", data: [summary], as_of, next_cursor: null, diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 952663b..ea62a8f 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,9 +1,17 @@ # Zero-seed launch reads (draft/local) -Frozen response contract: `zero-seed-launch-api/v1`. Behavioral policy: -`zero-seed-launch-draft-policy/v2`, CTO freeze revision -`ceebe872-8deb-4858-a192-8c013516def1`. Indexer source: -`76c42bfb1c1ce0311b120cd4f6e4c4c561160961`. Live instance lists are empty. +Draft response contract: `zero-seed-launch-api/v2` (v1 plus +`fee_schedule.decay_factor`, for the draft exponential creator-fee decay of +evm-contracts `b1cbb1c1a9a00de62072a63a8f4aab4d97065573`). Behavioral policy: +`zero-seed-launch-draft-policy/v2`. Indexer source: +`cdf58fcfca963aa3eaaaa5d388578ddf81739459`. Live instance lists are empty. + +`fee_schedule.fee_rate` is the contract's `feeAt` at the indexed block time: +`initial_fee` until `trading_start`, then +`final_fee + floor((initial_fee - final_fee) * (b^t - b^D) / (2^64 - b^D))` with +`b^n` the contract's `powQ64` (square-and-multiply, floor after every Q0.64 +multiply), then `final_fee` from `fee_end`. `decay_factor` is a literal Q0.64 +per-second multiplier, not a half-life; do not assume a linear schedule. ## Requests diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index b4a1e1b..a4c38b5 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -1,6 +1,8 @@ import { expect, test } from "bun:test"; import { + decayedFee, feeSchedule, + powQ64, LOCAL_INSTANCES, supportedInstances, supportedLaunch, @@ -32,14 +34,16 @@ const input = { duration: 3n, initial: 10n, final: 3n, + // 2^63: b^t = 2^(64-t), E = 2^61, so the excess is floor(7 * (2^(64-t) - 2^61) / (2^64 - 2^61)). + decayFactor: 1n << 63n, indexedTime: 999n, }; -test("fee phases and floor decrement use indexed time only", () => { +test("fee phases and the normalized exponential use indexed time only", () => { const cases = [ [999n, "before_trading_start", "10"], [1000n, "fee_decay", "10"], - [1001n, "fee_decay", "8"], - [1002n, "fee_decay", "6"], + [1001n, "fee_decay", "6"], + [1002n, "fee_decay", "4"], [1003n, "final_fee", "3"], [1000000000n, "final_fee", "3"], ] as const; @@ -47,15 +51,151 @@ test("fee phases and floor decrement use indexed time only", () => { const result = feeSchedule({ ...input, indexedTime }); expect(result.phase).toBe(phase); expect(result.fee_rate).toBe(rate); + expect(result.decay_factor).toBe((1n << 63n).toString()); expect(result.evaluated_at).toBe(Number(indexedTime)); } }); +// Contract vectors at evm-contracts b1cbb1c1 (test/vectors/zero-seed-launch.json): +// fixture 5% -> 0.3%, start 1000, study factors for D=3600 and D=600. +test("schedule reproduces the contract fee rows and chord counterexamples", () => { + const factors: Record = { + "3600": 18425445863997925462n, + "600": 18319323104848571946n, + }; + const rows = [ + ["3600", 0n, 922337203685477580n], + ["3600", 1n, 921320298688452636n], + ["3600", 1800n, 151673229050500749n], + ["3600", 3599n, 55356139728168999n], + ["3600", 3600n, 55340232221128654n], + ["600", 1n, 916253358061131122n], + ["600", 300n, 151673229050500755n], + ["600", 599n, 55435953504208715n], + ["600", 600n, 55340232221128654n], + ] as const; + for (const [duration, t, fee] of rows) + expect( + decayedFee( + 922337203685477580n, + 55340232221128654n, + factors[duration], + BigInt(duration), + t, + ), + ).toBe(fee); + // Nearly flat factors lie above the integer linear chord; reproduced, not corrected. + const exceptions = [ + [ + 18446744073692774400n, + 600n, + 561n, + 6145946118234823704n, + 3773859608321006899n, + 3928045231480721956n, + ], + [ + 18446744073675997184n, + 600n, + 596n, + 18262276632972456099n, + 16093730704593406921n, + 16108187677454293848n, + ], + [ + 18446744073709551608n, + 4294967295n, + 4197406844n, + 18446744073709551615n, + 18012629042273719369n, + 18022489992398216608n, + ], + [ + 18446744073709551608n, + 4294967295n, + 4028198074n, + 18446744073709551615n, + 14193599660735192734n, + 14457771176879541746n, + ], + [ + 18446744073709551608n, + 4294967295n, + 4074259523n, + 18446744073709551615n, + 11939927239388337278n, + 12274296501798491214n, + ], + ] as const; + for (const [factor, duration, t, initial, final, fee] of exceptions) + expect(decayedFee(initial, final, factor, duration, t)).toBe(fee); + expect(powQ64(12345n, 0n)).toBe(1n << 64n); +}); + +// SHA-256 chains h = sha256(h || fee as 8-byte big-endian) over every second t = 0..D, pinned in the +// contract vectors (fee_curve.all_second_digests_sha256); 25,212 evaluations in total. +test("every second of both study schedules at six rates matches the contract digests", () => { + const rates: Record = { + "5": 922337203685477580n, + "50": 9223372036854775808n, + "75": 13835058055282163712n, + "90": 16602069666338596454n, + "99": 18262276632972456099n, + max: 18446744073709551615n, + }; + const digests: [bigint, bigint, Record][] = [ + [ + 600n, + 18319323104848571946n, + { + "5": "6346e95d7a66797ce3270ff25d5fc19e64c2e9ccabf1c2dbc03a946276fe64b5", + "50": "73b08ebf1a8d045805ec42c84200bbd841e25e515a1aef7f7fb6d246c6b6027a", + "75": "ac34495b80e94c660319b488c697f7ecf05af9eeb3f4a03c27c1b7bc79d4e5f4", + "90": "87c5e5119e29faddc71e59753cca77fea297b4d76419fd0b66a78a340c4b7cef", + "99": "af32943cbe63a0991532e385ad42960e1ce4040d59981ca6d4a4d4b86dc20cb0", + max: "dce005ce818ddc4248d259c7a6e2b9c0652cb4b4705b70cf9f4f04f1270023af", + }, + ], + [ + 3600n, + 18425445863997925462n, + { + "5": "306aaf4b3acfabe5ba98a1ee86cf91ba6564e010f806d1f592f3a403620bfd77", + "50": "ac4fa207bb2beec03e9a122e5839482d14abb55c72a4b65c155da02ea15baf51", + "75": "c7eaeb00462cc16f7ce3684309604d97a9a22a1eaec15a1b8862bbfd68705771", + "90": "fb0df6da40ddcacfe08f4a599b3078a1e6dbe0017b97e73b6b4e384563247354", + "99": "ee35ebedb9d7c09c69675d9517cdc1dc26e177eb61e0aa76fd13eb63dee8cba8", + max: "82e00f48c0d5dc03d757e417a60a410a8ee9cfd0d41c12cfb72547bfc73ac4f7", + }, + ], + ]; + let checked = 0; + for (const [duration, factor, byRate] of digests) { + for (const [label, rate] of Object.entries(rates)) { + let chain = new Uint8Array(32); + for (let t = 0n; t <= duration; t++) { + const fee = decayedFee(rate, 55340232221128654n, factor, duration, t); + const word = new Uint8Array(40); + word.set(chain); + new DataView(word.buffer).setBigUint64(32, fee); + chain = new Uint8Array( + new Bun.CryptoHasher("sha256").update(word).digest(), + ); + checked++; + } + expect(Buffer.from(chain).toString("hex")).toBe(byRate[label]); + } + } + expect(checked).toBe(25_212); +}); + test("invalid indexed domains fail, never coerce or overflow", () => { for (const change of [ { start: -1n }, { duration: 0n }, { final: 11n }, + { decayFactor: 0n }, + { decayFactor: 1n << 64n }, { initial: 1n << 64n }, { indexedTime: -1n }, { start: (1n << 64n) - 1n }, @@ -67,7 +207,7 @@ test("invalid indexed domains fail, never coerce or overflow", () => { test("frozen schemas expose no staged status/stats/exhausted fields", () => { const empty = { - schema_version: "zero-seed-launch-api/v1", + schema_version: "zero-seed-launch-api/v2", as_of: { block_number: "1", block_hash: `0x${"a".repeat(64)}`, diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 4ff15a3..911fb06 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -2,7 +2,7 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; export const ABI_SHA256 = - "189ff93c0f6fb64f6e7dd0675872bf6686692f8303e6218b8c2521bfa5ced215"; + "bc51b39284a5a2a0970d3a804420762ce291dc01f9c21ddf31840e354a3792bb"; export const POLICY_SHA256 = "7864a2d33a0fa8964922a5225653ed19842c590091f289eb4e29306feac6f6e1"; const CORE = "0x00000000000014aa86c5d3c41765bb24e11bd701"; @@ -19,26 +19,27 @@ export interface LaunchInstance { abiSha256: string; } -// Constructor-executed LOCAL instances in indexer 76c42bf / fixture 41d416ce. +// Constructor-executed LOCAL instances in indexer cdf58fc / fixture b4026bee +// (evm-contracts b1cbb1c1, draft exponential creator-fee decay). // No live instance exists. Never enable these via a hosted configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - address: "0x510d7c117adeeb89bb33b9acb53995f3280eaa3a", + address: "0x5142e167b32877ca3cf3060010a0a1418e3ef1b9", core: CORE, quote: "0xffffffffffffffffffffffffffffffffffffffff", runtime: - "0x8d8839b97583f275a84aaf26cd6a5fa3ba21a7be04731a65f6140fc5040477c4", + "0x8fb360bb48006819049e6b9a53679ccaa01264e61465ce8e8cd2e8b185d3a8d1", quoteRuntime: WETH_RUNTIME, abiSha256: ABI_SHA256, }, { chainId: "31337", - address: "0x5195c03d30c11be73485ab76a35eb6f59fc52072", + address: "0x51174b5db84c81a906ec55445070fd84c8c8e655", core: CORE, quote: "0x0000000000000000000000000000000000010000", runtime: - "0x8751bab213ba226c4e45f6441b9df2db1f9c8ee9e9335320e180f3b348646014", + "0xa951db07ad354ea45df3ff1b720b69c7cc588c4feb44aaea79cd79639da61f6e", quoteRuntime: WETH_RUNTIME, abiSha256: ABI_SHA256, }, @@ -89,32 +90,70 @@ function seconds(value: bigint) { return Number(value); } +const Q64 = 1n << 64n; + +/** + * powQ64 of the contract (math/feeDecay.sol): factor ** exponent in Q0.64 by + * square-and-multiply, flooring after every multiply and squaring only while + * bits remain. BigInt division floors for these non-negative operands. + */ +export function powQ64(factor: bigint, exponent: bigint): bigint { + let result = Q64; + let base = factor; + let rest = exponent; + while (rest !== 0n) { + if ((rest & 1n) === 1n) result = (result * base) / Q64; + rest >>= 1n; + if (rest !== 0n) base = (base * base) / Q64; + } + return result; +} + +/** + * feeAt of the contract: initial until the start, then + * final + floor((initial - final) * (b^t - b^D) / (2^64 - b^D)), then final. + */ +export function decayedFee( + initial: bigint, + final: bigint, + factor: bigint, + duration: bigint, + elapsed: bigint, +): bigint { + if (elapsed === 0n) return initial; + if (elapsed >= duration) return final; + const endpoint = powQ64(factor, duration); + return ( + final + + ((initial - final) * (powQ64(factor, elapsed) - endpoint)) / + (Q64 - endpoint) + ); +} + export function feeSchedule(input: { start: bigint; duration: bigint; initial: bigint; final: bigint; + decayFactor: bigint; indexedTime: bigint; }): z.infer { - const { start, duration, initial, final, indexedTime } = input; - [start, duration, initial, final, indexedTime].forEach(uint64); - if (duration === 0n || final > initial) { + const { start, duration, initial, final, decayFactor, indexedTime } = input; + [start, duration, initial, final, decayFactor, indexedTime].forEach(uint64); + if (duration === 0n || final > initial || decayFactor === 0n) { throw new Error("Invalid indexed fee schedule"); } const end = uint64(start + duration); const phase = feePhase(start, end, indexedTime); const elapsed = indexedTime > start ? indexedTime - start : 0n; - // Contract floors the decrement (not the remaining fee). - const rate = - indexedTime >= end - ? final - : initial - ((initial - final) * elapsed) / duration; + const rate = decayedFee(initial, final, decayFactor, duration, elapsed); return { trading_start: seconds(start), fee_duration: seconds(duration), fee_end: seconds(end), initial_fee: initial.toString(), final_fee: final.toString(), + decay_factor: decayFactor.toString(), phase, fee_rate: rate.toString(), evaluated_at: seconds(indexedTime), diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index 73ff291..5d90d49 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -47,6 +47,7 @@ interface LaunchRow extends EventRow { fee_duration: bigint; initial_fee: Numeric; final_fee: Numeric; + decay_factor: Numeric; position_id: Numeric; tick_lower: number; tick_upper: number; @@ -90,7 +91,7 @@ function event(row: EventRow) { } function summary(row: LaunchRow, asOf: z.infer) { return { - schema_version: "zero-seed-launch-api/v1" as const, + schema_version: "zero-seed-launch-api/v2" as const, chain_id: decimal(row.chain_id), pool_id: hex(row.pool_id, 32), pool_key: { @@ -126,6 +127,7 @@ function summary(row: LaunchRow, asOf: z.infer) { duration: row.fee_duration, initial: BigInt(row.initial_fee), final: BigInt(row.final_fee), + decayFactor: BigInt(row.decay_factor), indexedTime: BigInt(asOf.block_time), }), created: event(row), @@ -297,7 +299,7 @@ export class LaunchQueries { `; const page = rows.slice(0, input.limit); return parseIndexed(LaunchList, { - schema_version: "zero-seed-launch-api/v1", + schema_version: "zero-seed-launch-api/v2", as_of: asOf, data: page.map((r) => summary(r, asOf)), next_cursor: @@ -360,7 +362,7 @@ export class LaunchQueries { }; }); return parseIndexed(SwapPage, { - schema_version: "zero-seed-launch-api/v1", + schema_version: "zero-seed-launch-api/v2", chain_id: input.chainId.toString(), pool_id: hex(input.poolId, 32), as_of: asOf, @@ -400,7 +402,7 @@ export class LaunchQueries { ), })); return parseIndexed(ClaimPage, { - schema_version: "zero-seed-launch-api/v1", + schema_version: "zero-seed-launch-api/v2", chain_id: input.chainId.toString(), pool_id: hex(input.poolId, 32), as_of: asOf, diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index 104a9d6..887bd7e 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,7 +1,7 @@ import { z } from "@hono/zod-openapi"; // Frozen API v1 shapes (EKU-1513); behavioral policy is separately v2. -export const SchemaVersion = z.literal("zero-seed-launch-api/v1"); +export const SchemaVersion = z.literal("zero-seed-launch-api/v2"); const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); const Address = z.string().regex(/^0x[0-9a-f]{40}$/); @@ -39,6 +39,8 @@ export const FeeSchedule = z.strictObject({ fee_end: Seconds, initial_fee: U, final_fee: U, + // Q0.64 per-second multiplier of the unscaled exponential, not a half-life. + decay_factor: U, phase: z.enum(["before_trading_start", "fee_decay", "final_fee"]), fee_rate: U, evaluated_at: Seconds, From 242954777a575431c79138114c22857202adcbc9 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 20 Oct 2026 13:27:08 -0400 Subject: [PATCH 08/19] Serve native-ETH zero-seed launches with periphery owners (schema v3) Move the launch reads to the forward-only ZeroSeedLaunch and its periphery at evm-contracts 4f1a306, over indexer schema zero-seed-launch-index/v2. The response schema version is now zero-seed-launch-api/v3: - quote_token.kind distinguishes native ETH (address 0, token0, 18 decimals, no ERC-20 metadata) from an ERC-20 quote. - fee_schedule.decay_factor is replaced by the fixed-base Q128 table with a 600 s half-life; the TypeScript fee reproduces all 25,212 rows of the protocol's independent all-second vectors. - creator is the owner at creation, owner the current transferable fee holder, controller the periphery; only periphery-controlled launches of the pinned instances are served. - Claims are periphery payouts linked to the extension ledger event of the same transaction, counted once. A new owners endpoint lists transfers and the list accepts an owner filter. The replay runner pins the new indexer head and fixture and checks every handler against the chain state of each block, including a reorg across transfers; its golden output is committed as a machine-readable example. --- openapi.json | 394 ++++- scripts/launchReplayCases.txt | 211 ++- scripts/verifyLaunchReplay.ts | 4 +- src/contract/cases/launches.ts | 47 +- src/queries.ts | 8 +- src/router.test.ts | 6 +- src/router.ts | 2 + src/routes/launches/README.md | 65 +- .../zero-seed-launch-api-v3.golden.json | 992 +++++++++++ src/routes/launches/golden.test.ts | 34 + src/routes/launches/index.ts | 60 +- src/routes/launches/policy.test.ts | 303 ++-- src/routes/launches/policy.ts | 130 +- src/routes/launches/queries.ts | 119 +- src/routes/launches/routes.test.ts | 4 + src/routes/launches/schema.ts | 37 +- .../vectors/fee-curve-all-seconds.json.gz | Bin 0 -> 355072 bytes .../launches/vectors/fee-decay-vectors.json | 1510 +++++++++++++++++ 18 files changed, 3567 insertions(+), 359 deletions(-) create mode 100644 src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json create mode 100644 src/routes/launches/golden.test.ts create mode 100644 src/routes/launches/vectors/fee-curve-all-seconds.json.gz create mode 100644 src/routes/launches/vectors/fee-decay-vectors.json diff --git a/openapi.json b/openapi.json index 9941858..741d602 100644 --- a/openapi.json +++ b/openapi.json @@ -9949,12 +9949,47 @@ }, { "schema": { - "type": "string", - "pattern": "^0x[0-9a-fA-F]{40}$" + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Address", + "description": "An address on the specified blockchain network" }, "required": false, + "description": "An address on the specified blockchain network", "name": "creator", "in": "query" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Address", + "description": "An address on the specified blockchain network" + }, + "required": false, + "description": "An address on the specified blockchain network", + "name": "owner", + "in": "query" } ], "responses": { @@ -9968,7 +10003,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v2" + "zero-seed-launch-api/v3" ] }, "data": { @@ -9979,7 +10014,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v2" + "zero-seed-launch-api/v3" ] }, "chain_id": { @@ -10097,23 +10132,39 @@ ], "minimum": 0, "maximum": 255 + }, + "kind": { + "type": "string", + "enum": [ + "native", + "erc20" + ] } }, "required": [ "address", "name", "symbol", - "decimals" + "decimals", + "kind" ], "additionalProperties": false }, "launch_token_is_token1": { "type": "boolean" }, + "controller": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, "creator": { "type": "string", "pattern": "^0x[0-9a-f]{40}$" }, + "owner": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, "ask_tick": { "type": "integer" }, @@ -10143,9 +10194,27 @@ "type": "string", "pattern": "^(0|[1-9][0-9]*)$" }, - "decay_factor": { - "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "decay": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "fixed_base_q128" + ] + }, + "half_life_seconds": { + "type": "number", + "enum": [ + 600 + ] + } + }, + "required": [ + "kind", + "half_life_seconds" + ], + "additionalProperties": false }, "phase": { "type": "string", @@ -10170,7 +10239,7 @@ "fee_end", "initial_fee", "final_fee", - "decay_factor", + "decay", "phase", "fee_rate", "evaluated_at" @@ -10252,7 +10321,9 @@ "token", "quote_token", "launch_token_is_token1", + "controller", "creator", + "owner", "ask_tick", "upper_tick", "fee_schedule", @@ -10352,7 +10423,7 @@ "Launches" ], "operationId": "get_ListLaunchClaims", - "summary": "List atomic full two-asset creator fee claims", + "summary": "List atomic full two-asset creator fee claims, each paid out once by the periphery", "parameters": [ { "schema": { @@ -10416,7 +10487,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v2" + "zero-seed-launch-api/v3" ] }, "chain_id": { @@ -10504,7 +10575,11 @@ ], "additionalProperties": false }, - "creator": { + "ledger_event_id": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "owner": { "type": "string", "pattern": "^0x[0-9a-f]{40}$" }, @@ -10523,7 +10598,8 @@ }, "required": [ "event", - "creator", + "ledger_event_id", + "owner", "recipient", "launch_token_amount", "quote_token_amount" @@ -10588,6 +10664,241 @@ } } }, + "/launches/{chainId}/{poolId}/owners": { + "get": { + "tags": [ + "Launches" + ], + "operationId": "get_ListLaunchOwners", + "summary": "List the creation owner and every later ownership transfer", + "parameters": [ + { + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true, + "name": "chainId", + "in": "path" + }, + { + "schema": { + "anyOf": [ + { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "A hexadecimal number" + }, + { + "type": "string", + "pattern": "^\\d+$", + "description": "A decimal number" + } + ], + "title": "Numeric", + "description": "A number represented in hexadecimal or decimal" + }, + "required": true, + "description": "A number represented in hexadecimal or decimal", + "name": "poolId", + "in": "path" + }, + { + "schema": { + "type": "string" + }, + "required": false, + "name": "after_event_id", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "required": false, + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Indexed zero-seed launch data", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "schema_version": { + "type": "string", + "enum": [ + "zero-seed-launch-api/v3" + ] + }, + "chain_id": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "pool_id": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "next_cursor": { + "type": [ + "string", + "null" + ] + }, + "as_of": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "block_number", + "block_hash", + "block_time" + ], + "additionalProperties": false + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "event": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + }, + "event_id": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "transaction_index": { + "type": "integer", + "minimum": 0 + }, + "event_index": { + "type": "integer", + "minimum": 0 + }, + "transaction_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + } + }, + "required": [ + "block_number", + "block_hash", + "block_time", + "event_id", + "transaction_index", + "event_index", + "transaction_hash" + ], + "additionalProperties": false + }, + "previous_owner": { + "type": [ + "string", + "null" + ], + "pattern": "^0x[0-9a-f]{40}$" + }, + "new_owner": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + } + }, + "required": [ + "event", + "previous_owner", + "new_owner" + ], + "additionalProperties": false + } + } + }, + "required": [ + "schema_version", + "chain_id", + "pool_id", + "next_cursor", + "as_of", + "data" + ], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "The request parameters are invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "The requested resource was not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "An unexpected server error occurred", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "503": { + "description": "The indexed head is missing, inconsistent or stale", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, "/launches/{chainId}/{poolId}/swaps": { "get": { "tags": [ @@ -10658,7 +10969,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v2" + "zero-seed-launch-api/v3" ] }, "chain_id": { @@ -10903,7 +11214,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v2" + "zero-seed-launch-api/v3" ] }, "chain_id": { @@ -11021,23 +11332,39 @@ ], "minimum": 0, "maximum": 255 + }, + "kind": { + "type": "string", + "enum": [ + "native", + "erc20" + ] } }, "required": [ "address", "name", "symbol", - "decimals" + "decimals", + "kind" ], "additionalProperties": false }, "launch_token_is_token1": { "type": "boolean" }, + "controller": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, "creator": { "type": "string", "pattern": "^0x[0-9a-f]{40}$" }, + "owner": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, "ask_tick": { "type": "integer" }, @@ -11067,9 +11394,27 @@ "type": "string", "pattern": "^(0|[1-9][0-9]*)$" }, - "decay_factor": { - "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "decay": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "fixed_base_q128" + ] + }, + "half_life_seconds": { + "type": "number", + "enum": [ + 600 + ] + } + }, + "required": [ + "kind", + "half_life_seconds" + ], + "additionalProperties": false }, "phase": { "type": "string", @@ -11094,7 +11439,7 @@ "fee_end", "initial_fee", "final_fee", - "decay_factor", + "decay", "phase", "fee_rate", "evaluated_at" @@ -11248,7 +11593,7 @@ "transferable": { "type": "boolean", "enum": [ - false + true ] }, "launch_token": { @@ -11305,6 +11650,10 @@ "type": "integer", "minimum": 0 }, + "owner_change_count": { + "type": "integer", + "exclusiveMinimum": 0 + }, "last_event_id": { "type": "string", "pattern": "^(0|-?[1-9][0-9]*)$" @@ -11317,6 +11666,7 @@ "quote_token", "swap_count", "claim_count", + "owner_change_count", "last_event_id" ], "additionalProperties": false @@ -11350,7 +11700,9 @@ "token", "quote_token", "launch_token_is_token1", + "controller", "creator", + "owner", "ask_tick", "upper_tick", "fee_schedule", diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index 9e63d47..d3cc823 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -6,9 +6,9 @@ WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; } - test("API actual handlers: emitted-state golden, screening, pages, ledgers, reorg and repair", async () => { + test("API actual handlers: emitted-state golden, owners, native quote, screening, pages, ledgers, reorg and repair", async () => { const db = await freshDb(); - const processors = arithmeticReplayProcessors(); + const processors = LAUNCH_ON(); const reader = new LaunchQueries(db.sql as never, { localHarness: true, instances: LOCAL_INSTANCES, maxIndexAgeSeconds: 60, }); @@ -17,6 +17,7 @@ spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), + spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), ]; const get = async (path: string, status = 200) => { const response = await app.fetch(new Request(`http://localhost${path}`), { PG_CONNECTION_STRING: urlFor(db.name) }); @@ -25,95 +26,171 @@ if (response.status !== status) throw new Error(JSON.stringify(body)); return body; }; - const fullFixture = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); - const base = `/launches/${CHAIN_ID}/${fixture.launches.l1!.poolId}`; + const lower = (a: string) => a.toLowerCase(); + const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); + const { creator, newOwner, feeRecipient, trader } = full.addresses; + const path = (label: string) => `/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`; + /** One launch at one block: API detail versus the chain state and the indexer's SQL feeAt. */ + const expectLaunch = async (state: any, label: string) => { + const chain = state.pools.find((p: any) => p.label === label); + const body = await get(path(label)); + const install = full.installations[label]; + const instance = full.instances.find((i: any) => i.label === install.instance); + const launchIs1 = body.launch_token_is_token1; + expect(body.schema_version).toBe("zero-seed-launch-api/v3"); + expect(body.principal.installed_supply).toBe(install.actualInstalledSupply); + expect(body.principal.liquidity).toBe(install.actualLiquidity); + expect(body.fee_ledger.launch_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 1 : 0]); + expect(body.fee_ledger.quote_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 0 : 1]); + expect(body.fee_ledger.transferable).toBe(true); + expect([body.owner, body.fee_ledger.holder, body.creator, body.controller]).toEqual( + [lower(chain.launchOwner), lower(chain.launchOwner), lower(install.owner), lower(chain.controller)]); + expect(body.controller).toBe(lower(instance.periphery)); + expect(body.quote_token.kind).toBe(instance.quoteKind); + expect(body.quote_token.address).toBe(lower(instance.quote)); + if (instance.quoteKind === "native") { + expect([body.quote_token.decimals, body.quote_token.name, body.quote_token.symbol, launchIs1]).toEqual([18, null, null, true]); + expect(body.pool_key.token0).toBe("0x0000000000000000000000000000000000000000"); + } + const tick = decodePoolState(chain.poolStateWord).tick; + expect(body.inventory.price_tick).toBe(launchIs1 ? -tick : tick); + expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); + expect(body.fee_schedule.decay).toEqual({ kind: "fixed_base_q128", half_life_seconds: 600 }); + expect(body.fee_schedule).not.toHaveProperty("decay_factor"); + // The API's TypeScript schedule equals the indexer's SQL feeAt and the contract's feeAt. + const [sql] = await db.sql<{ rate: string }[]>` + SELECT zero_seed_launch_fee_at(trading_start, fee_duration, initial_fee, final_fee, + ${body.as_of.block_time}::int8)::text AS rate + FROM zero_seed_launch_created WHERE pool_id = ${num(fixture.launches[label]!.poolId)}::numeric`; + expect(body.fee_schedule.fee_rate).toBe(sql!.rate); + expect(body.fee_schedule.fee_rate).toBe(chain.feeAtBlockTime); + expect(body).not.toHaveProperty("status"); + expect(body.inventory).not.toHaveProperty("exhausted"); + return body; + }; + /** Claims are counted once: payouts equal the ledger's claimed amounts. */ + const expectClaimsOnce = async (label: string) => { + const detail = await get(path(label)); + const claims = await get(`${path(label)}/claims`); + expect(claims.data.length).toBe(detail.fee_ledger.claim_count); + const sum = (side: string) => claims.data.reduce((n: bigint, c: any) => n + BigInt(c[side]), 0n).toString(); + expect(sum("launch_token_amount")).toBe(detail.fee_ledger.launch_token.claimed); + expect(sum("quote_token_amount")).toBe(detail.fee_ledger.quote_token.claimed); + return claims; + }; try { await replay(db, prefix, processors, async (state) => { await expectChainParity(db, state); await apiHead(db); - if (!state.pools.some((p) => p.label === "l1")) return; - const body = await get(base); - const launchState = state.pools.find((p) => p.label === "l1")!; - expect(body.principal.installed_supply).toBe(fullFixture.installations.l1.actualInstalledSupply); - expect(body.principal.liquidity).toBe(fullFixture.installations.l1.actualLiquidity); - expect(body.fee_ledger.launch_token.unclaimed).toBe(launchState.creatorFees[0]); - expect(body.fee_ledger.quote_token.unclaimed).toBe(launchState.creatorFees[1]); - expect(body.inventory.price_tick).toBe(decodePoolState(launchState.poolStateWord).tick); - expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); - // The API's TypeScript schedule equals the indexer's SQL feeAt and the stored factor. - const [sql] = await db.sql<{ rate: string; factor: string }[]>` - SELECT zero_seed_launch_fee_at(trading_start, fee_duration, initial_fee, final_fee, decay_factor, - ${body.as_of.block_time}::int8)::text AS rate, decay_factor::text AS factor - FROM zero_seed_launch_created WHERE pool_id = ${BigInt(fixture.launches.l1!.poolId).toString()}::numeric`; - expect(body.fee_schedule.fee_rate).toBe(sql!.rate); - expect(body.fee_schedule.decay_factor).toBe(sql!.factor); - expect(body.schema_version).toBe("zero-seed-launch-api/v2"); - expect(body).not.toHaveProperty("status"); - expect(body.inventory).not.toHaveProperty("exhausted"); + for (const pool of state.pools) await expectLaunch(state, pool.label); + }); + // A launch in the deployment block is served. + expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); + await replay(db, alternate, processors, async (state) => { + await apiHead(db); + for (const pool of state.pools) await expectLaunch(state, pool.label); }); - await replay(db, alternate, processors); - await apiHead(db); const list = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); - expect(list.data).toHaveLength(1); - expect(list.data[0].pool_id).toBe(fixture.launches.l3!.poolId); - const second = await get(`/launches?chainId=${CHAIN_ID}&limit=1&before_event_id=${list.next_cursor}`); - expect(second.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + expect(list.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l3!.poolId]); + const second = await get(`/launches?chainId=${CHAIN_ID}&limit=2&before_event_id=${list.next_cursor}`); + expect(second.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); expect(second.next_cursor).toBeNull(); - const lowWeth = await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`); - expect(lowWeth.launch_token_is_token1).toBe(true); - expect(lowWeth.principal.installed_supply).toBe(fullFixture.installations.l3.actualInstalledSupply); - await replay(db, canonical, processors); - await apiHead(db); - const canonicalList = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); - expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + const alternateL1 = await get(path("l1")); + expect([alternateL1.owner, alternateL1.creator]).toEqual([lower(newOwner), lower(creator)]); + const nativeAlternate = await get(path("l3")); + expect([nativeAlternate.creator, nativeAlternate.owner, nativeAlternate.quote_token.kind]).toEqual([lower(newOwner), lower(newOwner), "native"]); + + // Reorg onto the canonical branch: the alternate transfer, claims and l3 disappear. + await invalidateFrom(db.dao, fixture.forkPoint + 1); + await replay(db, canonical, processors, async (state) => { + await apiHead(db); + for (const pool of state.pools) await expectLaunch(state, pool.label); + }); + const canonicalList = await get(`/launches?chainId=${CHAIN_ID}`); + expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual( + ["l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); expect(canonicalList.next_cursor).toBeNull(); - await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`, 404); - for (const label of ["l2", "l4"]) { - await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`, 404); - await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}/swaps`, 404); - await get(`/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}/claims`, 404); - } - const detail = await get(base); - const swaps = await get(`${base}/swaps`); - const claims = await get(`${base}/claims`); + for (const suffix of ["", "/swaps", "/claims", "/owners"]) + await get(`${path("l3")}${suffix}`, 404); + // creator (owner at creation) and current owner filters + expect((await get(`/launches?chainId=${CHAIN_ID}&creator=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); + expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${feeRecipient}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l1!.poolId]); + expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${BigInt(trader).toString()}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l4!.poolId]); + expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${creator}`)).data).toEqual([]); + const owners = await get(`${path("l1")}/owners`); + expect(owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ + [null, lower(creator)], [lower(creator), lower(newOwner)], [lower(newOwner), lower(feeRecipient)]]); + const firstOwner = await get(`${path("l1")}/owners?limit=1`); + const laterOwners = await get(`${path("l1")}/owners?after_event_id=${firstOwner.next_cursor}`); + expect([...firstOwner.data, ...laterOwners.data]).toEqual(owners.data); + const detail = await get(path("l1")); + expect(detail.fee_ledger.owner_change_count).toBe(3); + const swaps = await get(`${path("l1")}/swaps`); expect(swaps.data.length).toBe(detail.fee_ledger.swap_count); - expect(claims.data.length).toBe(detail.fee_ledger.claim_count); - const firstSwap = await get(`${base}/swaps?limit=1`); - const later = await get(`${base}/swaps?after_event_id=${firstSwap.next_cursor}`); + const claims = await expectClaimsOnce("l1"); + await expectClaimsOnce("l2"); + await expectClaimsOnce("l4"); + // l1 claims: by the creator, by the recipient-owner after two transfers, then twice at zero + expect(claims.data.map((c: any) => c.owner)).toEqual( + [lower(creator), lower(feeRecipient), lower(feeRecipient), lower(feeRecipient)]); + expect(claims.data.at(-1).launch_token_amount).toBe("0"); + const firstSwap = await get(`${path("l1")}/swaps?limit=1`); + const later = await get(`${path("l1")}/swaps?after_event_id=${firstSwap.next_cursor}`); expect([...firstSwap.data, ...later.data]).toEqual(swaps.data); - const firstClaim = await get(`${base}/claims?limit=1`); - const laterClaims = await get(`${base}/claims?after_event_id=${firstClaim.next_cursor}`); + const firstClaim = await get(`${path("l1")}/claims?limit=1`); + const laterClaims = await get(`${path("l1")}/claims?after_event_id=${firstClaim.next_cursor}`); expect([...firstClaim.data, ...laterClaims.data]).toEqual(claims.data); - for (const row of [...swaps.data, ...claims.data]) { + for (const row of [...swaps.data, ...claims.data, ...owners.data]) { const [block] = await db.sql`SELECT block_hash, block_time FROM blocks WHERE chain_id=${CHAIN_ID} AND block_number=${row.event.block_number}`; expect(row.event.block_hash).toBe(`0x${block!.block_hash.toString(16).padStart(64, "0")}`); expect(row.event.block_time).toBe(Math.floor(block!.block_time.getTime() / 1000)); } + // native buys pay ETH in: quote delta positive on buys + expect(swaps.data.filter((s: any) => s.direction === "buy").every((s: any) => BigInt(s.quote_token_delta) > 0n)).toBe(true); if (process.env.LAUNCH_GOLDEN_DIR) { - await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ list, second, lowWeth, canonicalList, detail, swaps, claims }, null, 2)); + const l2 = await get(path("l2")); + const l2Claims = await get(`${path("l2")}/claims`); + const l2Owners = await get(`${path("l2")}/owners`); + await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ + schema_version: "zero-seed-launch-api/v3", + source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain.json", chain_id: String(CHAIN_ID) }, + list, second, canonicalList, nativeDetail: detail, nativeSwaps: swaps, nativeClaims: claims, nativeOwners: owners, + erc20Detail: l2, erc20Claims: l2Claims, erc20Owners: l2Owners, + }, null, 2)); } - const before = await get(base); + const before = await get(path("l1")); await replay(db, canonical, processors); await apiHead(db); - expect(await get(base)).toEqual(before); + expect(await get(path("l1"))).toEqual(before); await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; - expect(await get(base)).toEqual(before); - await db.sql`UPDATE zero_seed_launch_states SET claimed0 = accrued0 + 1 WHERE pool_key_id = (SELECT pool_key_id FROM pool_keys WHERE chain_id=${CHAIN_ID} AND pool_id=${BigInt(fixture.launches.l1!.poolId).toString()}::numeric)`; - await get(base, 500); + expect(await get(path("l1"))).toEqual(before); + await db.sql`UPDATE zero_seed_launch_states SET claimed0 = accrued0 + 1 WHERE pool_key_id = (SELECT pool_key_id FROM pool_keys WHERE chain_id=${CHAIN_ID} AND pool_id=${num(fixture.launches.l1!.poolId)}::numeric)`; + await get(path("l1"), 500); await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; - expect(await get(base)).toEqual(before); + expect(await get(path("l1"))).toEqual(before); await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes' WHERE chain_id=${CHAIN_ID}`; - await get(base, 503); + await get(path("l1"), 503); await apiHead(db); await db.sql`UPDATE indexer_cursor SET head_block_hash = 0 WHERE chain_id=${CHAIN_ID}`; - await get(base, 503); + await get(path("l1"), 503); await apiHead(db); + // A reader without the periphery-bound pins serves nothing. + const unpinned = new LaunchQueries(db.sql as never, { + localHarness: true, + instances: LOCAL_INSTANCES.map((p) => ({ ...p, periphery: "0x0000000000000000000000000000000000000001" })), + maxIndexAgeSeconds: 60, + }); + await expect(unpinned.detail(CHAIN_ID, BigInt(fixture.launches.l1!.poolId))).rejects.toThrow(/disabled/); const writerSql = postgres(urlFor(db.name), { max: 1, onnotice: () => {}, types: { bigint: postgres.BigInt, numeric: NumericIntegerType } }) as unknown as Db["sql"]; const writer = { sql: writerSql, dao: Reflect.construct(DAO, [writerSql, CHAIN_ID]) as DAO, name: db.name }; try { await Reflect.apply(Reflect.get(reader, "snapshot"), reader, [CHAIN_ID, async (tx: any, asOf: any) => { const [old] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; - await invalidateFrom(writer.dao, 9); + await invalidateFrom(writer.dao, fixture.forkPoint + 1); await apiHead(writer); const [same] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; const [sameHead] = await tx`SELECT head_block_number FROM indexer_cursor WHERE chain_id=${CHAIN_ID}`; @@ -123,12 +200,18 @@ } finally { await writerSql.end(); } - await get(`/launches/${CHAIN_ID}/${fixture.launches.l3!.poolId}`, 404); + expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); + // Back to the deployment block: only the launch created in it remains. + const l2Block = Number((await get(path("l2"))).created.block_number); + await invalidateFrom(db.dao, l2Block); + await apiHead(db); expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + await get(path("l2"), 404); + // Before the deployment block nothing is indexed: no coherent head. await invalidateFrom(db.dao, Number(before.created.block_number)); await apiHead(db); - expect((await get(`/launches?chainId=${CHAIN_ID}`)).data).toEqual([]); - await get(base, 404); + await get(`/launches?chainId=${CHAIN_ID}`, 503); expect((await db.sql`SELECT COUNT(*)::int AS n FROM zero_seed_launch_states`)[0]!.n).toBe(0); } finally { spies.forEach((s) => s.mockRestore()); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index f1c2efb..9793077 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -17,7 +17,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "cdf58fcfca963aa3eaaaa5d388578ddf81739459" + head.stdout.toString().trim() !== "200684630121eef0ce18c649cd5035f9b57cef60" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -38,7 +38,7 @@ const fixturePath = resolve( const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); if ( new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "b4026beee434641d3420de7db672d9424d79bad3a3ed86f44f50cf2bd5ca16fd" + "c4cea4e1d1accf56fcf7beb90743321cd6b1c4761e304651c3636dc9136d482b" ) throw new Error("Emitted fixture pin mismatch"); const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index bbddb6b..368e628 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -2,16 +2,17 @@ import { ContractCase, stubQuery } from "../harness"; import { LOCAL_INSTANCES } from "../../routes/launches/policy"; const pin = LOCAL_INSTANCES[0]; +const owner = `0x${"2".repeat(40)}`; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { - schema_version: "zero-seed-launch-api/v2" as const, + schema_version: "zero-seed-launch-api/v3" as const, chain_id: "31337", pool_id: zero, pool_key: { core_address: pin.core, - token0: `0x${"1".repeat(40)}`, - token1: pin.quote, + token0: pin.quote, + token1: `0x${"1".repeat(40)}`, config: zero, fee: "0" as const, tick_spacing: 100, @@ -25,12 +26,15 @@ const summary = { }, quote_token: { address: pin.quote, - name: "Wrapped Ether", - symbol: "WETH", + name: null, + symbol: null, decimals: 18, + kind: "native" as const, }, - launch_token_is_token1: false, - creator: `0x${"2".repeat(40)}`, + launch_token_is_token1: true, + controller: pin.periphery, + creator: owner, + owner, ask_tick: 0, upper_tick: 100, fee_schedule: { @@ -39,7 +43,10 @@ const summary = { fee_end: 4600, initial_fee: "10", final_fee: "3", - decay_factor: "18425445863997925462", + decay: { + kind: "fixed_base_q128" as const, + half_life_seconds: 600 as const, + }, phase: "fee_decay" as const, fee_rate: "10", evaluated_at: 1000, @@ -55,7 +62,7 @@ const summary = { }; const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; const identity = { - schema_version: "zero-seed-launch-api/v2" as const, + schema_version: "zero-seed-launch-api/v3" as const, chain_id: "31337", pool_id: zero, as_of, @@ -68,7 +75,7 @@ export const launchesCases: ContractCase[] = [ request: "/launches?chainId=31337", stub: () => stubQuery("readZeroSeedList", { - schema_version: "zero-seed-launch-api/v2", + schema_version: "zero-seed-launch-api/v3", data: [summary], as_of, next_cursor: null, @@ -92,12 +99,13 @@ export const launchesCases: ContractCase[] = [ reserves_basis: "core_pool_balance", }, fee_ledger: { - holder: summary.creator, - transferable: false, + holder: owner, + transferable: true, launch_token: ledger, quote_token: ledger, swap_count: 0, claim_count: 0, + owner_change_count: 1, last_event_id: "-1", }, inventory: { trading_open: true, no_bid: true, price_tick: 0 }, @@ -113,4 +121,19 @@ export const launchesCases: ContractCase[] = [ request: `${path}/claims`, stub: () => stubQuery("readZeroSeedClaims", { ...identity, data: [] }), }, + { + operation: "/launches/{chainId}/{poolId}/owners", + request: `${path}/owners`, + stub: () => + stubQuery("readZeroSeedOwners", { + ...identity, + data: [ + { + event: summary.created, + previous_owner: null, + new_owner: owner, + }, + ], + }), + }, ]; diff --git a/src/queries.ts b/src/queries.ts index 46664c2..3077edc 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -201,7 +201,9 @@ export class Queries { this.sql = sql; } - public readZeroSeedList(input: LaunchReadOptions & { creator?: bigint }) { + public readZeroSeedList( + input: LaunchReadOptions & { creator?: bigint; owner?: bigint }, + ) { return new LaunchQueries(this.sql).list(input); } @@ -217,6 +219,10 @@ export class Queries { return new LaunchQueries(this.sql).claims(input); } + public readZeroSeedOwners(input: LaunchReadOptions & { poolId: bigint }) { + return new LaunchQueries(this.sql).owners(input); + } + public async listErc20Tokens({ minVisibilityPriority, pageSize, diff --git a/src/router.test.ts b/src/router.test.ts index bd4d747..524a60b 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -24,14 +24,14 @@ describe("Hono app integration", () => { ), ); - expect(Object.keys(schema.paths ?? {})).toHaveLength(63); - expect(operations).toHaveLength(63); + expect(Object.keys(schema.paths ?? {})).toHaveLength(64); + expect(operations).toHaveLength(64); expect( operations.reduce( (count, operation) => count + (operation.parameters?.length ?? 0), 0, ), - ).toBe(212); + ).toBe(217); const getToken = operations.find( (operation) => diff --git a/src/router.ts b/src/router.ts index efa0360..4087711 100644 --- a/src/router.ts +++ b/src/router.ts @@ -69,6 +69,7 @@ import { GetLaunch, ListLaunchClaims, ListLaunches, + ListLaunchOwners, ListLaunchSwaps, } from "./routes/launches"; import { @@ -131,6 +132,7 @@ const routes: readonly ApiRoute[] = [ GetAuctionNftImage, ListLaunches, ListLaunchClaims, + ListLaunchOwners, ListLaunchSwaps, GetLaunch, ListProposals, diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index ea62a8f..76292de 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,30 +1,54 @@ # Zero-seed launch reads (draft/local) -Draft response contract: `zero-seed-launch-api/v2` (v1 plus -`fee_schedule.decay_factor`, for the draft exponential creator-fee decay of -evm-contracts `b1cbb1c1a9a00de62072a63a8f4aab4d97065573`). Behavioral policy: -`zero-seed-launch-draft-policy/v2`. Indexer source: -`cdf58fcfca963aa3eaaaa5d388578ddf81739459`. Live instance lists are empty. - -`fee_schedule.fee_rate` is the contract's `feeAt` at the indexed block time: -`initial_fee` until `trading_start`, then -`final_fee + floor((initial_fee - final_fee) * (b^t - b^D) / (2^64 - b^D))` with -`b^n` the contract's `powQ64` (square-and-multiply, floor after every Q0.64 -multiply), then `final_fee` from `fee_end`. `decay_factor` is a literal Q0.64 -per-second multiplier, not a half-life; do not assume a linear schedule. +Draft response contract: `zero-seed-launch-api/v3`, for the forward-only +`ZeroSeedLaunch` with a native ETH quote, the fixed-base creator-fee table and +the separate `ZeroSeedLaunchPeriphery` of evm-contracts +`4f1a3060b099e8a50469848118f44c0c65cfab9a`. Indexer schema +`zero-seed-launch-index/v2` (migration 00139). Live instance lists are empty. + +Changes from v2: + +- `quote_token.kind` is `native` or `erc20`. Native ETH is address 0, always + token0 (the launch token is token1), 18 decimals, and has no token contract: + its `name` and `symbol` are null and no ERC-20 metadata is read. +- `fee_schedule.decay_factor` is gone; `fee_schedule.decay` is + `{ kind: "fixed_base_q128", half_life_seconds: 600 }`. `fee_rate` is the + contract's `feeAt` at the indexed block time: `initial_fee` until + `trading_start`, then + `final_fee + floor((initial_fee - final_fee) * (decay(t) - decay(D)) / (2^128 - decay(D)))` + with `decay` the contract's `decayQ128` (one Q128 constant per exponent bit, + floor after every multiply, zero from 2^17 s), then `final_fee` from + `fee_end`. `policy.test.ts` checks it against all 25,212 rows of the + protocol's independent all-second vectors. +- `creator` is the owner at creation (`LaunchOwnerChanged` from the zero + address) and never changes; `owner` is the current owner, which holds the + unclaimed and future fees (`fee_ledger.holder`, `transferable: true`); + `controller` is the periphery that forwarded the creation. Only launches + whose controller is the pinned periphery are served. +- Claims are the periphery payouts (`owner`, `recipient`), each linked by + `ledger_event_id` to the extension's ledger event of the same transaction; + the amounts are counted once. +- New `GET /launches/{chainId}/{poolId}/owners`; `GET /launches` accepts + `owner` (current) besides `creator`. + +`fixtures/zero-seed-launch-api-v3.golden.json` is a machine-readable example of +every response, written by the actual handlers over the replayed local fixture +(native and WETH launches, transfers, claims, a reorg). ## Requests -- `GET /launches?chainId=31337&limit=50&before_event_id=...&creator=0x...` +- `GET /launches?chainId=31337&limit=50&before_event_id=...&creator=0x...&owner=0x...` - `GET /launches/{chainId}/{poolId}` - `GET /launches/{chainId}/{poolId}/swaps?limit=50&after_event_id=...` - `GET /launches/{chainId}/{poolId}/claims?limit=50&after_event_id=...` +- `GET /launches/{chainId}/{poolId}/owners?limit=50&after_event_id=...` One chain per list request gives an unambiguous indexed `as_of`. Pool IDs are 32-byte hex. Cursors are signed decimal event IDs: newest-first creations use -`before_event_id`; oldest-first swaps/claims use `after_event_id`. Limits are -1–200. A non-null `next_cursor` means another row was observed in that snapshot. -Cursor continuation is not a frozen historical snapshot: reorgs may change pages. +`before_event_id`; oldest-first swaps/claims/owners use `after_event_id`. +Limits are 1–200. A non-null `next_cursor` means another row was observed in +that snapshot. Cursor continuation is not a frozen historical snapshot: reorgs +may change pages. No stats, lifecycle status, staged funding, advances, terminal migration, user counts or connected-wallet analytics exist. Forwarding lockers are contracts, @@ -36,11 +60,12 @@ All hosted query delegates construct a reader with **no enabled instances**. They refuse launch reads with 404 without querying Postgres; missing database configuration also returns 404. There is no environment-binding enable switch. Only the explicit local test harness can pass the full reviewed instance -manifest. Constructor quote, Core, executed runtime, WETH runtime and ABI must -match together; synthetic 6-decimal fixtures and template runtimes are refused. +manifest. Extension and periphery addresses and runtimes, Core, constructor +quote and its kind (an ERC-20 also its runtime) and both ABIs must match +together; template runtimes are refused. -List SQL screens the extension, quote, Core, ordered pair, zero Core fee and -spacing before pagination. Detail and both histories use that same selection. +List SQL screens the extension, controller (periphery), quote and kind, Core, +ordered pair, zero Core fee and spacing before pagination. Detail and both histories use that same selection. Missing/unsupported launches return 404. A missing, mismatched or stale cursor head returns 503. Parameters return 400; malformed internal indexed output fails as 500, never a success or client-validation fallback. diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json new file mode 100644 index 0000000..69bde48 --- /dev/null +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json @@ -0,0 +1,992 @@ +{ + "schema_version": "zero-seed-launch-api/v3", + "source": { + "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain.json", + "chain_id": "31337" + }, + "list": { + "schema_version": "zero-seed-launch-api/v3", + "data": [ + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xbe2a2c8ab2cd8ffeedf462371c8114ae48c8edecffb20b718d04ed9b339d434f", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x4e86de5aa6960f956736011337ffcd9315808f03", + "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + }, + "token": { + "address": "0x4e86de5aa6960f956736011337ffcd9315808f03", + "name": "Zero Seed l3", + "symbol": "L3", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "creator": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001300, + "fee_duration": 3600, + "fee_end": 1800004900, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "fee_decay", + "fee_rate": "8599671045411758433", + "evaluated_at": 1800001360 + }, + "created": { + "block_number": "8", + "block_hash": "0x5156ccf3a84b32af8771c42cee8ce518c9bbe667de2989768107e809819e162b", + "block_time": 1800001300, + "event_id": "-9223372002494906359", + "transaction_index": 2, + "event_index": 8, + "transaction_hash": "0xaf2346f6df346f934441393f6d647f21f95581ad7ff7352f0f3877d53a7a8bd5" + }, + "as_of": { + "block_number": "9", + "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_time": 1800001360 + } + } + ], + "next_cursor": "-9223372002494906359", + "as_of": { + "block_number": "9", + "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_time": 1800001360 + } + }, + "second": { + "schema_version": "zero-seed-launch-api/v3", + "data": [ + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + }, + "token": { + "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 600, + "fee_end": 1800001600, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "fee_decay", + "fee_rate": "332352623220129400", + "evaluated_at": 1800001360 + }, + "created": { + "block_number": "3", + "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_time": 1800000020, + "event_id": "-9223372023969808377", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + }, + "as_of": { + "block_number": "9", + "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_time": 1800001360 + } + }, + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + }, + "token": { + "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 3600, + "fee_end": 1800004600, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "fee_decay", + "fee_rate": "6054471406081085352", + "evaluated_at": 1800001360 + }, + "created": { + "block_number": "2", + "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_time": 1800000010, + "event_id": "-9223372028264579064", + "transaction_index": 4, + "event_index": 7, + "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + }, + "as_of": { + "block_number": "9", + "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_time": 1800001360 + } + } + ], + "next_cursor": null, + "as_of": { + "block_number": "9", + "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_time": 1800001360 + } + }, + "canonicalList": { + "schema_version": "zero-seed-launch-api/v3", + "data": [ + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xe5c50d3246c67d6ffc896e8db9c4af89a8f4ee169276a99efb7fbbeb4ad5b2d5", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1e37090e07df7bc78f4493192f41d46da9e11b54", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + }, + "token": { + "address": "0x1e37090e07df7bc78f4493192f41d46da9e11b54", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 600, + "fee_end": 1800002000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_time": 1800001310, + "event_id": "-9223372002494906355", + "transaction_index": 2, + "event_index": 12, + "transaction_hash": "0xdfad1ca753b6d6634428f8e4b50adc9c8f36e39f3bc0287625018688ff9348ce" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + }, + "token": { + "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 600, + "fee_end": 1800001600, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "3", + "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_time": 1800000020, + "event_id": "-9223372023969808377", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + }, + "token": { + "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 3600, + "fee_end": 1800004600, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "2", + "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_time": 1800000010, + "event_id": "-9223372028264579064", + "transaction_index": 4, + "event_index": 7, + "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + } + } + ], + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + } + }, + "nativeDetail": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + }, + "token": { + "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 3600, + "fee_end": 1800004600, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "2", + "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_time": 1800000010, + "event_id": "-9223372028264579064", + "transaction_index": 4, + "event_index": 7, + "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", + "tick_lower": 9210300, + "tick_upper": 13815500, + "liquidity": "1111119116089565312898036", + "installed_supply": "999999999999999999999999422", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "984239888128788178301567600", + "quote_token": "15987150838889926474" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "transferable": true, + "launch_token": { + "accrued": "1445887341297225653249490", + "claimed": "1445887341297225653249490", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "9304077310520618614", + "claimed": "9304077310520618614", + "unclaimed": "0" + }, + "swap_count": 10, + "claim_count": 4, + "owner_change_count": 3, + "last_event_id": "-9223371972430135288" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13786928 + } + }, + "nativeSwaps": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0x20fabcf37b24d3febe443e7d00de5dca363db46434a561807860885233243a73", + "block_time": 1800001000, + "event_id": "-9223372015379939327", + "transaction_index": 0, + "event_index": 0, + "transaction_hash": "0x2c0edc9b05a36ffbba110359ccdfdc1de0874d23ef913c58429125f0614f5363" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "zero_fill", + "launch_token_delta": "0", + "quote_token_delta": "0", + "fee_rate": "9223372036854775808", + "fee_amount": "0", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "5", + "block_hash": "0x20fabcf37b24d3febe443e7d00de5dca363db46434a561807860885233243a73", + "block_time": 1800001000, + "event_id": "-9223372015379873789", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x17a3c26a91be99145e4c531c47ccbaed2a9edd1abbd18f94c3dc6db99f06a9c5" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-499541686868404598624947", + "quote_token_delta": "1000000000000000000", + "fee_rate": "9223372036854775808", + "fee_amount": "499541686868404598624948", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_time": 1800001060, + "event_id": "-9223372011084972030", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xd6ac0db70aee216cbf64973ca01f8d5d12e1126e20e18d94c71732dce0d0d298" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-10000000000000000000000000", + "quote_token_delta": "18937882440529117203", + "fee_rate": "8599671045411758433", + "fee_amount": "8828634399353909478", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_time": 1800001060, + "event_id": "-9223372011084906491", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0x9d0f216c68a4b7740223aef4e240cfedc0d97a9400690bd417adfe1b0cd9fd45" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "sell", + "launch_token_delta": "1000000000000000000000000", + "quote_token_delta": "-544053484045985401", + "fee_rate": "8599671045411758433", + "fee_amount": "475134182559670259", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "8", + "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_time": 1800001310, + "event_id": "-9223372002495037438", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x8531d94a52282cec1b752215b452de8e169ca05b74c90187d87d2136affd9657" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-1278292209449944997565916", + "quote_token_delta": "2000000000000000000", + "fee_rate": "6419851635182426398", + "fee_amount": "682341375631624879778841", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "11", + "block_hash": "0x9d83c56ebbfc980f3f263dc779592aa1874afe1e92134f63d8c12acaad6656db", + "block_time": 1800001756, + "event_id": "-9223371989610135550", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xfab9c9873a6c84adfea132bb05679bddb3b4438d3df5f07e94503c428acc383b" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-776368331230381396567584", + "quote_token_delta": "1000000000000000000", + "fee_rate": "3798635018806438810", + "fee_amount": "201332466835839644110932", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0x6df423c99479fce97adf6d6db7073feee625f98d91dc90ab3c53a31fafe4edcf", + "block_time": 1800002800, + "event_id": "-9223371985315168254", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x68531d9bddd8e68e17f740ca54b66acf8333e8a2f74161cbbc54e5564f309480" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-919140266670121284225811", + "quote_token_delta": "1000000000000000000", + "fee_rate": "1074010432735978337", + "fee_amount": "56822734749309055238917", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "13", + "block_hash": "0x425ceb7e4bc0e2b6ae079550f0a04c2f412d0f3ba244b68e667c2f0c60c1d5fa", + "block_time": 1800004599, + "event_id": "-9223371981020200958", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xfc50997c0e125302bbd6708f6eeb0196858151e034691c32e927324967ec5f8c" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-971298260506660135369876", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55508445646640394", + "fee_amount": "2931573374968989183135", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "14", + "block_hash": "0xcffe0667f9f0baeb6aae5126f4bc80f723835a358afe12f5092abb551fe79b53", + "block_time": 1800004600, + "event_id": "-9223371976725233662", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x2f230ecee05c2db2d840a605032816f1f4d15c72efd800e634e9dd1fe552d727" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "buy", + "launch_token_delta": "-969583775189083632828198", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2917503837078486312717", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000, + "event_id": "-9223371972430266366", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x2fa02aa348d948fd1eca6cb3846080b45305ac5eb862f7957d50424ddc062186" + }, + "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "direction": "sell", + "launch_token_delta": "100000000000000000000000", + "quote_token_delta": "-102600807072586714", + "fee_rate": "55340232221128654", + "fee_amount": "308728607038877", + "fee_asset": "quote_token" + } + ] + }, + "nativeClaims": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "6", + "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_time": 1800001060, + "event_id": "-9223372011084775411", + "transaction_index": 3, + "event_index": 12, + "transaction_hash": "0x083d593b8dde6a236885bc4a2e2a10441c8f14121bc713ae0df20cb8ccf02dc5" + }, + "ledger_event_id": "-9223372011084775413", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "499541686868404598624948", + "quote_token_amount": "9303768581913579737" + }, + { + "event": { + "block_number": "12", + "block_hash": "0x6df423c99479fce97adf6d6db7073feee625f98d91dc90ab3c53a31fafe4edcf", + "block_time": 1800002800, + "event_id": "-9223371985315037174", + "transaction_index": 2, + "event_index": 9, + "transaction_hash": "0x1b2124f6ed57417d0f9669d55efdee37bbab942c7d54425989bbb5c7c9c85204" + }, + "ledger_event_id": "-9223371985315037176", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "940496577216773579128690", + "quote_token_amount": "0" + }, + { + "event": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000, + "event_id": "-9223371972430200826", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0xe78428e73fabb02954050b2a73f88d2da21a6f856a773fdf908dc0199a6dacf0" + }, + "ledger_event_id": "-9223371972430200828", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "5849077212047475495852", + "quote_token_amount": "308728607038877" + }, + { + "event": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000, + "event_id": "-9223371972430135288", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0xc6fb29d089de5c6b95791bc8d516d1762374dc2af5d1afb936b4914da9cc20ce" + }, + "ledger_event_id": "-9223371972430135289", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "0", + "quote_token_amount": "0" + } + ] + }, + "nativeOwners": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "2", + "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_time": 1800000010, + "event_id": "-9223372028264579063", + "transaction_index": 4, + "event_index": 8, + "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + }, + { + "event": { + "block_number": "10", + "block_hash": "0x6985816762f8e211e4ce165c63cb5bb8f34f54df4523ffa28c872722c6b41962", + "block_time": 1800001700, + "event_id": "-9223371993904971767", + "transaction_index": 2, + "event_index": 8, + "transaction_hash": "0x3bec34f77c548d14d6b711499d285a1f325f01152fdf817b73e5cde0e81bad10" + }, + "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" + }, + { + "event": { + "block_number": "11", + "block_hash": "0x9d83c56ebbfc980f3f263dc779592aa1874afe1e92134f63d8c12acaad6656db", + "block_time": 1800001756, + "event_id": "-9223371989610070012", + "transaction_index": 1, + "event_index": 3, + "transaction_hash": "0x4a29c836cfa17977a6cf28621151fd3a968232e6c5b237c4fca35697a6979114" + }, + "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" + } + ] + }, + "erc20Detail": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + }, + "token": { + "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001000, + "fee_duration": 600, + "fee_end": 1800001600, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "3", + "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_time": 1800000020, + "event_id": "-9223372023969808377", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "installed_supply": "999999999999999999999999210", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "991072549435525463122574599", + "quote_token": "8999918293703352734" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "transferable": true, + "launch_token": { + "accrued": "226070717291339801421992", + "claimed": "217191134011045238943541", + "unclaimed": "8879583280294562478451" + }, + "quote_token": { + "accrued": "52679910194913302", + "claimed": "52679910194913302", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 2, + "last_event_id": "-9223371993905037306" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13799366 + } + }, + "erc20Claims": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_time": 1800001310, + "event_id": "-9223372002494971897", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x7f052bd84f2db16c2d1f69475a5bc91dbc37b5b929ab729c895e34b53990321d" + }, + "ledger_event_id": "-9223372002494971900", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "217191134011045238943541", + "quote_token_amount": "52679910194913302" + } + ] + }, + "erc20Owners": { + "schema_version": "zero-seed-launch-api/v3", + "chain_id": "31337", + "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "3", + "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_time": 1800000020, + "event_id": "-9223372023969808376", + "transaction_index": 1, + "event_index": 7, + "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + }, + { + "event": { + "block_number": "7", + "block_hash": "0x549dedc802b5dfc64a068ca3c6c80e90bd6528a4300a809af02c9fad87469b42", + "block_time": 1800001120, + "event_id": "-9223372006790004735", + "transaction_index": 0, + "event_index": 0, + "transaction_hash": "0x218a80132b6450ca23971df8208d9778e6ea2ab8d38544a53e17663852b95fa6" + }, + "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" + } + ] + } +} diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts new file mode 100644 index 0000000..a1e6185 --- /dev/null +++ b/src/routes/launches/golden.test.ts @@ -0,0 +1,34 @@ +import { expect, test } from "bun:test"; +import golden from "./fixtures/zero-seed-launch-api-v3.golden.json"; +import { + ClaimPage, + LaunchDetail, + LaunchList, + OwnerPage, + SCHEMA_VERSION, + SwapPage, +} from "./schema"; + +// Written by scripts/verifyLaunchReplay.ts (LAUNCH_GOLDEN_DIR) from the actual +// handlers over the indexer's replayed local fixture; a machine-readable +// example of every v3 response for integrators. +test("the emitted-state golden responses parse with the v3 schemas", () => { + expect(golden.schema_version).toBe(SCHEMA_VERSION); + for (const list of [golden.list, golden.second, golden.canonicalList]) + LaunchList.parse(list); + for (const detail of [golden.nativeDetail, golden.erc20Detail]) + LaunchDetail.parse(detail); + SwapPage.parse(golden.nativeSwaps); + for (const claims of [golden.nativeClaims, golden.erc20Claims]) + ClaimPage.parse(claims); + for (const owners of [golden.nativeOwners, golden.erc20Owners]) + OwnerPage.parse(owners); + expect(golden.nativeDetail.quote_token).toEqual({ + address: "0x0000000000000000000000000000000000000000", + name: null, + symbol: null, + decimals: 18, + kind: "native", + }); + expect(golden.erc20Detail.quote_token.kind).toBe("erc20"); +}); diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts index b9165cd..a8c166e 100644 --- a/src/routes/launches/index.ts +++ b/src/routes/launches/index.ts @@ -8,8 +8,17 @@ import { ErrorResponseType, StatusError, } from "../../shared/errors"; -import { LaunchList, LaunchDetail, SwapPage, ClaimPage } from "./schema"; -import { NumericStringType } from "../../shared/validation/address"; +import { + LaunchList, + LaunchDetail, + SwapPage, + ClaimPage, + OwnerPage, +} from "./schema"; +import { + AddressType, + NumericStringType, +} from "../../shared/validation/address"; const chainId = z.coerce .bigint() @@ -33,6 +42,11 @@ const cursor = z .max((1n << 63n) - 1n), ) .openapi({ type: "string" }); +// Numeric like every address query parameter, so the edge cache key folds it. +const address = AddressType.refine( + (value) => BigInt(value) < 1n << 160n, + "Address out of range", +); const limit = z.coerce.number().int().min(1).max(200).default(50); const params = z.object({ chainId, poolId }); const page = z.object({ after_event_id: cursor.optional(), limit }); @@ -68,22 +82,24 @@ export const ListLaunches = defineRoute( chainId, before_event_id: cursor.optional(), limit, - creator: z - .string() - .regex(/^0x[0-9a-fA-F]{40}$/) - .optional(), + // owner at creation + creator: address.optional(), + // current owner + owner: address.optional(), }), }, responses: { 200: response(LaunchList), ...errors }, }), async (c) => { - const { chainId, before_event_id, limit, creator } = c.req.valid("query"); + const { chainId, before_event_id, limit, creator, owner } = + c.req.valid("query"); const q = await launchQueries(c.env); const data = await q.readZeroSeedList({ chainId, cursor: before_event_id, limit, creator: creator === undefined ? undefined : BigInt(creator), + owner: owner === undefined ? undefined : BigInt(owner), }); return c.json(data, 200, headers); }, @@ -143,7 +159,8 @@ export const ListLaunchClaims = defineRoute( path: "/launches/{chainId}/{poolId}/claims", tags: ["Launches"], operationId: "get_ListLaunchClaims", - summary: "List atomic full two-asset creator fee claims", + summary: + "List atomic full two-asset creator fee claims, each paid out once by the periphery", request: { params, query: page }, responses: { 200: response(ClaimPage), ...errors }, }), @@ -163,3 +180,30 @@ export const ListLaunchClaims = defineRoute( ); }, ); + +export const ListLaunchOwners = defineRoute( + createRoute({ + method: "get", + path: "/launches/{chainId}/{poolId}/owners", + tags: ["Launches"], + operationId: "get_ListLaunchOwners", + summary: "List the creation owner and every later ownership transfer", + request: { params, query: page }, + responses: { 200: response(OwnerPage), ...errors }, + }), + async (c) => { + const { chainId, poolId } = c.req.valid("param"); + const { after_event_id, limit } = c.req.valid("query"); + const q = await launchQueries(c.env); + return c.json( + await q.readZeroSeedOwners({ + chainId, + poolId: BigInt(poolId), + cursor: after_event_id, + limit, + }), + 200, + headers, + ); + }, +); diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index a4c38b5..45938bc 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -1,15 +1,26 @@ import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { gunzipSync } from "node:zlib"; import { + DECAY_TABLE_Q128, + DECAY_ZERO_FROM, + decayQ128, decayedFee, feeSchedule, - powQ64, LOCAL_INSTANCES, supportedInstances, supportedLaunch, } from "./policy"; -import { ClaimPage, LaunchDetail, LaunchList, SwapPage } from "./schema"; +import { + ClaimPage, + LaunchDetail, + LaunchList, + OwnerPage, + SwapPage, +} from "./schema"; -test("local instance manifests bind every constructor/runtime/ABI field", () => { +test("local instance manifests bind every constructor/runtime/ABI/periphery field", () => { expect(supportedInstances(LOCAL_INSTANCES, true)).toEqual(LOCAL_INSTANCES); expect(supportedInstances(LOCAL_INSTANCES, false)).toEqual([]); expect(supportedInstances([], true)).toEqual([]); @@ -20,7 +31,14 @@ test("local instance manifests bind every constructor/runtime/ABI field", () => ).toEqual([]); } expect(supportedLaunch(pin, LOCAL_INSTANCES)).toBe(true); - for (const key of ["chainId", "address", "core", "quote"] as const) { + for (const key of [ + "chainId", + "address", + "periphery", + "core", + "quote", + "quoteKind", + ] as const) { expect( supportedLaunch({ ...pin, [key]: "unreviewed" }, LOCAL_INSTANCES), ).toBe(false); @@ -29,173 +47,144 @@ test("local instance manifests bind every constructor/runtime/ABI field", () => } }); +test("native and ERC-20 quote provenance stay distinct", () => { + const [native, weth] = LOCAL_INSTANCES; + expect([native.quote, native.quoteKind, native.quoteRuntime]).toEqual([ + "0x0000000000000000000000000000000000000000", + "native", + null, + ]); + expect(weth.quoteKind).toBe("erc20"); + expect(weth.quoteRuntime).toMatch(/^0x[0-9a-f]{64}$/); + // relabelling either kind, or giving native ETH a runtime, is unreviewed + expect(supportedInstances([{ ...native, quoteKind: "erc20" }], true)).toEqual( + [], + ); + expect( + supportedInstances([{ ...native, quoteRuntime: weth.quoteRuntime }], true), + ).toEqual([]); + expect( + supportedLaunch({ ...weth, quoteKind: "native" }, LOCAL_INSTANCES), + ).toBe(false); +}); + const input = { start: 1000n, - duration: 3n, - initial: 10n, - final: 3n, - // 2^63: b^t = 2^(64-t), E = 2^61, so the excess is floor(7 * (2^(64-t) - 2^61) / (2^64 - 2^61)). - decayFactor: 1n << 63n, + duration: 3600n, + initial: 9223372036854775808n, + final: 55340232221128654n, indexedTime: 999n, }; -test("fee phases and the normalized exponential use indexed time only", () => { +test("fee phases use indexed time only; 50% -> 0.3% over 3600 s", () => { const cases = [ - [999n, "before_trading_start", "10"], - [1000n, "fee_decay", "10"], - [1001n, "fee_decay", "6"], - [1002n, "fee_decay", "4"], - [1003n, "final_fee", "3"], - [1000000000n, "final_fee", "3"], + [999n, "before_trading_start", "9223372036854775808"], + [1000n, "fee_decay", "9223372036854775808"], + [4599n, "fee_decay", null], + [4600n, "final_fee", "55340232221128654"], + [1000000000n, "final_fee", "55340232221128654"], ] as const; for (const [indexedTime, phase, rate] of cases) { const result = feeSchedule({ ...input, indexedTime }); expect(result.phase).toBe(phase); - expect(result.fee_rate).toBe(rate); - expect(result.decay_factor).toBe((1n << 63n).toString()); + if (rate) expect(result.fee_rate).toBe(rate); + expect(result.decay).toEqual({ + kind: "fixed_base_q128", + half_life_seconds: 600, + }); expect(result.evaluated_at).toBe(Number(indexedTime)); } + // halvings: 600 s past the start the unscaled decay is exactly 2^127 - 1 + expect(decayQ128(600n)).toBe((1n << 127n) - 1n); }); -// Contract vectors at evm-contracts b1cbb1c1 (test/vectors/zero-seed-launch.json): -// fixture 5% -> 0.3%, start 1000, study factors for D=3600 and D=600. -test("schedule reproduces the contract fee rows and chord counterexamples", () => { - const factors: Record = { - "3600": 18425445863997925462n, - "600": 18319323104848571946n, - }; - const rows = [ - ["3600", 0n, 922337203685477580n], - ["3600", 1n, 921320298688452636n], - ["3600", 1800n, 151673229050500749n], - ["3600", 3599n, 55356139728168999n], - ["3600", 3600n, 55340232221128654n], - ["600", 1n, 916253358061131122n], - ["600", 300n, 151673229050500755n], - ["600", 599n, 55435953504208715n], - ["600", 600n, 55340232221128654n], - ] as const; - for (const [duration, t, fee] of rows) - expect( - decayedFee( - 922337203685477580n, - 55340232221128654n, - factors[duration], - BigInt(duration), - t, - ), - ).toBe(fee); - // Nearly flat factors lie above the integer linear chord; reproduced, not corrected. - const exceptions = [ - [ - 18446744073692774400n, - 600n, - 561n, - 6145946118234823704n, - 3773859608321006899n, - 3928045231480721956n, - ], - [ - 18446744073675997184n, - 600n, - 596n, - 18262276632972456099n, - 16093730704593406921n, - 16108187677454293848n, - ], - [ - 18446744073709551608n, - 4294967295n, - 4197406844n, - 18446744073709551615n, - 18012629042273719369n, - 18022489992398216608n, - ], - [ - 18446744073709551608n, - 4294967295n, - 4028198074n, - 18446744073709551615n, - 14193599660735192734n, - 14457771176879541746n, - ], - [ - 18446744073709551608n, - 4294967295n, - 4074259523n, - 18446744073709551615n, - 11939927239388337278n, - 12274296501798491214n, - ], - ] as const; - for (const [factor, duration, t, initial, final, fee] of exceptions) - expect(decayedFee(initial, final, factor, duration, t)).toBe(fee); - expect(powQ64(12345n, 0n)).toBe(1n << 64n); -}); +const vectorsDir = resolve(import.meta.dir, "vectors"); -// SHA-256 chains h = sha256(h || fee as 8-byte big-endian) over every second t = 0..D, pinned in the -// contract vectors (fee_curve.all_second_digests_sha256); 25,212 evaluations in total. -test("every second of both study schedules at six rates matches the contract digests", () => { - const rates: Record = { - "5": 922337203685477580n, - "50": 9223372036854775808n, - "75": 13835058055282163712n, - "90": 16602069666338596454n, - "99": 18262276632972456099n, - max: 18446744073709551615n, +// The protocol's independent vectors (250-digit Decimal generator, not this +// code): every second 0..D of 12 curves, rates 5/50/75/90/99/max% and +// durations 600/3600 s, [initial, final, duration, elapsed, fee]. +test("decayedFee reproduces all 25,212 independent all-second vectors and the manifest digests", () => { + const rows = JSON.parse( + gunzipSync( + readFileSync(resolve(vectorsDir, "fee-curve-all-seconds.json.gz")), + ).toString("utf8"), + ) as [string, string, string, string, string][]; + expect(rows.length).toBe(25_212); + const chains = new Map(); + for (const [initial, final, duration, elapsed, fee] of rows) { + const actual = decayedFee( + BigInt(initial), + BigInt(final), + BigInt(duration), + BigInt(elapsed), + ); + if (actual.toString() !== fee) + throw new Error(`${initial} ${duration} ${elapsed}: ${actual} != ${fee}`); + // h = sha256(h || fee as 8-byte big-endian), in elapsed order + const key = `${duration}:${initial}`; + const word = new Uint8Array(40); + word.set(chains.get(key) ?? new Uint8Array(32)); + new DataView(word.buffer).setBigUint64(32, actual); + chains.set( + key, + new Uint8Array(new Bun.CryptoHasher("sha256").update(word).digest()), + ); + } + const vectors = JSON.parse( + readFileSync(resolve(vectorsDir, "fee-decay-vectors.json"), "utf8"), + ) as { + table_q128: string[]; + zero_at_or_above: number; + decay_samples: { t: number; decay_q128: string }[]; + fee_rows: { + initial: string; + final: string; + duration: number; + elapsed: number; + fee: string; + }[]; + all_second_digests_sha256: Record>; }; - const digests: [bigint, bigint, Record][] = [ - [ - 600n, - 18319323104848571946n, - { - "5": "6346e95d7a66797ce3270ff25d5fc19e64c2e9ccabf1c2dbc03a946276fe64b5", - "50": "73b08ebf1a8d045805ec42c84200bbd841e25e515a1aef7f7fb6d246c6b6027a", - "75": "ac34495b80e94c660319b488c697f7ecf05af9eeb3f4a03c27c1b7bc79d4e5f4", - "90": "87c5e5119e29faddc71e59753cca77fea297b4d76419fd0b66a78a340c4b7cef", - "99": "af32943cbe63a0991532e385ad42960e1ce4040d59981ca6d4a4d4b86dc20cb0", - max: "dce005ce818ddc4248d259c7a6e2b9c0652cb4b4705b70cf9f4f04f1270023af", - }, - ], - [ - 3600n, - 18425445863997925462n, - { - "5": "306aaf4b3acfabe5ba98a1ee86cf91ba6564e010f806d1f592f3a403620bfd77", - "50": "ac4fa207bb2beec03e9a122e5839482d14abb55c72a4b65c155da02ea15baf51", - "75": "c7eaeb00462cc16f7ce3684309604d97a9a22a1eaec15a1b8862bbfd68705771", - "90": "fb0df6da40ddcacfe08f4a599b3078a1e6dbe0017b97e73b6b4e384563247354", - "99": "ee35ebedb9d7c09c69675d9517cdc1dc26e177eb61e0aa76fd13eb63dee8cba8", - max: "82e00f48c0d5dc03d757e417a60a410a8ee9cfd0d41c12cfb72547bfc73ac4f7", - }, - ], - ]; - let checked = 0; - for (const [duration, factor, byRate] of digests) { - for (const [label, rate] of Object.entries(rates)) { - let chain = new Uint8Array(32); - for (let t = 0n; t <= duration; t++) { - const fee = decayedFee(rate, 55340232221128654n, factor, duration, t); - const word = new Uint8Array(40); - word.set(chain); - new DataView(word.buffer).setBigUint64(32, fee); - chain = new Uint8Array( - new Bun.CryptoHasher("sha256").update(word).digest(), - ); - checked++; - } - expect(Buffer.from(chain).toString("hex")).toBe(byRate[label]); + const rates: Record = { + "5": "922337203685477580", + "50": "9223372036854775808", + "75": "13835058055282163712", + "90": "16602069666338596454", + "99": "18262276632972456099", + max: "18446744073709551615", + }; + // the manifest lists 12 digests; this file carries the five above 5% + let digests = 0; + for (const [duration, byRate] of Object.entries( + vectors.all_second_digests_sha256, + )) + for (const [label, digest] of Object.entries(byRate)) { + expect( + Buffer.from(chains.get(`${duration}:${rates[label]}`)!).toString("hex"), + ).toBe(digest); + digests++; } - } - expect(checked).toBe(25_212); + expect(digests).toBeGreaterThanOrEqual(10); + expect(DECAY_TABLE_Q128.map(String)).toEqual(vectors.table_q128); + expect(DECAY_ZERO_FROM).toBe(BigInt(vectors.zero_at_or_above)); + for (const { t, decay_q128 } of vectors.decay_samples) + expect(decayQ128(BigInt(t)).toString()).toBe(decay_q128); + for (const r of vectors.fee_rows) + expect( + decayedFee( + BigInt(r.initial), + BigInt(r.final), + BigInt(r.duration), + BigInt(r.elapsed), + ).toString(), + ).toBe(r.fee); }); test("invalid indexed domains fail, never coerce or overflow", () => { for (const change of [ { start: -1n }, { duration: 0n }, - { final: 11n }, - { decayFactor: 0n }, - { decayFactor: 1n << 64n }, + { duration: 1n << 32n }, + { final: input.initial + 1n }, { initial: 1n << 64n }, { indexedTime: -1n }, { start: (1n << 64n) - 1n }, @@ -203,11 +192,12 @@ test("invalid indexed domains fail, never coerce or overflow", () => { ]) { expect(() => feeSchedule({ ...input, ...change })).toThrow(); } + expect(() => decayQ128(-1n)).toThrow(); }); -test("frozen schemas expose no staged status/stats/exhausted fields", () => { +test("frozen schemas expose no staged status/stats/exhausted or decay-factor fields", () => { const empty = { - schema_version: "zero-seed-launch-api/v2", + schema_version: "zero-seed-launch-api/v3", as_of: { block_number: "1", block_hash: `0x${"a".repeat(64)}`, @@ -217,10 +207,16 @@ test("frozen schemas expose no staged status/stats/exhausted fields", () => { next_cursor: null, }; expect(LaunchList.safeParse(empty).success).toBe(true); + expect( + LaunchList.safeParse({ + ...empty, + schema_version: "zero-seed-launch-api/v2", + }).success, + ).toBe(false); expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe( false, ); - for (const schema of [SwapPage, ClaimPage]) { + for (const schema of [SwapPage, ClaimPage, OwnerPage]) { expect( schema.safeParse({ ...empty, @@ -233,4 +229,7 @@ test("frozen schemas expose no staged status/stats/exhausted fields", () => { expect(Object.keys(LaunchDetail.shape.inventory.shape)).not.toContain( "exhausted", ); + expect(Object.keys(LaunchDetail.shape.fee_schedule.shape)).not.toContain( + "decay_factor", + ); }); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 911fb06..e92f81e 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -1,10 +1,12 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; +// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 4f1a3060 +// build, as in the protocol manifest. export const ABI_SHA256 = - "bc51b39284a5a2a0970d3a804420762ce291dc01f9c21ddf31840e354a3792bb"; -export const POLICY_SHA256 = - "7864a2d33a0fa8964922a5225653ed19842c590091f289eb4e29306feac6f6e1"; + "21f9e2c0166ab28ce8573c481de5b0fc2f37dd7595d496d326f0a2d1a544254e"; +export const PERIPHERY_ABI_SHA256 = + "5c563488b843aee923d0d819a66489eeee543dd20d1582de1f5df643f5d08a75"; const CORE = "0x00000000000014aa86c5d3c41765bb24e11bd701"; const WETH_RUNTIME = "0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c"; @@ -12,36 +14,52 @@ const WETH_RUNTIME = export interface LaunchInstance { chainId: string; address: string; + runtime: string; + periphery: string; + peripheryRuntime: string; core: string; quote: string; - runtime: string; - quoteRuntime: string; + quoteKind: "native" | "erc20"; + /** null for native ETH, which has no code. */ + quoteRuntime: string | null; abiSha256: string; + peripheryAbiSha256: string; } -// Constructor-executed LOCAL instances in indexer cdf58fc / fixture b4026bee -// (evm-contracts b1cbb1c1, draft exponential creator-fee decay). -// No live instance exists. Never enable these via a hosted configuration. +// Constructor-executed LOCAL instances of the indexer replay fixture +// (evm-contracts 4f1a3060, anvil, no fork): native ETH and relocated plain +// WETH. No live instance exists. Never enable these via a hosted +// configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - address: "0x5142e167b32877ca3cf3060010a0a1418e3ef1b9", - core: CORE, - quote: "0xffffffffffffffffffffffffffffffffffffffff", + address: "0x5114703a4cf4256f0acb68f992c861d8c4d1b173", runtime: - "0x8fb360bb48006819049e6b9a53679ccaa01264e61465ce8e8cd2e8b185d3a8d1", - quoteRuntime: WETH_RUNTIME, + "0x846786a63ba1a2c976f748fdc94b8cbc8f76dc64f07452333d36ff564f281bf7", + periphery: "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + peripheryRuntime: + "0x57d0ee27c634ac08d08b60bf8e6c2f185c7e9444613ccfde5ac9bbb5e3f84f2a", + core: CORE, + quote: "0x0000000000000000000000000000000000000000", + quoteKind: "native", + quoteRuntime: null, abiSha256: ABI_SHA256, + peripheryAbiSha256: PERIPHERY_ABI_SHA256, }, { chainId: "31337", - address: "0x51174b5db84c81a906ec55445070fd84c8c8e655", - core: CORE, - quote: "0x0000000000000000000000000000000000010000", + address: "0x519435a42752da78e395999218d0ecc4fdcbcf4b", runtime: - "0xa951db07ad354ea45df3ff1b720b69c7cc588c4feb44aaea79cd79639da61f6e", + "0x56e32aeac03464c3babd754fc21e0594c42d82db9d9c0cc3dd1ce1ec20273750", + periphery: "0x94e1b338def908449e6ea7c7d254630b45d27562", + peripheryRuntime: + "0xb78ea649f2e1817c851f7417a604d1565a346c6bec022abc821e3f1110739ac8", + core: CORE, + quote: "0xffffffffffffffffffffffffffffffffffffffff", + quoteKind: "erc20", quoteRuntime: WETH_RUNTIME, abiSha256: ABI_SHA256, + peripheryAbiSha256: PERIPHERY_ABI_SHA256, }, ]; @@ -61,15 +79,20 @@ export function supportedInstances( } export function supportedLaunch( - row: Pick, + row: Pick< + LaunchInstance, + "chainId" | "address" | "periphery" | "core" | "quote" | "quoteKind" + >, enabled: readonly LaunchInstance[], ): boolean { return enabled.some( (pin) => pin.chainId === row.chainId && pin.address === row.address && + pin.periphery === row.periphery && pin.core === row.core && - pin.quote === row.quote, + pin.quote === row.quote && + pin.quoteKind === row.quoteKind, ); } @@ -90,43 +113,63 @@ function seconds(value: bigint) { return Number(value); } -const Q64 = 1n << 64n; +const Q128 = 1n << 128n; +/** Elapsed seconds from which the decay is exactly zero. */ +export const DECAY_ZERO_FROM = 131072n; +/** round(2^128 * 2^(-2^i/600)) for bits i = 0..16 (math/feeDecay.sol). */ +export const DECAY_TABLE_Q128: readonly bigint[] = [ + 339889484297159900493648602831395569057n, + 339497055287118257930533248299650400736n, + 338713556013920184336129943844147573651n, + 337151977828609613753562640265082287810n, + 334050386396171404885545237344599070716n, + 327932539264834336949184815807600758235n, + 316030922441737935988095136387330274498n, + 293507844215098455952964317090465751204n, + 253162852354938356992240649094357060196n, + 188347784201757897225807540435664088841n, + 104251325552682051605813724741512712010n, + 31939177389160626453527783486618815759n, + 2997836947964109484105344435429137776n, + 26410496811510722015611852309540293n, + 2049810421099131403277778219901n, + 12347753427443481862030n, + 448060n, +]; /** - * powQ64 of the contract (math/feeDecay.sol): factor ** exponent in Q0.64 by - * square-and-multiply, flooring after every multiply and squaring only while - * bits remain. BigInt division floors for these non-negative operands. + * decayQ128 of the contract: 2^(-elapsed/600) in Q128. Starting at 2^128, + * multiply by the constant of each set bit in ascending order and floor after + * every multiply; exactly zero from 2^17 seconds. BigInt division floors for + * these non-negative operands. */ -export function powQ64(factor: bigint, exponent: bigint): bigint { - let result = Q64; - let base = factor; - let rest = exponent; - while (rest !== 0n) { - if ((rest & 1n) === 1n) result = (result * base) / Q64; - rest >>= 1n; - if (rest !== 0n) base = (base * base) / Q64; - } +export function decayQ128(elapsed: bigint): bigint { + if (elapsed < 0n) throw new Error("Negative elapsed time"); + if (elapsed >= DECAY_ZERO_FROM) return 0n; + let result = Q128; + DECAY_TABLE_Q128.forEach((constant, bit) => { + if ((elapsed >> BigInt(bit)) & 1n) result = (result * constant) / Q128; + }); return result; } /** - * feeAt of the contract: initial until the start, then - * final + floor((initial - final) * (b^t - b^D) / (2^64 - b^D)), then final. + * decayedFee of the contract: initial at zero elapsed, then + * final + floor((initial - final) * (decay(t) - decay(D)) / (2^128 - decay(D))), + * then final from the duration on. */ export function decayedFee( initial: bigint, final: bigint, - factor: bigint, duration: bigint, elapsed: bigint, ): bigint { if (elapsed === 0n) return initial; if (elapsed >= duration) return final; - const endpoint = powQ64(factor, duration); + const endpoint = decayQ128(duration); return ( final + - ((initial - final) * (powQ64(factor, elapsed) - endpoint)) / - (Q64 - endpoint) + ((initial - final) * (decayQ128(elapsed) - endpoint)) / (Q128 - endpoint) ); } @@ -135,25 +178,24 @@ export function feeSchedule(input: { duration: bigint; initial: bigint; final: bigint; - decayFactor: bigint; indexedTime: bigint; }): z.infer { - const { start, duration, initial, final, decayFactor, indexedTime } = input; - [start, duration, initial, final, decayFactor, indexedTime].forEach(uint64); - if (duration === 0n || final > initial || decayFactor === 0n) { + const { start, duration, initial, final, indexedTime } = input; + [start, duration, initial, final, indexedTime].forEach(uint64); + if (duration === 0n || duration > 0xffffffffn || final > initial) { throw new Error("Invalid indexed fee schedule"); } const end = uint64(start + duration); const phase = feePhase(start, end, indexedTime); const elapsed = indexedTime > start ? indexedTime - start : 0n; - const rate = decayedFee(initial, final, decayFactor, duration, elapsed); + const rate = decayedFee(initial, final, duration, elapsed); return { trading_start: seconds(start), fee_duration: seconds(duration), fee_end: seconds(end), initial_fee: initial.toString(), final_fee: final.toString(), - decay_factor: decayFactor.toString(), + decay: { kind: "fixed_base_q128", half_life_seconds: 600 }, phase, fee_rate: rate.toString(), evaluated_at: seconds(indexedTime), diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index 5d90d49..1e4ae97 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -8,6 +8,8 @@ import { ClaimPage, LaunchDetail, LaunchList, + OwnerPage, + SCHEMA_VERSION, SwapPage, } from "./schema"; @@ -32,7 +34,11 @@ interface LaunchRow extends EventRow { token1: Numeric; token: Numeric; quote_token: Numeric; - creator: Numeric; + quote_kind: "native" | "erc20"; + controller: Numeric; + initial_owner: Numeric; + current_owner: Numeric; + owner_change_count: bigint; name: string; symbol: string; decimals: number; @@ -47,7 +53,6 @@ interface LaunchRow extends EventRow { fee_duration: bigint; initial_fee: Numeric; final_fee: Numeric; - decay_factor: Numeric; position_id: Numeric; tick_lower: number; tick_upper: number; @@ -91,7 +96,7 @@ function event(row: EventRow) { } function summary(row: LaunchRow, asOf: z.infer) { return { - schema_version: "zero-seed-launch-api/v2" as const, + schema_version: SCHEMA_VERSION, chain_id: decimal(row.chain_id), pool_id: hex(row.pool_id, 32), pool_key: { @@ -112,14 +117,11 @@ function summary(row: LaunchRow, asOf: z.infer) { symbol: row.symbol, decimals: row.decimals, }, - quote_token: { - address: hex(row.quote_token, 20), - name: row.quote_name, - symbol: row.quote_symbol, - decimals: row.quote_decimals, - }, + quote_token: quoteToken(row), launch_token_is_token1: row.token_is_token1, - creator: hex(row.creator, 20), + controller: hex(row.controller, 20), + creator: hex(row.initial_owner, 20), + owner: hex(row.current_owner, 20), ask_tick: row.ask_tick, upper_tick: row.upper_tick, fee_schedule: feeSchedule({ @@ -127,13 +129,30 @@ function summary(row: LaunchRow, asOf: z.infer) { duration: row.fee_duration, initial: BigInt(row.initial_fee), final: BigInt(row.final_fee), - decayFactor: BigInt(row.decay_factor), indexedTime: BigInt(asOf.block_time), }), created: event(row), as_of: asOf, }; } +// Native ETH has no token contract: 18 decimals, no ERC-20 metadata. +function quoteToken(row: LaunchRow) { + if (row.quote_kind === "native") + return { + address: hex(row.quote_token, 20), + name: null, + symbol: null, + decimals: 18, + kind: "native" as const, + }; + return { + address: hex(row.quote_token, 20), + name: row.quote_name, + symbol: row.quote_symbol, + decimals: row.quote_decimals, + kind: "erc20" as const, + }; +} function ledger(accrued: Numeric, claimed: Numeric) { return { accrued: decimal(accrued), @@ -163,8 +182,8 @@ function detail(row: LaunchRow, asOf: z.infer) { reserves_basis: "core_pool_balance", }, fee_ledger: { - holder: launch.creator, - transferable: false, + holder: launch.owner, + transferable: true, launch_token: row.token_is_token1 ? ledger(row.accrued1, row.claimed1) : ledger(row.accrued0, row.claimed0), @@ -173,6 +192,7 @@ function detail(row: LaunchRow, asOf: z.infer) { : ledger(row.accrued1, row.claimed1), swap_count: Number(row.swap_count), claim_count: Number(row.claim_count), + owner_change_count: Number(row.owner_change_count), last_event_id: decimal(row.last_event_id), }, inventory: { @@ -256,10 +276,13 @@ export class LaunchQueries { // Screening occurs in SQL BEFORE cursor/limit; no arbitrary quote can // occupy a page or be served by detail/history. const pins = this.enabled.filter((p) => p.chainId === chainId.toString()); + // Only launches created through the instance's periphery have an owner. const checks = pins.map( (p) => tx` (c.emitter = ${BigInt(p.address).toString()}::numeric + AND c.controller = ${BigInt(p.periphery).toString()}::numeric AND c.quote_token = ${BigInt(p.quote).toString()}::numeric + AND c.quote_kind = ${p.quoteKind} AND pk.core_address = ${BigInt(p.core).toString()}::numeric) `, ); @@ -269,6 +292,7 @@ export class LaunchQueries { qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, qt.token_decimals AS quote_decimals, s.accrued0, s.accrued1, s.claimed0, s.claimed1, s.swap_count, s.claim_count, s.last_event_id, + s.initial_owner, s.current_owner, s.owner_change_count, pt.balance0, pt.balance1, ps.tick FROM zero_seed_launch_created c JOIN zero_seed_launch_pool_keys registration USING (pool_key_id) @@ -278,20 +302,25 @@ export class LaunchQueries { JOIN pool_states ps USING (pool_key_id) JOIN pool_tvl pt USING (pool_key_id) LEFT JOIN erc20_tokens qt ON qt.chain_id = c.chain_id AND qt.token_address = c.quote_token + AND c.quote_kind = 'erc20' WHERE c.chain_id = ${chainId} AND pk.chain_id = c.chain_id AND pk.pool_extension = c.emitter AND pk.fee = 0 AND pk.token0 = LEAST(c.token, c.quote_token) AND pk.token1 = GREATEST(c.token, c.quote_token) AND pk.tick_spacing = c.tick_spacing + AND s.current_owner IS NOT NULL AND s.initial_owner IS NOT NULL AND (${supported}) AND ${filter} `; } - async list(input: LaunchReadOptions & { creator?: bigint }) { + async list(input: LaunchReadOptions & { creator?: bigint; owner?: bigint }) { return this.snapshot(input.chainId, async (tx, asOf) => { + const creator = input.creator?.toString() ?? null; + const owner = input.owner?.toString() ?? null; const filter = tx` (${input.cursor ?? null}::int8 IS NULL OR c.event_id < ${input.cursor ?? null}) - AND (${input.creator?.toString() ?? null}::numeric IS NULL OR c.creator = ${input.creator?.toString() ?? null}) + AND (${creator}::numeric IS NULL OR s.initial_owner = ${creator}) + AND (${owner}::numeric IS NULL OR s.current_owner = ${owner}) `; const rows = await tx` SELECT * FROM (${this.rows(tx, input.chainId, filter)}) l @@ -299,7 +328,7 @@ export class LaunchQueries { `; const page = rows.slice(0, input.limit); return parseIndexed(LaunchList, { - schema_version: "zero-seed-launch-api/v2", + schema_version: SCHEMA_VERSION, as_of: asOf, data: page.map((r) => summary(r, asOf)), next_cursor: @@ -362,7 +391,7 @@ export class LaunchQueries { }; }); return parseIndexed(SwapPage, { - schema_version: "zero-seed-launch-api/v2", + schema_version: SCHEMA_VERSION, chain_id: input.chainId.toString(), pool_id: hex(input.poolId, 32), as_of: asOf, @@ -376,23 +405,31 @@ export class LaunchQueries { async claims(input: LaunchReadOptions & { poolId: bigint }) { return this.snapshot(input.chainId, async (tx, asOf) => { const launch = await this.launch(tx, input.chainId, input.poolId); + // The periphery payout, joined to the extension claim it pays out; the + // ledger moved once, by the latter. const rows = await tx< (EventRow & { - creator: Numeric; + claimed_event_id: bigint; + owner: Numeric; recipient: Numeric; amount0: Numeric; amount1: Numeric; })[] >` - SELECT cl.*, b.block_hash, b.block_time FROM zero_seed_launch_fees_claimed cl - JOIN blocks b ON b.chain_id = cl.chain_id AND b.block_number = cl.block_number - WHERE cl.pool_key_id = ${launch.pool_key_id} - AND (${input.cursor ?? null}::int8 IS NULL OR cl.event_id > ${input.cursor ?? null}) - ORDER BY cl.event_id LIMIT ${input.limit + 1} + SELECT p.*, b.block_hash, b.block_time FROM zero_seed_launch_fees_paid p + JOIN zero_seed_launch_fees_claimed cl + ON cl.chain_id = p.chain_id AND cl.event_id = p.claimed_event_id + AND cl.pool_key_id = p.pool_key_id + AND (cl.amount0, cl.amount1) = (p.amount0, p.amount1) + JOIN blocks b ON b.chain_id = p.chain_id AND b.block_number = p.block_number + WHERE p.pool_key_id = ${launch.pool_key_id} + AND (${input.cursor ?? null}::int8 IS NULL OR p.event_id > ${input.cursor ?? null}) + ORDER BY p.event_id LIMIT ${input.limit + 1} `; const data = rows.slice(0, input.limit).map((row) => ({ event: event(row), - creator: hex(row.creator, 20), + ledger_event_id: decimal(row.claimed_event_id), + owner: hex(row.owner, 20), recipient: hex(row.recipient, 20), launch_token_amount: decimal( launch.token_is_token1 ? row.amount1 : row.amount0, @@ -402,7 +439,39 @@ export class LaunchQueries { ), })); return parseIndexed(ClaimPage, { - schema_version: "zero-seed-launch-api/v2", + schema_version: SCHEMA_VERSION, + chain_id: input.chainId.toString(), + pool_id: hex(input.poolId, 32), + as_of: asOf, + data, + next_cursor: + rows.length > input.limit ? data.at(-1)!.event.event_id : null, + }); + }); + } + + async owners(input: LaunchReadOptions & { poolId: bigint }) { + return this.snapshot(input.chainId, async (tx, asOf) => { + const launch = await this.launch(tx, input.chainId, input.poolId); + const rows = await tx< + (EventRow & { previous_owner: Numeric; new_owner: Numeric })[] + >` + SELECT o.*, b.block_hash, b.block_time FROM zero_seed_launch_owner_changed o + JOIN blocks b ON b.chain_id = o.chain_id AND b.block_number = o.block_number + WHERE o.pool_key_id = ${launch.pool_key_id} + AND (${input.cursor ?? null}::int8 IS NULL OR o.event_id > ${input.cursor ?? null}) + ORDER BY o.event_id LIMIT ${input.limit + 1} + `; + const data = rows.slice(0, input.limit).map((row) => ({ + event: event(row), + previous_owner: + BigInt(row.previous_owner) === 0n + ? null + : hex(row.previous_owner, 20), + new_owner: hex(row.new_owner, 20), + })); + return parseIndexed(OwnerPage, { + schema_version: SCHEMA_VERSION, chain_id: input.chainId.toString(), pool_id: hex(input.poolId, 32), as_of: asOf, diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index 9973508..cd3cc69 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -17,6 +17,7 @@ test("hosted launch entrypoints stay disabled without database or live pins", as base, `${base}/swaps`, `${base}/claims`, + `${base}/owners`, ]) { for (const env of [{}, localEnv]) { const response = await request(path, env); @@ -36,6 +37,9 @@ test("invalid arguments return 400 and the removed stats route returns 404", asy "/launches?chainId=31337&limit=0", "/launches?chainId=31337&before_event_id=9223372036854775808", `${base}/swaps?after_event_id=-9223372036854775809`, + `${base}/owners?limit=201`, + `/launches?chainId=31337&owner=0x1${"0".repeat(40)}`, + "/launches?chainId=31337&creator=owner", `/launches/31337/0x1${"0".repeat(64)}`, "/launches/31337/NaN", ]) { diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index 887bd7e..392c2dd 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,7 +1,8 @@ import { z } from "@hono/zod-openapi"; -// Frozen API v1 shapes (EKU-1513); behavioral policy is separately v2. -export const SchemaVersion = z.literal("zero-seed-launch-api/v2"); +// v3: native ETH quote kind, fixed-base fee table, periphery owner identity. +export const SCHEMA_VERSION = "zero-seed-launch-api/v3" as const; +export const SchemaVersion = z.literal(SCHEMA_VERSION); const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); const Address = z.string().regex(/^0x[0-9a-f]{40}$/); @@ -24,6 +25,9 @@ const Token = z.strictObject({ symbol: z.string().nullable(), decimals: z.number().int().min(0).max(255).nullable(), }); +// Native ETH is address 0, always token0, 18 decimals and has no token +// contract, so no ERC-20 metadata is read for it (name and symbol are null). +const QuoteToken = Token.extend({ kind: z.enum(["native", "erc20"]) }); const PoolKey = z.strictObject({ core_address: Address, token0: Address, @@ -39,8 +43,11 @@ export const FeeSchedule = z.strictObject({ fee_end: Seconds, initial_fee: U, final_fee: U, - // Q0.64 per-second multiplier of the unscaled exponential, not a half-life. - decay_factor: U, + // 2^(-t/600) from a fixed Q128 table, rescaled to reach final_fee at fee_end. + decay: z.strictObject({ + kind: z.literal("fixed_base_q128"), + half_life_seconds: z.literal(600), + }), phase: z.enum(["before_trading_start", "fee_decay", "final_fee"]), fee_rate: U, evaluated_at: Seconds, @@ -51,9 +58,14 @@ export const LaunchSummary = z.strictObject({ pool_id: Bytes32, pool_key: PoolKey, token: Token, - quote_token: Token, + quote_token: QuoteToken, launch_token_is_token1: z.boolean(), + // The locker that forwarded the creation: the instance's periphery. + controller: Address, + // Owner at creation (LaunchOwnerChanged from the zero address); never changes. creator: Address, + // Current owner: holds the unclaimed and future creator fees; transferable. + owner: Address, ask_tick: z.number().int(), upper_tick: z.number().int(), fee_schedule: FeeSchedule, @@ -76,11 +88,12 @@ export const LaunchDetail = LaunchSummary.extend({ }), fee_ledger: z.strictObject({ holder: Address, - transferable: z.literal(false), + transferable: z.literal(true), launch_token: LedgerSide, quote_token: LedgerSide, swap_count: z.number().int().nonnegative(), claim_count: z.number().int().nonnegative(), + owner_change_count: z.number().int().positive(), last_event_id: I, }), inventory: z.strictObject({ @@ -105,13 +118,22 @@ export const Swap = z.strictObject({ fee_amount: U, fee_asset: z.enum(["launch_token", "quote_token"]), }); +// One claim: the periphery payout event, linked to the extension ledger +// event of the same transaction. The amounts are counted once. export const Claim = z.strictObject({ event: EventRef, - creator: Address, + ledger_event_id: I, + owner: Address, recipient: Address, launch_token_amount: U, quote_token_amount: U, }); +export const OwnerChange = z.strictObject({ + event: EventRef, + // null for the creation + previous_owner: Address.nullable(), + new_owner: Address, +}); const PageIdentity = z.strictObject({ schema_version: SchemaVersion, chain_id: U, @@ -121,3 +143,4 @@ const PageIdentity = z.strictObject({ }); export const SwapPage = PageIdentity.extend({ data: z.array(Swap) }); export const ClaimPage = PageIdentity.extend({ data: z.array(Claim) }); +export const OwnerPage = PageIdentity.extend({ data: z.array(OwnerChange) }); diff --git a/src/routes/launches/vectors/fee-curve-all-seconds.json.gz b/src/routes/launches/vectors/fee-curve-all-seconds.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..39be40980ca71df25e480c43a6fe9eb1fee4f63c GIT binary patch literal 355072 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 z zS>;$L_3 zRVecuD{L2Ctes;6GJ)k3bD2B$>`y9%~|yKv2O1c{pI&y zedkwF&$TP>OQ^3Rl$`_W&d~I;1DuQ61ahN6)V{|Q@|F|DUG@e{ zj`r5(#c7H%z@MWz?qphkj=?FeD$#Gs9omH=I=;PwPt^*peF!{?Kbh97E@JRbQ|v4K z_eshEupFNLQE#X>*l-bMApw}pZ66-GCX~CnZT~Y&)%E=dO##nwyBuZv@tXk9MlXtt z!V}47|+%QajYetOiOitq=A?|5XGOIbI=3PT|k6W&hynZ z&3l8J1M)5PL|&qlU24aBJSQ6k8V%(UHTanrqjcJXynHwU+TqWh@pEVx(5hWP83fB#UvUqj1J>oKm;Hz*%D6fE7l7kw zk(gf}0bU6QS;d_rj(i3AK6|~p(@A?$BNxaAj>T_ZW|a?FhYv6+{{n>Xd~z8-RyVTg z>p2J7CuG4P6TNVcC?B9|K?p$aqg8hYeVOS>p-7p;wf;r_`}Ts_VhbJi#)hY3Ui9zb z_yLj-a1_9a#YmDkCV3C_@qtoOv;{1oGI##F1-d_4S!Oe-G1H&TwI4E?ssz7l%#_S{Z7O+6mOBj|O`cgT^mj@jJRqSqgQ=NJXVLz*wKbl``es39OAtdUZt^<3V+Q!j3p z|3X0^;fd5rwAmDL{3n=XMFRLGXOu)}joNdtk`?d(KYP&v;klg1E?TJ5#EKc-I|8+B?O;LAM>dNS;BnCA-U^@&MAnL6sj7AQH=;ftFr>4 zp+c*w3h2liuJJ4ALH6|G-9vmy7UyI0Q}kA!wW2^p%0xaeKxk zoy1uPWJ$}9Jdoq6+GVwqKC9`~@+9&vydjHhuN#O#7gB&IMKTW!@GM$h!Kn`l`)E<3 zY5Be%pstNIRHE$@7$pwcr1NDnl@i3tf$xLFfbi6lJ293MggF-noKFKRU)k+3#n~S( znTftabzkVd@|`&rAD_HK`3C%zx0|MDB7rp0oJ2|0)Wl=rvwEoo!IPgIaKDI^oTndN zkgb;9dlVcQ7h`FG;B@moWF_4NKwWUb3pkXNsH&NR-ua$l&eas`ZGHrLb!CBk@qD9~ z6mW2DI37E(tZ%@c{Di+3m9f=-K66I$Spho7DP}OFIb@;97vspu0zFhE9*g<5PUiA}~roOtWZ{7^Of)b4DDUcI|D;7M|fj33#JSv+vVS;!9p2@!dQ5VBw$ z`28`pN~DUY=jSLL!QjX^f8GNyfLaV%HKj6hMlYuyAw#P zPvMIlPAxaW%77hhj+E!NaP+bfMhgI%X94Ob2%&7KTV%~=Z|OifA#CjfSZ1Z`G7v_U`a{=aM9<^fy?obovz(iIS2 zdsgyXlm`V&UtDON>MC6`kZx;v;Wkq6MZ<(i!moAKxx0n%NfyUDWY*9y{GAh@xi4#Qf-#pQ*&kGXTuS2A^;)U6iNA)66s1L)$wRDp8&chG+e zI&w>keV0M;d3$P*M@cor<>8|WA=&t!CC4T?4rFzhu>%s<`I~Ey+b|Vqwe$Lj?>u;w zpdZNJ#FO6ewSw~AH^q+*z!MUo#fHf~uIPj%FB;%Q^^8wIv@1_i`R^qYAnJQPIdgFH z#kKR$#9$6UzR?ZI-DZ?>^W9gCc4sn+mM!;cy6(#4Zxs&APD+7^K# z^1PyISnm~YRp2F~OWu@ex>{n!#vy9CxW~8l!9K#|wszxhIFAT~xcm_@;48QDr*&_? zi>BVdJOA|e{R$SbBa3@Ci zrnlb~HOwHx8B(Pm`G6J=`M<~H#KLq>n&Q_4lFa4AsHaqvfa2)yn4Xzgr#{haVW$)e z*mSu)f6-r;)U<*-VE({%%~<>0<5pHOlv3v^bddgzJ^6inO4XoJ$(e2|Z#LoZyCknD z0%JQN#*(&XT;&NVv09LDSD*36qZ3D9F|pu{9n&hF0of$S6THNsK2C6n%kiDz_bpwe zI90EJY&}{2j8Av+$>0yp(GBO`wcoRpeF`C;z6v45&TcaHx};ykgs*qb=%VU_-Ca=KubleF5_ z{lvWQ1-W48jJ$fBS{y$UnaNeICst1!ta@>B?)uB{rZd(^Fugl$<6t&ROzn>V%!B_! zN9>3{_f-@vquOtIc^P8$FC7ejpbuw#}&**4R^+?7V?y%E^n0QJ&uW$|z)qUd+*N}9$XEQ{nomV@~*+44=coK}1j3#>E=d1O^Vq8IH6r-@}(?@n|+5$)Flh9D(rIa&QHY+E1 znz$Hao#LBcFV^744R$Lpck>0}UZXXs#k+%vDc~OhrO4hhj_tD$kX}vF-}e}fV;sB< z`6%z&3!4zcT=cqTwl66D-c#0Q~xO^F3=} zvsW4PgL9%;YaO`{+p9QKb=Ss`!LLr2^OK>cCCD}=zrlyM7rPaqgGUPyGg6{g#6>TOsPk|jC3xu8v%0D)rhl7DJ2xk)Z3YN33KAC3Mj7_CC^Z}64FJvZtxAJ7!y8=hdiUMAyMF-jEc`M ztLk@9=c8?>aV-M+$L+aK=Jga zc1$b?lPWU);AQI`ZRST7CNz<-$4K!9=4QVdR4cyOkaRnJ%3`~MI$yXZEr|a8bYzUb zjw=A^L^H&t689Ds;~DT2iPvqJ*s2VEu?zRB)emDuR~yT*GIF||Q)85`FTi{`^D$#U z4(C#l%l&iiNMMf&D;PJGWdHdUo#piWgVlpl7guJTv}U5)R|_aEsR`UI^KVTZFP1Eu z(4BV!QF_ynaOLA1ptG9Hc!Z*@@;@gED#CyucsiKj6o+|ta1B^}+&NwzwkEi032yZs z_UtiKDqx1qtt?BTXYS)ysXKY3PI7WLBJkb&f>sOy(4)fym z#zMnZiquhnbcJe-l$6g4r&v>>V=K*J|qR0M~FaV+7&<15cuguCZN%^RQ9-e=W@ zBA3_PTO&+tC`R1(SY)OA(b>owfZNzmGK0H`980@KF}+!PmF&Lt{&L78f*>{0q`uDD zzeJ5s+z{6A#q4%IA(tI4*W9^L+p zS+*pYusR0o49!3>bZQlGRh(_D?z+|Ng(2v^zFjU?HOU|UjDbtu*T87LF)@a`Cr*i) z0P;+ow_FNISTrxnAUEg?`vsc1yKjziu>^_fer|Me;=+^rh&|=P7ctSfbk`3JU`W@< zy^(nX<5*yN=UI&8CElZsDXACyD6sDNfFV`rJr66jz2N)Gqp^LCS@1*g*Ly*51ua%F z!R}+uhZAveIeV0OFf}R3>siEYkv)Wtk$J{!xAgf-_$rhRi2N6E6^STjo{bI9wOcL^MJXv6zR%zEC^^I5A=g3{t*Cj^-<=L8UoMf)as{fbqe$ zVtPQCt}pTjX+*wkQ0&w%Jv0hZqbclRbTsYFWYWs333osR(~kuqDQT)DrxHfpE833I zM^1)0@2L(FhFQPNqf+^jF+-ORq%_e6#-X#;<~kl8C_LxSJgYBanM&s&_U~Fqm~B)) zwU~3ivF{`#Xvn~#w#0pvlzXC9Vz4_+rhF$`{kR~vU!afq5;pR5Sm#F2n&ipA+P0Mx zgHA&hZ5zEw{mGN;K&2LY{oAM71@|{Uu_t)pNs@+P@>-gW{uU;+ZI^9-O&9i^LPOuYpHPKWZ3*heb(<2 z)A$abssV0WOOVy7U?5|#S;@aw^1COVqUQbafic3zR{+(qR==Z`7J!;+pElA@$v*be}Vtdu9`yLFyt#4))5%><5#Y zt*?C+zLRV;Ep46*^qy}?k$?L*qR0eS&N2(G;^c`u?)ju3B{k3{495lM?-p1`00D#K za}hLB*{xNY-2|zxJzxWpXfapws?^%~yAWd4nU8?UW@4)}uYUCjCz)hDr81=QY65;) zQ!d5->GQ+^lpF~aGq3Q<%(D}$%~}R0n4Sg9peo=HpC%5k zLaS%DZC=b=f?0-4i1uZ*t7Lb9nu<{R$X;viQ+iyCLi3u)-WhiDIU#fwI!;B_cXycDoQe*Uj z{nM=5ETZIT`D@P6$x+N(8mE}NFxsZxe-61xsI)NJvej{JRAE2I^fO{;^`@(2{jJT^ zer8_|E)mACVVB%8p=1Vu4_P*|wcG_wD80Qp-MO6u+r0h@fR1Uf-Jg~hE+s)X$m8b2mw zu+N_BgI|@B2$9S@GqJLVL))Fd<5T=J=DrwIG<%QD;Frxdmwt`ntahnCEh~v8NG=;k zEpgvJark~Li@~!;_uGj2>gXDIbZT}XT$#x2)ca&}qwNJTAe6`{VwxqnyD4})Db%mO z#xALq_T>nEwETIRj?F@=I5fc5 zN)$bg(svh(LYmi{Jq4G5T-H_+<~h$}qoT{wi0F>|(q`=Fl+(vkA(XNinpB$`D>U`M zy26ENqoC~3hjA~#t&!YSa`#*B2$f^%1>c5wFq@I9dIuE4k?CS}xj`;a*vC| z>e)|F;L&QNe$Y8`TyBW`{x1$@-RoP#TkjN9u1r@ZFPeYi?KIDPK;D3Nb(CtOJ>5lb zjyf+9NNF_b>{+d&T1vAUs~JawCFn|*jFHJ)LH$6+P#CB%6}}xBrYSJz?n1`<@!`({ zcQu;y5Rkh(gc^4CQNOi9E~+L_?@7>Q$GvIM(fTG|h!eg5wN*@VEUWRRmfa`)nS)mk zntqAj(e<@uzm=*EzDE4qtY&kn@c65SOZJBjYVAX3yX7f-e#zEfJ}*{Xe9)aH=pItEgNKDhPintl!sL_ zL%pzH@U!>7{GDiMmMmJQLQey*S0JlI(4yo{JQE{TLD7Y>{;D4v#{c`(H5aheJErOa z<9swv^dl~>l74}P>C+p3sn&3;im#)La%(oY0#1#E${8abcQ`d4(t>1U?e;B~T+$18oC3qT=-p8ZLVy zQggB69Fwi1AY(h%#4?_}c{ZLx zo_#(cf7C01sDX3>=0N#mcCu<%uZW3?d&`DxA8Co{sLKg@Pb7g@Ro9)@5vm= zO4IV?VVeRy{C2s9N`7f~0oWi8iLDx&#%@MJmY(>4T?1fK=-QUO4)Jy`Gm z5KoUeaxDV7LWmJMMp3Ycgo(HE0)$g3&FSzRM}&2^18PPFQJ(&Aj(wEzmJ_f8 zZ_$u7(8?F%F2sV!lCWplDD%9$s#W~6fS@4vM2f<;W}b;l0FDU~w<6n14Ki=_cn;Ui z`1yop2<=u+&Wq$R2{t0pwr^w9Q~r}*`-zLeumA&>10o$<`*&Wnv$(aZa6bJO+u$kE zcR=uO(T-9LEH~#$s1%i`nSDwBBA$IOcxTWRQbmj)8dHfVtB~NpS z;;y4#5Jy(gbog|Qp%~&bWHSML8mE&rEmIdpHthm44e$&fUtWUZ9^O6HJ|UE^7nV6% z87D%Ex#9^Xd$a`hwfHQ5*c2POxw>VbbpA%@iz7-N2<>)In_(e1P4K+dZ}#4V?YGZP z3?Izvj7{z|&wS&)+Wz+eG7MQZdrkjVEQsjPCYMbNU!ibf6^&ympT~`Gkpxhl)X0;R z@LG>7j#YkT=!K~hA(+@;dorL?yj=&lz-aBnA}W zM4(eBnPOwA6?{U0%H-ZjI8vCxZ$TL&WefEDmUx8)rU_mu8CJ?6hEJ`1ksO(5l~sZ^?xLG7p(KH!7634g498eqc}vJ3p0=$PiHQ^e6-kD~jcu zoF~Gkc*?PO9l}UPEda*?zD5V7?2sg7Ir&Z%P{A(pi;|{9ctVQgoPds~=!;rtn7%IB z`kyUYmGk8c_$RyDvSavnlfZ|~Tcm%B4*cY2?yxJQ_3&-(r*wc#rHPH-o+bsHz z9BjB#Tv+D8r3Z2*OE3y$Ys7FA;7m-QD5M$_{A8B5yc}Z z4j)J(8)@w57$(ai#`CEX#)U$6Htxgs;)twMycf~{hOZ$!mE?JP@}YN%w-EfxB&;o5P%ZJAtg9JdJ3`my+AUul`2Qs~z|xSBIF0aiOb7D6nX{*W zs0JbjHxSnJET^|3k7M|Pi}V!TqefvqD##vw19m~)Mn<@cWIMqsiI@X0$f22kADCPluFOxA2GEG)N;v=0lp|B)CHef9NFYQXgtdD^W`m^0JX{omyPnGHz8JzGX! z^m;SsD+SPq_+=x&)p0bU)1GO@mjMw@mBs>oa(WWq5#mxe!z85BokP=#yMAml9pHa(zt&2AthGpFx@S*q{(vl9iZ|ScvlTYHfLVUc-(Zx0s_ssX|GE z_k)9g#W)jLYL`+|WONUMM7erla{Yl3`!*y!qS(^ODTWV4>$*s_sPIC(VW%@6LY2x-phBHG_HB;baU0;S z)UuTb`?xDfO9)B`8HFSU$*;RAIo~+gWI!~7CP*E0-hFlsbR$Lnj{YW35-N^v)1ABNZ(g=%tbfmz4bA}G(h2IZeRfZw6$`_ z&oudsOpkci?|||lC1%34h7j&R5S3|5w@_F#T(c&b$fLmZn@KuufUF_Q+rSmlhD4}4 z1QfKy?XU!A1hgX;x8bjP@a04Izr@eJ*>G?@fM^?$lt2GmSCVnAbD-~tteFBVAE$D# zHrml8W9dE*C-TxMt7q+el#1(L;*bM;L7x0fqB`YT6uuE13XE+^rY6@Fm)+xuih*WJ zZuttv(NJ=}MoU-m0GEyhGE& zqYh~A3KJ53>m&R3sd4?+)<%#@!Crhc03jAjz{~RiUC{-k(0s`z-kyWsBpQH(xu2k- zf_D9{?T-yv0zi{CP8G}7UB6lNGzy?thK4ex(_~P~+X)NguS>n}&HA#GwVvpy2DH-G zWG&WN6bJJna}j`<3sR**VTvDamq*Zaw(;QcjE#r`U)#_?DS|aFV$%GA7i_@ukOZt1 zrb2Em3)>dctXAR7)PoO*x|B2^P`D=;AdRmBqOmWh;NiC|#>oJExKe<5G?4oggBLmt zCWBUMWQ`B2t`dokHgDnw$mY;hmH)n3YDv8$+bjU;^U^z$qaNIDOu2P0x&`riaw?b4 zos#yO(g{=nW|(SL^%>tox7ng);v0K`#k|NZzSIQYDJ7$TL6voziY?bf*{ys0(W`t7 zY>~(KkfUQ3F@sP45K!eh^&f>#USlx`XdK`zxBwE{t}No<^m7H-<30g7+Y44jp7%lL zi!md33q+2kKO5i2d|BcPcmO!CE2CT~{ynub?zF4eBs=U1RjWigt4>RLT&^^C8o1N??E2vnlEYxog&n zeZUaRG|~adsz7XWjqNtn?0jl*(pz7n>tBn*f86JP(EYU-jF~dA6=4%f2xbfLjX#uQ~xq%7a zst=SY2>4(^`IuvgYDF!W5yaBp1LRr;E^UkE!6R+8gLwjGr}kolwIxI@$GF-x0CB`U zz3r^*O{-0bgZ5EjZO!~18XxVr{*pWczQE{E%HUNxAzwCZ0d9-0!y&3Vx@uCl&n^RJ z5zHd<8(&UsB+`saHUJ;v%PfHv#vbEJQr@VE8D`6r+@Ipge`&=ygG<0-H90T>RyftJ z>|&oWAbYJQ6fB+qMBz|JlUG2oES3ZY#^?wDJHNM+TaAIlU9LT zinT^9CNxyfi_S_fH2!Aun{}>1+q23kj^57(EWI{)y}$!yjfACJpa!mvS3OUY@Ml#s zOgjuvzf&vWK%_}D^twH}Af4--8@@xT2&kyld9)_y=SsY6>GU`iU@5@q2uJDoXgK)O zAi%^dAq66jdf2;Aq-^Myn!lt5EE}wA_B`%@%V3mN(QBDuT;KpVXBfaCVa+8h3rVUZ z(S-+)2Sdsjr76*~F|7tUfYl-%chfg0TnTpL7eEG0&3#nPS^4>JWmNG+3{Z#dhFQ7E zRGEq3f4!#ovVKn`xZYt96LW4B$cnB6!YpQ9+$gOT08ojZ4Ns`HL>&TmFBBf$&tA@n zX`|i$%=RjHV9_C9t_s7WLobKnO#JW!m(3$tL0T5IUwy6G3X-NWsS5x^cjNS& zh0d}6&TnOSf$|?{V2x2RFwyvf%S8Z4y(=C2_sbuy*Mu?RLDp=5aRaPT)Cm=sxAE+EKbdDoGmbY#?)Bs*0+VsHk2!EhBl z6<`}u9%f)QVM5^ExYE6}&K|V(Mm$)2rx=|fW5Qi zAGTY15ADzqtxg8xRwWDF={ z9-CHph@IL3iO{H0WuJ_S;!i4pbz+wE$lrG;O0dL;Ok{)sf|avb;$e!2$GiieLS5#; zLqUA&Bs8;?#gA^G1>Z=i*DV?=~&XSGWlJunSW1Mu?J6Ux<^Qt!hO(O7ifGfc;I{njMg)KXl$R|Wq(&EYH=nqacK z;hyoTW>W^!O}YH7oRK;t;*`Y$=9%ax2M8TdaLLSQ<4+p}jWQeH-tvHYe%aA|;%)!I zqmDRggC5R-D5>C>hO}?aPzh6X>T7;SM%KJ@k{H8G<853igwZZz@OAJdGYPJ8gQ=bu z;crL;*|PFmv;}qvsf{qFLc?A~*1Rcmf6*&-g9JLuYRM=ph%EeYr_Yu9-yF76#{-><1A46#H{tU^(dPv8y9@CrS5B&;+Z1Z`IYv+ml`{KAV??BP}C9u;@3ygUdj zEhMjF=&}w@9KR-ac$jsHk>GZ|kv9feRJW+CG|?!y0r(ZIC-C9`ClKfcOWdZDL5trL{B5 z-&PzkR_axlnk!^xD;VMH^uqY5k^Q9)qDD3bDZ=EImJS2Jq1LaL9isB+W$DZ z>bR(%CQLU+cQ?`G7 z1Qt@$S6d^tNzyRLLtBi2@YIxM?>9_LkkSRa3pL`#awMQISZP!jZ-rZqVk6Zn_jFVHkOcRbrwg!BEEe1 zuM?4$mbLLOTd2HR++{u@Ef(T0IIBky$j>eRwZH-actbdKcnQr^G zdcN~2o^G?_UEQZ(ec~0(d38x@Iw2FGb8{!4+> zJGb0{O;%D*9sJK9!bf(b5RZdk8gsNH%5F6mqA)ln^moV1lo1cBwYTC*c=DOU3o?2i z{hI70N%pbu8u3Ei+;9&mgmQ7NI?G^?0oQ5goE{CE3f}X~`1^eQZjEL@MoL|tfhF?YddBRh;q6w*stiO$}BOwdS6d;kV2rPf1_kJ?J z$gd&#Z9DF?z!N%9Okww=NhM&N&G7DkwQAWf>-y=zI)AZ=HtQP(_02bvi(5^YSgu4q zmR_MQ*|3=yoTv{G_fR?)d()#Ten9kAF3s!hMl&4jr>p(L!GsgILDOIjr14;dktj0i zb(!6C6n+tIwps?3FmO5F07KidZ$&R8&zZdK5!(tpW|fm za~!^X*rAe;A}?cEMKYY`9Iob#WQ#&Qyt zH#VnajO;HDYIprnfV{0i6o7iWU%xV*i8>(}drsH-75mkp1<)ay3?16tiJ4+E+`@8X z7d3(Gboi9Xzc!u_-Y&C4l>`jl4m++sIQUP_UBmru@E z0<$rMhkngG??3*BM_zhuUwXKZ#$`PJ9Mh^>#VC#PN!MzPok@7d(RL}Ri9bPjJ{mb+ z{|K|FeJn!r6%L~!gnpyt*;SxC*Tp0B=Cen_?MS9V8dUIjx=oS{oC;613~DNbHpTSE zXqb%#iBisO!N-%H?BX$KJD26Z(T3b^yZlS+j}5%Dz%A#HLh6{77qsGndTrtn!dJBWUH)m*^S>hA9h1<}&6 z9;jM<@Wn;Dy$+th^i{)v*pb`q1<^L5N9dG8w#bmEN4!sPP&6G`eG8MnHl3K#^y_}b|IwbUv*jy9#AsKF`Q!DCdn(@KO71Hsi2;pEjn8FSKd~7gKcos*TA*dQ zat3~!rN7V9@HG%p=*)jHZ~grx(D3WZWR)h8yx>cLd(A|FbB4rK9TQ==yTV@MrtK7a zobU&l4+x5cKsYCQHvp69OkVo7D?V4XV1ivRxUf;3}l2kX};)DcmIEO!O)kHGphpv$Z zP$MgtCVA>GuPHY8{}&-I<2BZ{+E=_l|J^HAGJ!rp0>gc=qxPr!pTdu6gtUZW5mS(D ztYH8C-3wM`xa4sG{L^x`A;0^v!pqmbOa7RPf67s2QC;Bd6?W{b8!95di&}Ku@j#GM zQMMD-T47dxZaFshqHuZGNGcd1mCe?rwC`z#@?qD3dptwj?bN95IyA%Z@WRLh)~@?J z@bPAbdl#&Yl|(svF`I`SU$t1uadxGf^uI4zsd7yKFmaj;_O|&rEAJkn68CQ?0f5CN0yK>bgveYMknkh>y2MRS)6R^5aT|y zA(r(gg>IPM4Z2q+Eg@cF9`37msBu~Z$I^~u4&yQxGX`w?0(M>Axz+-6vo)=?<%F4~ZX`>AI%bDdEmpAaWXj*RXnPG|hP zskoBsLTnhyG?PVGuSwUazHQvh%Dg485_?Op+=DTo zQ}@8S4k6JVGpfxIV(wFGIF@)UTUL_hfs=P{7>y4V)9}($-i9>NC7wae$OYFS?_R{o z`uf~`4pBoxrOMn>j8hJxN5yq$EC2g`$2aUycs;h_$Be97MLcLLEO6tFyG%TF)OTJO zDrRkkdZek7_$5DK|HiMI%CT1U_bz)q#ubUmKVdM?aKE~MFdkxv%ed$!{xRHR7V+Io zz9`%>SLJrHbA-xN^>Aj9&N^D0e7_|G-H2xqSN}j@;qBSyhB1~Ucm?9-!Y+2%>-qDM zP%&CZBa^M8$a7wny+e^TDC5!>SYiIixzzEXmP4R8e|OnH85I95f)HX5`0 z_$QICW^fLZmc|OBfq5xN(c9!}3QZg?s`J0ZP_fRb;2B*CYheyIU#@is{H8f`RN#8D zEuDmIHx)v3p+=~YG!o-zeuiJn^WxuvQ(tR5~yi^sMrH_XFN)1_KKxbOZ{8E6+4XWT{j{@ zDIasRAaUey+{A8jDp`nb0j@b={<#RHty$>VsXP&{xe%@Oyhi>)toSOKhD_L1O+TZB z=SA0~Yh5^@yBf3X2epUZy=VC>IGsrvK~^iZwB~&iP2#<4v$Yp&jk%!fLA@;Qz97j4-lcn%%cH@i%8a zHif`susjXlN&eJgdW*U_BK-%i30c#&z(MjID8XDFE0RDaAIjg%0WHzT!qgV<(SSl! zAJoq)-^F<4RCIfo$3mHYzEQ?GlK{pV&tiRLBQ@W^})k>H_nr_zH7Vm4_k?`%R)SZ2q9nGGe6D)4mIf>YHVSeg?;Ih z=JnnS&NHlCe(bHrQIq%i*z71O;voga!2<7Q`}RYwLDi%|}M{9tfF~78ty&%HGLD zU9U_gTw)pb26jq|zOSqGJStaq?kDWTC9oXxB5holOffc9a~WZ~EA4a;tGiC94=r#^ z#x|26vhID}j!hx)8c#w^i$r7|Ggcd9LX$L!q~FX#Yt+UXnQ}}>tn~@Gj^~cke|g%M z&~!}()vuQDxca)4;2CU?DWRa|d(|3gYJ1MS0X+?MMYpUq5vC%t0`~S)SycngWK~qu z^Lp;K=2VN(!xp2zEM{T3j%^-!sM{grSR^|0U*48gg8SD3Qsn+fEe*khtN($&x;Wxg z&r~3^!F%B#JfzAY%1XD)d!ONS5HYxFMl#nM#V0{XUHWwR%kIr-4gV*o!yuRYqP6yk zOZJ@D8CHxaewg5pIZ07*vA*hu@|l9rm?7~VZ$u*NF|ZwFdtbq~Hj#HK`>pZL z3c*+{aoHh3Y}q3Rq2zPWZ&8nfx9MxfP@`0+PxTHD8&&RXf9KrocQ$cX?4ymtIk{q7 z$TdU#*&Age%=$Il=(_=t>Hx_ztk%^6&Uj2L`nsb_U;PcTwN7bF5gyNwqsg<8UVfgD zKU;Hf99`DoJ+3qOV|k>_?o*y*7czV!6_d<4GSnUE6ypabZC~k-f%D=8a0Lk8G+b+U zuT_op@^kc^emOpfIZ1tM_DUR)hv3MG6iq@W=0^cjPLR3EudFFqVt)70B4-uxo_)a< zA)9srsKf${GRcKaj2mcwZ7gUBMwF!4@0Vp&Pi*KBp3^H-4`!woW;xd zq4UTOc0`18u#7~Ae3EOcN)6iM8448Gj zI4Fl6ngEl;5I`RAMR$Trmi2S@6a}u8wR!U1l{nbXsfzb#rzxFK4ZW?_q#7CZV9tSG zAfX~Fsx9t>uJPPA_42D)%p(8TBRISy{6?8sKIm1~OIFozL8(wgm3c-WYmCO^XnDJv zrRQptE@2Cz;&${RWdeTltG-geOr}X|dg3dMM4E51Mpu zd^gBou~Xbli+y=Ll#rE@ucZz?W*tH3U3o-aJeoi!$`-y_?^RYw+wR?+NkCb9%Gf@< zO!1wn`N^EB6#aRD59vi`icabCxsl56o^UD3dch&JFV)QvQ66CN4Nl0e>5dQd%R{`Z zXK-ODZP-@z+aSBdYj+@A8n9tC5wV{J5-spyW>r!QX+oG{G{vR(IurRh5dugF2}Z?$ zBQR9Pu$2F#={(HJ0JcYbNu>IkwRikSeu`~%@twOVQRIXSI=p`40OibeFaL008L@GS z^9@418;BCT#O2v(ylM($5g|W!qpXZ=QZuXt^Em@&$spe`QrnL}ud`bkZz zt32Wow9Rlfb})Aes(0Jo1R8C$pjN{7;_mw7l1&cWv!@D!bS{P1!Z*wd^3zpaTn<-} zmmD?Rj*rBC`CiMATH6MXkbSK>d~)$9$|^EcrH)|3@b0PZhV0omd?;7`#!30TTgHMw zZMea^%r-A zFct|n>aQt+F*t@lnxSO@ZUqsxCOKd*1+4#0N`?`MXQ$hnlA*&GWHZ6VZ3AM6>>G2c zOqOVb7@VQi>Rtk6RGGpKwu<4xQFs31lTEJaz5G}=-PYbq*@)w#ZK}W`ZDZ%z#t(tt z<};-BqpV<%QX>6M4xtv32oGwe92qjzSofn2Yp+<-Y(-##K;C3SG(w_qy*5hV7WtcH zSB&bWYG{A;pU=IV#zNdfcIAY$`sWxA-zb}w#2&^uT8?}Lg3u2P7Hg&3&M3PV3ye7? zz5W_WHikC&G27334sL0z#JzEweF-NOWbK!#%6UZhsp*#0AONu%>kiM`O6_+ZglqkG z7-c2>q#LzVMOEXfTb<>`|A}5COFL+xI<`;9ZiFQV{>c95oJ7!J_co?!n_!HGxw;*f zopSg>PyBf^V9m|YPS75}1sE^;-HjJvdIht@r+D4VX+R z(ae&B99{nVq-H#DSW*eQDU#t=GS|zk*Ov_P=-O;76IFO(B6T3Jp-fH%#z|u&x`@8(0QkidTr>#2T;KU7W!$~X~*Q{YL8A%s<@%A&RQTB!Y+01;BcX)h##Agv}BV1-{ zHT7RCIr;op=$zzqyW#F5^*%X!m;raP3YYZQCtC|qRd~WH^~?dG`L<_6{;``;Nhizw^5NT z2qdcgq4}NFooT181s5n9dPZ{5*SP9uoeUM4#iSzSozf>xyj%mp=$J(4NXW0!OD?C7 zzc)$_e+*`lwa)`^C~@e1wXfj@SsHrLP@WU*=F9&se~z57zm;Y}jz=lE8TBCissL&k zeA^3>iIx+qNxEDOq* zOfSY985L_o{JDt`XM3>@QT#EuD588*^bTbq%LC|S0*ZK~GTs8Zgn*r+DQN2TGrwR) z4=X6YfJHG*_zY`E(9E9*$zuoC#WYj%lX53C$mDyt*snxZDDYf`%L2l6=?`;F|9~QB zk_;SMf7X{YeP{~NpH2UW^lcowYwU+fdDC+$hZ{?UAy?yhk52aUS{$6LI*t-UcrOLM zNcV%(a*92gfxqA6ZwZ9EUr9XV-yaGym*gY0u$86f%FBf$O45ik=|?GDw)H(l{oQCA z9C-`fk2N9c)JLV*W5zy6CQX4x?lr)#sC2r1sQ*yLx$HW)+yqMVRrwnh*lM>HyTX3Kh)IdD@DRE>Vil0Uf@(Yn^1Vpn{cbW`a1(8ydDdEEDq6 zi-nsy%4)X>uKZA%& zfp{9j$jrY&t%AM2kGAykH;OE;yG+Kb_v_eSGQ z6QfHpgb1GiGeJa-j;~>hMT2{P!H%+C2=y>MW!lvZpOzwzvdWuAd}Xo7Ll~i44tBJX z6Xkhx8kwxxRC!%22;{9Idx`wm2Eok3z`%{N>U)$$)r*0}bI&E1X0QMtsudGFrx*Hn zD{rzI%M4kCFLSK!#?7^ha`M!}*MFzSP=Yxoa8$}uwZcrCMcU7kyit<9#bC1GEbbhF zR@;t@IdtwSLYa@~Ed_m7ykKp$;UAF({p2HVpd!}&bVovJuuWkU!@ zCTDM8JU@#d{+P51$-gz)mKk)@v&r~geu053OP;3{vE)8z%O&VLr9`x>fsKh4+~>yw zwlTCPaTt-Ea$HK+*9zFzmp_kT$U(2YV)S7gm{hA?Fj6Y9P+MOs`*s9GsqpTdlEcdB z(DPQ{zu>TRN-{AgOl8mhvrb4AWu@dY3EQ%>xVAgfivtrArj7Tabrkt<3|}aTBNDKl z4%Y9CbJnDSpc&*}`Q3bsi_c^v6o}X0jqAVp=bxXiTzU6IyfP9ftAj4ic0hRJ|Ev|`a0{3^8^A3@ zGj1IMr*nidH0+ZD4meRds&r)Xztf#dB4G1(_Vj*gObxVy`u+y}UK>Q}{MC;T?P#tR zO@3DIZ-J(tRU`OuubzWUvZWRhdT-R{;)CIIAZ*@_9$wOBTIsaS71%8rPSRJ63v}AX zZ-!g1lOx0Ab;j*K48$UtC7baRnR377(WeSIb<=-;2BsnE)i-8`e>nKBGr9}(l$m{v zM$59ZNFDzKRE_9-i0AKyxg2DACY}J+nL{IoUiT2K7i&6w05;_m66w#kqMs9$JvNGa z`Dqn|XmM6*2N9sID?LJ_i{g8X^%(L0>8sU`~zJ{alPgr zA849+b2q-Cc(NnyIj&?~mGKsIwbThUNmAxC*I&}&|F0|=pRBI$EQ#5z)&K+~!TpYC z$6ybK!V!?ANG%{|%nW@#ao!MZYD)J;IZ|{lBEM8fZs&YG2DWLmEYYM35ApsJr{q4U z{i_!c;Y!;|s(%Ubv;&tc8cPwZHsl|?ST4`+l94Zo{Q0Jf9E$YxQJ&|~FLC`-lBb6H z#^b@7IVYd3M2=2+9owO2I;+i_IVY&P;Wr_}@?!z+l-@;@Rhe7w-l#7$_I6z z|B0&S9U`xH=?8I;B*I9%rL>x>GqcRVxG9cg$kGd^Os$gtpERC~LYiK10*Rp{8;r8B zO|Q)8_boBP0QdunyLTw&oy0yLMA8Qc(4E+O(sjHiLx}3f1CIY>OinD{Rweb}&EZ$z zKDk>dplOTMw|_;$vYG`(8f^CLa@xa|A^!`sWZEt8Vz#lLVB`O(g67K=WXQQt-V0~f zP@ok)GB_6OKq}J?NdFoLQATQ*%-aSm?)7~OU%@8-gmdu%$^boFXu5L&(p%55>B45v zcSw6GM?r>w@>{H)skjXCnius%f?%vugY&+mnTc)HPfVf%3@t>TZLjo_mOmcy`S~&< zm{{Q@H~*a##p4tUjOC7p!f+adPy5wv#veR4gn8_^7J2$P%jl1Rj78!CNe2#Rn%Tk- zc*}^_ks=+uds7JXrWlyDVwEQl9+MvSq0*BVs8{X)fz(4U>#IUAn566wQE(=D8tEj$ z0?c@v?J4)hNEBQ4H2nF_?K`bmbBrZ}cnDq}d-<(}XZAz)5N!Ki3Bo{})UJiXDdA3g zRzidb#+*~A;NJ(g)VcHjRTk-)UNA2~#p*B1%cc5Ku2XYW=(Tb?^rKWXEGFF;#dCO^ zE+JCfQj|0`T{nEOF|Q_Gxlfs=RK$5AT_c=yf|_m+<)Wuat>7$qNG@7!wbWerEBNFc zy1B^dWgLMG-eb;w)m1NV;OkO$OWo&961F0jO@#W_;5edq3W2!M7v% zrC)JumJ$dQ1QaRYg;K^3PTBsj*yAUF{Kp;x@tDt24_4n}(}Q1Q3tO9cm5zo&dgYxl z6p8tT1p+f2WuYuF2q%f@(l=E*h$~&e8`)myLc-c*tgaPV$E3~7A8t9PmRQ*?!x}E- za z0n69Uu%gl?UPtxp{_lc+PuWGw26?W?B&gpKfe0F~KZL$w{2w`_#8`3#`Jb!&l-zjg zKV$CcPmP#wuNP=1=}RYwcPI&Qik;uF6QF!iBeK=cGzcba)FT$?N=>!g?GZiTE$wu83^-(Z^SVo3!IPw+BcG2>F7cg z9vUI|HL?pcf)(OE#hC{GV&SlI+@%p>(pT)H{sW=-?$d}EoI{~AZ`6$uc$|k&B8esl zT!Z88&5iIN5?}HMi&-aiWq5!X^*5j_3Ilt!jF(aH~IqW~2u{2T>hiYIPzV z+}cpW9`Jk6>+~#PR@%IeGYSv)y^r-%x%UD*e63FZ-1pUfk+XfUm;A5Xcy8BYpVJ0> z6Ozw&4ki4=*!-kZAI<37SvYkwJT3{ADr!CCjy7IP(q68!MOW_YOXDCzL3>FWwsuqv zlq>!+l}~Twf6b|~OrS_L$jSf0NiJ`2IJP2a^>-H;xBa1l;fTO8|5C$Cn(D$eV&78}a7C4YL4rR#j)WE#dTM5BP_ zBD5cJn%yfB=GH6`oA}Qxa>5)^`*QCYFJA1N!YYg%DtB*z7jd^AwQ`f))ak4+L0Pf_yFz3Plzv(h_XP&qFCP#g zRH(nt7W}~*dzDU$%34FA%B3^8SvmqwiK7t@coVuzxcC#cfX0Xs>uGVDQYf zVZF0tm4|#_cygbaxL}CBCAlf-_JKQ4>-dpF1FzV!uuJsu%6AUb$TI8=F$e z?PT4Vi$b;h{L3k8v$g&o*E@>6%M`<~Y~EnuxOPVgsW-Nr;+YRrwS1(=J=+(BB`N5Q zy06yZTt%|IE+1GgDG8bAGxW=sMP_dwST+8`P;j)BZ?wF*_x)I8ox^9i-cHhzk?r?i zdcLtPm7X%=fx#{3#(A*@;7MK5dVgaW;;>Nb-_Fc$c@+Xp^P@Fdh#wJ_gC`4+`BouY zf?!o=axtQT%RTOD;F}z9IT82BpO1s?owtfs&1ELHI4Ae-Kq`!n1{Q2!ot$EE;}=Y0 zPJlfVvX6G;+ZR0wpkR1$3j54+&%ejtIeQZDpEOKzXG>*vzYyiFP-R}vSMmE&T5qwn zoQ(Ro8zFmB`B))$VvKr1tUf@;5{UQD`N9^I<+rDGRd#O2gCqAGEvPkiPWHB_Krxma zD#m&lXiSF%Xj;r~OuLS`CRHgY?ssnYHf1B{De6imj;6oiEsEEmRCJsQt>g(UwsAb` zZ`{lNlG23-K_no(e>x8bmtE&cOzba@6{m#M{_Z9|FGoEx%uzvQ|5g*nMx{VcAwJ)c z2iVJ2xRnx7BUMfPAlA<~a)lPobHF2 z@u*rvfmF4J+mMLJ3@b(OZ9m)9KA)4Z1lxGU0`C@0wdLQE|KZ~KW@OjF!U$Vs{VE?H zTF5mhQ4sjmR^8h^ewqFV$ZTkHOc;%M6L3dj5=VEF+ikJ$iRtCva53eo6&}{EOui3Dw=V9Z*-_Nt)@O4MZb+k6gv(FmBvLS-43iABQ&(Mu{PP?V7Qe zP)8^8*40lpC+)zatz2>lMMMQhj35o(d%42n-bq2H-=Is7uFcAESoC0@!NFRLEokjJ zgwRwpLMfmMJFX4!X|*6ABcN~o>>xy_b~B=$^woxhoq*MulszKRe?r!Z+`AOv-;1bQ z@?99cgdXusy{pB>F7qieTy-IqC3b`|a{hk-gGBHksOUaWMZUgPckI$9|ZNHt2Uq@#8z1hGS>6pXD;dn^S^k?)bf^ z4U6-JvQF)Fx%Y!8$?LHg6fV-P#QPDxizg#Kx;X?SE22z>PRhoKry;&iLh?s5^Vl`6 z!v4EXsgI-{t@dv@yCPg`vJ7XJHnU+;RVDhpGuiVb+I<*bEPnoL(LFelN=bl&<% zYQpBm+^nKr7xLan{1jfGeA=ZXyjs?f=ERE2T}7PFjg^#)D8wO5{M@-K; z)vGwmR?q1_IvTbBOcX<RyswtN_~?H1{q*I{ zN4`68<*4%d_W@SOUvHhJ%hW;ri|99K@KaIEf5cZ(BooN(-UMY0$nBf`vB92>_*BMY zXr#7{>=fRuW%o)V;f=Cd+>OrMckaD!W|uz6I=fVg5n3_7LxiMyb{_^=5)WTiroF&L z>8276AdMWN+?mR+m$9jq%#xG|phf#UiM~>b>~Q=fA@y%?_oD2#vrlg3LY-@rY1qz% zPx1yJ;zi%=ViR}jKU>f@v2{X$0O4uLqB!Xy!rfZhi)TFDFcw05pq#6?o}qg9jciB2 z>Z{46k9KT#&+S~IYgoY~@11x>XqMV9_edSuSJ;IIrPAbpyaUZR;4$^HDw*sRY}1>w@|J$i3&a5TB@y4~I~CcwyzX2j)ulT9T!1C;Lnd@mEUgZ0)Pi#4?aPR|v55L}v@$B3=<1zY-zu?H<{FxELUWr4h znU=x4Wm2@+9lGH?C8t2>`LljbOYgo~7jGy;A^8W3uhp*RhW1E$LnRJ{3^#&|YyeU+ zb`kBfwju>aPz>fmBP&D$FYjP@wb8xaJzIlnk2CW{G~%)@Tv==(bDv&ZIp~nzp5m!S z?sC}RmKNH%WQ*qee!SyT{ZN|=lPK`mt(uHTq>b%97&i=5_R({QM@@>%f#)RaeIuhH z0-L?#!4IQyqTg)KfY>O_Q5KE?PMypO{i4#0x|7IhKI86}RBG`P#`Y`Wk!==GyT+~A zNwCA8a_q4DbiumD+-esf?J@=XRLvehEq~eQ14p-0-=@1y$CM_BDD+aO*a2X!9 zPLWxud4661*lJ=GKX6uzNR?sfPrxU|E4K7xToHQ+Ra^eS#3k#ca+e<}gBcas)rHs` zD!+tgtNcnl4PT)&c^RL`#b&MWgBFPaRnf!qi%d&n`g`t6XPk#m&!uG#Wb85)s7aE$ z^a3Xx9#&&D#WF>}^_yvcJ~aLyWa?vdPhQT@8n8o;ge&zWUKNdWV*gmXa=%HyEiFyt z$5tNkZShs2K{p+QO(VLCS#KFKMA2`_7fllubw`9FeSmiRzTfgqw486av7)-NfP<^{4b&_$@wgshDn@E_YBs^+2>bt25YT>)pQ=5 zc=(uP=11!wSeVf_>5Ib3<{I$$!LbhUm~<-)**P4H3SOgJ>5P^oEfvH>n8ZhNp}o{k zw&%{8JWUl7qT&CqEvb)}3&{)SI^!=(tA?fB2(25kBXZG@tAX&T|ovK?9QO++whyjMU#)=IVm35 zdx{t~PA_=-qtbpTwx#ApAO_lNFMDF;)@eNRI0kM81Vhqa8a#RAZF17#l*09fZ)~$7 zy*Q;11Fvlu=5B#I7>|vU)#6<6IL;9;h4;LC5vPT2*->6|Uw180y0!|-J?xtsMMLZi z4~&G>Vt&&{9@sSS2ajvXe-;XBh+v$P!OTH!U1J_!X{$(aVxBQ6+%ELi5hGwMYY;`; z=FEp^=RrJ@t~&R_QMGvv}+~CJ_AKUTh3w2x=xb4FP>KhUC3(>Z1|Ooppy6e&elfi zO5$2t7?T6RsAKAd4{xM0*DJd_Ey)Uafd|Pvqh(b=t+FFH@_2?IkfsseA4}hMhi}aJ zx2j}M6bLaJT1O0z)u(?XG^Y`R@`YZR>46E;mh_d*ns+r+0g=SHCtNXgS2xHS`!Mlh zg9%^0Ld`GwG*%^hsd;^(+lX$Y`@xla$SW4Fg8{2o$u|#p8wi zHY_FM&-m-hyOQ_y&a^Tydxv!Bc5|=J3dREQgH8cI;F2Nx!a9p9!~aX~>5#SiI?B~Tz#j2hyr-2_ zjlrcA_94&vV%c&`O5-wx!ce=~{l99cpCRB8ys^ew?w5{wr|9BV`;5|qNA>8Mq3E;g zgUeBX0=pvwVZ10~q5I+tK9-x~9p2{+o^U7@+&k>%RV6y(?swfYvD($WLn$*~pp_lH zUbGBzQA||iTMPM=be#Z%j%WUK8XeWxu*Lf}*^ zFj|bFSZBunN%pl?>3#O;cwv_1^P4OjI;n3{Y-3WM!t*=4X@1BP@~rgoP&o@=jGT@y zC<Ot3S+CBfx^Ge&^_shmx`?&hSP z>*?-Iu$4}`<` z@qP;GEh&#H%M-x33gq)Qok4CuNKBO}C8hbk)jC>GL4tEV>Y0)hhRYrJLc-oF)rmRB zgfHfMsbthklp*Pv3xDM?WnvhE$H|J;e!4*TOX5%81|0P1nai*IP33a(SNPfd z%1yubDf=gKUGhxG8U4KhyNEF`rwOwm9pjJHGICLIh^vF$`_5)$l=-wmp zm2_|Ea10v(4oOj&C9IMX6;ozNfux- zWNCd=oxIc~PXM)DT`NH^T|#&3pzQ;|?a`S(@iTfQ9Zp#21JJ$b%O*lp0Zr3#7W)}i z!)o((tSMA1a$4*FEH&VW)B?L!>QdtWd;-aF*_Iamm_kg?|1+$#ejHMDy;*YP85MaZ zp=qTC1_sBss8T`?u|ZM)a}0yl&)qjdv+YYfXIRi>c#7ASTghHNvizWyz;J^UJ7#F0 zd>a3Sid1wBk|sfycyGWcz%+ZNyv)R-#o&HOKn2i`(H1s)3g7FM1mV=+H4>I;^(8TcP# z0Tar*-xhr<04I${pQYREY}F21Q(LVCGWbL(sI>{dshZ6o+XE6>F`8*hrc;_UJhQ&= zrs4SMEq66d+(Dsh2w~MkKJ3C9p$d#zHrHKIi!+5&cB+3O@u>8!%&+E@a5wQCmTi<} z2@U5lb*Le0Mat&9suaRcj|6J7p#5f_CP3PYC99~!d@UGBQ$@B20@FOgo|?)}f*7Me z&$?7MVqPP|lf&Cqwfaw0TrRmo_Ph0KbC=YfRKU;VVNMysG`a{}VV?os86MQbU{wLD z2?=a^#*wVu?BnxaOQcYZJ)a>)e}d($gLg@ZbMk?9U@M_gG31&4e-6%oMXVR?4{4&? zyXpv4IIbSY5kCm8zfu60VDM{zR}Mw$YiQCZgMV5VYj_9B1KKiKZ`pd*7G&G})TL~( zVysaODW=G^BA&BEIDGx9b0(Dcq<@|>lBUGeH&6lr7A3UjlNyFNzmsR)&YWAf0;2On zS>~BESqbLB6cV6IC|o8acd5ecGNbFC19_!Cejt0w7vcQB4s%r}I4$)EUM}fz%cHS? zN)f^(j8Y7Z_9CbtQS223m^k})1zy9XopAqU{LEvgJqCt%q-lD2#^BHdw2WWhGO^Hk z5Ve$2VV;0I7nd_t8&Aw_jvYU86^|RKus-y2*n+UsNEl%4=`$!j`~$5-N#55VjYR5w zB%CFAe)wl+Oar7nA(r&1{PmP>`(wjnXnt7mrx#^j-|S3i0bAzMMIp~-e@x+a5nmU? zno?Xs6AH-(n7Vro;HJ2@t**Tf9n@g?EN>(MK*T?CjX~YQ^+bysccg&$A*O+^oYW}z zx{vYsM;>%HB@80W|6R3m_@4vF`KrR1sd#BPow#)LnF>J0hdfwt@t8Ov+VCGR0~L&V zk`y^Ej8>S>{$H-^(ZnMB_DADdM>TON(D*E?u#r6@(;s#Q4k2rz>}10u*~y~~uaV0K^{ z+O9ePXIl;GM+)a#g7@WD@e>Jo+#v;$%qP*rK?I)w3|uf2?x$iP5m~!wEc*KA{&Xv?uv)tIcs2E@);f=@;{mg&el^g+*voRRapFe+yCix>niL5>-i-#Oj!uWf& z?gWN0{vUXvx$VbjAFm?S-9&hR#H1mdJc8ZGq9Mie01`!7mcmBTRTr)U0SzFl%&SrK zg3!}`eE@jF-3)aSaTu}t63&4Az}7k(-5y<3J}XBSWwpjQ6I4XyguJQfX8Q}+EP7Ml znYOzUI)3eP4C2K&d$>f~t{f{KG~fsz$l)vLvmHgwms^x|ZGg<-c~B*zg&wmiFbhcM z3!3;b$_q#V2zQ<3@Bjjrv6i27*TR{Ut9@VvfrpXa!Vjn7m8zQVb;pFIT&rlj_sVpg zYbZF9{zYpO(!y%6X1W&vJw{-Q7d7II9n6q2LItpvf(GS93Ov_e@K7AwXl{^YP%~gp zu94Y8MYC^PfJAH@h?gHSyQZPpKIx!{GDSfYuDnWm%KS0yq)M#!n1=J-spmdk_&?Isl-gh;18 zvpC^hII?>>cfK0oDT#qhJc%CVGks&7d+gFiWD$`0v|covFnU;Y|NMLdkSo4n+*-s^ z+eipjI)3pEM(R(e>$b&qf(1 z9P*%{Q|3H)FpFkshpT342_rN6jF__2J}wkpFGD-DUK z)-3qu>?vlEVJ;D;m61X{z)E~S`?qA7?9PMz{UPRXR0R%{pYbi2+rm! zwl`^XcSb1I;5WR_M3Es~m#S--f!Yi}Rw5YFz;liKA;yNdaFexi4AMMt5?BVed*|`_ zXgp|3j*b3%B#+@olh{=vC`QvCG0R1dde*h5gUDyy-cP}1K$qspQX0VrM#8z(X>MVY z*Rc9+0%+^oFXWda1K9WURhvbHv@~P2b^Gs)$%A+R0TJ}AaQ$u=#JLyCPU}?VmHhD~ zO>==4+7V(X`KZxXKy9IgN3Xyx_bPe+T!~b0$-~(IA2hU1wF)E*SzTC0mxH&D-&-TI z+#t)#M0gh!n;I;J){O;-cd+e_AQ{Jkt+IJhkX@iq?ZXnjg@{{enIHVWH5)BOUnG=& z0gW{aY;&a z#3Gbu1p7#JjjV0H5)=u4(e@Y&2)c-figbeFzHLnWl7+JZO9Q_Z6b$VdT9T|*R1yIz zDKr6;4-Gl?yFdmm*e8AQ;%6a`2boIVxxX`({r`_#G(PAYzLj0fD#_ws1&sp<;PXqf zY3x~MaunTTK6SLZUw96@>VAb*f^XKI2_JhYo)DP{7JhB&4ND_%_Ut8`i+TQmnZ1OW5-{ICH(@i zwQ*gnMTf#wvOKCX;ENn~T@Jg~cSrFQW+6i>QXZ!ds`JpUZi0+7ZHe`)R!*h}=hh%2W^ktc~p9ZXEqZ8yRKc!yCfh%&JU_ ze^Wr>H%J5ZWr20up z!G~IL?=#H}5>|Lh{To$BnH%GD)mEtYwEG&H0jGZ2Y=tVbugR4goFsf`(Aq5BN)EbF z;KKyQH$P8r#3u?v4|Mis*yA_BB!F?acK?s0vkZ&kYr{C*UDB|0 zx5N^%G}0jgD$*d`4bmk7N~d&piIj9ncMC`?jg*3d@7e$Rajw~&Idf{xocs6OPnD{G zH_9~0Ko5dt#U_4QH6eWvyqHQ!hpKG?1 z();P+ud(=9U)xh`8g2H%rM!#mSNBpxhifnkl&qh|vlW+8RiOp>gCcGWoUh(=<~r2# zv7N)(SzcEN?@BW0nuX_e>o!djY1L=8JduqEdpeW#-%O{`9QMvcVxPKz^ zkwOcvn2(Jw{mx2v+nM5Z+RfYBZJVT8)m3wSWK4b+k^5ZR2h)pdTkbk}tQQ_-dICq0 zBm3`g*t2x8cYUMRh0?FrE$^6|+rD$G9Sw}Ox~E)b#6jKX%~Yoh?rP=#hK*dSFUTni z{#GO+xFv516u4b!d=zvTgO(ki^Vm;pVb_-}jOGL7D@kJc-plq8o^40N;X7K|ABm}P z_G`aUC0qoA)O#7RRjTG`v#ytU92s90DYE=p{^q++xKro3v!Ob#u}?)3@zv1&>(cRL z$prRrIy74hiwi3H-L>Qo-`GcZa?y>Y#2tSks*2huv1QPov4f66Ot*9%@1+=y7Oqvg ziI&?AtiQrLiKbrm(wCHK45F-x9vS0y(kG?erzxdn>DTx_7dIvN-3AP>DL&aT(KW!P|S?ETNi{1*Mr3j?{t(B~o+Cp@RSc7n0Og(2y zq>|8kZfBb93Z6*J{W*$@4%gUB z7z3ovrF-1uNgjOZqF$}|QWy%ln9$p{fERGQ5GUR9B%#&AJ zmf2d;oHdsqU&e<@6Del^CVox=Q8x<2f=tTwZu?(5IhXl*ikmkuz?1n!l38rzVb>8 zTOyeP|0?fS@=XLDGe^8s5a7NNPmsKo_2*VcdGUtpTp>1OM9^aVY+t!;P?K#Di{cOU ztdH`aTjE!gK8%`p*hiG05J5q69<7D!-u9bZk&RKu`^xXlQ0SEA$UnSy*}lCORu4&6 zJ7E4zOZC<0|D*SP)ypyC39ZtaGjhHY<+daZ1R%% zca7Pc_W8V?-+J9`Bxg;_ZOvr%^mWP2OHG@CCs{d}6(*#LvbG!9;ERxHK%t+FGGpqs z#KdLxq;)p+y;uB?S8s)%zODa~JN}gPRERdAO`ZxYstY$y&>Cpht}X(O=sNRRPEKhKKaxd_X?9`^%gogc zc6oea=#d#rd{jnGbl$g=#6TS?H!LArAQ;0`ekt{dVVqj(EU=IM@f&X{Ft^~3ifZH7 z6GnK>l>0xC?Wer&oqh3PT_UzOdub2C1XnPK?ARQoLH{C$z*w+IhdK=a7v;^bWhew4@lyiF!$Z_5l^0{Qr?9h51 zs?*GgUZyM|_LeE|@?f7}jy;t1pHAsm`1mwxJ4TMNWV{Q$N<<~DbS6v<+$B;kwTg0b zR^Lux1aA?c%C_PHYnT^1MN<7}I%D1tiN7xFe<&!&oSGjB=tQ)Wltl=2W) z%Y2jz{GCV;aX$RM(98xQ7~74t*$cfeezf>TZ$8Que*Pf9(xX=J>^WT_;m(xAWx*yz zT!5$H(N#Vz)J3D>{S-mkR=(h2m_#Bcr#lB)NgVn!_P7edeWYos`8&<_Q${BzgtLeY za-bl(!@1nT#J@y>PG#JpV7nhMefE`V!b2n`mjWeCDQv3>^d3LMDEh;V#y!Y9d;tn5 z33O>5@bQwoQc1h19%vgC|Lqrf)c4TM8}a9Nn*=LQ!iYL$O(tm5^aiU5&!$p#MMaQP z;yF*(Z6wqVk?Grliu=vZZNFJ#jQ|3pLO?K|C42p?N#=j3N^xIY7>V83>&M*5e<5BR z4HXI9^TpR%O1qS=oUMAOW2+8jQ&j6eHRemr$%~U2yADSZ7fP zo`3rjSF`yMmy<8hjBj83*34VHZsB{e%15KK-D0UHR|HQPy|d^D2z*ppW{ql|{3W2d zCT)r!oMRjQDz5ZLcyumKRDYAuz1ckH58>Y_3Rc})-Qp9sN$0~pEv0?orxfMC^^X`{ zO}*LGA$*vUOTeOcU)IkH%4$@7@;B@wyKHqfM`FXNg(C+c()85a6#o_-rtRx&CWk<* zXi=Cg(FFlU_bv{_-EVenqbMC%Fh9F3R}Oa|B^I3CQvuc#@j&>Y0?;I6w5Mb?S0%Fd zw56sm8E}}yeV?1QL9|Th@9ugSua4-Z3EtbAa~(woiAOK+S#U?Vs-kgJ8@;;=CW7-V zgkgPZ(7oHv1t`Q_CC9W*ywGy>*y>yQXV_QQ=or0v)~f&wiLF_E1M8UVe%-w0ac?Qp z6F)G9?7%*%o&94cA0oHykS7X*dLr3CmEMX+55_}Dy2lEuMq`T@=;fk5c5zvxMNa?5 zwEw|gc%UOIv^8d6OyVX>;Zi80fToZC&hxv(bsn;3K^JCB*>knmpEcYmA*0sCQiP;B z%;QgTFhp$=r>j^!bEj)0FjR1aT1&yc{ zWbVgrTd^Wpz2d?@WOn~{q;j0JMRuGr<&|cJhtU=$EZ}0*=knr=9Q{mK@8;$=GVi6XL4#szJdHGDJdwZ~9!Hlm*UA1wFq{OvzwrYcMO zC|G@CPnH!S*YG$J3H*(fnI)q6v~GyG|Ii_DzjSl8d-~9IAv8#@-egW*MLX$}O9|c8 zzhF149y0(wQ~mFAJSFD16XqX3Ot`mBGv6H?Cu8L?;JjO#Bz80@zelex>X z5-oCTAugU1E5o6Y+pSU5ufi&0DVK~x$H%&a>^+cy;TnU^&_kgp^4uqR3g~tieSCE8 z@V;#1s=GuIQ)IERzj!{hJG$AcU!OQ~Qoh?$*pe-6Zg_2zBU6QTP$QaV)KrWXFbn-n zZ-^o)|B8O4ULEgVp5P>D`l_rzI`*DG8{PfThU!N^ZEGhZL@ya~u zF$KJdy!Ve)?}uk|sHR^Bo{qd?upCO17S~*wrMH%o*jT~Bd_Fo8Q7!%O#E%;2@ z?;Km2jbAK1hQ>6;Qf6EC=)8qMvqE|-oa8Th8FTCHl|WUHzJEHlqYrFpgHr8C{;jd| zKVLGtS{mcRdd*?F7hn?7cx4*pna*WXW`LNI-bY_~D(EG*_J2eWKsY&`)qctrf;|3o zAY;YbU|~pCQCw}Ij8g6%!1d+}AFX?4uo{ZeGAezGE2aEhc7Mn6v|-a1Zuq{32E<+n5f)R(GFzcf!CJ zT#482VAD(1@WhM_=e|I+e4NR-xk_;DMu0TAc)`|zjn<%1;`b6b)W+*gz- zXrxb_g9bO}5@Wi0=Id#m(`ui3a)Ap&3imFMsJ%M5HA;49wsSVEy;RK9nPZ%=Go~m) zp)bw25+DKGAMIk&r>vaNP>^p#6#vXqMc87e=hgP{JhLqc>2s~<;QdJ=k=3+YCVSkx zPj|GDBzlY_6va>{>*vp7oS$lot*vfaqy3T7<3~=snOa?calR2TZ?H$)UW`>^iH2$Y z(|)a~RCVq-XY7>Lc7+mL-9)>wR}+`&MQFgB0&kHuq^8s#2xU!op+6}UtsL2}#iCBx zf!H@neE@YzwOvmhLr8zw^8Vjwe|y?>Aolz+lefI|l1s@e6W7WeThLTr&KTpI`6q3V zo*_b~2Qm6^Jn_ehhoh)ONts+@GRiuRI0n_CX8jyv5?jx)*B|#p92gPU_vf+7qBs(y z12oD>P|N`#Jaaw4#A5FMTKe3cmsKh>l~ggbFF-^3o1Ai!g7V8c5Z~fqE07CU#K(?D z!X|L0l_s%Ns|<^FjCtR>!3M6_$;OsbsAwlvP3w zYyv2e#3$!O%SWeTpAe!Ha{cU#W%_Fx_bdan2Q+~)(z=n5CeCSA#5vgO*JYCSxbr3} zIo9sqJ~_n*qsFtDwhT!okdWr%*MvV9=gXl~70%tKe)k+K1Rala1vMhS7~LhP!VPL(naEt0n=`LpDr}`;}AP z&czTB_?P$8Qow31WN^M^VjE2^FMOHs2bKLKO~DmnSJr^Ym3MYLiz4@jGIm(B;1iLI z1;%^}Lqs5N_=LnQhOJeG6uqPA-83GFDenZT{I!I^SRy5s?I+VDl!peh&CeIx;j*#= z6PwpnVb=F;20xjIi7>^nLr!p({4dS3&}400dr`-|2<0;x_=moe{_CzOdPTZZ`%yD8 zunM8UD%J?lC$EGuH$y?h*)$=CGHeWer~RjVJ&4=WLH#4xZce+CyE*)y@MXHmaFv|k zX7nz%lJ#m!R-yFK#519v^|axdl3E!tAym6(P5&rWo41S6WU^P912zZq$nYxD8y8tm zx2E(^h43YCgmg16uT4lZ^+Zl3ep^Luf^s>DL(0)z5(cg&h$dfb%KIU z*yeOTxm}EJu)3{~Ko{XlW(QDVNun8h1{W*Wqr@EE+AO@pgm@~L4o$9H!XkTBlW0;7 zR>u)W%ggqKt?=ML<83-da{iSkB~=H_{Psm(fUB6cVE4Azp5*l)5PseZM>-!oMascf zF91B#Qive-z+-rlZ~!JBa4O@naT_Ah5&IUi?6fcSv`oGsC0H?hwQeQ{v2D$Z(}X|n zdz6;P{On1%c@%1P|sQq@EevhroSSZT`w>296- z5*hT^GbNDBCh!JLf}_lgPt(5hGyLk=5ikLA@la_YEDTT(C*XCFqZJh>`gV17J7{B63kzRXPlk0ZnRZ>4gSlRk+|%e&)^uFg%Gq3@4ghb zsFrycC9lUgEz#xD7rVr!g)Yps{X!FSP)K;wd#HDJ+5*S-H>VN3^a>!3@kWOl&{eTF z!5Fmsg3#BOH$^8}bB8fT@s6{i>VgVut$?^|F2&ew{#4RT!S$jrw-v+3jD5A?qK5cg z_j1}{(z=5%G!j!Vq_Ntki@gGZhFU7xMw9(7G4wz3DpY=DXYs#OJ#~6X94y>l$pegT46TC+ffA zaBe9?(qOA)q*4Pkbo4JmH8uU(oFtan@xa)?(|2VL*Jyud_P@<^t2!*}nFH-aDODS~ ztn$~DG^IEtyI(k;zb++QT_vsC{f*v47;7YRHgy2RIG)ybj~vSCefbEZlBusOKhjlr^jb4EfTor?XN$?t5TnFc zkC+C=p0& zFxUXjbcpx7(s zQMp?!@E{JnIWXw@J%!K76;`G6)wtkqD)fpZkv3t6TJOY*fdmghYmGGt2pRlh?2`rR zqd%2m$KF4|mw4UnrtQ-wsJ3ibbh>Bz9(Yl4#X%t{g}fQ(s|P1HKzm4X6Ro0>3S;~X z5QGM(@p~*r1O1QGgR8VMcSAL`5)ak1u=Xr5I!b5W)yn@DQ%0k zUQki{6%e^VO&W9s9d)f!0X+Q$$p74UlS!5E#5;ABbk zCl8N9SUBaqA|1wtGRWVCwpi`Q0o$nXwsWw1oi3*kJ%W&dE1~4Fj=5-FeF!fG0U9)! z#86KGYpg}Y=%wZ{&H5=|top688~`UweifL;Sc;)WO9)6yoe?5{SM%;}|LOw7&~rpS zkL>=YEJYRn#y${$n?i3z-jOLB7IQU%?C)!yEnh5`Vgy`$dBf^u+R`L0<58! zyW#IR^4Rl%febNTuNCssDD4^v_f4RRhBeuuA}dNM-L@wUy$23Atz5}r!8{I(!#Reo zz^aF6O+;ZKc7_stl%U29&<&#KiY9pIk;Wfc^8o@NfPq%f1|Jm%8;i=@2v~&IT|NkQ zNkq(FdV@GkMy+?mzRctH=f_+bC_hG{)E(c)G2y942v0%JJ=RQ$zcrST-w--~UW8n* zPa=MBldCIGN46bdV&CpM^dkFY{4#fF`xgGnkc~+aPfKn&Q5zm6QK!PYrbz zS$hzn+vk92fRJ34ZE}L;J#Yt1gOZ{$#<|eHX~I7M!ID=^hoeU4ynkM9K92^9-uRUL zSIFx&lgdYcnviuSWQpo3dYieT`JZ7@JcT1_UiBngcEw;K#RubFcT3cL1^+Z%IOj_Y zsG$uU_wS=JJ}3Pn6Axg7mjw=TcKG0UYd0X)(ud4lPwv<|S!xl3|Mo6FZ}j)_Zm_oi z38ch@QhNbR!8nrFqx z^~?>8;y3{CqTcarOz98iYV!Wi$OOMf-}a}U_Agi*eCg#IU`Fr3wkmD8jbJz;wI`?U z3t{*#0=@S}L4R8b(RgSWe~hur!`BaEil9pC|bRSJY@a$s_jiD*=!F-cKyR!cShs#^-S*aR1P79D8+b zN_O2UgLpFKwK-ni8)!YZZW z2;{a}I36jD>o65);|&U!p8*N70Q?A1l-C9(H@gbXS0WMD9if- z{4x^bj@VT|;tlJA$DI)17=lF}&7yX8{|w~yyuD3kR}oFmz#2BrL!rxJcAR)WEJA9N zl{{grrsW+fPby6+Vn69f!M_&JKq{7(Nv>(PR~y^+=EW?l~ zMy|lEYf~VswAsU2qa0V_40Ho>sWyTMl&ntCwiCoh-}w|$xITPY?)4;?2u0qI)zW1P zS{1%R;1{9eCNBg7Sax-tToBG!rpyG%^Nx(YWbX_Su32>#7&1m$5K$cjB^1mN+2T%Z zke>s>HF9B})p$)E(zk#$q^rs{m0bb`Ve?Ay{Tt&7W6&Ba2|tHWu90m@2Oz-2MSEmj zwVf=6DhzxV2K1IFe( z3k&Vur!h~7$IW&IiP6#%;*h6J=-yQJu6go+*y+9j*;o;+4+A zBe!a&5esvUSTVTN4cUrEWYYd^5C6*=H;XUw;%Dh&63o6Wxy3W0Wupsqqfyu|DCxY& z*sc#V^SZJfe{^sb4p3StqsY~tH=v}W@pcQMRwA#-`W?(4TCrN^pJ~_2KYGxw7b)s8ByY%pLTz&_+Je z(%`RSp%cn3KMXK)g-A)~jx%#kb_W4;KIK|2YZa5*tT#I6uuKDiWHzU$=bA zwq-0Er64M)(0APqD3Q1*eme3*v!p%eAA|eC%R0onkeQ|s)BX05>qZMCGQ285#@IVa zhd=+E0AOOzu|RCPE=t|A>YY@p`0Yz8#^1@fPuzyDziV_6@b! z_EazZbmd1VCV`BQ1^N6Z0!Jq~Ql+nvt<4sfp0>535bY0mYuq_Yi}knAru|V!NV8*} z%H2N!lWBQq-bdOMa~IW=z>X8Rr>z!UU6p3^)%63I9yQT~r9JeKSje5OnzQ}CPg<9f zqP_mcNM;gmAyb5k7DAKi&s(U1Qe$x`lNF1()z^tXFT+Fi=Z`-M?*1{(CO4+v?NIa; zCNL*J8WQxW`qiI*mGB{!5Xih5lx@mHFuf(oal=nv6A6Ax>Yw2^EF-`8=M8?mNmvkB z9*F(4_$`_16NDQ@c0$nS+@gtsftL|L3Mans_EU_`6e)8KN+1pd$yA~_+L>3Ld6L1h zz&_c&GLG$OJekFNnvORJDL?xJ=i!PtSeWIXlJ@0a6@vIC`gKP1>_W+hheU#>M%2Gk zOV=aPQxMJ|)!7VbK4xgRty@`y2X*D$)uu7DuF>L%A5KZF!?{AG<+m1mbNRovX-B^{ zuCUd!6ZXvAm&b!G;1#KHC({pd4bopE61$+}3HO8*-H6JEd@cB`HWK0#xOTwOPk7K>w1~7(h|1S? zzX)7g4uYMm^{$M5=xi9b{Z|hjMT_4crAr1jGf!+OWDxX&S|yHcviT1^zZ%5NJdR~I zdVVVqdQhLiA#?^m_^s%XsM}@q;_fdYbm@cgoYn##(fL$#-*D zqMfu3YtJVhNgBsvH+kBRx}0ZVqoshXN2JM@l!zJat9>Kwr|9>rt`K~oHO8zZZpc{w znp54D0zs-d4p0%?ur-D6i3^mXXN~1z3MiPqnN=<3{h$8aPD_nl$l3VvEBfxQtxYD_ zD&%#&hweWUJScqZ#=G7|nAnk!6Tk2Hm$;^+A6O32F{72Bo-Isr{ zOw^ZHH!GAvRz{iuGy)}cOzfgaMoQBo6`xG#zPgr&1tg1MZr7Tr-Y;-K8)qDLl5PjuLnQ^m}R|qB=i%&0(288 zz#3`0Yu_ZYjo;F)Pxx-|KCf(X6)FPFc=~eTPuE9KT~R2@V`R?jUayn2 zhcJomKZG(8Ep`zf3~uvfvcqcd-tv!SziiUV7XK=f7bbe&sojoLFvii9w6qxI5$po} z_yOtS@xoVxWL}tDc;r&th}N*QTui?JX%JWma^YmEG#$_-Jnk-fAJi8fwjn*&qGW2q zuo7}o1WH(S-nkIscgZt`MPG8D9S$vP z4<>x>Z6tfvN{dowULF--OI9x_XgTjpJ=ra|vSIBxRTc5mt*=}>TQ}(5%vZeoC%LTs z>~|U_|9z4Bd|z>B2ASL$PP5q0S}Tt(R9P)DGNk6hg1=*<8gHZNuX5;;uu0p(q^ru# zq7KZC0^$vDt>O-7O;4?A z#;%$`p;QJ>ARjAC z{!2)P?7Tk@E$HlnbkdpH1(spWJuuqxnbz++^_5>N-s?Rzn@bt5)y+Izd^*m+Cl?ju zpi#zDJxE#K_?tn1NnG%G_aC!rLj(EGGL{Mvn!erYg^jN#74qIt43^nB(v;)#^pPb)lEpYaSgv>U%H@Ldy;5KHNuwbNZ-uXO|B z2FFla)o35(ru*pxy^>rHpZtfc`xxP7W7Z%-hEWWTi`1bR+mdRsIvPqTN3Nk}_)Ji9 z5qq-Td2=E8d?#!qDuYA$MOUwMIAQ3k?=Xy(s0>q+PNGkfNM@WlZi8Xp2uZE-DpA`c z((@`Nq>lR3$@KX|A#>(D<;Jpc0H9#)FK8XxAk#cM%Bx)gXG?@fB~+#{&2mW9nxisg z-{!QcFZUOTdFN#GfL~+j!OsYs|AYuHAio563MTiba5R|ep|gyjE4;@1F{6EdQWo`y z^@9P3C$cVxLuFPd?s?SgYoGeZjBPZ%>9^t4W~i1YNesA@5N0uc7H3EPyd5pL62}V6 zBSz$Irvi0>7J!6h-5xr!&n5_`JCd>y#hAi(eb#;M(rQ#Z0sgA5(e2HMjOX9}70_Y+ld2c~0xVMi_RZl!T7KTzNRILT9%TTE*o zqVYoL@GQ90COW~(g(gcH{nH8JdjZm_zwKm)b{5kkZ!7hV>O?`C0f;>VHCAjWohjRbQYP4 zog(>*M$%=eW(08b|G50TQTEGNotf3KhJZn)F{aI=wtbm|FW;T&whBdpH|+8FNS<*4 z>uDqt@*w&ZBZhu_{CKXYCvBg3twNfEx38%3%JIFfNc$4n@{xlkc8aN}Nmir5UJ+@r z`Z!|iRxOGSTc$Ui$PjuSmMR<5?jxD4oQA?wUT$$2?IJ<-a!5L{-@dhpK0 z!~dA|1lyd5hY_vvN44Omy}8U6<`9B82OLMTsDr{d0||c>IS5BRjBx4zm)FiB-kFU= z?w`TWN86KWhm5dluziQ+mZMrt^=wIvZniE%9E)sntIYy6BicZchl1CP$cAn5X;|Eu zc26_d{y%WVXcS4(8k;fncMD}Ci1Wbh5VnIlGQ4Y+5G0DSd)56f@9xE28!0y%+BRCX zh#fDfwuB=DwAJ(!=b8>F3R+&rx7Z^Rr2MKpe)Qqkw5d#17$Rs~$4HroELJqahvK-N z^)D=gqEH1FCgkYnZ@fFN@WfU}t|PX8zG7GbYS@;#88wTv&%wHIHe~KM<$D$rD1IJ& z&`iqInBc^%tO5_4*mxna@c}YxMIYsx$9k~^50?Mxp-nK`1~o8~2qC2H9^FlBZckpy z0{i@@$ktCd>N1qtV9sY%x8c&=EBG@XV#usZd9bY1=E8En%V}heX+ubPpMhui@R4Uw z$q6=9KU(5`7UL^h<=vvK0mlPQUjbUfeM(l=Q#YWyxsNn1&say4=lGIQ+QL3aiuNU# zDzT4`VRL83{%*Z=H`D04wfZ+9rzMYBH%LzE#HKj+w1+pp zWg75G_YgFcqFOvJ;>b+>S7!{5@zlOx`lVB_d{9O!9s-itWWYuuYaj0|!;8e#TW*Jx zwnGZKDVdYMX8XO;%0uNOHvBRk4KJQCUGvwV^!5ft8bq%eYSX5pIAs1>?6 zdy`1(Rc~809cI7H;vGXUGspgE!x59*#A?=DFGH7jw`jBU|jxcE!d*M6hEO zvDCA}qy-#+*t%XPS!DClG}O6MnxzLntB0tJZ8I$Lc+xEREXHxkk?%M9o&rj6Nj<`~ zCvhAFACt4Ns2$px3b>M-1LJ*f`FaB38;`ar=@5Jx83%3Y=s#qE>p=Wi%$wEt*3cN2 z=RIctvemG68KuWe5014{Zz_RUN4w~r9Bk3k(d$~1^h+tyxw<#m-HDaQSUV%lz*0oS zq2alC+hE57ct_M{pk%j==Aypb$;ML=7riVk(|SH5XJ{3#h&n|&RmWG5>e?YzUGAHc z`3%Ij3&^O*ReoHAe#UGD>3%i`kgSqOU8!@n=Bf}71z8JR6BgKGnl6kA^bFZIUBYG6 z$MR-1U0@yp*%z3Yv~7*sLbDxz(qBI?Ti|-UdNjInb}j>AK!?1z%aI+u&Dr3_o4Wu7 zXbwq}Q=mkJk)6_lyoKxgy)Ejs2{*|IkshgzV$f^-YHGhN;yv(Z2;Pg_RxEthL0Q=h zWCoS`M;;{HJ~5F|uYlNZNQD19LOliJJ#9~UHLwlgpt2NrjZB$@yTcF+O0|YCy zuL2_q2yDLqu7Oism%yF+&;?aP_`jq`)aPCD+6|_E3yO}xJW5SI6XkmvT>aH(76+*0 zJ(v02$i1ZmTPZ1sOc>5IGA;Qhsv)S1Y6zzWRJktv_ZOsc$zxv;YExnwy}q65uK3A+ z$$_eKxcX*RF@>jSC%S?B{@akCQ}@=Nuz&^;SPh%lK$b&42$3?Hn8sIxi^=~obOJiAo>$O{x?R3#SVsxbA&#dgXo0in3)4P zu@}$ooH(Jy3?DoM6p=P6VDuIU2MP?V$2o=?^jstStrYUxdi0-%qk7gri zHmY|jtFD#7M_2JYK&#;ju_5{Zaz?piS_nTOA-hgYIqLnhPQmN}gfS))R3iQSrb7c0 z5R4il)Dpinfc^Hi#2t}LBg9qx+K!M_V=k>4|FLy&EL~?l)k-sp= zc#|_h>jYA$Fs)!`el&(oJ?01cE-LVbyp6_%2682MBciq0)L&SIyw?eC^w>a{H>sHI zq-=i1@sj{qna+L@Q%3H>v&eL4l07Ma?MDvQ`?6p~jlcyc;C%8t&Ih~ja7DSlSLm?k zLiVW0C&;O=f?NUIAcgv-B_0n-I+cdp3Si7!Gdaud-@!a}F=UlqK;I9PbH>`k9_F6@ z;)U=t7^Al^e;?u>7#oZrNQzFXn-fK-u~G=4`VclHy{m;uKHf@F1ussQ#uAz!&ZYhw z{B3<>!~jm{iR5XpU`xER0`JdU0Jnd?t2c1pm2)tWAfu!IUN4b^rUd zfKwter$z-Q%&Hs?Zc2;wBHjK=;$tnf9$?60Mr#V0B8Ta;8++cZ{J3|8lK)!}9ijag zJa`GxYf!o%!+q_vpqpjyW>-OKir^P8)m|%022ZJXd>fCA)R#&1&BwI0yI7iy>csrBrJ=gRodS@UfuHZf>%VUPox) zO=gEo78@kwe%v>JO^80%dvVcO=FdC@)))wOB~QJC`#xGC*^W~glsJ#E3Qf=@xbczN zx;5y7G;En;Wk%y9792`=AP^eH;A;OViGZCVp9oa-7EFP~u17LzO4fL~b;i_*8 zrQryYMCOi~S0cJ-rx3w(^aXQLz|2+9$)NhBB~Vb}^oaNSj|AKd|kxH{5p$Yj|2z~Krd|VA|aRK!4jH{j^lxz&6vagyp&&1z*3b37&mlV z;64$#IFjyyA432zv+@Nd0(Y2Q7<=J%15#&sM6-urj*-vPs8Ir=7G(3kl+{YQO}07E z4oq;R99TL1?so&TPo93e;1Hg}^!)k92(Rbg5(c$=iQU-QTo~?LqPoMotvBN_)cP{8{+1+N~Y~a`>#PloC8ZvUwCSdu(!*bUuqBj$;{ur~u9g zUOz4We#q>3(_sz*2^ei4#BwuNq`3FAdjR$F>a8GC3uZ8fttJA8JVb}0pL@zuoO!qy zAWn|#RgYTZ$US2bo&YEUsu8V`PTf%Vu_eM-#deZXF^g8)PNC>s*dH(iWi><5N{-q* zhwWFr5m`(8&`~k@!ee%e@B5dN5auaNkjWT<{m{;4iG5(K;Fm17IiHrqMcJsI3;K94 zmV@0RJ^w(nX5%xCdn^i-`yhvIPttN9#8OhO4a2qk^podzT6zSvVge0JlB zHC7>VQJ%2u_!0?V9K--SLz_I+m&9=$WQ$YyUWgP=j z=XJ)4y1?5sno~5~iLEfCXKD+q1$4XWsn*f=Yw^#F5q@oOk|fHIWy!nmZ+5Z3WeGwt zAj*5;F~b?10D`i`^&*8okb5Pl*dd(eo_-b`6T~l{lU_!_2ZvXT{47#+?%acbBj2V% zT8UG^HmjZ^ZMK7fMhVxV(X$|6Pj0O0rWq#3N5f^%OD42f|5 z%Q4vjgG$~OpO;@wiI>$j`0FZIOfFbboxkksEp(=5KLbDsQuep*!{Uqr<~I^Pk$@&( z<0-t*&{1#=E4CH<^gF!QX|qZ4*)fuuIM@TQWQ2{t&W#|Qmg|4ivxJk`jzVU>1}6jN z>jKTe=K`W~Qf_2KnR^?~&KTr)E`iUcwBTm<=<-0*1qF5&Oi}l-=D>w$oL$n@bcBJ- zmf5mpFJVtFC-`INfXEi9qTe@(gj~1+QB6o?mhCSXf3)nBo*1<2E{K$O0?N7lP1eQVb8qKfAwd(OM;=N&qr<7`Y6~ekP5%3e# zUBsMHRH7GA@WXlMf3HQqCST1~RW2d;0)iXs5*+}SuF|EoYGc0R9#L^6n=$zInJGR@@%GKvF06Z#ACGBD{ zdkPZe5s;^6#Ce;mv_a&l?QcrZGx9vb=7Bd!(#WH11lfwN!rQcwfkDqQ2;z98P*nnK zAM3C>L;k$^j~o#(wb!QWvsbhkM9{t^X9Wey`5$lT&wyVP_3B!93RAHC`l#5@ER;P& zvOD^-=%0mZM8-T*h@M!$(tf&(`{AYgUydA99t-6enEGVj&tE?kET>rg{& zLV1{>`tQ0hB1YW?755V6(C)pu#ILH&HPRO!S4?H%Jh0X9JRW>R5&is)i>eXdLw^1n2W|9@T2j94jU90Geg*Z6( z{K|`7bsg>(2q)B)kNe}C>M)>=)weKFQ}|a~qe$vZJ!VWn0I7X(l^&;qY=HmWk}q2@ zjJOwFp-m-fcs(bZ@h2|sg5SkcYgV|U5kN@-TNW_}za-*H&c3*;7kqhMh{~JatLrVx zefdG~*i@jja$ZS$OoHtft__<;80yj3$Ho`WGnjYOeU%Rh2Vjk4kM=FhP1P7e>>_`>>?vb@^W zsprK@MYEX`-NZSc%oVan{btXLA>R!$ zhmZ2i8@3r^V^Ko&3j}Mr-uP-#RvXWwaqk#5tH`?(=KEB$<>18bf8qYItu@aU)YE(C zVM>DA+WzzTX6xSn6XHNXinR{oEoe{ZqTPT(XJZ`Vo4X*#)$?N~%TA@L0pTN|frWAV$ zMEP#=^oL&KU*P(qJtve2brfU%JAO=E?%GyJr}huWeEvrMc*s=vRbywVXSr5oUdy;X zXHU)Kt?bGy90unviHV|T`2NpzZ)blalZ7wPOIJSo2X>dc7(ib17Gi#y2BM7x)}_uP ze|d>#IVxL&Lwj=MG{OGeRj+3h>MZB9zG$`pX=RAugJ5f?t;gJC>EeE7HYY^;HOl#+ zh7&oL!_i6SP*?CEiuDLua}n7`^o$2`vTbPov$l)Z+Y=VLAZ~&qU2Rd-`-Ky+k(jg( zz3;{hzu644BsFPjXOkGoXeMwpzzkE2dg&*jsyuDa_r>%7lnaPJOoY~ZZyB%7d)qmY zXEn@4L!ujqBAA@{QdE2u56yYSscBVAV?HFBBZZaVLbSIk{7u$*F}v)u{6CrmpVstr z+?-ExS)i}nSqNhcs~t8Oc9V`NX1V3%UojJunjg2VV_B2OGGK^cpO~Zfwpeggx}c-# zAwsfkZ-1XZ21?@hWYFwk|B^3-bNsPenp#CFMzA3?)iNK$>i>SkmmWPzSR~f#WhV0F z{e>KJn_kA6VkxThg4d#w0KEHp0F9KAO@7S7a|-@foYp5*e8d!u5(9vM3O>bIKIxzC zzu0olk#mLI#?D%lax^DvM<3pV95M^3+Jh}xkdThbWqA*HGaoXegqaZ!^~lVd%?AU*(!x$$aiR462?7eoHbYY|aUYkv~HT^^}dY@t<}G*jP~_Tm-N{U97AAgJFXHFp{CT_Fn&-`WuN z;AJJ|MU3kgjA`zy$y-{fA%}?Oy9E6 zV~kxb76~!TT=zObBmxzyGr2)%bq0Q?e&a7Gb#ynoLDbk_@gVkbl0u8kto4@&F1{?DeE)>H59P{;kT&x-nuA# zJ;K9x$7T6*XY@x|3FdDIMI%Tpozt6zB!B&O;#=Wv`(RAa)-?&@|1Xi}kF68$3U?k5 z_zMgb=cF2@d@vW86M(09Z!S|$p2`4Xl^x&QL@IH1|LF@CF;$^aqNtYc8};D)b=jU( zO`)nsAxBdDihM$>P(4{5?x#x!r+afcE3(b?nz=9fA$bu*uYR~B{_?fu--NIErZasd z1GkA$c4LhAqBLIj4V%&W+a+n#1FAMpRC$nLNx7(1CQkcCC5s8_ooBcL>X(^X27wIHeT4Q}sNkI)lDp?=I zW6l`z)YZ$t0Mev)3Vkd4_?B&99jl}?I`W}VGOWYqq9?HS{r*1|nABIx-Xlnn%xmYA zm9CV7NUN>p#P1063sj?bmrT(_OH#^ky?r*$+D+tjooJSploUxQJ zo(U9zL}VK}GRJqO#9JM`*8~;(D}>i>Ae7jv zXX5d*e}~gd+sBCRYY|;%Mk0p4Tz>TQnR-=*0Rs_Xc?`PfkrK4}qh0^<`QPA5MFdUW zu4y$KtA%&bFg2%RK8u#U{51DftUbW7f6LZ>?iPc3E2gGFg3jS*XI7>i(gn~g;eW|7 zY@3eFBD#gQIbo+WaU9wPA4*gdwz2nx{}g@TcSWL*=`JUxZyOji+vD^DzvI zlY>~X;p;8+5ALcD8ODgpF4zBg6g%YSS!r81@Zf^zrSDoP8!q_1PqQjhz;kUyNx5&2 zov)fDOy;tcbR0PG{=}N{hVp!LC$@LP$zzyiFed6?c6le;qWS9+vG15+q+(9;r3Y<( z#mGBxHFPJ#ybsz;;h$pm4d!USG?<(t2fDMBOmap0TVPOqfuaAlt?52cXanJ^Z``Eq z|LfvgI9WuXvl9C(eR%FnD^jyuCl^kP>@DJ(>JzPAWj#b;U*q~As%7HvOXHu%BS~TGf*_1=8i8;&;7MH1?`* z?xQDccx)=}SLUOyYpvH`U=vfiq*5V%My5-q=${HKK56_4OdFXi*Y>c&R5{`^TS_M>3AA-v8i=ggYGWm&*k!E+3Hmp&#B@Aj9({h zhX3PVZsU3J$Vw~d7J@ILM(^PqlNb`RX$bF5*Iz)QX($jchLjtPGrTNVG`C0&A=}Il z%i@?eew}RZ3OR%@D16qU@NW>vkzvm^Eq|n;w1$P*-(G$<#k@vj`oM$bllAs$a01zq zLL$PGTFI@3yL7^tl2By`&DF?Ny^PQ3-lZibKgJ(U_CY1;X5_tO>PM3@eV$V@v|=)X zjjMD#l#?^9j{$6qG~>=T^kVUDp7B(aD_!e$2$yFnKF7$YBKHBW*t3I8x zJ{Jur&Ey5*8)>OGr+LTH%*)fI_@0GSli|q;0v{Oy_F(_%gGvU2XM261hftd@z^5w6 zxi=2Te-Z(SBI9bxzzW>+uIhl5E`QRgA8ZrP|*|2+sNCHi4`taPv!9s7$F#^A9# zTWz|(2v^#ODxV=V6)~8Q-%|YSZ3#Q zCv&(YD%p>){~}!*+c>`>ZglnvRosmkY+k9noz0NVN%XN}Si<~z2t}1C6flJ&sc}g9 z=y3bDt%)1HuJPu5jmC22@<#a23l(tM5ljU4U5V6MGN%2pT8i`=0GuJSa8Jp{1 z$i`ND^_MqclUBb1CyB-3iP5iKY6Y*6bjW;`!$pXS{Urs!!O4V<2kn@TUU^&C;g7m zR*%JX_7+Mi>V`oszk|d@`2Si4;0{iM%?qe0j_N1MthYctKl#lI@U_=+WGCkFQ0jyu zQ5*%Lr&26n)rMQ3;1DOoWgQu;U$92tL1{3O;7oD91X?|}${a8jGXHMW%n0RWE#|JC z9$TCUgX&a$aN(!vzF^7Zb?xBq8ilB}se!!R5{1)t{e*-O^0X~WB31Xk@{>iF5s>t;g{3C)D^^i z)X_x$JoGwpF*mGWgpd(k=%g9~WqF>aPLDs_$9B#%f?9Lk+YwM75S@vO7m8AV62<+8 z^PM!mIZU0~#i7(4rdufXR|{YzgF@p_K#L)(UpPg+4XWu*_D8#+`KPs(=<6FJRSt=O zoUQY^)d;dl#>NBV^vQAGqCF>|vc}@On`HWc2-EegLRYp18Ary)5PH_{&C6jb3uzMX zex#%1iD(~G{yoCP@2A`9kLEvDovpGw4~eqInNG`Je9)HCsbMWf2ZY;fsZqX-qq6hm z-_jUDS?jLxG-P-$@N6xU>$aB_=j1(u1a{w)!zuahW7&zr@I@*8NRG$!mO=)flG29=sSiw5I^k7)R9H zYHYGH__X?fLPwaa9`e|D(8#6z)0oMg45jXNkF=0QVb&BqeBT|HzHX8mCPb}?fM#6W zDAYw=(O>>X?7wb8FmJ$xRvO*VJ{y-ML4i^Xi?z|0N z!{>tgZyS#70P@%n>M@bBQ&IrT(|7)r%#AiQI>{5~(hoPcTp(3zg~9Jn6(ty?5( z9iV9u&Lk=coj5_Dn8Lr|NPu^0cqInHAv(chL{r=y?W-d;{%L|JlpNOcn{0GH_HMmF z+GH_5pYxG4RcHPKlwIb>_(c#2cxP_I=p99m4tm8W&JS(I|DjZs+GMdvMV;L z9Dm&t9o&xAE|<*?l(c$A&{YG{)3|H#?_kWH6p-?_quEnal$}25;qyz7?Iw?FfR6lP zYMsr0{MOexZ<(2`oSn6UBL>IVZBr7AgeXVM?f)CZERY>lC^r#K=zJxZgU3!y*$}$^ zZ;9?%Y2Yu3yR1tTr3sbcKZl|RHQY*Co+UqL{(!xP|q%7Tk2Nj`yz%=FIa5fzds?C*% z0mYm~X11+#W*lMX+rR`|L+b~ZaQ?~%!*f;MO-eBAsYOpQLv_-I4sy5+=OP*;&!Oe# zC#&cLuwxtIVE2Y)(^Y2RpWyqzKm!-+Tb9fwueh~Ba%1vAIIc!Lh|xU+qADj_i#Ij>rZ zI)m>ArPo+xMCt}SdRNQu0uscaAl%iG>mdYvDqosW=|>O7 zf~Mz7(6w6fwku~y{2WBWs|3)xzWR*^H^Q17&nd`R97>+!zEswY6mHUTQsAVfFP}BZ zBF}aTrkRR1njfDvOCLc+__t$c`YD8i`84Jw)|6V)w(M+Fi?^1@5{Bp}ilT|`OMoo$ z+=ZvCB_r_K{@XBkM*&k*Rt@*6Fw%s?Ui-UUzaU!4KoOWsV)%cpKZj`;@+5#i5_2+U--@a?D5zTltTqLwu|~)W zGD0qqTL3J9S6)b9p1@luPsU~P0I1&kY~{h@A?Z26<^B%t{?)Ozr{EAy(~Ig3ZY>=q zRG0@)67l=!B z|M`9S?azLaVADwsd{>caTzs7v0lghUQ1|^VVDhm0$pVTCC|fW6_s#Gjjse-1b6iTW zI4-F)`ZJQi?M7GKI|@*W!PT6@aIm-2A7L>HP)XKflmYZya*$NI>0i|!^`J06S0b;|+K*}+$abVHZI5dzqIy5}yPHq*@~ znZLmwohZbGcRgI0!@qH&(uj?1h#Bh68@V=#O8jllM47U6wd)tnu43$q_5s~Zk!3Hi zUHjtg_d8R-TFw;|M}b6>mkeM!X!9`}3qv7N#=SiQr3VCyJ*woLvc@{eYxe;)Wn5yWEu8F`(4C`p7Yu_B(DD!Aufv7MYq@*Mow`AX(Snt6# zHN=arR@lZ{Uf>$6lx?1LO*dcX z11f69+6qx*IJ3tyE8C7hZFiASM6p9`WCnsz(QuRh1=S2s1zi94OcZq;Kc>QPG+3%b zPbn~^d4=0Jn%HhEDlsB1r8SG{qA|_kln~$1y9|`PhFpimkY(^s7Z;W^6!qpz3v6%B zh+{=J^ed3PC3q+}n14snp5qEs!GdH=Kh#QL8-$|#7M!Phn&eA|;IWaQT&?_sLZS=MX0e)^v?F8dK^K$rW8Ma24oAb=H>>(!?j*rDm2bwz_k?Fg~`6ij>>e z0jAQ{#y8sfX-PFBNB3mk)>7_K=AM*#B1z9VuM}Ad)k10K)8H!Y00_8aUTrLIVbO+P z+kNQ`ijwcG*wV29E>!&2|8NVF6QTf{f6nGH`8O?8tN#;-B#`H} zmuX#VhMp!^x=l%fV%Sq7-0IcWM8Cp!PaZeLp~n zat%~tq*m(RJ5QcKpwpZpEdJ71mLsKY<~4lp5}nY}Z+eN**Wa1MmQ4=*?$`Zo)X)bw zG6Wk$|M6o&%Ct;M!;E@|at@5S1& z9aLUq&?sq;`*3R(vzi(M@kJ+1u03}%g&*U!3_FIvsow|@sY|GGXK-uXPz!brD<{h! zPqss#GD^k~o09%DMZ(Yw?vVBC@Y$RS5`yp$ARs@+n%(@OZ=-d4br99=Ihz3~6V^qn z5qE+#2BU-EmV>z9Dj#5v--y0_D>WnY?~UzU><~iQX!l29xR|W+BF_8haopteEXjJo zT9_@)r9~L^A}duGHo^EJ)t1Wu AMdXx23#I5L3<@{Jj7#e8xok#fH^h>mF;LL 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îJ{V)mZSz{N>6 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

~(dan_KS_=jxA{SIb3-gt9VgzWuVAyRH5` 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¬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 W>#a6%= zero_at_or_above", + "fee_formula": "final + floor((initial - final) * (decay(t) - decay(D)) / (2**128 - decay(D))) for 0 < t < D; initial at t == 0; final at t >= D", + "decay_samples": [ + { + "t": 0, + "decay_q128": "340282366920938463463374607431768211456" + }, + { + "t": 1, + "decay_q128": "339889484297159900493648602831395569057" + }, + { + "t": 2, + "decay_q128": "339497055287118257930533248299650400736" + }, + { + "t": 3, + "decay_q128": "339105079367080975794817953502835654533" + }, + { + "t": 600, + "decay_q128": "170141183460469231731687303715884105727" + }, + { + "t": 1200, + "decay_q128": "85070591730234615865843651857942052863" + }, + { + "t": 3599, + "decay_q128": "5323057870043623375265121302168923694" + }, + { + "t": 3600, + "decay_q128": "5316911983139663491615228241121378303" + }, + { + "t": 4095, + "decay_q128": "3001302186225928519447628915004417640" + }, + { + "t": 4096, + "decay_q128": "2997836947964109484105344435429137776" + }, + { + "t": 65535, + "decay_q128": "448578" + }, + { + "t": 65536, + "decay_q128": "448060" + }, + { + "t": 131071, + "decay_q128": "0" + }, + { + "t": 131072, + "decay_q128": "0" + } + ], + "fee_rows": [ + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 0, + "fee": "9223372036854775808" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1, + "fee": "9202201616401419411" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 2, + "fee": "9181055638855315221" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3, + "fee": "9159934075995213866" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 7, + "fee": "9075691408129487047" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 8, + "fee": "9054691496519770908" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 15, + "fee": "8908369439011360162" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 16, + "fee": "8887562713709937803" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 60, + "fee": "7995460709951335352" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 255, + "fee": "4544718788142383059" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 256, + "fee": "4528950239025284596" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 599, + "fee": "65937678028368294" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 600, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 601, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1800, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3599, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 0, + "fee": "13835058055282163712" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1, + "fee": "13803238529972994238" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 2, + "fee": "13771455742796052931" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3, + "fee": "13739709651334290934" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 7, + "fee": "13613091396010955051" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 8, + "fee": "13581528148581462183" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 15, + "fee": "13361603446451114442" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 16, + "fee": "13330330561541330735" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 60, + "fee": "11989485095127495662" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 255, + "fee": "6802957498364342015" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 256, + "fee": "6779257083494175915" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 599, + "fee": "71268385174867108" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 600, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 601, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1800, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3599, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 0, + "fee": "16602069666338596454" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1, + "fee": "16563860678115939134" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 2, + "fee": "16525695805160495556" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3, + "fee": "16487574996537737174" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 7, + "fee": "16335531388739835853" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 8, + "fee": "16297630139818476947" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 15, + "fee": "16033543850914967009" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 16, + "fee": "15995991270240166494" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 60, + "fee": "14385899726233191848" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 255, + "fee": "8157900724497517389" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 256, + "fee": "8129441190175510706" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 599, + "fee": "74466809462766396" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 600, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 601, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1800, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3599, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 0, + "fee": "18262276632972456099" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1, + "fee": "18220233967001706071" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 2, + "fee": "18178239842579161131" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3, + "fee": "18136294203659804918" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 7, + "fee": "17968995384377164334" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 8, + "fee": "17927291334560685805" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 15, + "fee": "17636708093593278550" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 16, + "fee": "17595387695459467949" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 60, + "fee": "15823748504896609559" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 255, + "fee": "8970866660177422613" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 256, + "fee": "8939551654184311581" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 599, + "fee": "76385864035505968" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 600, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 601, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1800, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3599, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 0, + "fee": "18446744073709551615" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1, + "fee": "18404275443544569064" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 2, + "fee": "18361855846736790639" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3, + "fee": "18319485226673368001" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 7, + "fee": "18150491383892423054" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 8, + "fee": "18108364800643153456" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 15, + "fee": "17814837453890868721" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 16, + "fee": "17773098409372723666" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 60, + "fee": "15983509480303655972" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 255, + "fee": "9061196208586300971" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 256, + "fee": "9029563927963067234" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 599, + "fee": "76599092321365921" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 600, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 601, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 1800, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3599, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 0, + "fee": "9223372036854775808" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1, + "fee": "9212618807418150336" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 2, + "fee": "9201877993426478367" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3, + "fee": "9191149580545157043" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 7, + "fee": "9148359654010184691" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 8, + "fee": "9137693032240170144" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 15, + "fee": "9063370717315263099" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 16, + "fee": "9052802221924064440" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 60, + "fee": "8599671045411758433" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 255, + "fee": "6846913243858004888" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 256, + "fee": "6838903822084240589" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 599, + "fee": "4571976806974695801" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 600, + "fee": "4566593977358320110" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 601, + "fee": "4561217362640007375" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1800, + "fee": "1074010432735978337" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3599, + "fee": "55508445646640394" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "9223372036854775808", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 0, + "fee": "13835058055282163712" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1, + "fee": "13818895756712426836" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 2, + "fee": "13802752118781282045" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3, + "fee": "13786627119943561666" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 7, + "fee": "13722313085493613281" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 8, + "fee": "13706280959815140713" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 15, + "fee": "13594573174606075194" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 16, + "fee": "13578688534651899342" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 60, + "fee": "12897624170759157718" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 255, + "fee": "10263197454942952691" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 256, + "fee": "10251159148977154037" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 599, + "fee": "6843926794275282897" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 600, + "fee": "6835836303886082412" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 601, + "fee": "6827755154601213974" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1800, + "fee": "1586419990339021438" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3599, + "fee": "55593060045791169" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "13835058055282163712", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 0, + "fee": "16602069666338596454" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1, + "fee": "16582661926288992736" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 2, + "fee": "16563276593994164252" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3, + "fee": "16543913643582604439" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 7, + "fee": "16466685144383670434" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 8, + "fee": "16447433716360123053" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 15, + "fee": "16313294648980562450" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 16, + "fee": "16294220322288600283" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 60, + "fee": "15476396045967597289" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 255, + "fee": "12312967981593921373" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 256, + "fee": "12298512345112902106" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 599, + "fee": "8207096786655635154" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 600, + "fee": "8197381699802739793" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 601, + "fee": "8187677829777937934" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1800, + "fee": "1893865724900847298" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3599, + "fee": "55643828685281634" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "16602069666338596454", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 0, + "fee": "18262276632972456099" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1, + "fee": "18240921628034932275" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 2, + "fee": "18219591279121893575" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3, + "fee": "18198285557766030102" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 7, + "fee": "18113308379717704726" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 8, + "fee": "18092125370287112458" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 15, + "fee": "17944527533605254804" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 16, + "fee": "17923539394870620848" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 60, + "fee": "17023659171092661031" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 255, + "fee": "13542830297584502582" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 256, + "fee": "13526924262794350947" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 599, + "fee": "9024998782083846509" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 600, + "fee": "9014308937352734222" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 601, + "fee": "9003631434883972310" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1800, + "fee": "2078333165637942814" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3599, + "fee": "55674289868975912" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "18262276632972456099", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4096, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 0, + "fee": "18446744073709551615" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1, + "fee": "18425172706006703335" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 2, + "fee": "18403626244136085722" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3, + "fee": "18382104659341966287" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 7, + "fee": "18296266516977041870" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 8, + "fee": "18274868887390111280" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 15, + "fee": "18125775631896887287" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 16, + "fee": "18104574847379734244" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 60, + "fee": "17195577296106557002" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 255, + "fee": "13679481666027900494" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 256, + "fee": "13663414475870067485" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 599, + "fee": "9115876781575869992" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 600, + "fee": "9105078630413844714" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 601, + "fee": "9094292946562420574" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 1800, + "fee": "2098829547942064538" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3599, + "fee": "55677674444941943" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3600, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 3601, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4095, + "fee": "55340232221128654" + }, + { + "initial": "18446744073709551615", + "final": "55340232221128654", + "duration": 3600, + "elapsed": 4096, + "fee": "55340232221128654" + } + ], + "all_second_digests_sha256": { + "600": { + "50": "a5d0666688ef4a2dd7153351cf2871a5b84a4fbf7023bbb876bc2de8c740cf23", + "75": "8acacd858306553f71f73f15197ccf8915d3473a6a1d3ced8876ff355fd2a865", + "90": "d29e09a6613fbd52bccbcc0e49ba74ea7469bcea65904887c67b3115603b7109", + "99": "47a588db69ac7fe12529c7cb9b15fe91b41494195360caa9bccb939ae8133cc4", + "max": "7c849a9c92021aa28ed2e4a32f7c33b15c2ff8513157f822229f53e85c0f9612" + }, + "3600": { + "50": "9d60494802e0ebc9136779b23c27cd4a18f13b9c68102aa343908cf6d422b3f6", + "75": "700df4fd9f07ce4eedc4ad337d17836c478b0a2b1e752ae3cbfdf72940aae60f", + "90": "4cfbc14801fe33bef5a1c0201d2cc03a992d446efd4bacdd3883469e8dc7d5d1", + "99": "7988ddeb4a28991a8b29a05eb9ce25763fc27d41a25a370d19f5b1364f285bfc", + "max": "56a3f80709937290c4323f106110af97b27ec87e019266c65bd91eb3aa316e02" + } + }, + "old_factor_q64": "18425445863997925462", + "canonical_max_abs_diff_vs_old_q64": { + "600": 32, + "1800": 82, + "3600": 120, + "7200": 133, + "86400": 133 + } +} From 4b1e5b1c6537102b50a0c83738d3952459b8981a Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 21 Oct 2026 13:26:19 -0400 Subject: [PATCH 09/19] Serve requested, installed and sink supply and per-launch quotes (launch schema v4) ZeroSeedLaunch at evm-contracts 40af6cb takes the quote per launch (LaunchParameters.quoteToken) instead of a constructor quote, and installs at most the requested supply, minting the remainder to address(0). - LaunchSummary gains supply {requested, installed, sink, sink_holder}, validated (0 < installed <= requested, sink = requested - installed) before serving; principal.installed_supply is removed. - The instance manifest carries no quote. A launch is served only when its own quote is native ETH or an ERC-20 in a reviewed list bound by chain, address and runtime codehash; list SQL screens on it before pagination. Hosted lists stay empty and hosted reads stay disabled. - Queries read indexer schema zero-seed-launch-index/v3; ABI pins follow 40af6cb. - The replay runner pins the updated indexer harness and fixture, and the golden is regenerated from the actual handlers as v4. --- openapi.json | 79 ++- scripts/launchReplayCases.txt | 107 +++- scripts/verifyLaunchReplay.ts | 9 +- src/contract/cases/launches.ts | 19 +- ...on => zero-seed-launch-api-v4.golden.json} | 584 ++++++++++++------ src/routes/launches/golden.test.ts | 56 +- src/routes/launches/policy.test.ts | 166 ++++- src/routes/launches/policy.ts | 160 +++-- src/routes/launches/queries.ts | 56 +- src/routes/launches/routes.test.ts | 22 + src/routes/launches/schema.ts | 17 +- 11 files changed, 966 insertions(+), 309 deletions(-) rename src/routes/launches/fixtures/{zero-seed-launch-api-v3.golden.json => zero-seed-launch-api-v4.golden.json} (53%) diff --git a/openapi.json b/openapi.json index 741d602..795868e 100644 --- a/openapi.json +++ b/openapi.json @@ -10003,7 +10003,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "data": { @@ -10014,7 +10014,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "chain_id": { @@ -10150,6 +10150,36 @@ ], "additionalProperties": false }, + "supply": { + "type": "object", + "properties": { + "requested": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "installed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "sink": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "sink_holder": { + "type": "string", + "enum": [ + "0x0000000000000000000000000000000000000000" + ] + } + }, + "required": [ + "requested", + "installed", + "sink", + "sink_holder" + ], + "additionalProperties": false + }, "launch_token_is_token1": { "type": "boolean" }, @@ -10320,6 +10350,7 @@ "pool_key", "token", "quote_token", + "supply", "launch_token_is_token1", "controller", "creator", @@ -10487,7 +10518,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "chain_id": { @@ -10734,7 +10765,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "chain_id": { @@ -10969,7 +11000,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "chain_id": { @@ -11214,7 +11245,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v3" + "zero-seed-launch-api/v4" ] }, "chain_id": { @@ -11350,6 +11381,36 @@ ], "additionalProperties": false }, + "supply": { + "type": "object", + "properties": { + "requested": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "installed": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "sink": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "sink_holder": { + "type": "string", + "enum": [ + "0x0000000000000000000000000000000000000000" + ] + } + }, + "required": [ + "requested", + "installed", + "sink", + "sink_holder" + ], + "additionalProperties": false + }, "launch_token_is_token1": { "type": "boolean" }, @@ -11529,10 +11590,6 @@ "type": "string", "pattern": "^(0|[1-9][0-9]*)$" }, - "installed_supply": { - "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" - }, "locked": { "type": "boolean", "enum": [ @@ -11575,7 +11632,6 @@ "tick_lower", "tick_upper", "liquidity", - "installed_supply", "locked", "withdrawable", "pool_reserves", @@ -11699,6 +11755,7 @@ "pool_key", "token", "quote_token", + "supply", "launch_token_is_token1", "controller", "creator", diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index d3cc823..a91aab8 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -6,11 +6,11 @@ WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; } - test("API actual handlers: emitted-state golden, owners, native quote, screening, pages, ledgers, reorg and repair", async () => { + test("API actual handlers: emitted-state golden, supply, owners, per-launch quotes, screening, pages, ledgers, reorg and repair", async () => { const db = await freshDb(); const processors = LAUNCH_ON(); const reader = new LaunchQueries(db.sql as never, { - localHarness: true, instances: LOCAL_INSTANCES, maxIndexAgeSeconds: 60, + localHarness: true, instances: LOCAL_INSTANCES, quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, }); const spies = [ spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), @@ -35,20 +35,34 @@ const chain = state.pools.find((p: any) => p.label === label); const body = await get(path(label)); const install = full.installations[label]; + const launch = full.launches[label]; const instance = full.instances.find((i: any) => i.label === install.instance); const launchIs1 = body.launch_token_is_token1; - expect(body.schema_version).toBe("zero-seed-launch-api/v3"); - expect(body.principal.installed_supply).toBe(install.actualInstalledSupply); - expect(body.principal.liquidity).toBe(install.actualLiquidity); + expect(body.schema_version).toBe("zero-seed-launch-api/v4"); + // requested = totalSupply, installed = Core's position delta, sink = balanceOf(0) + expect(body.supply).toEqual({ + requested: launch.requestedSupply, installed: launch.installedSupply, sink: launch.sinkSupply, + sink_holder: "0x0000000000000000000000000000000000000000", + }); + expect([body.supply.requested, body.supply.sink]).toEqual([chain.totalSupply, chain.sinkBalance]); + expect([body.supply.installed, body.supply.installed]).toEqual([install.coreInstalledDelta, install.installation.installedSupply]); + expect(BigInt(body.supply.sink) > 0n).toBe(true); + expect(body.principal).not.toHaveProperty("installed_supply"); + expect(body.principal.liquidity).toBe(install.liquidity); + expect(body.principal.liquidity).toBe(install.coreLiquidityDelta); + expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.find((l: any) => l.pool_id === body.pool_id).supply).toEqual(body.supply); expect(body.fee_ledger.launch_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 1 : 0]); expect(body.fee_ledger.quote_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 0 : 1]); expect(body.fee_ledger.transferable).toBe(true); expect([body.owner, body.fee_ledger.holder, body.creator, body.controller]).toEqual( [lower(chain.launchOwner), lower(chain.launchOwner), lower(install.owner), lower(chain.controller)]); expect(body.controller).toBe(lower(instance.periphery)); - expect(body.quote_token.kind).toBe(instance.quoteKind); - expect(body.quote_token.address).toBe(lower(instance.quote)); - if (instance.quoteKind === "native") { + expect(body.pool_key.extension).toBe(lower(instance.extension)); + // the launch's own quote, LaunchParameters.quoteToken + expect(body.quote_token.kind).toBe(install.quoteKind); + expect(body.quote_token.address).toBe(lower(install.quote)); + expect(launchIs1).toBe(launch.tokenIsToken1); + if (install.quoteKind === "native") { expect([body.quote_token.decimals, body.quote_token.name, body.quote_token.symbol, launchIs1]).toEqual([18, null, null, true]); expect(body.pool_key.token0).toBe("0x0000000000000000000000000000000000000000"); } @@ -151,15 +165,66 @@ } // native buys pay ETH in: quote delta positive on buys expect(swaps.data.filter((s: any) => s.direction === "buy").every((s: any) => BigInt(s.quote_token_delta) > 0n)).toBe(true); + // native token1 and ERC-20 token0 launches share the one extension + const l2 = await get(path("l2")); + expect([detail.pool_key.extension, detail.launch_token_is_token1, detail.quote_token.kind, detail.supply.sink]).toEqual( + [lower(full.instances[0].extension), true, "native", "578"]); + expect([l2.pool_key.extension, l2.launch_token_is_token1, l2.quote_token.kind, l2.supply.sink]).toEqual( + [lower(full.instances[0].extension), false, "erc20", "790"]); + // no ERC-20 metadata is indexed for the local quote + expect(l2.quote_token).toEqual({ address: "0xffffffffffffffffffffffffffffffffffffffff", name: null, symbol: null, decimals: null, kind: "erc20" }); + // l2 ownership: creator, transferred to newOwner on the prefix branch + const l2Owners = await get(`${path("l2")}/owners`); + expect(l2Owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ + [null, lower(creator)], [lower(creator), lower(newOwner)]]); + expect([l2.creator, l2.owner]).toEqual([lower(creator), lower(newOwner)]); + const l2Swaps = await get(`${path("l2")}/swaps`); + expect(l2Swaps.data.length).toBe(l2.fee_ledger.swap_count); + // ERC-20 quote launches are served only while their quote is reviewed + const nativeOnly = (quotes: unknown[]) => new LaunchQueries(db.sql as never, { + localHarness: true, instances: LOCAL_INSTANCES, quotes: quotes as never, maxIndexAgeSeconds: 60, + }); + for (const quotes of [[], LOCAL_QUOTES.map((q) => ({ ...q, runtime: `0x${"0".repeat(64)}` })), LOCAL_QUOTES.map((q) => ({ ...q, chainId: "1" }))]) { + const restricted = nativeOnly(quotes); + const spy = [ + spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => restricted.list(input)), + spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => restricted.detail(chain, pool)), + spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => restricted.swaps(input)), + spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => restricted.claims(input)), + spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => restricted.owners(input)), + ]; + try { + // screened before pagination: the native launch fills the page + const page = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); + expect(page.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + expect(page.next_cursor).toBeNull(); + for (const label of ["l2", "l4"]) + for (const suffix of ["", "/swaps", "/claims", "/owners"]) { + const body = await get(`${path(label)}${suffix}`, 404); + expect(body).toEqual({ status: 404, error: "Launch not found" }); + } + expect(await get(path("l1"))).toEqual(detail); + } finally { + spy.forEach((s) => s.mockRestore()); + } + } + spies.forEach((s) => s.mockRestore()); + spies.splice(0, spies.length, + spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), + spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), + spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), + spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), + spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), + ); + expect(await get(path("l2"))).toEqual(l2); if (process.env.LAUNCH_GOLDEN_DIR) { - const l2 = await get(path("l2")); const l2Claims = await get(`${path("l2")}/claims`); - const l2Owners = await get(`${path("l2")}/owners`); + const l4 = await get(path("l4")); await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ - schema_version: "zero-seed-launch-api/v3", + schema_version: "zero-seed-launch-api/v4", source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain.json", chain_id: String(CHAIN_ID) }, list, second, canonicalList, nativeDetail: detail, nativeSwaps: swaps, nativeClaims: claims, nativeOwners: owners, - erc20Detail: l2, erc20Claims: l2Claims, erc20Owners: l2Owners, + erc20Detail: l2, erc20Swaps: l2Swaps, erc20Claims: l2Claims, erc20Owners: l2Owners, erc20TraderOwnedDetail: l4, }, null, 2)); } const before = await get(path("l1")); @@ -172,6 +237,23 @@ await get(path("l1"), 500); await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; expect(await get(path("l1"))).toEqual(before); + // Malformed indexed supply fails as 500 on detail and list, never served. + const [check] = await db.sql<{ name: string; def: string }[]>` + SELECT conname AS name, pg_get_constraintdef(oid) AS def FROM pg_constraint + WHERE conrelid = 'zero_seed_launch_created'::regclass AND contype = 'c' + AND pg_get_constraintdef(oid) LIKE '%installed_supply%'`; + await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created DROP CONSTRAINT "${check!.name}"`); + await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; + const l1Pool = num(fixture.launches.l1!.poolId); + for (const installed of ["0", "1000000000000000000000000001"]) { + await db.sql`UPDATE zero_seed_launch_created SET installed_supply = ${installed}::numeric WHERE pool_id = ${l1Pool}::numeric`; + await get(path("l1"), 500); + await get(`/launches?chainId=${CHAIN_ID}`, 500); + } + await db.sql`UPDATE zero_seed_launch_created SET installed_supply = ${before.supply.installed}::numeric WHERE pool_id = ${l1Pool}::numeric`; + await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; + await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created ADD CONSTRAINT "${check!.name}" ${check!.def}`); + expect(await get(path("l1"))).toEqual(before); await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes' WHERE chain_id=${CHAIN_ID}`; await get(path("l1"), 503); await apiHead(db); @@ -182,6 +264,7 @@ const unpinned = new LaunchQueries(db.sql as never, { localHarness: true, instances: LOCAL_INSTANCES.map((p) => ({ ...p, periphery: "0x0000000000000000000000000000000000000001" })), + quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, }); await expect(unpinned.detail(CHAIN_ID, BigInt(fixture.launches.l1!.poolId))).rejects.toThrow(/disabled/); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index 9793077..9848ee5 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -5,8 +5,7 @@ import { resolve, dirname } from "node:path"; import { mkdir } from "node:fs/promises"; const root = process.env.ZERO_SEED_INDEXER_WORKTREE; -const scratch = - process.env.PAPERCLIP_RUN_SCRATCH_DIR ?? process.env.PAPERCLIP_SCRATCH_DIR; +const scratch = process.env.LAUNCH_REPLAY_SCRATCH_DIR; const pg = process.env.TEST_PG_CONNECTION_STRING; if (!root || !scratch || !pg) throw new Error( @@ -17,7 +16,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "200684630121eef0ce18c649cd5035f9b57cef60" + head.stdout.toString().trim() !== "d783593ea71371acc1851b9e887b064026d83609" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -38,7 +37,7 @@ const fixturePath = resolve( const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); if ( new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "c4cea4e1d1accf56fcf7beb90743321cd6b1c4761e304651c3636dc9136d482b" + "5dd281b580fb68dfaba00778d2241adf42c7b64ec5e8a70bfab8c9111ecf0ebb" ) throw new Error("Emitted fixture pin mismatch"); const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); @@ -60,7 +59,7 @@ const extra = await Bun.file( ).text(); source = `import { spyOn } from "bun:test"; import { LaunchQueries } from ${JSON.stringify(resolve(api, "routes/launches/queries"))}; -import { LOCAL_INSTANCES } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; +import { LOCAL_INSTANCES, LOCAL_QUOTES } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; import { Queries } from ${JSON.stringify(resolve(api, "queries"))}; import { app } from ${JSON.stringify(resolve(api, "router"))}; ${source}`; diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 368e628..b2b0134 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -1,17 +1,19 @@ import { ContractCase, stubQuery } from "../harness"; import { LOCAL_INSTANCES } from "../../routes/launches/policy"; +const NATIVE = "0x0000000000000000000000000000000000000000" as const; + const pin = LOCAL_INSTANCES[0]; const owner = `0x${"2".repeat(40)}`; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { - schema_version: "zero-seed-launch-api/v3" as const, + schema_version: "zero-seed-launch-api/v4" as const, chain_id: "31337", pool_id: zero, pool_key: { core_address: pin.core, - token0: pin.quote, + token0: NATIVE, token1: `0x${"1".repeat(40)}`, config: zero, fee: "0" as const, @@ -25,12 +27,18 @@ const summary = { decimals: 18, }, quote_token: { - address: pin.quote, + address: NATIVE, name: null, symbol: null, decimals: 18, kind: "native" as const, }, + supply: { + requested: "1000000000000000000000000000", + installed: "999999999999999999999999422", + sink: "578", + sink_holder: NATIVE, + }, launch_token_is_token1: true, controller: pin.periphery, creator: owner, @@ -62,7 +70,7 @@ const summary = { }; const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; const identity = { - schema_version: "zero-seed-launch-api/v3" as const, + schema_version: "zero-seed-launch-api/v4" as const, chain_id: "31337", pool_id: zero, as_of, @@ -75,7 +83,7 @@ export const launchesCases: ContractCase[] = [ request: "/launches?chainId=31337", stub: () => stubQuery("readZeroSeedList", { - schema_version: "zero-seed-launch-api/v3", + schema_version: "zero-seed-launch-api/v4", data: [summary], as_of, next_cursor: null, @@ -92,7 +100,6 @@ export const launchesCases: ContractCase[] = [ tick_lower: 0, tick_upper: 100, liquidity: "100", - installed_supply: "1000", locked: true, withdrawable: false, pool_reserves: { launch_token: "1000", quote_token: "0" }, diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json similarity index 53% rename from src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json rename to src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json index 69bde48..dde2517 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v3.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json @@ -1,27 +1,27 @@ { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "source": { "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain.json", "chain_id": "31337" }, "list": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "data": [ { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xbe2a2c8ab2cd8ffeedf462371c8114ae48c8edecffb20b718d04ed9b339d434f", + "pool_id": "0xeaede813919244a048fb74e7d5aaae13714f8c0919ee578d9b21c13d6ddef612", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x4e86de5aa6960f956736011337ffcd9315808f03", - "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "token1": "0x00f65546aad85b256b946eaa4268ac0a54f9527f", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0x4e86de5aa6960f956736011337ffcd9315808f03", + "address": "0x00f65546aad85b256b946eaa4268ac0a54f9527f", "name": "Zero Seed l3", "symbol": "L3", "decimals": 18 @@ -33,8 +33,14 @@ "decimals": 18, "kind": "native" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": true, - "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -55,45 +61,45 @@ }, "created": { "block_number": "8", - "block_hash": "0x5156ccf3a84b32af8771c42cee8ce518c9bbe667de2989768107e809819e162b", + "block_hash": "0xb10d7fdd9e697c098f838e7651efb5c609eef3fbb69f2d465ad2baca47a4d6eb", "block_time": 1800001300, - "event_id": "-9223372002494906359", + "event_id": "-9223372002494906358", "transaction_index": 2, - "event_index": 8, - "transaction_hash": "0xaf2346f6df346f934441393f6d647f21f95581ad7ff7352f0f3877d53a7a8bd5" + "event_index": 9, + "transaction_hash": "0xdd2f935e81b85959edb01732c55dae213da72e4780ee03c0cb53ed2cfb0e428d" }, "as_of": { "block_number": "9", - "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", "block_time": 1800001360 } } ], - "next_cursor": "-9223372002494906359", + "next_cursor": "-9223372002494906358", "as_of": { "block_number": "9", - "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", "block_time": 1800001360 } }, "second": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "data": [ { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -105,8 +111,14 @@ "decimals": null, "kind": "erc20" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": false, - "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "ask_tick": -13815500, @@ -127,34 +139,34 @@ }, "created": { "block_number": "3", - "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", "block_time": 1800000020, - "event_id": "-9223372023969808377", - "transaction_index": 1, + "event_id": "-9223372023969873913", + "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" }, "as_of": { "block_number": "9", - "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", "block_time": 1800001360 } }, { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", - "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "address": "0xf3f802e8826977ff3af68c6747daf11118685836", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -166,8 +178,14 @@ "decimals": 18, "kind": "native" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": true, - "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -188,16 +206,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", "block_time": 1800000010, - "event_id": "-9223372028264579064", - "transaction_index": 4, + "event_id": "-9223372028264710136", + "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" }, "as_of": { "block_number": "9", - "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", "block_time": 1800001360 } } @@ -205,28 +223,28 @@ "next_cursor": null, "as_of": { "block_number": "9", - "block_hash": "0x505342906234bb6ba6906ef6bb2c3353fb374897521eee83933216bdeb16aaa1", + "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", "block_time": 1800001360 } }, "canonicalList": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "data": [ { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xe5c50d3246c67d6ffc896e8db9c4af89a8f4ee169276a99efb7fbbeb4ad5b2d5", + "pool_id": "0xfae7fde391805bfe23cfa7670cd3a9bcb21cbc542ef0659f3a630bf9efb89f59", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1e37090e07df7bc78f4493192f41d46da9e11b54", + "token0": "0x98730fb771aeb50970e91097b5819bde61a607ae", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0x1e37090e07df7bc78f4493192f41d46da9e11b54", + "address": "0x98730fb771aeb50970e91097b5819bde61a607ae", "name": "Zero Seed l4", "symbol": "L4", "decimals": 18 @@ -238,8 +256,14 @@ "decimals": null, "kind": "erc20" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": false, - "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -260,34 +284,34 @@ }, "created": { "block_number": "8", - "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", "block_time": 1800001310, - "event_id": "-9223372002494906355", + "event_id": "-9223372002494906354", "transaction_index": 2, - "event_index": 12, - "transaction_hash": "0xdfad1ca753b6d6634428f8e4b50adc9c8f36e39f3bc0287625018688ff9348ce" + "event_index": 13, + "transaction_hash": "0xb12debf1dec2c43a557a5758e6a2081d10f7b3e6dfc054ea6cc5437eb0d72687" }, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 } }, { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -299,8 +323,14 @@ "decimals": null, "kind": "erc20" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": false, - "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -321,34 +351,34 @@ }, "created": { "block_number": "3", - "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", "block_time": 1800000020, - "event_id": "-9223372023969808377", - "transaction_index": 1, + "event_id": "-9223372023969873913", + "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" }, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 } }, { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", - "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "address": "0xf3f802e8826977ff3af68c6747daf11118685836", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -360,8 +390,14 @@ "decimals": 18, "kind": "native" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": true, - "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -382,16 +418,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", "block_time": 1800000010, - "event_id": "-9223372028264579064", - "transaction_index": 4, + "event_id": "-9223372028264710136", + "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" }, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 } } @@ -399,25 +435,25 @@ "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 } }, "nativeDetail": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", - "config": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173000000000000000080000064", + "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x5114703a4cf4256f0acb68f992c861d8c4d1b173" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0xb373ee3ecea11aed4f3c2be4f8acf9b6f6fcfe6d", + "address": "0xf3f802e8826977ff3af68c6747daf11118685836", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -429,8 +465,14 @@ "decimals": 18, "kind": "native" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": true, - "controller": "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -451,16 +493,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", "block_time": 1800000010, - "event_id": "-9223372028264579064", - "transaction_index": 4, + "event_id": "-9223372028264710136", + "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" }, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "principal": { @@ -468,7 +510,6 @@ "tick_lower": 9210300, "tick_upper": 13815500, "liquidity": "1111119116089565312898036", - "installed_supply": "999999999999999999999999422", "locked": true, "withdrawable": false, "pool_reserves": { @@ -502,27 +543,27 @@ } }, "nativeSwaps": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "5", - "block_hash": "0x20fabcf37b24d3febe443e7d00de5dca363db46434a561807860885233243a73", + "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", "block_time": 1800001000, "event_id": "-9223372015379939327", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0x2c0edc9b05a36ffbba110359ccdfdc1de0874d23ef913c58429125f0614f5363" + "transaction_hash": "0x9708d7f0d99e081fa4be5c2e2ec58be0cc10260d8efdb3740217e781694674c1" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "zero_fill", "launch_token_delta": "0", "quote_token_delta": "0", @@ -533,14 +574,14 @@ { "event": { "block_number": "5", - "block_hash": "0x20fabcf37b24d3febe443e7d00de5dca363db46434a561807860885233243a73", + "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", "block_time": 1800001000, "event_id": "-9223372015379873789", "transaction_index": 1, "event_index": 2, - "transaction_hash": "0x17a3c26a91be99145e4c531c47ccbaed2a9edd1abbd18f94c3dc6db99f06a9c5" + "transaction_hash": "0x8ded8b7580791d6d4ed503d8e6898a190df449b5d55b90213da6a8f1276912ed" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-499541686868404598624947", "quote_token_delta": "1000000000000000000", @@ -551,14 +592,14 @@ { "event": { "block_number": "6", - "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", "block_time": 1800001060, "event_id": "-9223372011084972030", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xd6ac0db70aee216cbf64973ca01f8d5d12e1126e20e18d94c71732dce0d0d298" + "transaction_hash": "0x1bd89ad947ad1e7829428f7ea17d10090960c4d5965e78cea72f3a2a3d7e964b" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-10000000000000000000000000", "quote_token_delta": "18937882440529117203", @@ -569,14 +610,14 @@ { "event": { "block_number": "6", - "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", "block_time": 1800001060, "event_id": "-9223372011084906491", "transaction_index": 1, "event_index": 4, - "transaction_hash": "0x9d0f216c68a4b7740223aef4e240cfedc0d97a9400690bd417adfe1b0cd9fd45" + "transaction_hash": "0xc006a7b2373e3a990507edce1633dc961bb410cf33bfcd73565b48bb47d24f92" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", "launch_token_delta": "1000000000000000000000000", "quote_token_delta": "-544053484045985401", @@ -587,14 +628,14 @@ { "event": { "block_number": "8", - "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", "block_time": 1800001310, "event_id": "-9223372002495037438", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x8531d94a52282cec1b752215b452de8e169ca05b74c90187d87d2136affd9657" + "transaction_hash": "0x7df0eb3faffd65a19798f5997f594149161fbb55f690b48da2cbdf6953ad8b1f" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-1278292209449944997565916", "quote_token_delta": "2000000000000000000", @@ -605,14 +646,14 @@ { "event": { "block_number": "11", - "block_hash": "0x9d83c56ebbfc980f3f263dc779592aa1874afe1e92134f63d8c12acaad6656db", + "block_hash": "0x1a69c81dee7f058c093171599388168f8532a99859d079396b3f6c379896c981", "block_time": 1800001756, "event_id": "-9223371989610135550", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xfab9c9873a6c84adfea132bb05679bddb3b4438d3df5f07e94503c428acc383b" + "transaction_hash": "0x4745307dc6a809bcea741a4a83405b50badf44d179c092cc121b611e7eacbf22" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-776368331230381396567584", "quote_token_delta": "1000000000000000000", @@ -623,14 +664,14 @@ { "event": { "block_number": "12", - "block_hash": "0x6df423c99479fce97adf6d6db7073feee625f98d91dc90ab3c53a31fafe4edcf", + "block_hash": "0xe90ec094b4fc8bad141075e242bc96a025091d064cdf458d779f43bc6cc5a5a1", "block_time": 1800002800, "event_id": "-9223371985315168254", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x68531d9bddd8e68e17f740ca54b66acf8333e8a2f74161cbbc54e5564f309480" + "transaction_hash": "0x25fff11db3ba53c8d82b6ab2e6d722e79126d869cd7a4087bc8442667cf0ecbb" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-919140266670121284225811", "quote_token_delta": "1000000000000000000", @@ -641,14 +682,14 @@ { "event": { "block_number": "13", - "block_hash": "0x425ceb7e4bc0e2b6ae079550f0a04c2f412d0f3ba244b68e667c2f0c60c1d5fa", + "block_hash": "0x08282b9a2a600de126d2cc6db1068d1c656f31c314a5f7223fa75572d2f0ed90", "block_time": 1800004599, "event_id": "-9223371981020200958", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xfc50997c0e125302bbd6708f6eeb0196858151e034691c32e927324967ec5f8c" + "transaction_hash": "0xf45e7869162ea1b208d7074069e74da24e4e5e4a1e084c793f20420a188c636c" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-971298260506660135369876", "quote_token_delta": "1000000000000000000", @@ -659,14 +700,14 @@ { "event": { "block_number": "14", - "block_hash": "0xcffe0667f9f0baeb6aae5126f4bc80f723835a358afe12f5092abb551fe79b53", + "block_hash": "0x21f5448b2e2b66fd73b3e8f91d94c2b319ea9707e04d37c9e195a86ee376867d", "block_time": 1800004600, "event_id": "-9223371976725233662", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x2f230ecee05c2db2d840a605032816f1f4d15c72efd800e634e9dd1fe552d727" + "transaction_hash": "0x8c1e82c6ced397e8ffb2096fbc6f2f7fe8f5fb3a12c2cfd92decbd3d31d3759f" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", "launch_token_delta": "-969583775189083632828198", "quote_token_delta": "1000000000000000000", @@ -677,14 +718,14 @@ { "event": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000, "event_id": "-9223371972430266366", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x2fa02aa348d948fd1eca6cb3846080b45305ac5eb862f7957d50424ddc062186" + "transaction_hash": "0x60bb0cbe48eb8de8260e95d0382c47043156a0312c161711d5dfa1afe60fc1e5" }, - "forwarding_locker": "0x03c8b90854b90aa22448b11e885f692972da441c", + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", "launch_token_delta": "100000000000000000000000", "quote_token_delta": "-102600807072586714", @@ -695,25 +736,25 @@ ] }, "nativeClaims": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "6", - "block_hash": "0x822e6c794e1d37374edea041842c7ca14e5c72314d93c88b5fbbd35054b8cd9a", + "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", "block_time": 1800001060, "event_id": "-9223372011084775411", "transaction_index": 3, "event_index": 12, - "transaction_hash": "0x083d593b8dde6a236885bc4a2e2a10441c8f14121bc713ae0df20cb8ccf02dc5" + "transaction_hash": "0x5e46ec4c91f3e21746c4efe74c978cae36409a6a3108c8120f80b22438a33fb6" }, "ledger_event_id": "-9223372011084775413", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", @@ -724,12 +765,12 @@ { "event": { "block_number": "12", - "block_hash": "0x6df423c99479fce97adf6d6db7073feee625f98d91dc90ab3c53a31fafe4edcf", + "block_hash": "0xe90ec094b4fc8bad141075e242bc96a025091d064cdf458d779f43bc6cc5a5a1", "block_time": 1800002800, "event_id": "-9223371985315037174", "transaction_index": 2, "event_index": 9, - "transaction_hash": "0x1b2124f6ed57417d0f9669d55efdee37bbab942c7d54425989bbb5c7c9c85204" + "transaction_hash": "0xf8afbe837ae5e7c7599dc2427eae78af2b72ab08986812ed37d27109f847303c" }, "ledger_event_id": "-9223371985315037176", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", @@ -740,12 +781,12 @@ { "event": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000, "event_id": "-9223371972430200826", "transaction_index": 1, "event_index": 5, - "transaction_hash": "0xe78428e73fabb02954050b2a73f88d2da21a6f856a773fdf908dc0199a6dacf0" + "transaction_hash": "0x3599b7df8c3d82c60994f1686e0ed5782c67e16665dacbcc73148ab98cf65ebf" }, "ledger_event_id": "-9223371972430200828", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", @@ -756,12 +797,12 @@ { "event": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000, "event_id": "-9223371972430135288", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xc6fb29d089de5c6b95791bc8d516d1762374dc2af5d1afb936b4914da9cc20ce" + "transaction_hash": "0x642169c64dba21fb1701328774d97b241730756a486b5c08336dccd8232a262e" }, "ledger_event_id": "-9223371972430135289", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", @@ -772,25 +813,25 @@ ] }, "nativeOwners": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc0a9f26952af6a470e5798edc551951d998bf408a332128e643c867e0c556a71", + "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "2", - "block_hash": "0x0949efffbe557d633095323a3feef8a39979b3def4ecc9f2544a6acbdcea65b5", + "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", "block_time": 1800000010, - "event_id": "-9223372028264579063", - "transaction_index": 4, + "event_id": "-9223372028264710135", + "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x5068570381de4aad3322b60a16d63a6f2104c17a78f75e0cc1bab330fc1145da" + "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -798,12 +839,12 @@ { "event": { "block_number": "10", - "block_hash": "0x6985816762f8e211e4ce165c63cb5bb8f34f54df4523ffa28c872722c6b41962", + "block_hash": "0x5a776223d6ae2f50072e6c96d9d2aa8e30e531ad1d2d83b5beec273f7f562f4b", "block_time": 1800001700, "event_id": "-9223371993904971767", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x3bec34f77c548d14d6b711499d285a1f325f01152fdf817b73e5cde0e81bad10" + "transaction_hash": "0x787ab24441fae7bb3b0835e29b8d942142c0d47445c0a539963caa14683317ab" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" @@ -811,12 +852,12 @@ { "event": { "block_number": "11", - "block_hash": "0x9d83c56ebbfc980f3f263dc779592aa1874afe1e92134f63d8c12acaad6656db", + "block_hash": "0x1a69c81dee7f058c093171599388168f8532a99859d079396b3f6c379896c981", "block_time": 1800001756, "event_id": "-9223371989610070012", "transaction_index": 1, "event_index": 3, - "transaction_hash": "0x4a29c836cfa17977a6cf28621151fd3a968232e6c5b237c4fca35697a6979114" + "transaction_hash": "0xdc53fc5509baf5e0338b3a2c8b0cc1bcb2a06a84c4810efbdbd51ed7f02e3511" }, "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" @@ -824,20 +865,20 @@ ] }, "erc20Detail": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519435a42752da78e395999218d0ecc4fdcbcf4b000000000000000080000064", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519435a42752da78e395999218d0ecc4fdcbcf4b" + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" }, "token": { - "address": "0x1035f59f12382cb3d9f8251c01f02993a47895f5", + "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -849,8 +890,14 @@ "decimals": null, "kind": "erc20" }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, "launch_token_is_token1": false, - "controller": "0x94e1b338def908449e6ea7c7d254630b45d27562", + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -871,16 +918,16 @@ }, "created": { "block_number": "3", - "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", "block_time": 1800000020, - "event_id": "-9223372023969808377", - "transaction_index": 1, + "event_id": "-9223372023969873913", + "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" }, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "principal": { @@ -888,7 +935,6 @@ "tick_lower": -13815500, "tick_upper": -9210300, "liquidity": "1111119116089565312897934", - "installed_supply": "999999999999999999999999210", "locked": true, "withdrawable": false, "pool_reserves": { @@ -921,26 +967,111 @@ "price_tick": -13799366 } }, + "erc20Swaps": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "next_cursor": null, + "as_of": { + "block_number": "15", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", + "block_time": 1800001000, + "event_id": "-9223372015379808250", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0x9725df1fe1acfc2593d258e910cc0eef090984ff6281515e8ac2efd04af0165b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-1000000000000000000000000", + "quote_token_delta": "1053598203898266036", + "fee_rate": "922337203685477580", + "fee_amount": "52679910194913302", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", + "block_time": 1800001060, + "event_id": "-9223372011084840952", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x032a76cc6771b8369669f8629fb0289a5013d2be5af05230436c1ac2f3921c0c" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-4751426503371359422372847", + "quote_token_delta": "5000000000000000000", + "fee_rate": "806216816634246792", + "fee_amount": "217152236676102150438122", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "7", + "block_hash": "0x1270c2e1c5ef400e9002228de50d222f53b3d3a807ca1f00941fe067cea6a2ca", + "block_time": 1800001120, + "event_id": "-9223372006789939197", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x32795fdcee3f55b41c171d5b6f2a376f328fb63203aaa93cc22206e812079d06" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "1028166339388655397096", + "quote_token_delta": "-1000000000000000", + "fee_rate": "697872664525424975", + "fee_amount": "38897334943088505419", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "10", + "block_hash": "0x5a776223d6ae2f50072e6c96d9d2aa8e30e531ad1d2d83b5beec273f7f562f4b", + "block_time": 1800001700, + "event_id": "-9223371993905037306", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0x0bfbd738e4a8b913847650fc2a4d641202be197cf85e2e8f7bc6ca893ca6b537" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-2950981510151226309026868", + "quote_token_delta": "3000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "8879583280294562478451", + "fee_asset": "launch_token" + } + ] + }, "erc20Claims": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "8", - "block_hash": "0x13b99f85adb836506011d6448d26584dbcfe1ab595264166e617a3e88e9bfef9", + "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", "block_time": 1800001310, "event_id": "-9223372002494971897", "transaction_index": 1, "event_index": 6, - "transaction_hash": "0x7f052bd84f2db16c2d1f69475a5bc91dbc37b5b929ab729c895e34b53990321d" + "transaction_hash": "0x04962c1513ebf17ca269f9012dc1bd3952af011a4b6818dfe4206e4db44dc6f1" }, "ledger_event_id": "-9223372002494971900", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", @@ -951,25 +1082,25 @@ ] }, "erc20Owners": { - "schema_version": "zero-seed-launch-api/v3", + "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xc27e3d1a2b492b8fbe32fd539a341e1424d8c629c79ee1f796408c485955fc33", + "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", "next_cursor": null, "as_of": { "block_number": "15", - "block_hash": "0xecef597665affca442275df9ff477e169af3093da277fed9bef5a0a62193189d", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "3", - "block_hash": "0xa9abadea893663c37ed3954507f986d8a7788cd439163688161c5ee39e69745b", + "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", "block_time": 1800000020, - "event_id": "-9223372023969808376", - "transaction_index": 1, + "event_id": "-9223372023969873912", + "transaction_index": 0, "event_index": 7, - "transaction_hash": "0x5f39558028ce580139bc2c2cf0d4d221015e88d2edfd7f6265c070be17eb8fea" + "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -977,16 +1108,119 @@ { "event": { "block_number": "7", - "block_hash": "0x549dedc802b5dfc64a068ca3c6c80e90bd6528a4300a809af02c9fad87469b42", + "block_hash": "0x1270c2e1c5ef400e9002228de50d222f53b3d3a807ca1f00941fe067cea6a2ca", "block_time": 1800001120, "event_id": "-9223372006790004735", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0x218a80132b6450ca23971df8208d9778e6ea2ab8d38544a53e17663852b95fa6" + "transaction_hash": "0xfb377b6060ef22c736c9c03587573fac7502691b6188a37ad2d3fb5b4dd065e7" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" } ] + }, + "erc20TraderOwnedDetail": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xfae7fde391805bfe23cfa7670cd3a9bcb21cbc542ef0659f3a630bf9efb89f59", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + }, + "token": { + "address": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 600, + "fee_end": 1800002000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", + "block_time": 1800001310, + "event_id": "-9223372002494906354", + "transaction_index": 2, + "event_index": 13, + "transaction_hash": "0xb12debf1dec2c43a557a5758e6a2081d10f7b3e6dfc054ea6cc5437eb0d72687" + }, + "as_of": { + "block_number": "15", + "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "992953944708816951043107488", + "quote_token": "7091146575104629573" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "transferable": true, + "launch_token": { + "accrued": "347802764559152447542916", + "claimed": "347802764559152447542916", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "2358132882502121", + "claimed": "2358132882502121", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371976725168121" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13802777 + } } } diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index a1e6185..18ed916 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import golden from "./fixtures/zero-seed-launch-api-v3.golden.json"; +import golden from "./fixtures/zero-seed-launch-api-v4.golden.json"; import { ClaimPage, LaunchDetail, @@ -11,14 +11,19 @@ import { // Written by scripts/verifyLaunchReplay.ts (LAUNCH_GOLDEN_DIR) from the actual // handlers over the indexer's replayed local fixture; a machine-readable -// example of every v3 response for integrators. -test("the emitted-state golden responses parse with the v3 schemas", () => { +// example of every v4 response for integrators. +test("the emitted-state golden responses parse with the v4 schemas", () => { expect(golden.schema_version).toBe(SCHEMA_VERSION); for (const list of [golden.list, golden.second, golden.canonicalList]) LaunchList.parse(list); - for (const detail of [golden.nativeDetail, golden.erc20Detail]) + for (const detail of [ + golden.nativeDetail, + golden.erc20Detail, + golden.erc20TraderOwnedDetail, + ]) LaunchDetail.parse(detail); - SwapPage.parse(golden.nativeSwaps); + for (const swaps of [golden.nativeSwaps, golden.erc20Swaps]) + SwapPage.parse(swaps); for (const claims of [golden.nativeClaims, golden.erc20Claims]) ClaimPage.parse(claims); for (const owners of [golden.nativeOwners, golden.erc20Owners]) @@ -32,3 +37,44 @@ test("the emitted-state golden responses parse with the v3 schemas", () => { }); expect(golden.erc20Detail.quote_token.kind).toBe("erc20"); }); + +test("native token1 and ERC-20 token0 launches share one extension and split requested/installed/sink exactly", () => { + const sink = "0x0000000000000000000000000000000000000000"; + const native = golden.nativeDetail; + const erc20 = golden.erc20Detail; + expect(native.pool_key.extension).toBe(erc20.pool_key.extension); + expect([native.launch_token_is_token1, erc20.launch_token_is_token1]).toEqual( + [true, false], + ); + expect(native.supply).toEqual({ + requested: "1000000000000000000000000000", + installed: "999999999999999999999999422", + sink: "578", + sink_holder: sink, + }); + expect(erc20.supply).toEqual({ + requested: "1000000000000000000000000000", + installed: "999999999999999999999999210", + sink: "790", + sink_holder: sink, + }); + for (const launch of [ + ...golden.canonicalList.data, + native, + erc20, + golden.erc20TraderOwnedDetail, + ]) { + const { requested, installed, sink } = launch.supply; + expect(BigInt(requested) - BigInt(installed)).toBe(BigInt(sink)); + expect(launch).not.toHaveProperty("principal.installed_supply"); + } + // creator stays; owner follows transfers + for (const [detail, owners] of [ + [native, golden.nativeOwners], + [erc20, golden.erc20Owners], + ] as const) { + expect(detail.creator).not.toBe(detail.owner); + expect(owners.data[0]!.new_owner).toBe(detail.creator); + expect(owners.data.at(-1)!.new_owner).toBe(detail.owner); + } +}); diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index 45938bc..c313f66 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -8,64 +8,140 @@ import { decayQ128, decayedFee, feeSchedule, + launchSupply, LOCAL_INSTANCES, + LOCAL_QUOTES, supportedInstances, supportedLaunch, + supportedQuotes, } from "./policy"; import { ClaimPage, LaunchDetail, LaunchList, + LaunchSummary, OwnerPage, SwapPage, } from "./schema"; -test("local instance manifests bind every constructor/runtime/ABI/periphery field", () => { +const NATIVE = "0x0000000000000000000000000000000000000000"; + +test("local instance manifests bind every runtime/ABI/periphery/Core field and carry no quote", () => { expect(supportedInstances(LOCAL_INSTANCES, true)).toEqual(LOCAL_INSTANCES); expect(supportedInstances(LOCAL_INSTANCES, false)).toEqual([]); expect(supportedInstances([], true)).toEqual([]); + expect(LOCAL_INSTANCES).toHaveLength(1); for (const pin of LOCAL_INSTANCES) { + expect(Object.keys(pin).some((key) => /quote/i.test(key))).toBe(false); for (const key of Object.keys(pin) as (keyof typeof pin)[]) { expect( supportedInstances([{ ...pin, [key]: "unreviewed" }], true), ).toEqual([]); } - expect(supportedLaunch(pin, LOCAL_INSTANCES)).toBe(true); - for (const key of [ - "chainId", - "address", - "periphery", - "core", - "quote", - "quoteKind", - ] as const) { + const native = { ...pin, quote: NATIVE, quoteKind: "native" as const }; + expect(supportedLaunch(native, LOCAL_INSTANCES, [])).toBe(true); + for (const key of ["chainId", "address", "periphery", "core"] as const) { expect( - supportedLaunch({ ...pin, [key]: "unreviewed" }, LOCAL_INSTANCES), + supportedLaunch( + { ...native, [key]: "unreviewed" }, + LOCAL_INSTANCES, + LOCAL_QUOTES, + ), ).toBe(false); } - expect(supportedLaunch(pin, [])).toBe(false); + expect(supportedLaunch(native, [], LOCAL_QUOTES)).toBe(false); } }); -test("native and ERC-20 quote provenance stay distinct", () => { - const [native, weth] = LOCAL_INSTANCES; - expect([native.quote, native.quoteKind, native.quoteRuntime]).toEqual([ - "0x0000000000000000000000000000000000000000", - "native", - null, +test("quote support is per launch: native, or a reviewed ERC-20 by chain/address/runtime", () => { + const [pin] = LOCAL_INSTANCES; + const [token] = LOCAL_QUOTES; + expect([token.chainId, token.address, token.runtime]).toEqual([ + "31337", + "0xffffffffffffffffffffffffffffffffffffffff", + "0xe8ccd7c4eb891208d5e04a27a31dfaf2b9bce01e083d7b36f10b6dd6ea643e63", ]); - expect(weth.quoteKind).toBe("erc20"); - expect(weth.quoteRuntime).toMatch(/^0x[0-9a-f]{64}$/); - // relabelling either kind, or giving native ETH a runtime, is unreviewed - expect(supportedInstances([{ ...native, quoteKind: "erc20" }], true)).toEqual( - [], - ); + expect(supportedQuotes(LOCAL_QUOTES, true)).toEqual(LOCAL_QUOTES); + // hosted: no ERC-20 quote is ever supported + expect(supportedQuotes(LOCAL_QUOTES, false)).toEqual([]); + for (const key of Object.keys(token) as (keyof typeof token)[]) + expect(supportedQuotes([{ ...token, [key]: "unreviewed" }], true)).toEqual( + [], + ); + const erc20 = { + ...pin, + quote: token.address, + quoteKind: "erc20" as const, + }; + expect(supportedLaunch(erc20, LOCAL_INSTANCES, LOCAL_QUOTES)).toBe(true); + expect(supportedLaunch(erc20, LOCAL_INSTANCES, [])).toBe(false); + expect( + supportedLaunch( + { ...erc20, quote: `0x${"e".repeat(40)}` }, + LOCAL_INSTANCES, + LOCAL_QUOTES, + ), + ).toBe(false); expect( - supportedInstances([{ ...native, quoteRuntime: weth.quoteRuntime }], true), - ).toEqual([]); + supportedLaunch( + { ...erc20, chainId: "1" }, + [{ ...pin, chainId: "1" }], + LOCAL_QUOTES, + ), + ).toBe(false); + // relabelling either kind is refused expect( - supportedLaunch({ ...weth, quoteKind: "native" }, LOCAL_INSTANCES), + supportedLaunch( + { ...erc20, quoteKind: "native" }, + LOCAL_INSTANCES, + LOCAL_QUOTES, + ), ).toBe(false); + expect( + supportedLaunch( + { ...pin, quote: NATIVE, quoteKind: "erc20" }, + LOCAL_INSTANCES, + [{ ...token, address: NATIVE }], + ), + ).toBe(false); +}); + +test("supply: 0 < installed <= requested and sink = requested - installed, else refused", () => { + const requested = 10n ** 27n; + expect( + launchSupply({ + requested, + installed: 999999999999999999999999422n, + sink: 578n, + }), + ).toEqual({ + requested: "1000000000000000000000000000", + installed: "999999999999999999999999422", + sink: "578", + sink_holder: NATIVE, + }); + expect(launchSupply({ requested, installed: requested, sink: 0n }).sink).toBe( + "0", + ); + const max = (1n << 127n) - 1n; + expect( + launchSupply({ requested: max, installed: 1n, sink: max - 1n }), + ).toEqual({ + requested: max.toString(), + installed: "1", + sink: (max - 1n).toString(), + sink_holder: NATIVE, + }); + for (const bad of [ + { requested, installed: 0n, sink: requested }, + { requested, installed: -1n, sink: requested + 1n }, + { requested, installed: requested + 1n, sink: -1n }, + { requested, installed: requested - 578n, sink: 577n }, + { requested, installed: requested - 578n, sink: 579n }, + { requested: max + 1n, installed: 1n, sink: max }, + { requested: 0n, installed: 0n, sink: 0n }, + ]) + expect(() => launchSupply(bad)).toThrow(/Invalid indexed launch supply/); }); const input = { @@ -197,7 +273,7 @@ test("invalid indexed domains fail, never coerce or overflow", () => { test("frozen schemas expose no staged status/stats/exhausted or decay-factor fields", () => { const empty = { - schema_version: "zero-seed-launch-api/v3", + schema_version: "zero-seed-launch-api/v4", as_of: { block_number: "1", block_hash: `0x${"a".repeat(64)}`, @@ -210,7 +286,7 @@ test("frozen schemas expose no staged status/stats/exhausted or decay-factor fie expect( LaunchList.safeParse({ ...empty, - schema_version: "zero-seed-launch-api/v2", + schema_version: "zero-seed-launch-api/v3", }).success, ).toBe(false); expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe( @@ -233,3 +309,35 @@ test("frozen schemas expose no staged status/stats/exhausted or decay-factor fie "decay_factor", ); }); + +test("v4 supply is on every summary and replaces principal.installed_supply", () => { + expect(Object.keys(LaunchSummary.shape)).toContain("supply"); + expect(Object.keys(LaunchSummary.shape.supply.shape)).toEqual([ + "requested", + "installed", + "sink", + "sink_holder", + ]); + expect(Object.keys(LaunchDetail.shape.principal.shape)).not.toContain( + "installed_supply", + ); + expect(Object.keys(LaunchDetail.shape.principal.shape)).toContain( + "liquidity", + ); + const supply = LaunchSummary.shape.supply; + const ok = { + requested: "1000000000000000000000000000", + installed: "999999999999999999999999210", + sink: "790", + sink_holder: NATIVE, + }; + expect(supply.safeParse(ok).success).toBe(true); + for (const bad of [ + { ...ok, sink: 790 }, + { ...ok, requested: "1e27" }, + { ...ok, installed: "-1" }, + { ...ok, sink_holder: `0x${"1".repeat(40)}` }, + { ...ok, burned: "790" }, + ]) + expect(supply.safeParse(bad).success).toBe(false); +}); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index e92f81e..1d29af6 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -1,16 +1,19 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; -// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 4f1a3060 +// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 40af6cb // build, as in the protocol manifest. export const ABI_SHA256 = - "21f9e2c0166ab28ce8573c481de5b0fc2f37dd7595d496d326f0a2d1a544254e"; + "999068f8e41867e972f4485c0105f584a2c10d7537615e6b4517807548fcd4bf"; export const PERIPHERY_ABI_SHA256 = - "5c563488b843aee923d0d819a66489eeee543dd20d1582de1f5df643f5d08a75"; -const CORE = "0x00000000000014aa86c5d3c41765bb24e11bd701"; -const WETH_RUNTIME = - "0x8a3a1f6a9f9dce633117adee5b458245835a8645a8c8726a26382a4622508b1c"; + "df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305"; +const NATIVE = "0x0000000000000000000000000000000000000000" as const; +/** + * An extension and the periphery that forwards to it. An instance has no + * quote: every launch names its own (LaunchParameters.quoteToken), and one + * instance serves native ETH and ERC-20 quotes alike. + */ export interface LaunchInstance { chainId: string; address: string; @@ -18,84 +21,141 @@ export interface LaunchInstance { periphery: string; peripheryRuntime: string; core: string; - quote: string; - quoteKind: "native" | "erc20"; - /** null for native ETH, which has no code. */ - quoteRuntime: string | null; abiSha256: string; peripheryAbiSha256: string; } -// Constructor-executed LOCAL instances of the indexer replay fixture -// (evm-contracts 4f1a3060, anvil, no fork): native ETH and relocated plain -// WETH. No live instance exists. Never enable these via a hosted +/** A reviewed ERC-20 quote. Native ETH (address 0) needs no entry. */ +export interface LaunchQuote { + chainId: string; + address: string; + runtime: string; +} + +// The constructor-executed LOCAL instance of the indexer replay fixture +// (evm-contracts 40af6cb, anvil, no fork), serving native-ETH and ERC-20 +// quote launches. No live instance exists. Never enable it via a hosted // configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - address: "0x5114703a4cf4256f0acb68f992c861d8c4d1b173", + address: "0x519ddb6580e97e08e639d601dc6e378285c3ef34", runtime: - "0x846786a63ba1a2c976f748fdc94b8cbc8f76dc64f07452333d36ff564f281bf7", - periphery: "0xb27e16c3d01850191c1e027f7a3372cd2e1fb324", + "0x32160cd19576b73812a6c557ca8f9697ac1c3cd821576afc70e9a9b00bf888be", + periphery: "0x094511cf93e73feca154da35a2404e8bde1111b4", peripheryRuntime: - "0x57d0ee27c634ac08d08b60bf8e6c2f185c7e9444613ccfde5ac9bbb5e3f84f2a", - core: CORE, - quote: "0x0000000000000000000000000000000000000000", - quoteKind: "native", - quoteRuntime: null, + "0x91a98d2669389244ad8553d84f5c7e3c2947d5d4b5abb519be93d68142cce7c1", + core: "0x00000000000014aa86c5d3c41765bb24e11bd701", abiSha256: ABI_SHA256, peripheryAbiSha256: PERIPHERY_ABI_SHA256, }, +]; + +// The fixture's plain 18-decimal test ERC-20 (evm-contracts +// test/TestToken.sol), relocated to the highest address so the launch token +// is token0. Local only; the hosted quote list is empty. +export const LOCAL_QUOTES: readonly LaunchQuote[] = [ { chainId: "31337", - address: "0x519435a42752da78e395999218d0ecc4fdcbcf4b", + address: "0xffffffffffffffffffffffffffffffffffffffff", runtime: - "0x56e32aeac03464c3babd754fc21e0594c42d82db9d9c0cc3dd1ce1ec20273750", - periphery: "0x94e1b338def908449e6ea7c7d254630b45d27562", - peripheryRuntime: - "0xb78ea649f2e1817c851f7417a604d1565a346c6bec022abc821e3f1110739ac8", - core: CORE, - quote: "0xffffffffffffffffffffffffffffffffffffffff", - quoteKind: "erc20", - quoteRuntime: WETH_RUNTIME, - abiSha256: ABI_SHA256, - peripheryAbiSha256: PERIPHERY_ABI_SHA256, + "0xe8ccd7c4eb891208d5e04a27a31dfaf2b9bce01e083d7b36f10b6dd6ea643e63", }, ]; -/** An explicit local harness manifest, never an address-only allowlist. */ -export function supportedInstances( - requested: readonly LaunchInstance[], +function reviewed( + requested: readonly T[], + pins: readonly T[], localHarness: boolean, -): readonly LaunchInstance[] { +): readonly T[] { if (!localHarness) return []; return requested.filter((request) => - LOCAL_INSTANCES.some((pin) => - (Object.keys(pin) as (keyof LaunchInstance)[]).every( + pins.some((pin) => + (Object.keys(pin) as (keyof T)[]).every( (key) => pin[key] === request[key], ), ), ); } +/** An explicit local harness manifest, never an address-only allowlist. */ +export function supportedInstances( + requested: readonly LaunchInstance[], + localHarness: boolean, +): readonly LaunchInstance[] { + return reviewed(requested, LOCAL_INSTANCES, localHarness); +} + +/** ERC-20 quotes bound by chain, address and runtime; empty when hosted. */ +export function supportedQuotes( + requested: readonly LaunchQuote[], + localHarness: boolean, +): readonly LaunchQuote[] { + return reviewed(requested, LOCAL_QUOTES, localHarness); +} + +/** Native ETH, or an ERC-20 in the enabled quote list of the chain. */ +export function supportedQuote( + row: { chainId: string; quote: string; quoteKind: "native" | "erc20" }, + quotes: readonly LaunchQuote[], +): boolean { + if (row.quoteKind === "native") return row.quote === NATIVE; + return ( + row.quote !== NATIVE && + quotes.some((q) => q.chainId === row.chainId && q.address === row.quote) + ); +} + export function supportedLaunch( - row: Pick< - LaunchInstance, - "chainId" | "address" | "periphery" | "core" | "quote" | "quoteKind" - >, + row: Pick & { + quote: string; + quoteKind: "native" | "erc20"; + }, enabled: readonly LaunchInstance[], + quotes: readonly LaunchQuote[], ): boolean { - return enabled.some( - (pin) => - pin.chainId === row.chainId && - pin.address === row.address && - pin.periphery === row.periphery && - pin.core === row.core && - pin.quote === row.quote && - pin.quoteKind === row.quoteKind, + return ( + enabled.some( + (pin) => + pin.chainId === row.chainId && + pin.address === row.address && + pin.periphery === row.periphery && + pin.core === row.core, + ) && supportedQuote(row, quotes) ); } +const INT128_MAX = (1n << 127n) - 1n; +export const SINK_HOLDER = NATIVE; + +/** + * The launch-token supply split: requested (LaunchParameters.supply, the + * token's totalSupply), installed (in the position) and sink (minted to + * address 0). Anything outside 0 < installed <= requested <= int128 max with + * sink = requested - installed is malformed indexed data. + */ +export function launchSupply(input: { + requested: bigint; + installed: bigint; + sink: bigint; +}) { + const { requested, installed, sink } = input; + if ( + installed <= 0n || + installed > requested || + requested > INT128_MAX || + sink !== requested - installed + ) { + throw new Error("Invalid indexed launch supply"); + } + return { + requested: requested.toString(), + installed: installed.toString(), + sink: sink.toString(), + sink_holder: SINK_HOLDER, + }; +} + const UINT64_MAX = (1n << 64n) - 1n; function uint64(value: bigint) { if (value < 0n || value > UINT64_MAX) { diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index 1e4ae97..4eec21c 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -2,7 +2,14 @@ import type postgres from "postgres"; import type { Sql } from "postgres"; import { z } from "@hono/zod-openapi"; import { StatusError } from "../../shared/errors"; -import { feeSchedule, supportedInstances, type LaunchInstance } from "./policy"; +import { + feeSchedule, + launchSupply, + supportedInstances, + supportedQuotes, + type LaunchInstance, + type LaunchQuote, +} from "./policy"; import { BlockRef, ClaimPage, @@ -57,7 +64,9 @@ interface LaunchRow extends EventRow { tick_lower: number; tick_upper: number; liquidity: Numeric; - supply: Numeric; + requested_supply: Numeric; + installed_supply: Numeric; + sink_supply: Numeric; balance0: Numeric; balance1: Numeric; tick: number; @@ -118,6 +127,11 @@ function summary(row: LaunchRow, asOf: z.infer) { decimals: row.decimals, }, quote_token: quoteToken(row), + supply: launchSupply({ + requested: BigInt(row.requested_supply), + installed: BigInt(row.installed_supply), + sink: BigInt(row.sink_supply), + }), launch_token_is_token1: row.token_is_token1, controller: hex(row.controller, 20), creator: hex(row.initial_owner, 20), @@ -172,7 +186,6 @@ function detail(row: LaunchRow, asOf: z.infer) { tick_lower: row.tick_lower, tick_upper: row.tick_upper, liquidity: decimal(row.liquidity), - installed_supply: decimal(row.supply), locked: true, withdrawable: false, pool_reserves: { @@ -212,15 +225,23 @@ export interface LaunchReadOptions { /** All constituent reads share a read-only repeatable-read snapshot. */ export class LaunchQueries { private readonly enabled: readonly LaunchInstance[]; + private readonly quotes: readonly LaunchQuote[]; constructor( private readonly sql: DB, options: { localHarness: boolean; instances: readonly LaunchInstance[]; + quotes: readonly LaunchQuote[]; maxIndexAgeSeconds: number; - } = { localHarness: false, instances: [], maxIndexAgeSeconds: 60 }, + } = { + localHarness: false, + instances: [], + quotes: [], + maxIndexAgeSeconds: 60, + }, ) { this.enabled = supportedInstances(options.instances, options.localHarness); + this.quotes = supportedQuotes(options.quotes, options.localHarness); this.maxAge = options.maxIndexAgeSeconds; if (!Number.isFinite(this.maxAge) || this.maxAge <= 0) { throw new Error("Invalid local index freshness bound"); @@ -273,20 +294,29 @@ export class LaunchQueries { } private rows(tx: DB, chainId: bigint, filter: postgres.Fragment) { - // Screening occurs in SQL BEFORE cursor/limit; no arbitrary quote can + // Screening occurs in SQL BEFORE cursor/limit; no unsupported quote can // occupy a page or be served by detail/history. - const pins = this.enabled.filter((p) => p.chainId === chainId.toString()); + const chain = chainId.toString(); + const pins = this.enabled.filter((p) => p.chainId === chain); // Only launches created through the instance's periphery have an owner. - const checks = pins.map( - (p) => tx` + const instance = pins + .map( + (p) => tx` (c.emitter = ${BigInt(p.address).toString()}::numeric AND c.controller = ${BigInt(p.periphery).toString()}::numeric - AND c.quote_token = ${BigInt(p.quote).toString()}::numeric - AND c.quote_kind = ${p.quoteKind} AND pk.core_address = ${BigInt(p.core).toString()}::numeric) `, - ); - const supported = checks.reduce((a, b) => tx`${a} OR ${b}`); + ) + .reduce((a, b) => tx`${a} OR ${b}`); + // The launch's own quote: native ETH, or a reviewed ERC-20 of the chain. + const erc20 = this.quotes + .filter((q) => q.chainId === chain) + .map((q) => tx`c.quote_token = ${BigInt(q.address).toString()}::numeric`) + .reduce((a, b) => tx`${a} OR ${b}`, tx`FALSE`); + const quote = tx` + ((c.quote_kind = 'native' AND c.quote_token = 0) + OR (c.quote_kind = 'erc20' AND c.quote_token <> 0 AND (${erc20}))) + `; return tx` SELECT c.*, b.block_hash, b.block_time, pk.core_address, pk.token0, pk.token1, qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, @@ -309,7 +339,7 @@ export class LaunchQueries { AND pk.token1 = GREATEST(c.token, c.quote_token) AND pk.tick_spacing = c.tick_spacing AND s.current_owner IS NOT NULL AND s.initial_owner IS NOT NULL - AND (${supported}) AND ${filter} + AND (${instance}) AND ${quote} AND ${filter} `; } diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index cd3cc69..f0d8e8b 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -2,6 +2,8 @@ import { expect, spyOn, test } from "bun:test"; import { app } from "../../router"; import { Queries } from "../../queries"; import { StatusError } from "../../shared/errors"; +import { LOCAL_INSTANCES, LOCAL_QUOTES } from "./policy"; +import { LaunchQueries } from "./queries"; const pool = `0x${"1".repeat(64)}`; const base = `/launches/31337/${pool}`; @@ -30,6 +32,26 @@ test("hosted launch entrypoints stay disabled without database or live pins", as } }); +test("the hosted reader enables no instance and no ERC-20 quote", async () => { + const hosted = new LaunchQueries((() => { + throw new Error("no query may run"); + }) as never); + expect(Reflect.get(hosted, "enabled")).toEqual([]); + expect(Reflect.get(hosted, "quotes")).toEqual([]); + await expect( + hosted.list({ chainId: 31337n, limit: 1 }), + ).rejects.toMatchObject({ status: 404 }); + // even a harness that passes the reviewed lists is refused when not local + const notLocal = new LaunchQueries((() => {}) as never, { + localHarness: false, + instances: LOCAL_INSTANCES, + quotes: LOCAL_QUOTES, + maxIndexAgeSeconds: 60, + }); + expect(Reflect.get(notLocal, "enabled")).toEqual([]); + expect(Reflect.get(notLocal, "quotes")).toEqual([]); +}); + test("invalid arguments return 400 and the removed stats route returns 404", async () => { for (const path of [ "/launches", diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index 392c2dd..a3b7a92 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,7 +1,7 @@ import { z } from "@hono/zod-openapi"; -// v3: native ETH quote kind, fixed-base fee table, periphery owner identity. -export const SCHEMA_VERSION = "zero-seed-launch-api/v3" as const; +// v4: requested/installed/sink supply, per-launch quote. +export const SCHEMA_VERSION = "zero-seed-launch-api/v4" as const; export const SchemaVersion = z.literal(SCHEMA_VERSION); const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); @@ -52,13 +52,25 @@ export const FeeSchedule = z.strictObject({ fee_rate: U, evaluated_at: Seconds, }); +// Raw launch-token units. requested is LaunchParameters.supply and the +// token's totalSupply; installed is what Core took into the position at +// creation (all circulating inventory); sink = requested - installed was +// minted to address 0 and can never move. It is not a burn. +const Supply = z.strictObject({ + requested: U, + installed: U, + sink: U, + sink_holder: z.literal("0x0000000000000000000000000000000000000000"), +}); export const LaunchSummary = z.strictObject({ schema_version: SchemaVersion, chain_id: U, pool_id: Bytes32, pool_key: PoolKey, token: Token, + // The launch's own quote, LaunchParameters.quoteToken. quote_token: QuoteToken, + supply: Supply, launch_token_is_token1: z.boolean(), // The locker that forwarded the creation: the instance's periphery. controller: Address, @@ -80,7 +92,6 @@ export const LaunchDetail = LaunchSummary.extend({ tick_lower: z.number().int(), tick_upper: z.number().int(), liquidity: U, - installed_supply: U, locked: z.literal(true), withdrawable: z.literal(false), pool_reserves: SideAmounts, From 8f18246a4904a43fa758bcd8aa2aae84def6c810 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 21 Oct 2026 13:27:58 -0400 Subject: [PATCH 10/19] Document launch schema v4 and the per-launch quote policy Describe the requested/installed/sink supply, the removal of principal.installed_supply, the reviewed ERC-20 quote list and the replay pins and commands, and keep the launch tests lint-clean. --- src/routes/launches/README.md | 146 ++++++++++++++++++----------- src/routes/launches/golden.test.ts | 2 +- src/routes/launches/routes.test.ts | 9 +- 3 files changed, 98 insertions(+), 59 deletions(-) diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 76292de..753e10d 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,39 +1,47 @@ # Zero-seed launch reads (draft/local) -Draft response contract: `zero-seed-launch-api/v3`, for the forward-only -`ZeroSeedLaunch` with a native ETH quote, the fixed-base creator-fee table and -the separate `ZeroSeedLaunchPeriphery` of evm-contracts -`4f1a3060b099e8a50469848118f44c0c65cfab9a`. Indexer schema -`zero-seed-launch-index/v2` (migration 00139). Live instance lists are empty. - -Changes from v2: - -- `quote_token.kind` is `native` or `erc20`. Native ETH is address 0, always - token0 (the launch token is token1), 18 decimals, and has no token contract: - its `name` and `symbol` are null and no ERC-20 metadata is read. -- `fee_schedule.decay_factor` is gone; `fee_schedule.decay` is - `{ kind: "fixed_base_q128", half_life_seconds: 600 }`. `fee_rate` is the - contract's `feeAt` at the indexed block time: `initial_fee` until - `trading_start`, then - `final_fee + floor((initial_fee - final_fee) * (decay(t) - decay(D)) / (2^128 - decay(D)))` - with `decay` the contract's `decayQ128` (one Q128 constant per exponent bit, - floor after every multiply, zero from 2^17 s), then `final_fee` from - `fee_end`. `policy.test.ts` checks it against all 25,212 rows of the - protocol's independent all-second vectors. -- `creator` is the owner at creation (`LaunchOwnerChanged` from the zero - address) and never changes; `owner` is the current owner, which holds the - unclaimed and future fees (`fee_ledger.holder`, `transferable: true`); - `controller` is the periphery that forwarded the creation. Only launches - whose controller is the pinned periphery are served. -- Claims are the periphery payouts (`owner`, `recipient`), each linked by - `ledger_event_id` to the extension's ledger event of the same transaction; - the amounts are counted once. -- New `GET /launches/{chainId}/{poolId}/owners`; `GET /launches` accepts - `owner` (current) besides `creator`. - -`fixtures/zero-seed-launch-api-v3.golden.json` is a machine-readable example of -every response, written by the actual handlers over the replayed local fixture -(native and WETH launches, transfers, claims, a reorg). +Draft response contract: `zero-seed-launch-api/v4`, for the forward-only +`ZeroSeedLaunch` and its separate `ZeroSeedLaunchPeriphery` of evm-contracts +`40af6cb359b0de2f61f15e47b6622a00c48b5b08` (ABI sha256 of `jq -cS .abi`: +ZeroSeedLaunch `999068f8e41867e972f4485c0105f584a2c10d7537615e6b4517807548fcd4bf`, +periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). +Indexer schema `zero-seed-launch-index/v3` (migration 00139). Live instance +and quote lists are empty. + +Changes from v3: + +- Every `LaunchSummary` (list and detail) has + `supply: { requested, installed, sink, sink_holder }`, decimal strings of + raw launch-token units. `requested` is `LaunchParameters.supply` as + submitted and the token's `totalSupply`; `installed` is what Core took into + the position at creation (the launch-token delta of Core's + `PositionUpdated`), all of the circulating inventory; `sink` is + `requested - installed`, minted to `sink_holder` (address 0) and never + spendable. It is not a burn: `totalSupply` stays `requested`. The API + refuses (500) indexed rows outside `0 < installed <= requested` or with + `sink != requested - installed`. +- `principal.installed_supply` is removed (use `supply.installed`); + `principal.liquidity` is the liquidity the extension computed and + installed. +- The quote is per launch (`LaunchParameters.quoteToken`): one extension and + periphery serve native ETH and ERC-20 quotes. `quote_token` is the launch's + own quote; an instance has no quote. Native ETH is address 0, always token0 + (the launch token is token1), 18 decimals, null name and symbol. An ERC-20 + quote above the launch token's address makes the launch token token0. + +Unchanged from v3: the fixed-base fee table (`fee_schedule.decay` is +`{ kind: "fixed_base_q128", half_life_seconds: 600 }` and `fee_rate` the +contract's `feeAt` at the indexed block time, checked in `policy.test.ts` +against all 25,212 rows of the protocol's independent all-second vectors), +`creator` (owner at creation, never changes) versus `owner` (current, +transferable holder of unclaimed and future fees) and `controller` (the +periphery), periphery claim payouts linked by `ledger_event_id` and counted +once, and the `/owners` history and `owner`/`creator` list filters. + +`fixtures/zero-seed-launch-api-v4.golden.json` is a machine-readable example of +every response, written by the actual handlers over the replayed local fixture: +a native-ETH (launch token1, sink 578) and two ERC-20 (launch token0, sink 790) +launches on one extension, ownership transfers, claims, swaps and a reorg. ## Requests @@ -60,12 +68,18 @@ All hosted query delegates construct a reader with **no enabled instances**. They refuse launch reads with 404 without querying Postgres; missing database configuration also returns 404. There is no environment-binding enable switch. Only the explicit local test harness can pass the full reviewed instance -manifest. Extension and periphery addresses and runtimes, Core, constructor -quote and its kind (an ERC-20 also its runtime) and both ABIs must match -together; template runtimes are refused. - -List SQL screens the extension, controller (periphery), quote and kind, Core, -ordered pair, zero Core fee and spacing before pagination. Detail and both histories use that same selection. +manifest and quote list. Extension and periphery addresses and runtimes, Core +and both ABIs must match an instance together. Quote support is per launch: +native ETH is always eligible; an ERC-20 quote only if it is in the reviewed +quote list by chain, address and runtime codehash. The local list holds only +the fixture's plain test ERC-20 (evm-contracts `test/TestToken.sol`, +relocated to `0xff…ff`); the hosted list is empty, so no ERC-20 quote launch +can be served there even if launch reads were enabled. + +List SQL screens the extension, controller (periphery), Core, the launch's own +quote and kind against the supported quote list, ordered pair, zero Core fee +and spacing before pagination, so an unsupported quote never occupies a page. +Detail and all histories use that same selection: an unsupported launch is 404. Missing/unsupported launches return 404. A missing, mismatched or stale cursor head returns 503. Parameters return 400; malformed internal indexed output fails as 500, never a success or client-validation fallback. @@ -76,47 +90,69 @@ are `no-store`. Local test freshness is 60 seconds from cursor `last_updated`; this is not an approved live/hosted parameter. Fee rates and phases depend only on the indexed block timestamp. HTTP does not accept a wall-clock fee override. -Principal is the immutable installed supply/L pair and Core event-accounted pool +Principal is the immutable installed liquidity and Core event-accounted pool reserves (including rounding dust). Creator accrued/claimed/unclaimed ledgers are separate, per asset. Upper-bound leftover token residue and Core unowned token/quote dust are different quantities; no shared <=90 bound or fabricated claimable-funds estimate is exposed. Both assets are claimed atomically in full. -Requested supply and build/expiry times are not all emitted onchain. The API -must not fabricate them. Producer/owner surfaces separately bind a single -preview's requested→actual supply/L, 31 UTF-8 byte metadata, all calldata and -exact sink/start/expiry constraints. This read API does not close L-3 end-to-end. +The installed supply is not emitted by `LaunchCreated`; the indexer takes it +from Core's position update of the creation and refuses a creation without +exactly one consistent update. Build/expiry times are not emitted onchain and +are not served. Producer/owner surfaces separately bind a preview's +requested supply and computed installation, 31 UTF-8 byte metadata, all +calldata and start/expiry constraints; this read API does not. ## Local verification and stop/reset -Run `bun test src/routes/launches src/responseSchemas.test.ts -src/openapiSnapshot.test.ts`, `bun run lint`, and `bunx tsc --noEmit`. +Run `bun run format:check`, `bun run lint`, `bun run typecheck` and +`bun test` (or `bun test src/routes/launches src/responseSchemas.test.ts +src/openapiSnapshot.test.ts` for the launch routes). `bun run openapi` +regenerates `openapi.json`. For actual emitted-state/Postgres/handler replay, provide the exact pinned -indexer checkout, a disposable loopback Postgres 18, and the run scratch path: +indexer checkout (EkuboProtocol/indexer +`d783593ea71371acc1851b9e887b064026d83609`, fixture +`tests/fixtures/zero-seed-launch/local-chain.json` sha256 +`5dd281b580fb68dfaba00778d2241adf42c7b64ec5e8a70bfab8c9111ecf0ebb`), a +disposable loopback Postgres 18 and a scratch directory: ```sh +docker run -d --rm --name launch-replay-pg -p 127.0.0.1:55453:5432 \ + --tmpfs /var/lib/postgresql:rw \ + -e POSTGRES_USER=fixture -e POSTGRES_PASSWORD=fixture postgres:18 ZERO_SEED_INDEXER_WORKTREE=/path/to/pinned/indexer \ TEST_PG_CONNECTION_STRING=postgres://fixture:fixture@127.0.0.1:55453/postgres \ -PAPERCLIP_RUN_SCRATCH_DIR=/path/to/run-scratch \ -LAUNCH_GOLDEN_DIR=/path/to/run-scratch \ +LAUNCH_REPLAY_SCRATCH_DIR=/path/to/scratch \ +LAUNCH_GOLDEN_DIR=/path/to/scratch \ bun scripts/verifyLaunchReplay.ts +docker stop launch-replay-pg ``` +With `LAUNCH_GOLDEN_DIR` set, the run writes `actual-handler-golden.json`; +copy it to `fixtures/zero-seed-launch-api-v4.golden.json` and run +`bun run format`. + The runner checks source/fixture pins, creates a scratch copy of the reviewed -indexer test harness, appends API tests, and executes actual stream/DAO/SQL -replay without editing the indexer checkout. Test-only query-method spies wire +indexer test harness, appends API tests (`scripts/launchReplayCases.txt`), +and executes actual stream/DAO/SQL replay without editing the indexer +checkout. The API cases check every launch's supply against the chain +(`totalSupply`, `balanceOf(address(0))`, Core's position delta), that +ERC-20 quote launches are 404 and absent from lists when their quote is not +in the reviewed list (empty, wrong runtime or wrong chain) while the native +launch is served, and that a malformed indexed supply fails as 500. Test-only query-method spies wire the actual HTTP handlers to real local SQL with the reviewed local manifest; they do not stub response rows. All temporary databases are dropped by the harness. No RPC, fork URL, provider/secret read or production migration is used. Safe stop is launch support OFF. A pre-issuance source rollback to the PR base -without launch routes is safe; the old staged or arbitrary-quote drafts are -not. Reset only the dedicated disposable local test databases/container. +without launch routes is safe; the old staged, constructor-quote or +arbitrary-quote drafts are not. Reset only the dedicated disposable local test databases/container. Application rollback cannot refund, withdraw, rebalance or migrate immutable principal. No merge/deploy/live issuance or authority change is authorized. Live provenance/registration/state, production query performance/capacity/costs, -served artifacts, mined UserOps, full WETH metadata reproduction, downstream +served artifacts, mined UserOps, ERC-20 quote metadata (not indexed for the +local quote, so its name, symbol and decimals are null), downstream whole-stack QA/Sage, external audit and legal/provider/wallet/board/release controls remain NOT PROVEN/HOLD. diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 18ed916..8c26a4e 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -74,7 +74,7 @@ test("native token1 and ERC-20 token0 launches share one extension and split req [erc20, golden.erc20Owners], ] as const) { expect(detail.creator).not.toBe(detail.owner); - expect(owners.data[0]!.new_owner).toBe(detail.creator); + expect(owners.data[0].new_owner).toBe(detail.creator); expect(owners.data.at(-1)!.new_owner).toBe(detail.owner); } }); diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index f0d8e8b..4240b34 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -38,9 +38,12 @@ test("the hosted reader enables no instance and no ERC-20 quote", async () => { }) as never); expect(Reflect.get(hosted, "enabled")).toEqual([]); expect(Reflect.get(hosted, "quotes")).toEqual([]); - await expect( - hosted.list({ chainId: 31337n, limit: 1 }), - ).rejects.toMatchObject({ status: 404 }); + const refused = await hosted.list({ chainId: 31337n, limit: 1 }).then( + () => undefined, + (error: unknown) => error, + ); + expect(refused).toBeInstanceOf(StatusError); + expect(refused).toMatchObject({ status: 404 }); // even a harness that passes the reviewed lists is refused when not local const notLocal = new LaunchQueries((() => {}) as never, { localHarness: false, From cf05546b0b5f49194cab76b7c9348d9929574b58 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 21 Oct 2026 20:51:41 -0400 Subject: [PATCH 11/19] Serve zero-seed launches of evm-contracts 550afc4: full uint64 creator fees The protocol now accepts every uint64 creator fee rate (2^64 - 1, just under 100%, is the maximum) and drops FeeTooHigh() from the interface. The response shape is unchanged, so the contract stays zero-seed-launch-api/v4. - Re-pin the ZeroSeedLaunch ABI (sha256 efae9388...e41e) and the local replay instance (extension 0x511b...6d8b, periphery 0x2f0b...ef64 and their runtime hashes); the periphery ABI and the local quote are unchanged. - Fee fields (initial_fee, final_fee and both fee_rates) are validated as canonical decimal uint64 strings. An indexed swap fee rate above 2^64 - 1 was previously served as is; it now fails as 500 like an out-of-range schedule fee, never wrapped or truncated. OpenAPI documents the range. - Re-pin the emitted-state replay to indexer c78a48e (fixture sha256 09d77457...4fcc), which adds two launches created normally at the maximum rate: native 2^64 - 1 -> 0.3% over 3600 s and ERC-20 flat at 2^64 - 1. The replay checks that the served instance equals the fixture's, that both launches serve 2^64 - 1 exactly (schedule, phase, swap rates, exact-input net floor(gross / 2^64), exact-output paid calculated * 2^64, ledgers and claims) and that a stored fee of 2^64 fails as 500. - Regenerate the golden responses with both launches and test them, and unit-test the whole uint64 range and malformed fee encodings. --- openapi.json | 28 +- scripts/launchReplayCases.txt | 88 +- scripts/verifyLaunchReplay.ts | 4 +- src/routes/launches/README.md | 28 +- .../zero-seed-launch-api-v4.golden.json | 882 ++++++++++++++---- src/routes/launches/golden.test.ts | 87 +- src/routes/launches/policy.test.ts | 112 ++- src/routes/launches/policy.ts | 14 +- src/routes/launches/schema.ts | 19 +- 9 files changed, 1060 insertions(+), 202 deletions(-) diff --git a/openapi.json b/openapi.json index 795868e..1538bf7 100644 --- a/openapi.json +++ b/openapi.json @@ -10218,11 +10218,15 @@ }, "initial_fee": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "final_fee": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "decay": { "type": "object", @@ -10256,7 +10260,9 @@ }, "fee_rate": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "evaluated_at": { "type": "integer", @@ -11110,7 +11116,9 @@ }, "fee_rate": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "fee_amount": { "type": "string", @@ -11449,11 +11457,15 @@ }, "initial_fee": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "final_fee": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "decay": { "type": "object", @@ -11487,7 +11499,9 @@ }, "fee_rate": { "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" }, "evaluated_at": { "type": "integer", diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index a91aab8..5965e78 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -29,6 +29,13 @@ const lower = (a: string) => a.toLowerCase(); const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); const { creator, newOwner, feeRecipient, trader } = full.addresses; + // The served local instance is the fixture's, field for field. + expect(LOCAL_INSTANCES).toEqual(full.instances.map((i: any) => ({ + chainId: String(CHAIN_ID), address: lower(i.extension), runtime: i.extensionRuntimeKeccak, + periphery: lower(i.periphery), peripheryRuntime: i.peripheryRuntimeKeccak, core: lower(full.addresses.core), + abiSha256: full.provenance.launchAbiSha256, peripheryAbiSha256: full.provenance.peripheryAbiSha256, + }))); + expect(LOCAL_QUOTES).toEqual([{ chainId: String(CHAIN_ID), address: lower(full.addresses.erc20Quote), runtime: full.provenance.erc20QuoteRuntimeKeccak }]); const path = (label: string) => `/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`; /** One launch at one block: API detail versus the chain state and the indexer's SQL feeAt. */ const expectLaunch = async (state: any, label: string) => { @@ -123,18 +130,20 @@ }); const canonicalList = await get(`/launches?chainId=${CHAIN_ID}`); expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual( - ["l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); + ["l6", "l5", "l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); expect(canonicalList.next_cursor).toBeNull(); for (const suffix of ["", "/swaps", "/claims", "/owners"]) await get(`${path("l3")}${suffix}`, 404); // creator (owner at creation) and current owner filters expect((await get(`/launches?chainId=${CHAIN_ID}&creator=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); + ["l5", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${feeRecipient}`)).data.map((l: any) => l.pool_id)).toEqual( [fixture.launches.l1!.poolId]); expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${BigInt(trader).toString()}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l4!.poolId]); - expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${creator}`)).data).toEqual([]); + ["l6", "l4"].map((l) => fixture.launches[l]!.poolId)); + // the creator still owns only l5 (l1 and l2 were transferred) + expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( + [fixture.launches.l5!.poolId]); const owners = await get(`${path("l1")}/owners`); expect(owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ [null, lower(creator)], [lower(creator), lower(newOwner)], [lower(newOwner), lower(feeRecipient)]]); @@ -148,6 +157,48 @@ const claims = await expectClaimsOnce("l1"); await expectClaimsOnce("l2"); await expectClaimsOnce("l4"); + // Launches created at the maximum rate 2^64 - 1 (just under 100%) are + // served exactly: decimal strings, never rounded, never 2^64. + const MAX = (2n ** 64n - 1n).toString(); + const l5 = await get(path("l5")); + const l6 = await get(path("l6")); + expect(l5.fee_schedule).toMatchObject({ initial_fee: MAX, final_fee: "55340232221128654", fee_duration: 3600 }); + expect(l6.fee_schedule).toMatchObject({ initial_fee: MAX, final_fee: MAX, fee_duration: 1 }); + // the canonical head is past both fee windows + expect([l5.fee_schedule.phase, l5.fee_schedule.fee_rate]).toEqual(["final_fee", "55340232221128654"]); + expect([l6.fee_schedule.phase, l6.fee_schedule.fee_rate]).toEqual(["final_fee", MAX]); + expect([l5.quote_token.kind, l5.launch_token_is_token1, l6.quote_token.kind, l6.launch_token_is_token1]).toEqual( + ["native", true, "erc20", false]); + expect([l5.creator, l5.owner, l6.creator, l6.owner]).toEqual([lower(creator), lower(creator), lower(trader), lower(trader)]); + const l5Swaps = await get(`${path("l5")}/swaps`); + const l6Swaps = await get(`${path("l6")}/swaps`); + const l5Claims = await expectClaimsOnce("l5"); + const l6Claims = await expectClaimsOnce("l6"); + // the exact-input fee differs by launch with the pool price it met + for (const [launch, page, exactInFee] of [[l5, l5Swaps, 999083373736809197195735n], [l6, l6Swaps, 999083373736809197454718n]] as const) { + expect(page.data.length).toBe(launch.fee_ledger.swap_count); + // every swap fee is accrued once to the creator ledger of its asset + for (const asset of ["launch_token", "quote_token"]) + expect(page.data.filter((s: any) => s.fee_asset === asset).reduce((n: bigint, s: any) => n + BigInt(s.fee_amount), 0n).toString()) + .toBe(launch.fee_ledger[asset].accrued); + // at the maximum: exact input nets floor(gross / 2^64) of the output; exact output pays calculated * 2^64 + const atMax = page.data.filter((s: any) => s.fee_rate === MAX); + expect(atMax.map((s: any) => [s.direction, s.fee_asset])).toEqual([["buy", "launch_token"], ["buy", "quote_token"]]); + const [exactIn, exactOut] = atMax; + const net = -BigInt(exactIn.launch_token_delta); + expect([net, BigInt(exactIn.quote_token_delta)]).toEqual([54160n, 10n ** 18n]); + expect(BigInt(exactIn.fee_amount)).toBe(exactInFee); + expect((net + BigInt(exactIn.fee_amount)) / 2n ** 64n).toBe(net); + expect([BigInt(exactOut.launch_token_delta), BigInt(exactOut.quote_token_delta)]).toEqual( + [-(10n ** 10n), 184817928874495997640704n]); + expect(BigInt(exactOut.quote_token_delta)).toBe(10019n * 2n ** 64n); + expect(BigInt(exactOut.fee_amount)).toBe(184817928874495997630685n); + expect(BigInt(exactOut.quote_token_delta) - BigInt(exactOut.fee_amount)).toBe(10019n); + } + // l5 decays from the maximum: one swap mid-window, then at the final fee + expect(l5Swaps.data.map((s: any) => s.fee_rate)).toEqual( + [MAX, MAX, "3470655575073783216", "55340232221128654", "55340232221128654"]); + expect(l6Swaps.data.map((s: any) => s.fee_rate)).toEqual([MAX, MAX]); // l1 claims: by the creator, by the recipient-owner after two transfers, then twice at zero expect(claims.data.map((c: any) => c.owner)).toEqual( [lower(creator), lower(feeRecipient), lower(feeRecipient), lower(feeRecipient)]); @@ -195,15 +246,16 @@ ]; try { // screened before pagination: the native launch fills the page - const page = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); - expect(page.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); + const page = await get(`/launches?chainId=${CHAIN_ID}&limit=2`); + expect(page.data.map((l: any) => l.pool_id)).toEqual(["l5", "l1"].map((l) => fixture.launches[l]!.poolId)); expect(page.next_cursor).toBeNull(); - for (const label of ["l2", "l4"]) + for (const label of ["l2", "l4", "l6"]) for (const suffix of ["", "/swaps", "/claims", "/owners"]) { const body = await get(`${path(label)}${suffix}`, 404); expect(body).toEqual({ status: 404, error: "Launch not found" }); } expect(await get(path("l1"))).toEqual(detail); + expect(await get(path("l5"))).toEqual(l5); } finally { spy.forEach((s) => s.mockRestore()); } @@ -225,6 +277,8 @@ source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain.json", chain_id: String(CHAIN_ID) }, list, second, canonicalList, nativeDetail: detail, nativeSwaps: swaps, nativeClaims: claims, nativeOwners: owners, erc20Detail: l2, erc20Swaps: l2Swaps, erc20Claims: l2Claims, erc20Owners: l2Owners, erc20TraderOwnedDetail: l4, + maxNativeDetail: l5, maxNativeSwaps: l5Swaps, maxNativeClaims: l5Claims, + maxErc20Detail: l6, maxErc20Swaps: l6Swaps, maxErc20Claims: l6Claims, }, null, 2)); } const before = await get(path("l1")); @@ -254,6 +308,26 @@ await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created ADD CONSTRAINT "${check!.name}" ${check!.def}`); expect(await get(path("l1"))).toEqual(before); + // A stored fee above uint64 (2^64, exactly 100%) fails as 500 on every + // read that serves it, never wrapped, truncated or served. + const l6Before = await get(path("l6")); + const l6SwapsBefore = await get(`${path("l6")}/swaps`); + const l6Pool = num(fixture.launches.l6!.poolId); + const Q64 = (2n ** 64n).toString(); + await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; + await db.sql`UPDATE zero_seed_launch_created SET initial_fee = ${Q64}::numeric WHERE pool_id = ${l6Pool}::numeric`; + await get(path("l6"), 500); + await get(`/launches?chainId=${CHAIN_ID}`, 500); + await db.sql`UPDATE zero_seed_launch_created SET initial_fee = ${MAX}::numeric WHERE pool_id = ${l6Pool}::numeric`; + await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; + await db.sql`ALTER TABLE zero_seed_launch_swapped DISABLE TRIGGER no_updates_zero_seed_launch_swapped`; + const l6Swap = BigInt(l6SwapsBefore.data[0].event.event_id); + await db.sql`UPDATE zero_seed_launch_swapped SET fee_rate = ${Q64}::numeric WHERE chain_id = ${CHAIN_ID} AND event_id = ${l6Swap}`; + await get(`${path("l6")}/swaps`, 500); + await db.sql`UPDATE zero_seed_launch_swapped SET fee_rate = ${MAX}::numeric WHERE chain_id = ${CHAIN_ID} AND event_id = ${l6Swap}`; + await db.sql`ALTER TABLE zero_seed_launch_swapped ENABLE TRIGGER no_updates_zero_seed_launch_swapped`; + expect(await get(path("l6"))).toEqual(l6Before); + expect(await get(`${path("l6")}/swaps`)).toEqual(l6SwapsBefore); await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes' WHERE chain_id=${CHAIN_ID}`; await get(path("l1"), 503); await apiHead(db); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index 9848ee5..a54a342 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -16,7 +16,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "d783593ea71371acc1851b9e887b064026d83609" + head.stdout.toString().trim() !== "c78a48e3aff1df7a8e3298545eee8be04474063d" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -37,7 +37,7 @@ const fixturePath = resolve( const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); if ( new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "5dd281b580fb68dfaba00778d2241adf42c7b64ec5e8a70bfab8c9111ecf0ebb" + "09d7745740af507a02304e26c0e20d6f27b1047fd707c056ef4d936c67a74fcc" ) throw new Error("Emitted fixture pin mismatch"); const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 753e10d..79a9804 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -2,8 +2,8 @@ Draft response contract: `zero-seed-launch-api/v4`, for the forward-only `ZeroSeedLaunch` and its separate `ZeroSeedLaunchPeriphery` of evm-contracts -`40af6cb359b0de2f61f15e47b6622a00c48b5b08` (ABI sha256 of `jq -cS .abi`: -ZeroSeedLaunch `999068f8e41867e972f4485c0105f584a2c10d7537615e6b4517807548fcd4bf`, +`550afc40280423128c02eb5ffd213aa959b34e2a` (ABI sha256 of `jq -cS .abi`: +ZeroSeedLaunch `efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e`, periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). Indexer schema `zero-seed-launch-index/v3` (migration 00139). Live instance and quote lists are empty. @@ -29,6 +29,15 @@ Changes from v3: (the launch token is token1), 18 decimals, null name and symbol. An ERC-20 quote above the launch token's address makes the launch token token0. +Creator fees span the whole uint64 range. `initial_fee`, `final_fee` and +both `fee_rate`s are Q0.64 rates as canonical decimal strings from `0` to +`18446744073709551615` (2^64 - 1, the maximum, just under 100%; the fee +fraction is value / 2^64, never exactly 100%), never JSON numbers. The +contract checks only `final_fee <= initial_fee` and a nonzero duration; there +is no ceiling below 2^64 - 1 and no `FeeTooHigh` error. The API refuses (500) an +indexed fee above 2^64 - 1 instead of wrapping, truncating or serving it. +The response shape is unchanged, so the contract stays v4. + Unchanged from v3: the fixed-base fee table (`fee_schedule.decay` is `{ kind: "fixed_base_q128", half_life_seconds: 600 }` and `fee_rate` the contract's `feeAt` at the indexed block time, checked in `policy.test.ts` @@ -41,7 +50,10 @@ once, and the `/owners` history and `owner`/`creator` list filters. `fixtures/zero-seed-launch-api-v4.golden.json` is a machine-readable example of every response, written by the actual handlers over the replayed local fixture: a native-ETH (launch token1, sink 578) and two ERC-20 (launch token0, sink 790) -launches on one extension, ownership transfers, claims, swaps and a reorg. +launches on one extension, ownership transfers, claims, swaps and a reorg, +plus two launches created at the maximum rate: `maxNativeDetail` (native, +2^64 - 1 decaying to 0.3% over 3600 s) and `maxErc20Detail` (ERC-20, flat at +2^64 - 1), with their exact-input, exact-output and decayed swaps and claims. ## Requests @@ -112,9 +124,9 @@ regenerates `openapi.json`. For actual emitted-state/Postgres/handler replay, provide the exact pinned indexer checkout (EkuboProtocol/indexer -`d783593ea71371acc1851b9e887b064026d83609`, fixture +`c78a48e3aff1df7a8e3298545eee8be04474063d`, fixture `tests/fixtures/zero-seed-launch/local-chain.json` sha256 -`5dd281b580fb68dfaba00778d2241adf42c7b64ec5e8a70bfab8c9111ecf0ebb`), a +`09d7745740af507a02304e26c0e20d6f27b1047fd707c056ef4d936c67a74fcc`), a disposable loopback Postgres 18 and a scratch directory: ```sh @@ -140,7 +152,11 @@ checkout. The API cases check every launch's supply against the chain (`totalSupply`, `balanceOf(address(0))`, Core's position delta), that ERC-20 quote launches are 404 and absent from lists when their quote is not in the reviewed list (empty, wrong runtime or wrong chain) while the native -launch is served, and that a malformed indexed supply fails as 500. Test-only query-method spies wire +launches are served, that the local instance and quote pins equal the +fixture's, that the maximum-rate launches serve 2^64 - 1 exactly (schedule, +swap rates, exact-input net floor(gross / 2^64), exact-output paid +calculated * 2^64, ledgers), and that a malformed indexed supply or an indexed +fee of 2^64 fails as 500. Test-only query-method spies wire the actual HTTP handlers to real local SQL with the reviewed local manifest; they do not stub response rows. All temporary databases are dropped by the harness. No RPC, fork URL, provider/secret read or production migration is used. diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json index dde2517..609fbd9 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json @@ -10,18 +10,18 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xeaede813919244a048fb74e7d5aaae13714f8c0919ee578d9b21c13d6ddef612", + "pool_id": "0xf03731816bad65fe90b668df070664d89a72452ac47622c34228b51fcefa97b6", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x00f65546aad85b256b946eaa4268ac0a54f9527f", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "token1": "0x9417527c774454cb137497120651a1396cb46e39", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0x00f65546aad85b256b946eaa4268ac0a54f9527f", + "address": "0x9417527c774454cb137497120651a1396cb46e39", "name": "Zero Seed l3", "symbol": "L3", "decimals": 18 @@ -40,7 +40,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -61,16 +61,16 @@ }, "created": { "block_number": "8", - "block_hash": "0xb10d7fdd9e697c098f838e7651efb5c609eef3fbb69f2d465ad2baca47a4d6eb", + "block_hash": "0xa34547d55cdf18b5d6cd1017f08159bbf4ace82afd4348e3a1905129d603b60a", "block_time": 1800001300, "event_id": "-9223372002494906358", "transaction_index": 2, "event_index": 9, - "transaction_hash": "0xdd2f935e81b85959edb01732c55dae213da72e4780ee03c0cb53ed2cfb0e428d" + "transaction_hash": "0x154cf448eada43524c2f317ad1f3e0e505d5e99ced13aaf91e3d0e52cb21eb7b" }, "as_of": { "block_number": "9", - "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", + "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", "block_time": 1800001360 } } @@ -78,7 +78,7 @@ "next_cursor": "-9223372002494906358", "as_of": { "block_number": "9", - "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", + "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", "block_time": 1800001360 } }, @@ -88,18 +88,18 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -118,7 +118,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "ask_tick": -13815500, @@ -139,34 +139,34 @@ }, "created": { "block_number": "3", - "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", + "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" + "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" }, "as_of": { "block_number": "9", - "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", + "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", "block_time": 1800001360 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf3f802e8826977ff3af68c6747daf11118685836", + "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -185,7 +185,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -206,16 +206,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", + "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" + "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" }, "as_of": { "block_number": "9", - "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", + "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", "block_time": 1800001360 } } @@ -223,7 +223,7 @@ "next_cursor": null, "as_of": { "block_number": "9", - "block_hash": "0xa09b98786e7f3e4a6a190a02730a39b5b1789adf205f22887e2d27bb4f54c842", + "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", "block_time": 1800001360 } }, @@ -233,18 +233,152 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xfae7fde391805bfe23cfa7670cd3a9bcb21cbc542ef0659f3a630bf9efb89f59", + "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "token0": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "address": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "name": "Zero Seed l6", + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 1, + "fee_end": 1800001401, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_time": 1800001310, + "event_id": "-9223372002494775266", + "transaction_index": 4, + "event_index": 29, + "transaction_hash": "0x183728b1a3e44504363c9de57e8e3da1c44db6537a97828107e7b0657cecbd14" + }, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + }, + "token": { + "address": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "name": "Zero Seed l5", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 3600, + "fee_end": 1800005000, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_time": 1800001310, + "event_id": "-9223372002494840810", + "transaction_index": 3, + "event_index": 21, + "transaction_hash": "0x64bbf42368abd232d30334ff37fcc33c510daee65a974356c5bd06af39773380" + }, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x0a4ffce27d439f707aaee89bd3ef673e6f0f7fed91b8cc0155de093fde32f6b3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + }, + "token": { + "address": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", "name": "Zero Seed l4", "symbol": "L4", "decimals": 18 @@ -263,7 +397,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -284,34 +418,34 @@ }, "created": { "block_number": "8", - "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", "block_time": 1800001310, "event_id": "-9223372002494906354", "transaction_index": 2, "event_index": 13, - "transaction_hash": "0xb12debf1dec2c43a557a5758e6a2081d10f7b3e6dfc054ea6cc5437eb0d72687" + "transaction_hash": "0xf6161759835347763d4d5ca093b5956e6e5777f095a6502ee501e4b04c83f712" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -330,7 +464,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -351,34 +485,34 @@ }, "created": { "block_number": "3", - "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", + "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" + "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf3f802e8826977ff3af68c6747daf11118685836", + "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -397,7 +531,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -418,42 +552,42 @@ }, "created": { "block_number": "2", - "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", + "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" + "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 } } ], "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 } }, "nativeDetail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf3f802e8826977ff3af68c6747daf11118685836", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf3f802e8826977ff3af68c6747daf11118685836", + "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -472,7 +606,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -493,16 +627,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", + "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" + "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "principal": { @@ -534,7 +668,7 @@ "swap_count": 10, "claim_count": 4, "owner_change_count": 3, - "last_event_id": "-9223371972430135288" + "last_event_id": "-9223371968135167992" }, "inventory": { "trading_open": true, @@ -545,23 +679,23 @@ "nativeSwaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "5", - "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", + "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", "block_time": 1800001000, "event_id": "-9223372015379939327", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0x9708d7f0d99e081fa4be5c2e2ec58be0cc10260d8efdb3740217e781694674c1" + "transaction_hash": "0x9b60bd3704ec2424597faae581e3068b4cb8faf171d716843acf3451f9f60699" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "zero_fill", @@ -574,12 +708,12 @@ { "event": { "block_number": "5", - "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", + "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", "block_time": 1800001000, "event_id": "-9223372015379873789", "transaction_index": 1, "event_index": 2, - "transaction_hash": "0x8ded8b7580791d6d4ed503d8e6898a190df449b5d55b90213da6a8f1276912ed" + "transaction_hash": "0xcba53ebcd987cc8ddd85345d300532606e053df74c935f35699e896f5087b85e" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -592,12 +726,12 @@ { "event": { "block_number": "6", - "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", + "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", "block_time": 1800001060, "event_id": "-9223372011084972030", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x1bd89ad947ad1e7829428f7ea17d10090960c4d5965e78cea72f3a2a3d7e964b" + "transaction_hash": "0xf6c61efadcd163f35b2de1be77f5be89a8e2a7aed5be35ebbf68bd41aecf8950" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -610,12 +744,12 @@ { "event": { "block_number": "6", - "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", + "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", "block_time": 1800001060, "event_id": "-9223372011084906491", "transaction_index": 1, "event_index": 4, - "transaction_hash": "0xc006a7b2373e3a990507edce1633dc961bb410cf33bfcd73565b48bb47d24f92" + "transaction_hash": "0xa0f80fe18b87ea86598786d6ee43e1303090b049a0c8231b3b0163eb52ed4e62" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -628,12 +762,12 @@ { "event": { "block_number": "8", - "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", "block_time": 1800001310, "event_id": "-9223372002495037438", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x7df0eb3faffd65a19798f5997f594149161fbb55f690b48da2cbdf6953ad8b1f" + "transaction_hash": "0x4aa5aa59453fc2f6c03c1f5703e8cad2b51fbdf1c6ddd694f319868f096f9dbc" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -646,12 +780,12 @@ { "event": { "block_number": "11", - "block_hash": "0x1a69c81dee7f058c093171599388168f8532a99859d079396b3f6c379896c981", + "block_hash": "0x337614e1d72935cec1f18d71ccc21a8f34c3d8eb8a4c8f347c0bd945c178c5eb", "block_time": 1800001756, "event_id": "-9223371989610135550", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x4745307dc6a809bcea741a4a83405b50badf44d179c092cc121b611e7eacbf22" + "transaction_hash": "0x7f5d3dede97bd4c4e8708e1e335164c5e2b3b917a94ce717ee0d393e01fa6899" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -664,12 +798,12 @@ { "event": { "block_number": "12", - "block_hash": "0xe90ec094b4fc8bad141075e242bc96a025091d064cdf458d779f43bc6cc5a5a1", + "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", "block_time": 1800002800, "event_id": "-9223371985315168254", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x25fff11db3ba53c8d82b6ab2e6d722e79126d869cd7a4087bc8442667cf0ecbb" + "transaction_hash": "0xc26dd317b4e548ec29284a5727a4d7324470c001cb76bf6cc29dbe4936bda49f" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -682,12 +816,12 @@ { "event": { "block_number": "13", - "block_hash": "0x08282b9a2a600de126d2cc6db1068d1c656f31c314a5f7223fa75572d2f0ed90", + "block_hash": "0xb6f8390364d32a5ada052833408e1d6667194ce8787d927bbed408364e1ecf8d", "block_time": 1800004599, "event_id": "-9223371981020200958", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xf45e7869162ea1b208d7074069e74da24e4e5e4a1e084c793f20420a188c636c" + "transaction_hash": "0x8dbc15287d2b9d87305a5b7e4c9a2c64e4f9a43c2f694b4d104dacf2b3810de1" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -700,12 +834,12 @@ { "event": { "block_number": "14", - "block_hash": "0x21f5448b2e2b66fd73b3e8f91d94c2b319ea9707e04d37c9e195a86ee376867d", + "block_hash": "0xb9cff80895c3c2a933f0553a8bd5b6f53450163c53d94dcd23060f2ef5d9e8a5", "block_time": 1800004600, "event_id": "-9223371976725233662", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x8c1e82c6ced397e8ffb2096fbc6f2f7fe8f5fb3a12c2cfd92decbd3d31d3759f" + "transaction_hash": "0xd042c39b6f458c6be0858843678b6d3e571bf52d93702103e1f13df7ab5af790" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -717,13 +851,13 @@ }, { "event": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000, - "event_id": "-9223371972430266366", + "event_id": "-9223371968135299070", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x60bb0cbe48eb8de8260e95d0382c47043156a0312c161711d5dfa1afe60fc1e5" + "transaction_hash": "0xd1d7ffbdbbc5f5d36c671d1d0c0f4ef1e5811351abb9c37d613755f422ec1a23" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -738,23 +872,23 @@ "nativeClaims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "6", - "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", + "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", "block_time": 1800001060, "event_id": "-9223372011084775411", "transaction_index": 3, "event_index": 12, - "transaction_hash": "0x5e46ec4c91f3e21746c4efe74c978cae36409a6a3108c8120f80b22438a33fb6" + "transaction_hash": "0x26a54a1867ab551a062c62bff50eb6a18e1caafa0f0adb8352f8877b843257ae" }, "ledger_event_id": "-9223372011084775413", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", @@ -765,12 +899,12 @@ { "event": { "block_number": "12", - "block_hash": "0xe90ec094b4fc8bad141075e242bc96a025091d064cdf458d779f43bc6cc5a5a1", + "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", "block_time": 1800002800, "event_id": "-9223371985315037174", "transaction_index": 2, "event_index": 9, - "transaction_hash": "0xf8afbe837ae5e7c7599dc2427eae78af2b72ab08986812ed37d27109f847303c" + "transaction_hash": "0x050d4bcf62c12351ac16c66ab7b839f048b4c913f8cf05a9d4ec3ab4221fd3e2" }, "ledger_event_id": "-9223371985315037176", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", @@ -780,15 +914,15 @@ }, { "event": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000, - "event_id": "-9223371972430200826", + "event_id": "-9223371968135233530", "transaction_index": 1, "event_index": 5, - "transaction_hash": "0x3599b7df8c3d82c60994f1686e0ed5782c67e16665dacbcc73148ab98cf65ebf" + "transaction_hash": "0xc36d9d6bc2ec2d0e0d0e1e203c529c01a2839cbeb356cc931f7ceadd956242c5" }, - "ledger_event_id": "-9223371972430200828", + "ledger_event_id": "-9223371968135233532", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "launch_token_amount": "5849077212047475495852", @@ -796,15 +930,15 @@ }, { "event": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000, - "event_id": "-9223371972430135288", + "event_id": "-9223371968135167992", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x642169c64dba21fb1701328774d97b241730756a486b5c08336dccd8232a262e" + "transaction_hash": "0xa6d80d862539cbcd6d28348c674ad575d83b9da3a73dcd1aa46be86d1d8bf247" }, - "ledger_event_id": "-9223371972430135289", + "ledger_event_id": "-9223371968135167993", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "launch_token_amount": "0", @@ -815,23 +949,23 @@ "nativeOwners": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf25a6d9d7ebcce22f88bd2da99e0ba098e5b81aa9d0e59d5e2058a50417f144b", + "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "2", - "block_hash": "0x02a4be6c4181e7dbfd929b596ffa629ce40079428de4c790460638b389ad69fb", + "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", "block_time": 1800000010, "event_id": "-9223372028264710135", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0xd7040ae49f820983ab5e5e52f40210bda17ad50afa7efec67b85eb270eeb5717" + "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -839,12 +973,12 @@ { "event": { "block_number": "10", - "block_hash": "0x5a776223d6ae2f50072e6c96d9d2aa8e30e531ad1d2d83b5beec273f7f562f4b", + "block_hash": "0x07601d853abadc6ece2e5a9c4dd0bdfbdba5d9b4161df116c63abbabc1810a6f", "block_time": 1800001700, "event_id": "-9223371993904971767", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x787ab24441fae7bb3b0835e29b8d942142c0d47445c0a539963caa14683317ab" + "transaction_hash": "0x1b9be8d1b1f03634f57be21492b10be90846b178218f1bdff679c86d63a79a6e" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" @@ -852,12 +986,12 @@ { "event": { "block_number": "11", - "block_hash": "0x1a69c81dee7f058c093171599388168f8532a99859d079396b3f6c379896c981", + "block_hash": "0x337614e1d72935cec1f18d71ccc21a8f34c3d8eb8a4c8f347c0bd945c178c5eb", "block_time": 1800001756, "event_id": "-9223371989610070012", "transaction_index": 1, "event_index": 3, - "transaction_hash": "0xdc53fc5509baf5e0338b3a2c8b0cc1bcb2a06a84c4810efbdbd51ed7f02e3511" + "transaction_hash": "0xb27bb2f30760241ff30b5b65985a4017344c635b5dc1a47cc166c7f7a5763a0e" }, "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" @@ -867,18 +1001,18 @@ "erc20Detail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0xf4d27a0a5195f911cdc5730d34f60fee98e55c29", + "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -897,7 +1031,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -918,16 +1052,16 @@ }, "created": { "block_number": "3", - "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", + "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" + "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "principal": { @@ -970,23 +1104,23 @@ "erc20Swaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "5", - "block_hash": "0xb741a993cbd99ae11127fcb9bb28234e04532f31a679f03c836197f1dc93565e", + "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", "block_time": 1800001000, "event_id": "-9223372015379808250", "transaction_index": 2, "event_index": 5, - "transaction_hash": "0x9725df1fe1acfc2593d258e910cc0eef090984ff6281515e8ac2efd04af0165b" + "transaction_hash": "0xd3f40de861118a466ce507a1e67d2578def0aab8b6d990d2cb0b053398d84424" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -999,12 +1133,12 @@ { "event": { "block_number": "6", - "block_hash": "0x01c3e2e4596b56af7f6133347b2672e7be2f1f6d632512c0c0ea53b1b49b3530", + "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", "block_time": 1800001060, "event_id": "-9223372011084840952", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x032a76cc6771b8369669f8629fb0289a5013d2be5af05230436c1ac2f3921c0c" + "transaction_hash": "0xa9bcd109b1eb193f56b3caf3ca901f3a77e6ad16a02d02b87dc1db9768e02354" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1017,12 +1151,12 @@ { "event": { "block_number": "7", - "block_hash": "0x1270c2e1c5ef400e9002228de50d222f53b3d3a807ca1f00941fe067cea6a2ca", + "block_hash": "0x1b32c54fcc000000f5e7d1382384d0bf84613c4669c73a0132eee9b20833b1a0", "block_time": 1800001120, "event_id": "-9223372006789939197", "transaction_index": 1, "event_index": 2, - "transaction_hash": "0x32795fdcee3f55b41c171d5b6f2a376f328fb63203aaa93cc22206e812079d06" + "transaction_hash": "0xbe8d49418840c8f29e979b057ad3b2d52b4a58f4fd0a318b998cf28fb9b73793" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -1035,12 +1169,12 @@ { "event": { "block_number": "10", - "block_hash": "0x5a776223d6ae2f50072e6c96d9d2aa8e30e531ad1d2d83b5beec273f7f562f4b", + "block_hash": "0x07601d853abadc6ece2e5a9c4dd0bdfbdba5d9b4161df116c63abbabc1810a6f", "block_time": 1800001700, "event_id": "-9223371993905037306", "transaction_index": 1, "event_index": 5, - "transaction_hash": "0x0bfbd738e4a8b913847650fc2a4d641202be197cf85e2e8f7bc6ca893ca6b537" + "transaction_hash": "0x0df04a504f49df9320a2fe056382f8e7d61d09724faf975121183399f3b016d8" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1055,23 +1189,23 @@ "erc20Claims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "8", - "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", "block_time": 1800001310, "event_id": "-9223372002494971897", "transaction_index": 1, "event_index": 6, - "transaction_hash": "0x04962c1513ebf17ca269f9012dc1bd3952af011a4b6818dfe4206e4db44dc6f1" + "transaction_hash": "0xc9932fbea79758476c6438b5007e2a96dceecd2fb2f3e12a2eceaabae84b1535" }, "ledger_event_id": "-9223372002494971900", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", @@ -1084,23 +1218,23 @@ "erc20Owners": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x4c069ac95e8be8e3122e8fef35ce64947aaa0c63c2dc869c21a5deb60b120e06", + "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", "next_cursor": null, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "3", - "block_hash": "0x50fbee48c437582d1aa4283fa37001d89021690e795a7df18b73c5169d3c5488", + "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", "block_time": 1800000020, "event_id": "-9223372023969873912", "transaction_index": 0, "event_index": 7, - "transaction_hash": "0x9b5d5e578839239bc9f340547f9b9eb9eb5acf732db64ecfbfd2ce1bc24c575c" + "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -1108,12 +1242,12 @@ { "event": { "block_number": "7", - "block_hash": "0x1270c2e1c5ef400e9002228de50d222f53b3d3a807ca1f00941fe067cea6a2ca", + "block_hash": "0x1b32c54fcc000000f5e7d1382384d0bf84613c4669c73a0132eee9b20833b1a0", "block_time": 1800001120, "event_id": "-9223372006790004735", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0xfb377b6060ef22c736c9c03587573fac7502691b6188a37ad2d3fb5b4dd065e7" + "transaction_hash": "0x8ea0b38e6a8f437d20b72115e05985c7f7d898b1eb050762b889029dd2c4d0fa" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" @@ -1123,18 +1257,18 @@ "erc20TraderOwnedDetail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xfae7fde391805bfe23cfa7670cd3a9bcb21cbc542ef0659f3a630bf9efb89f59", + "pool_id": "0x0a4ffce27d439f707aaee89bd3ef673e6f0f7fed91b8cc0155de093fde32f6b3", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "token0": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519ddb6580e97e08e639d601dc6e378285c3ef34000000000000000080000064", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x519ddb6580e97e08e639d601dc6e378285c3ef34" + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" }, "token": { - "address": "0x98730fb771aeb50970e91097b5819bde61a607ae", + "address": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", "name": "Zero Seed l4", "symbol": "L4", "decimals": 18 @@ -1153,7 +1287,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x094511cf93e73feca154da35a2404e8bde1111b4", + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -1174,16 +1308,16 @@ }, "created": { "block_number": "8", - "block_hash": "0x7c14221aec8026b38e8931e21203f70c86190936046f4637aab96c66a568ad26", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", "block_time": 1800001310, "event_id": "-9223372002494906354", "transaction_index": 2, "event_index": 13, - "transaction_hash": "0xb12debf1dec2c43a557a5758e6a2081d10f7b3e6dfc054ea6cc5437eb0d72687" + "transaction_hash": "0xf6161759835347763d4d5ca093b5956e6e5777f095a6502ee501e4b04c83f712" }, "as_of": { - "block_number": "15", - "block_hash": "0xcfce944e46417fb32963e5a464f5f01175d1c54cadaffdb3ef3bcee02e45cf36", + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", "block_time": 2115361000 }, "principal": { @@ -1222,5 +1356,421 @@ "no_bid": false, "price_tick": -13802777 } + }, + "maxNativeDetail": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + }, + "token": { + "address": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "name": "Zero Seed l5", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 3600, + "fee_end": 1800005000, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_time": 1800001310, + "event_id": "-9223372002494840810", + "transaction_index": 3, + "event_index": 21, + "transaction_hash": "0x64bbf42368abd232d30334ff37fcc33c510daee65a974356c5bd06af39773380" + }, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", + "tick_lower": 9210300, + "tick_upper": 13815500, + "liquidity": "1111119116089565312898036", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "997008130316076698400737225", + "quote_token": "2999999999999999984" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "transferable": true, + "launch_token": { + "accrued": "1189704288668331578335754", + "claimed": "1189704288668331578335754", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "184817928874495997630716", + "claimed": "184817928874495997630716", + "unclaimed": "0" + }, + "swap_count": 5, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371972430135287" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13810107 + } + }, + "maxNativeSwaps": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "next_cursor": null, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_time": 1800001400, + "event_id": "-9223371998199742454", + "transaction_index": 5, + "event_index": 9, + "transaction_hash": "0x94e9238f337010a600547c1e1c8ec90bba7e88f23252acfe13d2edd7ff12095f" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-54160", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "999083373736809197195735", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_time": 1800001400, + "event_id": "-9223371998199676915", + "transaction_index": 6, + "event_index": 12, + "transaction_hash": "0x74c42b57ecb80330b8e658f39a0fc4546a8483883e1ec3f5e5ea7f601baf138a" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "184817928874495997640704", + "fee_rate": "18446744073709551615", + "fee_amount": "184817928874495997630685", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_time": 1800002800, + "event_id": "-9223371985314971636", + "transaction_index": 3, + "event_index": 11, + "transaction_hash": "0x36221f8b1fa47934ff42b13ed5acac3363c4611df67ded71cb16b45c9237a5f8" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-809653862584492283386164", + "quote_token_delta": "1000000000000000000", + "fee_rate": "3470655575073783216", + "fee_amount": "187634420851270191404116", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_time": 1800005000, + "event_id": "-9223371972430266366", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x65a6c51604107c08957a1ad72d9cd0212440b4164ee0470839460c5cf3b80cbb" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "10000000000", + "quote_token_delta": "-10004", + "fee_rate": "55340232221128654", + "fee_amount": "31", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_time": 1800005000, + "event_id": "-9223371972430200827", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0xc5d46b191849b5e2c7d6d64359740c0ead89f8cabd8daa4ac9b31d5b0ad2e0e5" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-992511532670477737486119", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2986494080252189735903", + "fee_asset": "launch_token" + } + ] + }, + "maxNativeClaims": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "next_cursor": null, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "15", + "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_time": 1800005000, + "event_id": "-9223371972430135287", + "transaction_index": 2, + "event_index": 8, + "transaction_hash": "0x66ace5e090da8c20820023aed2c486d2c4ab255d552c703eb1ba957161b82ba5" + }, + "ledger_event_id": "-9223371972430135289", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "1189704288668331578335754", + "quote_token_amount": "184817928874495997630716" + } + ] + }, + "maxErc20Detail": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + }, + "token": { + "address": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "name": "Zero Seed l6", + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "trading_start": 1800001400, + "fee_duration": 1, + "fee_end": 1800001401, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_time": 1800001310, + "event_id": "-9223372002494775266", + "transaction_index": 4, + "event_index": 29, + "transaction_hash": "0x183728b1a3e44504363c9de57e8e3da1c44db6537a97828107e7b0657cecbd14" + }, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999000916626263180802490332", + "quote_token": "1000000000000010019" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "transferable": true, + "launch_token": { + "accrued": "999083373736809197454718", + "claimed": "999083373736809197454718", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "184817928874495997630685", + "claimed": "184817928874495997630685", + "unclaimed": "0" + }, + "swap_count": 2, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371985314906095" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13813701 + } + }, + "maxErc20Swaps": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "next_cursor": null, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_time": 1800001400, + "event_id": "-9223371998199611376", + "transaction_index": 7, + "event_index": 15, + "transaction_hash": "0xdbedd9e1daf7b972b455b95a75fed50b61b0414833d667aafa66af20b20b8731" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-54160", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "999083373736809197454718", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_time": 1800001400, + "event_id": "-9223371998199545836", + "transaction_index": 8, + "event_index": 19, + "transaction_hash": "0x5d6d7dcc03eb15a3951643f978a3d469689d61383459648dbdebdfdc6d14c2fa" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "184817928874495997640704", + "fee_rate": "18446744073709551615", + "fee_amount": "184817928874495997630685", + "fee_asset": "quote_token" + } + ] + }, + "maxErc20Claims": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "next_cursor": null, + "as_of": { + "block_number": "16", + "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "12", + "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_time": 1800002800, + "event_id": "-9223371985314906095", + "transaction_index": 4, + "event_index": 16, + "transaction_hash": "0x983e3ab51adc335d162a8801ee7973e29937ca95569c2bf16215bd7f391c0884" + }, + "ledger_event_id": "-9223371985314906098", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "999083373736809197454718", + "quote_token_amount": "184817928874495997630685" + } + ] } } diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 8c26a4e..7f832b7 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -20,11 +20,23 @@ test("the emitted-state golden responses parse with the v4 schemas", () => { golden.nativeDetail, golden.erc20Detail, golden.erc20TraderOwnedDetail, + golden.maxNativeDetail, + golden.maxErc20Detail, ]) LaunchDetail.parse(detail); - for (const swaps of [golden.nativeSwaps, golden.erc20Swaps]) + for (const swaps of [ + golden.nativeSwaps, + golden.erc20Swaps, + golden.maxNativeSwaps, + golden.maxErc20Swaps, + ]) SwapPage.parse(swaps); - for (const claims of [golden.nativeClaims, golden.erc20Claims]) + for (const claims of [ + golden.nativeClaims, + golden.erc20Claims, + golden.maxNativeClaims, + golden.maxErc20Claims, + ]) ClaimPage.parse(claims); for (const owners of [golden.nativeOwners, golden.erc20Owners]) OwnerPage.parse(owners); @@ -78,3 +90,74 @@ test("native token1 and ERC-20 token0 launches share one extension and split req expect(owners.data.at(-1)!.new_owner).toBe(detail.owner); } }); + +const MAX = "18446744073709551615"; +const FINAL = "55340232221128654"; + +test("launches created at the maximum rate 2^64 - 1 are served exactly, never as 2^64", () => { + const l5 = golden.maxNativeDetail; + const l6 = golden.maxErc20Detail; + // l5: native quote, max -> 0.3% over 3600 s; l6: ERC-20 quote, flat max + expect([l5.quote_token.kind, l5.launch_token_is_token1]).toEqual([ + "native", + true, + ]); + expect([l6.quote_token.kind, l6.launch_token_is_token1]).toEqual([ + "erc20", + false, + ]); + expect(l5.fee_schedule).toMatchObject({ + initial_fee: MAX, + final_fee: FINAL, + fee_duration: 3600, + phase: "final_fee", + fee_rate: FINAL, + }); + expect(l6.fee_schedule).toMatchObject({ + initial_fee: MAX, + final_fee: MAX, + fee_duration: 1, + phase: "final_fee", + fee_rate: MAX, + }); + expect( + golden.canonicalList.data + .filter((l) => l.fee_schedule.initial_fee === MAX) + .map((l) => l.pool_id), + ).toEqual([l6.pool_id, l5.pool_id]); + // l5 decays from the maximum; its swaps carry the contract's feeAt + expect(golden.maxNativeSwaps.data.map((s) => s.fee_rate)).toEqual([ + MAX, + MAX, + "3470655575073783216", + FINAL, + FINAL, + ]); + expect(golden.maxErc20Swaps.data.map((s) => s.fee_rate)).toEqual([MAX, MAX]); + for (const [detail, swaps, claims] of [ + [l5, golden.maxNativeSwaps, golden.maxNativeClaims], + [l6, golden.maxErc20Swaps, golden.maxErc20Claims], + ] as const) { + // exact input at the maximum nets floor(gross / 2^64); exact output + // pays calculated * 2^64 + const [exactIn, exactOut] = swaps.data; + const net = -BigInt(exactIn.launch_token_delta); + expect(net).toBe(54160n); + expect((net + BigInt(exactIn.fee_amount)) / 2n ** 64n).toBe(net); + expect(BigInt(exactOut.quote_token_delta)).toBe(10019n * 2n ** 64n); + expect( + BigInt(exactOut.quote_token_delta) - BigInt(exactOut.fee_amount), + ).toBe(10019n); + // every fee accrues once to its asset's creator ledger and is claimed + for (const asset of ["launch_token", "quote_token"] as const) { + const accrued = swaps.data + .filter((s) => s.fee_asset === asset) + .reduce((n, s) => n + BigInt(s.fee_amount), 0n); + expect(detail.fee_ledger[asset].accrued).toBe(accrued.toString()); + expect(detail.fee_ledger[asset].unclaimed).toBe("0"); + expect( + claims.data.reduce((n, c) => n + BigInt(c[`${asset}_amount`]), 0n), + ).toBe(accrued); + } + } +}); diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index c313f66..c44a346 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -177,7 +177,8 @@ const vectorsDir = resolve(import.meta.dir, "vectors"); // The protocol's independent vectors (250-digit Decimal generator, not this // code): every second 0..D of 12 curves, rates 5/50/75/90/99/max% and -// durations 600/3600 s, [initial, final, duration, elapsed, fee]. +// durations 600/3600 s, [initial, final, duration, elapsed, fee]. These are +// study rates, not limits: every uint64 rate is valid and max is 2^64 - 1. test("decayedFee reproduces all 25,212 independent all-second vectors and the manifest digests", () => { const rows = JSON.parse( gunzipSync( @@ -255,6 +256,113 @@ test("decayedFee reproduces all 25,212 independent all-second vectors and the ma ).toBe(r.fee); }); +const MAX = (1n << 64n) - 1n; +test("the whole uint64 fee range is served exactly; 2^64 - 1 is the maximum, not a cap below it", () => { + // l5 of the local fixture: max -> 0.3% over 3600 s, trading from 1800001400 + const l5 = { + start: 1800001400n, + duration: 3600n, + initial: MAX, + final: 55340232221128654n, + }; + const at = (indexedTime: bigint) => feeSchedule({ ...l5, indexedTime }); + expect(at(1800001399n)).toMatchObject({ + phase: "before_trading_start", + initial_fee: "18446744073709551615", + fee_rate: "18446744073709551615", + }); + expect(at(1800001400n).fee_rate).toBe("18446744073709551615"); + // the rate the contract applied to the fixture swap 1400 s in + expect(at(1800002800n)).toMatchObject({ + phase: "fee_decay", + fee_rate: "3470655575073783216", + }); + expect(at(1800005000n)).toMatchObject({ + phase: "final_fee", + fee_rate: "55340232221128654", + }); + // l6: flat at the maximum + expect( + feeSchedule({ + start: 1800001400n, + duration: 1n, + initial: MAX, + final: MAX, + indexedTime: 2115361000n, + }), + ).toMatchObject({ + initial_fee: MAX.toString(), + final_fee: MAX.toString(), + phase: "final_fee", + fee_rate: MAX.toString(), + }); + // rates above floor(2^64 * 99 / 100) are ordinary rates; zero is valid too + for (const [initial, final] of [ + [MAX, 0n], + [18262276632972456100n, 18262276632972456099n], + [0n, 0n], + ]) + expect( + feeSchedule({ ...l5, initial, final, indexedTime: 1800001400n }) + .initial_fee, + ).toBe(initial.toString()); +}); + +test("fee fields are canonical decimal uint64 strings; 2^64 and malformed encodings are never served", () => { + const schedule = LaunchDetail.shape.fee_schedule; + const ok = feeSchedule({ ...input, indexedTime: 1000n }); + expect(schedule.safeParse(ok).success).toBe(true); + for (const field of ["initial_fee", "final_fee", "fee_rate"] as const) { + expect(schedule.safeParse({ ...ok, [field]: MAX.toString() }).success).toBe( + true, + ); + for (const bad of [ + "18446744073709551616", + "36893488147419103231", + "100000000000000000000", + "018446744073709551615", + "01", + "-1", + "+1", + " 1", + "1 ", + "0x1", + "1e18", + "1.0", + "", + Number(MAX), + 1, + ]) + expect(schedule.safeParse({ ...ok, [field]: bad }).success).toBe(false); + } + const swap = SwapPage.shape.data.element; + const row = { + event: { + block_number: "1", + block_hash: `0x${"a".repeat(64)}`, + block_time: 1000, + event_id: "1", + transaction_index: 0, + event_index: 0, + transaction_hash: `0x${"b".repeat(64)}`, + }, + forwarding_locker: `0x${"c".repeat(40)}`, + direction: "buy", + launch_token_delta: "-54160", + quote_token_delta: "1000000000000000000", + fee_rate: MAX.toString(), + fee_amount: "999083373736809197195735", + fee_asset: "launch_token", + }; + expect(swap.safeParse(row).success).toBe(true); + for (const fee_rate of ["18446744073709551616", "01", "1e3", Number(MAX)]) + expect(swap.safeParse({ ...row, fee_rate }).success).toBe(false); + // fee amounts are token units, not rates: they may exceed 2^64 + expect( + swap.safeParse({ ...row, fee_amount: (1n << 100n).toString() }).success, + ).toBe(true); +}); + test("invalid indexed domains fail, never coerce or overflow", () => { for (const change of [ { start: -1n }, @@ -262,6 +370,8 @@ test("invalid indexed domains fail, never coerce or overflow", () => { { duration: 1n << 32n }, { final: input.initial + 1n }, { initial: 1n << 64n }, + { initial: 1n << 64n, final: 1n << 64n }, + { initial: 1n << 64n, final: MAX }, { indexedTime: -1n }, { start: (1n << 64n) - 1n }, { indexedTime: BigInt(Number.MAX_SAFE_INTEGER) + 1n }, diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 1d29af6..4960b28 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -1,10 +1,10 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; -// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 40af6cb +// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 550afc4 // build, as in the protocol manifest. export const ABI_SHA256 = - "999068f8e41867e972f4485c0105f584a2c10d7537615e6b4517807548fcd4bf"; + "efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e"; export const PERIPHERY_ABI_SHA256 = "df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305"; const NATIVE = "0x0000000000000000000000000000000000000000" as const; @@ -33,18 +33,18 @@ export interface LaunchQuote { } // The constructor-executed LOCAL instance of the indexer replay fixture -// (evm-contracts 40af6cb, anvil, no fork), serving native-ETH and ERC-20 +// (evm-contracts 550afc4, anvil, no fork), serving native-ETH and ERC-20 // quote launches. No live instance exists. Never enable it via a hosted // configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - address: "0x519ddb6580e97e08e639d601dc6e378285c3ef34", + address: "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b", runtime: - "0x32160cd19576b73812a6c557ca8f9697ac1c3cd821576afc70e9a9b00bf888be", - periphery: "0x094511cf93e73feca154da35a2404e8bde1111b4", + "0xb8c6d15f45c41e9dc43ac0e6ab8ea941dc833514cdb0b82f381c65f42f73a858", + periphery: "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", peripheryRuntime: - "0x91a98d2669389244ad8553d84f5c7e3c2947d5d4b5abb519be93d68142cce7c1", + "0x49988478ba70cc8d2587eb63141b333c7ef0a67c4639c3a2ee25de4c4597999e", core: "0x00000000000014aa86c5d3c41765bb24e11bd701", abiSha256: ABI_SHA256, peripheryAbiSha256: PERIPHERY_ABI_SHA256, diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index a3b7a92..0f24351 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -5,6 +5,17 @@ export const SCHEMA_VERSION = "zero-seed-launch-api/v4" as const; export const SchemaVersion = z.literal(SCHEMA_VERSION); const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); +// A Q0.64 fee rate: every uint64, 0 to 2^64 - 1. The fee fraction is +// value / 2^64, so the maximum is just under 100%, never exactly 100%. +const UINT64_MAX = (1n << 64n) - 1n; +const Q64 = U.max(20) + .refine( + (value) => /^(0|[1-9][0-9]*)$/.test(value) && BigInt(value) <= UINT64_MAX, + ) + .openapi({ + description: + "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%", + }); const Address = z.string().regex(/^0x[0-9a-f]{40}$/); const Bytes32 = z.string().regex(/^0x[0-9a-f]{64}$/); const Seconds = z.number().int().nonnegative(); @@ -41,15 +52,15 @@ export const FeeSchedule = z.strictObject({ trading_start: Seconds, fee_duration: z.number().int().positive(), fee_end: Seconds, - initial_fee: U, - final_fee: U, + initial_fee: Q64, + final_fee: Q64, // 2^(-t/600) from a fixed Q128 table, rescaled to reach final_fee at fee_end. decay: z.strictObject({ kind: z.literal("fixed_base_q128"), half_life_seconds: z.literal(600), }), phase: z.enum(["before_trading_start", "fee_decay", "final_fee"]), - fee_rate: U, + fee_rate: Q64, evaluated_at: Seconds, }); // Raw launch-token units. requested is LaunchParameters.supply and the @@ -125,7 +136,7 @@ export const Swap = z.strictObject({ direction: z.enum(["buy", "sell", "zero_fill"]), launch_token_delta: I, quote_token_delta: I, - fee_rate: U, + fee_rate: Q64, fee_amount: U, fee_asset: z.enum(["launch_token", "quote_token"]), }); From 620aa1154e276b829865204ba54bfc54f6c95537 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Thu, 22 Oct 2026 02:26:09 -0400 Subject: [PATCH 12/19] Serve zero-seed launches of evm-contracts 43d6628: exact token0 installation evm-contracts 43d6628 installs the exact largest token0 liquidity the requested supply pays for; the shared helper it used before floored lower * upper / 2^128 first and installed less at low prices. The API serves the indexed liquidity and Core's position delta and never recomputes the installation, so the response contract stays v4. A sink of "0" is now possible when the requested supply is exactly installable. - Re-pin the local instance (extension, periphery and their runtimes) and the emitted-state replay to indexer 91a7036 (fixture sha256 ec308953...e7d9), which adds two ERC-20 token0 launches at low-price geometry: 1e30 over ticks -88080384..-85983232 at spacing 524288 (liquidity 115073586432, sink 8589236567713153558) and an int128-scale supply over the full width at spacing 1 with no sink. - The replay checks both against the chain after every block, their exact served supply and liquidity, list order, creator/owner filters and quote screening; regenerate the golden responses with both and test them. --- scripts/launchReplayCases.txt | 28 +- scripts/verifyLaunchReplay.ts | 4 +- src/routes/launches/README.md | 20 +- .../zero-seed-launch-api-v4.golden.json | 812 +++++++++++++----- src/routes/launches/golden.test.ts | 43 + src/routes/launches/policy.ts | 12 +- 6 files changed, 664 insertions(+), 255 deletions(-) diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index 5965e78..9a7d0ab 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -53,7 +53,8 @@ }); expect([body.supply.requested, body.supply.sink]).toEqual([chain.totalSupply, chain.sinkBalance]); expect([body.supply.installed, body.supply.installed]).toEqual([install.coreInstalledDelta, install.installation.installedSupply]); - expect(BigInt(body.supply.sink) > 0n).toBe(true); + // every launch sinks a remainder except l8, whose int128-scale supply installs exactly + expect(BigInt(body.supply.sink) > 0n).toBe(label !== "l8"); expect(body.principal).not.toHaveProperty("installed_supply"); expect(body.principal.liquidity).toBe(install.liquidity); expect(body.principal.liquidity).toBe(install.coreLiquidityDelta); @@ -130,20 +131,32 @@ }); const canonicalList = await get(`/launches?chainId=${CHAIN_ID}`); expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual( - ["l6", "l5", "l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); + ["l8", "l7", "l6", "l5", "l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); expect(canonicalList.next_cursor).toBeNull(); + // Low-price token0 launches serve the exact installation the chain recorded: l7 sinks the + // remainder of the largest affordable liquidity, l8 installs its whole supply. + const l7 = await get(path("l7")); + const l8 = await get(path("l8")); + expect([l7.supply, l7.principal.liquidity, l7.launch_token_is_token1, l7.quote_token.kind]).toEqual([ + { requested: "1000000000000000000000000000000", installed: "999999999991410763432286846442", + sink: "8589236567713153558", sink_holder: "0x0000000000000000000000000000000000000000" }, + "115073586432", false, "erc20"]); + expect([l8.supply, l8.principal.liquidity, l8.launch_token_is_token1]).toEqual([ + { requested: "170141183460469231694794709727778658247", installed: "170141183460469231694794709727778658247", + sink: "0", sink_holder: "0x0000000000000000000000000000000000000000" }, + "9223595582101085260", false]); for (const suffix of ["", "/swaps", "/claims", "/owners"]) await get(`${path("l3")}${suffix}`, 404); // creator (owner at creation) and current owner filters expect((await get(`/launches?chainId=${CHAIN_ID}&creator=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( - ["l5", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); + ["l7", "l5", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${feeRecipient}`)).data.map((l: any) => l.pool_id)).toEqual( [fixture.launches.l1!.poolId]); expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${BigInt(trader).toString()}`)).data.map((l: any) => l.pool_id)).toEqual( - ["l6", "l4"].map((l) => fixture.launches[l]!.poolId)); - // the creator still owns only l5 (l1 and l2 were transferred) + ["l8", "l6", "l4"].map((l) => fixture.launches[l]!.poolId)); + // the creator still owns only l7 and l5 (l1 and l2 were transferred) expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l5!.poolId]); + ["l7", "l5"].map((l) => fixture.launches[l]!.poolId)); const owners = await get(`${path("l1")}/owners`); expect(owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ [null, lower(creator)], [lower(creator), lower(newOwner)], [lower(newOwner), lower(feeRecipient)]]); @@ -249,7 +262,7 @@ const page = await get(`/launches?chainId=${CHAIN_ID}&limit=2`); expect(page.data.map((l: any) => l.pool_id)).toEqual(["l5", "l1"].map((l) => fixture.launches[l]!.poolId)); expect(page.next_cursor).toBeNull(); - for (const label of ["l2", "l4", "l6"]) + for (const label of ["l2", "l4", "l6", "l7", "l8"]) for (const suffix of ["", "/swaps", "/claims", "/owners"]) { const body = await get(`${path(label)}${suffix}`, 404); expect(body).toEqual({ status: 404, error: "Launch not found" }); @@ -279,6 +292,7 @@ erc20Detail: l2, erc20Swaps: l2Swaps, erc20Claims: l2Claims, erc20Owners: l2Owners, erc20TraderOwnedDetail: l4, maxNativeDetail: l5, maxNativeSwaps: l5Swaps, maxNativeClaims: l5Claims, maxErc20Detail: l6, maxErc20Swaps: l6Swaps, maxErc20Claims: l6Claims, + lowPriceErc20Detail: l7, fullWidthErc20Detail: l8, }, null, 2)); } const before = await get(path("l1")); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index a54a342..d7ee664 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -16,7 +16,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "c78a48e3aff1df7a8e3298545eee8be04474063d" + head.stdout.toString().trim() !== "91a70364ecb06a2aef107261aee323f65890c0f5" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -37,7 +37,7 @@ const fixturePath = resolve( const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); if ( new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "09d7745740af507a02304e26c0e20d6f27b1047fd707c056ef4d936c67a74fcc" + "ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9" ) throw new Error("Emitted fixture pin mismatch"); const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 79a9804..bca5fb5 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -2,7 +2,7 @@ Draft response contract: `zero-seed-launch-api/v4`, for the forward-only `ZeroSeedLaunch` and its separate `ZeroSeedLaunchPeriphery` of evm-contracts -`550afc40280423128c02eb5ffd213aa959b34e2a` (ABI sha256 of `jq -cS .abi`: +`43d6628841b3ce91dafa048b7ceef64bed55fc8e` (ABI sha256 of `jq -cS .abi`: ZeroSeedLaunch `efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e`, periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). Indexer schema `zero-seed-launch-index/v3` (migration 00139). Live instance @@ -38,6 +38,13 @@ is no ceiling below 2^64 - 1 and no `FeeTooHigh` error. The API refuses (500) an indexed fee above 2^64 - 1 instead of wrapping, truncating or serving it. The response shape is unchanged, so the contract stays v4. +At `43d6628` the extension installs the exact largest token0 liquidity the +requested supply pays for (an ERC-20 quote above the launch token), where it +previously installed less at low prices and sank the difference. The API +serves the indexed liquidity and Core's position delta and never recomputes +the installation, so nothing here changes; `sink` can now be `"0"` when the +requested supply is exactly installable (no mint to address 0 happens). + Unchanged from v3: the fixed-base fee table (`fee_schedule.decay` is `{ kind: "fixed_base_q128", half_life_seconds: 600 }` and `fee_rate` the contract's `feeAt` at the indexed block time, checked in `policy.test.ts` @@ -53,7 +60,12 @@ a native-ETH (launch token1, sink 578) and two ERC-20 (launch token0, sink 790) launches on one extension, ownership transfers, claims, swaps and a reorg, plus two launches created at the maximum rate: `maxNativeDetail` (native, 2^64 - 1 decaying to 0.3% over 3600 s) and `maxErc20Detail` (ERC-20, flat at -2^64 - 1), with their exact-input, exact-output and decayed swaps and claims. +2^64 - 1), with their exact-input, exact-output and decayed swaps and claims, +and two ERC-20 launches at low-price token0 geometry: `lowPriceErc20Detail` +(1e30 over ticks -88080384..-85983232 at spacing 524288; liquidity +115073586432, sink 8589236567713153558) and `fullWidthErc20Detail` (an +int128-scale supply over the full width at spacing 1 that installs all of it, +sink `"0"`). ## Requests @@ -124,9 +136,9 @@ regenerates `openapi.json`. For actual emitted-state/Postgres/handler replay, provide the exact pinned indexer checkout (EkuboProtocol/indexer -`c78a48e3aff1df7a8e3298545eee8be04474063d`, fixture +`91a70364ecb06a2aef107261aee323f65890c0f5`, fixture `tests/fixtures/zero-seed-launch/local-chain.json` sha256 -`09d7745740af507a02304e26c0e20d6f27b1047fd707c056ef4d936c67a74fcc`), a +`ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9`), a disposable loopback Postgres 18 and a scratch directory: ```sh diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json index 609fbd9..7143023 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json @@ -10,18 +10,18 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xf03731816bad65fe90b668df070664d89a72452ac47622c34228b51fcefa97b6", + "pool_id": "0x4b680c35b2badd8287a7bd61a44c5105c047e06496b6aa83cc95255893c7f5b3", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x9417527c774454cb137497120651a1396cb46e39", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0x7dba39967ec66d5ab5d69322dd4a0185fce5616d", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x9417527c774454cb137497120651a1396cb46e39", + "address": "0x7dba39967ec66d5ab5d69322dd4a0185fce5616d", "name": "Zero Seed l3", "symbol": "L3", "decimals": 18 @@ -40,7 +40,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -61,16 +61,16 @@ }, "created": { "block_number": "8", - "block_hash": "0xa34547d55cdf18b5d6cd1017f08159bbf4ace82afd4348e3a1905129d603b60a", + "block_hash": "0xbe7197377e734384f7b383e0d9b1368626e44974650a34baea70919defbae284", "block_time": 1800001300, "event_id": "-9223372002494906358", "transaction_index": 2, "event_index": 9, - "transaction_hash": "0x154cf448eada43524c2f317ad1f3e0e505d5e99ced13aaf91e3d0e52cb21eb7b" + "transaction_hash": "0x46b30a084c978e5760a9afdd6a47a93c2c83018d3c177dcdb88daeb191902910" }, "as_of": { "block_number": "9", - "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", + "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", "block_time": 1800001360 } } @@ -78,7 +78,7 @@ "next_cursor": "-9223372002494906358", "as_of": { "block_number": "9", - "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", + "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", "block_time": 1800001360 } }, @@ -88,18 +88,18 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -118,7 +118,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "ask_tick": -13815500, @@ -139,34 +139,34 @@ }, "created": { "block_number": "3", - "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", + "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" + "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" }, "as_of": { "block_number": "9", - "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", + "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", "block_time": 1800001360 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -185,7 +185,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -206,16 +206,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", + "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" + "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" }, "as_of": { "block_number": "9", - "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", + "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", "block_time": 1800001360 } } @@ -223,7 +223,7 @@ "next_cursor": null, "as_of": { "block_number": "9", - "block_hash": "0x53bbaf6dad79712ccd58286076842d50bbeb8f69a57f5f937cb898aba09cbb12", + "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", "block_time": 1800001360 } }, @@ -233,18 +233,152 @@ { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "token0": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000001", + "fee": "0", + "tick_spacing": 1, + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" + }, + "token": { + "address": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "170141183460469231694794709727778658247", + "installed": "170141183460469231694794709727778658247", + "sink": "0", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -88722835, + "upper_tick": 88722835, + "fee_schedule": { + "trading_start": 1800005200, + "fee_duration": 3600, + "fee_end": 1800008800, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", + "block_time": 1800005100, + "event_id": "-9223371968135233522", + "transaction_index": 1, + "event_index": 13, + "transaction_hash": "0xd7641c80752a761d38a809de9b96f57c3e55e14e611a63f3820002394d1c1b6a" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080080000", + "fee": "0", + "tick_spacing": 524288, + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" + }, + "token": { + "address": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -88080384, + "upper_tick": -85983232, + "fee_schedule": { + "trading_start": 1800005200, + "fee_duration": 3600, + "fee_end": 1800008800, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", + "block_time": 1800005100, + "event_id": "-9223371968135299065", + "transaction_index": 0, + "event_index": 6, + "transaction_hash": "0x4d42f78bdb07af1928ff236c63ba1c30d30f26a57f57f0faa75b0c5a26b0c456" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x4c955b66e40af689e262687acc83b95067e4233f", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "address": "0x4c955b66e40af689e262687acc83b95067e4233f", "name": "Zero Seed l6", "symbol": "L6", "decimals": 18 @@ -263,7 +397,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -284,34 +418,34 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494775266", "transaction_index": 4, "event_index": 29, - "transaction_hash": "0x183728b1a3e44504363c9de57e8e3da1c44db6537a97828107e7b0657cecbd14" + "transaction_hash": "0x3d8f9644ad6ca9f2e28ecf0f767897c5f301f6a70b59ca64b4fdff8a1c59c3c5" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0xf69b28f6d9353d0ba18323fac2879eec12803414", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "address": "0xf69b28f6d9353d0ba18323fac2879eec12803414", "name": "Zero Seed l5", "symbol": "L5", "decimals": 18 @@ -330,7 +464,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "ask_tick": -13815500, @@ -351,34 +485,34 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494840810", "transaction_index": 3, "event_index": 21, - "transaction_hash": "0x64bbf42368abd232d30334ff37fcc33c510daee65a974356c5bd06af39773380" + "transaction_hash": "0x643a2714881f72d7158b0912c7cd24879b85683c0bc17841c723f2866385d0f8" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x0a4ffce27d439f707aaee89bd3ef673e6f0f7fed91b8cc0155de093fde32f6b3", + "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", + "token0": "0x15c551f561ac16b237e0fd3304b02998d968662c", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", + "address": "0x15c551f561ac16b237e0fd3304b02998d968662c", "name": "Zero Seed l4", "symbol": "L4", "decimals": 18 @@ -397,7 +531,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -418,34 +552,34 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494906354", "transaction_index": 2, "event_index": 13, - "transaction_hash": "0xf6161759835347763d4d5ca093b5956e6e5777f095a6502ee501e4b04c83f712" + "transaction_hash": "0x1c1833a17444efbe77b5a25bc119f47b12074cbfd83cd2d3ea0d78af1ef48e90" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -464,7 +598,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -485,34 +619,34 @@ }, "created": { "block_number": "3", - "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", + "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" + "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } }, { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -531,7 +665,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -552,42 +686,42 @@ }, "created": { "block_number": "2", - "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", + "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" + "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } } ], "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 } }, "nativeDetail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xf4cbadf1173d5e80e826a42dfc83fd56a6c6d7d1", + "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", "name": "Zero Seed l1", "symbol": "L1", "decimals": 18 @@ -606,7 +740,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "ask_tick": -13815500, @@ -627,16 +761,16 @@ }, "created": { "block_number": "2", - "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", + "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", "block_time": 1800000010, "event_id": "-9223372028264710136", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" + "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "principal": { @@ -668,7 +802,7 @@ "swap_count": 10, "claim_count": 4, "owner_change_count": 3, - "last_event_id": "-9223371968135167992" + "last_event_id": "-9223371959545233400" }, "inventory": { "trading_open": true, @@ -679,23 +813,23 @@ "nativeSwaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "5", - "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", + "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", "block_time": 1800001000, "event_id": "-9223372015379939327", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0x9b60bd3704ec2424597faae581e3068b4cb8faf171d716843acf3451f9f60699" + "transaction_hash": "0x7101a2ad1381043e769e388ac9ee21d094d4dbe8c93c84d38bf072d108029e23" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "zero_fill", @@ -708,12 +842,12 @@ { "event": { "block_number": "5", - "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", + "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", "block_time": 1800001000, "event_id": "-9223372015379873789", "transaction_index": 1, "event_index": 2, - "transaction_hash": "0xcba53ebcd987cc8ddd85345d300532606e053df74c935f35699e896f5087b85e" + "transaction_hash": "0xa14ca4de32c2672ab646c4d823ce141e9657299870ae99fce26e2467d0272dd5" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -726,12 +860,12 @@ { "event": { "block_number": "6", - "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", + "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", "block_time": 1800001060, "event_id": "-9223372011084972030", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xf6c61efadcd163f35b2de1be77f5be89a8e2a7aed5be35ebbf68bd41aecf8950" + "transaction_hash": "0x720e06e34823d2a562377aa6a0a16a8de28b5cb15d4d61aa1cf4a2f8d9a353b2" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -744,12 +878,12 @@ { "event": { "block_number": "6", - "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", + "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", "block_time": 1800001060, "event_id": "-9223372011084906491", "transaction_index": 1, "event_index": 4, - "transaction_hash": "0xa0f80fe18b87ea86598786d6ee43e1303090b049a0c8231b3b0163eb52ed4e62" + "transaction_hash": "0xdf37d43acac819505ea91f11619c0f7e9e5d84eb1cccc384398e0cce7d50359d" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -762,12 +896,12 @@ { "event": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002495037438", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x4aa5aa59453fc2f6c03c1f5703e8cad2b51fbdf1c6ddd694f319868f096f9dbc" + "transaction_hash": "0x6b98b5033142e35944c23543c97bbeb9b4879a3a075557b3f903b289e13476ce" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -780,12 +914,12 @@ { "event": { "block_number": "11", - "block_hash": "0x337614e1d72935cec1f18d71ccc21a8f34c3d8eb8a4c8f347c0bd945c178c5eb", + "block_hash": "0x6ea9295face4622cbdf33529eae79521a13eefdf71888967138a7a639db30643", "block_time": 1800001756, "event_id": "-9223371989610135550", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x7f5d3dede97bd4c4e8708e1e335164c5e2b3b917a94ce717ee0d393e01fa6899" + "transaction_hash": "0x19401fd497b838f5503198c57f20c5422b2ea245a8dcb5e3c48f83cd66e1c7c4" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -798,12 +932,12 @@ { "event": { "block_number": "12", - "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", "block_time": 1800002800, "event_id": "-9223371985315168254", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xc26dd317b4e548ec29284a5727a4d7324470c001cb76bf6cc29dbe4936bda49f" + "transaction_hash": "0xe76a36ccc6d284264f86273a7514e8d8b9d7d79ed9525556ddae58bdce198e74" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -816,12 +950,12 @@ { "event": { "block_number": "13", - "block_hash": "0xb6f8390364d32a5ada052833408e1d6667194ce8787d927bbed408364e1ecf8d", + "block_hash": "0x0fc5cfd47afcb3edb9cdd04bccd5769cf75ccca7139bd9d19c15dff629fd9038", "block_time": 1800004599, "event_id": "-9223371981020200958", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x8dbc15287d2b9d87305a5b7e4c9a2c64e4f9a43c2f694b4d104dacf2b3810de1" + "transaction_hash": "0xe5a32b0292d8ae36aedbf269823ddad51a5a33e27a5633551e9956066a5dd375" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -834,12 +968,12 @@ { "event": { "block_number": "14", - "block_hash": "0xb9cff80895c3c2a933f0553a8bd5b6f53450163c53d94dcd23060f2ef5d9e8a5", + "block_hash": "0x421411fb521b7492f6b6c2f9f4e33ad574b3ab9a15667650b47e934599e6ba70", "block_time": 1800004600, "event_id": "-9223371976725233662", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xd042c39b6f458c6be0858843678b6d3e571bf52d93702103e1f13df7ab5af790" + "transaction_hash": "0x0a72fdbffef404ac2c7b84ff25e82a7a29e01767c42eff6dfbf03d4375b5f0ad" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -851,13 +985,13 @@ }, { "event": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000, - "event_id": "-9223371968135299070", + "event_id": "-9223371959545364478", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0xd1d7ffbdbbc5f5d36c671d1d0c0f4ef1e5811351abb9c37d613755f422ec1a23" + "transaction_hash": "0x8e947f7865f3615194b0a725a8331516296870c2e4ba77682995c780a6c012f8" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -872,23 +1006,23 @@ "nativeClaims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "6", - "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", + "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", "block_time": 1800001060, "event_id": "-9223372011084775411", "transaction_index": 3, "event_index": 12, - "transaction_hash": "0x26a54a1867ab551a062c62bff50eb6a18e1caafa0f0adb8352f8877b843257ae" + "transaction_hash": "0xde312b3f9fad6f43239d2c82f024b2b46a7f815b7e50d93107fa577afba2a3f3" }, "ledger_event_id": "-9223372011084775413", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", @@ -899,12 +1033,12 @@ { "event": { "block_number": "12", - "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", "block_time": 1800002800, "event_id": "-9223371985315037174", "transaction_index": 2, "event_index": 9, - "transaction_hash": "0x050d4bcf62c12351ac16c66ab7b839f048b4c913f8cf05a9d4ec3ab4221fd3e2" + "transaction_hash": "0x04231beabae417154fd0367d2ba8d2bc15e55a4f76d363bb118788ec79a740a5" }, "ledger_event_id": "-9223371985315037176", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", @@ -914,15 +1048,15 @@ }, { "event": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000, - "event_id": "-9223371968135233530", + "event_id": "-9223371959545298938", "transaction_index": 1, "event_index": 5, - "transaction_hash": "0xc36d9d6bc2ec2d0e0d0e1e203c529c01a2839cbeb356cc931f7ceadd956242c5" + "transaction_hash": "0x8035e454bddab3d597bd744221fe8da62e7e42b4510908f8497431433b6588e8" }, - "ledger_event_id": "-9223371968135233532", + "ledger_event_id": "-9223371959545298940", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "launch_token_amount": "5849077212047475495852", @@ -930,15 +1064,15 @@ }, { "event": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000, - "event_id": "-9223371968135167992", + "event_id": "-9223371959545233400", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xa6d80d862539cbcd6d28348c674ad575d83b9da3a73dcd1aa46be86d1d8bf247" + "transaction_hash": "0xa812559a27a75f1acfcd2b44c8bcb69b023e817631a2ebea0b4b4fb84fd8d9c9" }, - "ledger_event_id": "-9223371968135167993", + "ledger_event_id": "-9223371959545233401", "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "launch_token_amount": "0", @@ -949,23 +1083,23 @@ "nativeOwners": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x131336bd4d4640da92257f5592dc397c0b5ef043ce9c931b85db7f20a05e793b", + "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "2", - "block_hash": "0x1a8ab4b053e2145ed8be760d9cbf82e94f7adbb9637649073d3c74bb7ae35bc1", + "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", "block_time": 1800000010, "event_id": "-9223372028264710135", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x4487e737dd2908e11d356a3a2b22bda10de0256118dee4deac7134d8d74276f1" + "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -973,12 +1107,12 @@ { "event": { "block_number": "10", - "block_hash": "0x07601d853abadc6ece2e5a9c4dd0bdfbdba5d9b4161df116c63abbabc1810a6f", + "block_hash": "0x8ab8e6f901763a260512c10e8a3a39516cbfcfa57ef761c750b168308ca24a84", "block_time": 1800001700, "event_id": "-9223371993904971767", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x1b9be8d1b1f03634f57be21492b10be90846b178218f1bdff679c86d63a79a6e" + "transaction_hash": "0x44cbd6d884fb3b0c0938c1796adcb3e909a23d5698975dd48200c203bda2f799" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" @@ -986,12 +1120,12 @@ { "event": { "block_number": "11", - "block_hash": "0x337614e1d72935cec1f18d71ccc21a8f34c3d8eb8a4c8f347c0bd945c178c5eb", + "block_hash": "0x6ea9295face4622cbdf33529eae79521a13eefdf71888967138a7a639db30643", "block_time": 1800001756, "event_id": "-9223371989610070012", "transaction_index": 1, "event_index": 3, - "transaction_hash": "0xb27bb2f30760241ff30b5b65985a4017344c635b5dc1a47cc166c7f7a5763a0e" + "transaction_hash": "0x06896be8fb3348efa4c8fab6fe02e1ebe9abf60f232b0fca2b12a3a09de1d5bf" }, "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" @@ -1001,18 +1135,18 @@ "erc20Detail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x1b381d0316bb19d36bf3e7c2482737ab8c211be9", + "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", "name": "Zero Seed l2", "symbol": "L2", "decimals": 18 @@ -1031,7 +1165,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", "ask_tick": -13815500, @@ -1052,16 +1186,16 @@ }, "created": { "block_number": "3", - "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", + "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", "block_time": 1800000020, "event_id": "-9223372023969873913", "transaction_index": 0, "event_index": 6, - "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" + "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "principal": { @@ -1104,23 +1238,23 @@ "erc20Swaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "5", - "block_hash": "0x8d42cab5d9761d793843f65144fd4aa7bc278d987b043f8880c24251dcd484a7", + "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", "block_time": 1800001000, "event_id": "-9223372015379808250", "transaction_index": 2, "event_index": 5, - "transaction_hash": "0xd3f40de861118a466ce507a1e67d2578def0aab8b6d990d2cb0b053398d84424" + "transaction_hash": "0xbb3aca3565c12f4da3bc017b3d214c3f7cb6e96f30821b339ddf212447e32120" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1133,12 +1267,12 @@ { "event": { "block_number": "6", - "block_hash": "0xda9ea329b1ae6a9acc463b106424e2956348b9eec00c943256b6cd53fb5844d7", + "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", "block_time": 1800001060, "event_id": "-9223372011084840952", "transaction_index": 2, "event_index": 7, - "transaction_hash": "0xa9bcd109b1eb193f56b3caf3ca901f3a77e6ad16a02d02b87dc1db9768e02354" + "transaction_hash": "0x1cfc924a5fb6f41ec7d2f434ad3047f6f90c0ef3dba9387d8fdbfd9df23ad3ff" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1151,12 +1285,12 @@ { "event": { "block_number": "7", - "block_hash": "0x1b32c54fcc000000f5e7d1382384d0bf84613c4669c73a0132eee9b20833b1a0", + "block_hash": "0xff33c4024127fbdc44d4efa422b1338004300bd9ec2bcc192559f8e841c95abf", "block_time": 1800001120, "event_id": "-9223372006789939197", "transaction_index": 1, "event_index": 2, - "transaction_hash": "0xbe8d49418840c8f29e979b057ad3b2d52b4a58f4fd0a318b998cf28fb9b73793" + "transaction_hash": "0x8a15e755864852c0b63f4d0f2e59654806784d006e1dd03d936cf194d264dfbc" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -1169,12 +1303,12 @@ { "event": { "block_number": "10", - "block_hash": "0x07601d853abadc6ece2e5a9c4dd0bdfbdba5d9b4161df116c63abbabc1810a6f", + "block_hash": "0x8ab8e6f901763a260512c10e8a3a39516cbfcfa57ef761c750b168308ca24a84", "block_time": 1800001700, "event_id": "-9223371993905037306", "transaction_index": 1, "event_index": 5, - "transaction_hash": "0x0df04a504f49df9320a2fe056382f8e7d61d09724faf975121183399f3b016d8" + "transaction_hash": "0x62ab75a1f1b6314c79e488b2c68edf69bde7bd2809167bc757789c77d0232203" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1189,23 +1323,23 @@ "erc20Claims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494971897", "transaction_index": 1, "event_index": 6, - "transaction_hash": "0xc9932fbea79758476c6438b5007e2a96dceecd2fb2f3e12a2eceaabae84b1535" + "transaction_hash": "0x75d5bd663c7deb7aa1ae4a89ec006586b2655a5bb98b899b5f9fe63a047edd39" }, "ledger_event_id": "-9223372002494971900", "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", @@ -1218,23 +1352,23 @@ "erc20Owners": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28597e873304c98eb0af47f51451f78729fdc6b4d80e27e475ac1299797f30aa", + "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "3", - "block_hash": "0xb5f9178ecc38a3c552046f2b80e31c97ede2d7ff3ce23ff205a5412995eff43e", + "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", "block_time": 1800000020, "event_id": "-9223372023969873912", "transaction_index": 0, "event_index": 7, - "transaction_hash": "0xe4e9265446f36310c0345b028d33bf7d501eb81b1972134057a3b0a9ef15d725" + "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" }, "previous_owner": null, "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" @@ -1242,12 +1376,12 @@ { "event": { "block_number": "7", - "block_hash": "0x1b32c54fcc000000f5e7d1382384d0bf84613c4669c73a0132eee9b20833b1a0", + "block_hash": "0xff33c4024127fbdc44d4efa422b1338004300bd9ec2bcc192559f8e841c95abf", "block_time": 1800001120, "event_id": "-9223372006790004735", "transaction_index": 0, "event_index": 0, - "transaction_hash": "0x8ea0b38e6a8f437d20b72115e05985c7f7d898b1eb050762b889029dd2c4d0fa" + "transaction_hash": "0x36028e6bf502a2ccc0a0a5a10351189682a3f16cb0b4d7204308f7e45f3757f8" }, "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" @@ -1257,18 +1391,18 @@ "erc20TraderOwnedDetail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x0a4ffce27d439f707aaee89bd3ef673e6f0f7fed91b8cc0155de093fde32f6b3", + "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", + "token0": "0x15c551f561ac16b237e0fd3304b02998d968662c", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xd69e1589aa5f8e9a7eb66d1dbeb2cbd761c7c51d", + "address": "0x15c551f561ac16b237e0fd3304b02998d968662c", "name": "Zero Seed l4", "symbol": "L4", "decimals": 18 @@ -1287,7 +1421,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -1308,16 +1442,16 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494906354", "transaction_index": 2, "event_index": 13, - "transaction_hash": "0xf6161759835347763d4d5ca093b5956e6e5777f095a6502ee501e4b04c83f712" + "transaction_hash": "0x1c1833a17444efbe77b5a25bc119f47b12074cbfd83cd2d3ea0d78af1ef48e90" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "principal": { @@ -1360,18 +1494,18 @@ "maxNativeDetail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "token1": "0xf69b28f6d9353d0ba18323fac2879eec12803414", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0x5db261b7a2a3405df25bf0258516cd66d2c5b5f4", + "address": "0xf69b28f6d9353d0ba18323fac2879eec12803414", "name": "Zero Seed l5", "symbol": "L5", "decimals": 18 @@ -1390,7 +1524,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": true, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "ask_tick": -13815500, @@ -1411,16 +1545,16 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494840810", "transaction_index": 3, "event_index": 21, - "transaction_hash": "0x64bbf42368abd232d30334ff37fcc33c510daee65a974356c5bd06af39773380" + "transaction_hash": "0x643a2714881f72d7158b0912c7cd24879b85683c0bc17841c723f2866385d0f8" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "principal": { @@ -1463,23 +1597,23 @@ "maxNativeSwaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "9", - "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", "block_time": 1800001400, "event_id": "-9223371998199742454", "transaction_index": 5, "event_index": 9, - "transaction_hash": "0x94e9238f337010a600547c1e1c8ec90bba7e88f23252acfe13d2edd7ff12095f" + "transaction_hash": "0x8690243222241437f36d68d2d865f9d5061b360d4c3af753bb0a7af0b4bb07e9" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1492,12 +1626,12 @@ { "event": { "block_number": "9", - "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", "block_time": 1800001400, "event_id": "-9223371998199676915", "transaction_index": 6, "event_index": 12, - "transaction_hash": "0x74c42b57ecb80330b8e658f39a0fc4546a8483883e1ec3f5e5ea7f601baf138a" + "transaction_hash": "0x40cef279c3f5f1666d679d795f7a8f6b084232ed79eb73c45d1a7f9e405ac76a" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1510,12 +1644,12 @@ { "event": { "block_number": "12", - "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", "block_time": 1800002800, "event_id": "-9223371985314971636", "transaction_index": 3, "event_index": 11, - "transaction_hash": "0x36221f8b1fa47934ff42b13ed5acac3363c4611df67ded71cb16b45c9237a5f8" + "transaction_hash": "0xc6e5e1b2ea18a8d937090b4014607429ce989f394942831d05882813b796fed3" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1528,12 +1662,12 @@ { "event": { "block_number": "15", - "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", "block_time": 1800005000, "event_id": "-9223371972430266366", "transaction_index": 0, "event_index": 1, - "transaction_hash": "0x65a6c51604107c08957a1ad72d9cd0212440b4164ee0470839460c5cf3b80cbb" + "transaction_hash": "0xe4311d28091142c8375515c91e761ab90b1fa35e43dbc8acc4b33b56dd4f277d" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "sell", @@ -1546,12 +1680,12 @@ { "event": { "block_number": "15", - "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", "block_time": 1800005000, "event_id": "-9223371972430200827", "transaction_index": 1, "event_index": 4, - "transaction_hash": "0xc5d46b191849b5e2c7d6d64359740c0ead89f8cabd8daa4ac9b31d5b0ad2e0e5" + "transaction_hash": "0x71a097485a947e72963b3169d74c554f30d88c53432ed63915a202d3ca048dd7" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1566,23 +1700,23 @@ "maxNativeClaims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0x28d10d8cd13e6244f1d9cec40bf4698d89503f4810549e622de36a06ceaa127c", + "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "15", - "block_hash": "0x57f2004337e161620c18bc3f241245521153ff06a3873490ea9be08898daac41", + "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", "block_time": 1800005000, "event_id": "-9223371972430135287", "transaction_index": 2, "event_index": 8, - "transaction_hash": "0x66ace5e090da8c20820023aed2c486d2c4ab255d552c703eb1ba957161b82ba5" + "transaction_hash": "0xbc018ffb338fb8c9a8b26eea8dc3c2e465d7e7812def80586cdec74b5f9ecd1d" }, "ledger_event_id": "-9223371972430135289", "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", @@ -1595,18 +1729,18 @@ "maxErc20Detail": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "pool_key": { "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "token0": "0x4c955b66e40af689e262687acc83b95067e4233f", "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b000000000000000080000064", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", "fee": "0", "tick_spacing": 100, - "extension": "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b" + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" }, "token": { - "address": "0xc8131d1b2f2597c1237ff5583f34f80a4cfef135", + "address": "0x4c955b66e40af689e262687acc83b95067e4233f", "name": "Zero Seed l6", "symbol": "L6", "decimals": 18 @@ -1625,7 +1759,7 @@ "sink_holder": "0x0000000000000000000000000000000000000000" }, "launch_token_is_token1": false, - "controller": "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "ask_tick": -13815500, @@ -1646,16 +1780,16 @@ }, "created": { "block_number": "8", - "block_hash": "0x437d4bcc7f2bab89a697f821fc4c2e6ae730cecd093460e28e030efa3da0950d", + "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", "block_time": 1800001310, "event_id": "-9223372002494775266", "transaction_index": 4, "event_index": 29, - "transaction_hash": "0x183728b1a3e44504363c9de57e8e3da1c44db6537a97828107e7b0657cecbd14" + "transaction_hash": "0x3d8f9644ad6ca9f2e28ecf0f767897c5f301f6a70b59ca64b4fdff8a1c59c3c5" }, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "principal": { @@ -1698,23 +1832,23 @@ "maxErc20Swaps": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "9", - "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", "block_time": 1800001400, "event_id": "-9223371998199611376", "transaction_index": 7, "event_index": 15, - "transaction_hash": "0xdbedd9e1daf7b972b455b95a75fed50b61b0414833d667aafa66af20b20b8731" + "transaction_hash": "0x47478bc3ca05cb40f297a8c445abb490ff9d7a1de77dfe0b2c27ed36074cb975" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1727,12 +1861,12 @@ { "event": { "block_number": "9", - "block_hash": "0x6a50fdb9350d9aa4359ca1f2bfcab0e3b989ac714a92de765b60ed4bc32d6a13", + "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", "block_time": 1800001400, "event_id": "-9223371998199545836", "transaction_index": 8, "event_index": 19, - "transaction_hash": "0x5d6d7dcc03eb15a3951643f978a3d469689d61383459648dbdebdfdc6d14c2fa" + "transaction_hash": "0x59356010bcdd4c01e4d82b52f04c8ab1a9e44eaec2a2a5e83bad5f26f9932ad9" }, "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", "direction": "buy", @@ -1747,23 +1881,23 @@ "maxErc20Claims": { "schema_version": "zero-seed-launch-api/v4", "chain_id": "31337", - "pool_id": "0xa650e97fa2f14e8d301a8f6e94b27c196a01bbd37d3f295a7408640c1cbaeba2", + "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "next_cursor": null, "as_of": { - "block_number": "16", - "block_hash": "0x49a48e5916e64df7f10ae074c24e7c43e107cfa46aa3729d32c38837a46fae6a", + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", "block_time": 2115361000 }, "data": [ { "event": { "block_number": "12", - "block_hash": "0xa856d45a5c31925b222c100e1b8c74fec2234c3057a982dd3828f2119af7c5c0", + "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", "block_time": 1800002800, "event_id": "-9223371985314906095", "transaction_index": 4, "event_index": 16, - "transaction_hash": "0x983e3ab51adc335d162a8801ee7973e29937ca95569c2bf16215bd7f391c0884" + "transaction_hash": "0x01487b24cd7bfb68cd5eed42a8fb2a826af16a3f93d707f8214ce30e2e73287f" }, "ledger_event_id": "-9223371985314906098", "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", @@ -1772,5 +1906,211 @@ "quote_token_amount": "184817928874495997630685" } ] + }, + "lowPriceErc20Detail": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080080000", + "fee": "0", + "tick_spacing": 524288, + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" + }, + "token": { + "address": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -88080384, + "upper_tick": -85983232, + "fee_schedule": { + "trading_start": 1800005200, + "fee_duration": 3600, + "fee_end": 1800008800, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", + "block_time": 1800005100, + "event_id": "-9223371968135299065", + "transaction_index": 0, + "event_index": 6, + "transaction_hash": "0x4d42f78bdb07af1928ff236c63ba1c30d30f26a57f57f0faa75b0c5a26b0c456" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000fac00000fae00000", + "tick_lower": -88080384, + "tick_upper": -85983232, + "liquidity": "115073586432", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999999999891410763432286846442", + "quote_token": "1" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "transferable": true, + "launch_token": { + "accrued": "0", + "claimed": "0", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "1", + "claimed": "0", + "unclaimed": "1" + }, + "swap_count": 1, + "claim_count": 0, + "owner_change_count": 1, + "last_event_id": "-9223371963840331774" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -88080384 + } + }, + "fullWidthErc20Detail": { + "schema_version": "zero-seed-launch-api/v4", + "chain_id": "31337", + "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000001", + "fee": "0", + "tick_spacing": 1, + "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" + }, + "token": { + "address": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "170141183460469231694794709727778658247", + "installed": "170141183460469231694794709727778658247", + "sink": "0", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -88722835, + "upper_tick": 88722835, + "fee_schedule": { + "trading_start": 1800005200, + "fee_duration": 3600, + "fee_end": 1800008800, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay": { + "kind": "fixed_base_q128", + "half_life_seconds": 600 + }, + "phase": "final_fee", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", + "block_time": 1800005100, + "event_id": "-9223371968135233522", + "transaction_index": 1, + "event_index": 13, + "transaction_hash": "0xd7641c80752a761d38a809de9b96f57c3e55e14e611a63f3820002394d1c1b6a" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000fab6326d0549cd93", + "tick_lower": -88722835, + "tick_upper": 88722835, + "liquidity": "9223595582101085260", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "170141183460469231594794709727778658247", + "quote_token": "1" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "transferable": true, + "launch_token": { + "accrued": "0", + "claimed": "0", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "1", + "claimed": "0", + "unclaimed": "1" + }, + "swap_count": 1, + "claim_count": 0, + "owner_change_count": 1, + "last_event_id": "-9223371963840266234" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -88722835 + } } } diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 7f832b7..4812514 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -22,6 +22,8 @@ test("the emitted-state golden responses parse with the v4 schemas", () => { golden.erc20TraderOwnedDetail, golden.maxNativeDetail, golden.maxErc20Detail, + golden.lowPriceErc20Detail, + golden.fullWidthErc20Detail, ]) LaunchDetail.parse(detail); for (const swaps of [ @@ -161,3 +163,44 @@ test("launches created at the maximum rate 2^64 - 1 are served exactly, never as } } }); + +test("low-price ERC-20 token0 launches serve the exact installation, including an empty sink", () => { + const sinkHolder = "0x0000000000000000000000000000000000000000"; + const l7 = golden.lowPriceErc20Detail; + const l8 = golden.fullWidthErc20Detail; + // 1e30 over ticks -88080384..-85983232 at spacing 524288: the largest + // affordable liquidity, 115073586432 (the shared helper gave 106052588672) + expect([ + l7.supply, + l7.principal.liquidity, + l7.launch_token_is_token1, + ]).toEqual([ + { + requested: "1000000000000000000000000000000", + installed: "999999999991410763432286846442", + sink: "8589236567713153558", + sink_holder: sinkHolder, + }, + "115073586432", + false, + ]); + // an int128-scale supply over the full width at spacing 1 installs exactly + expect([ + l8.supply, + l8.principal.liquidity, + l8.launch_token_is_token1, + ]).toEqual([ + { + requested: "170141183460469231694794709727778658247", + installed: "170141183460469231694794709727778658247", + sink: "0", + sink_holder: sinkHolder, + }, + "9223595582101085260", + false, + ]); + for (const detail of [l7, l8]) + expect(BigInt(detail.supply.installed) + BigInt(detail.supply.sink)).toBe( + BigInt(detail.supply.requested), + ); +}); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 4960b28..347fefb 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -1,7 +1,7 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; -// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 550afc4 +// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 43d6628 // build, as in the protocol manifest. export const ABI_SHA256 = "efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e"; @@ -33,18 +33,18 @@ export interface LaunchQuote { } // The constructor-executed LOCAL instance of the indexer replay fixture -// (evm-contracts 550afc4, anvil, no fork), serving native-ETH and ERC-20 +// (evm-contracts 43d6628, anvil, no fork), serving native-ETH and ERC-20 // quote launches. No live instance exists. Never enable it via a hosted // configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - address: "0x511b4327c2a2b6fd803b4ae85f2f9d831e046d8b", + address: "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f", runtime: - "0xb8c6d15f45c41e9dc43ac0e6ab8ea941dc833514cdb0b82f381c65f42f73a858", - periphery: "0x2f0be2fc5a2825cecd23dbf6ed33ed8ff823ef64", + "0xd6b4c7ce8da56e9948fdb004a035d9b52dbd3690bcb03c1269bbf2a9981d8ed5", + periphery: "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", peripheryRuntime: - "0x49988478ba70cc8d2587eb63141b333c7ef0a67c4639c3a2ee25de4c4597999e", + "0x2a9329dbb064a2c031115c7c120c8b6a1f31de167f33374180e4dc73d975974b", core: "0x00000000000014aa86c5d3c41765bb24e11bd701", abiSha256: ABI_SHA256, peripheryAbiSha256: PERIPHERY_ABI_SHA256, From 5e261c756c9b00b0c11084dac7b6e755d3a2eb59 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Thu, 22 Oct 2026 12:29:42 -0400 Subject: [PATCH 13/19] Pin the launch replay to indexer 03e95fd, where the launch migration is 00140 main applied 00139_mev_capture_v21, so the unapplied zero-seed launch migration was renumbered to 00140 with an unchanged schema. The replay fixture and its SHA-256 are unchanged. --- scripts/verifyLaunchReplay.ts | 2 +- src/routes/launches/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index d7ee664..b3fe920 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -16,7 +16,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "91a70364ecb06a2aef107261aee323f65890c0f5" + head.stdout.toString().trim() !== "03e95fd8a457a8e5aa24799f4d5242a5fff66a22" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index bca5fb5..3248ab8 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -5,8 +5,8 @@ Draft response contract: `zero-seed-launch-api/v4`, for the forward-only `43d6628841b3ce91dafa048b7ceef64bed55fc8e` (ABI sha256 of `jq -cS .abi`: ZeroSeedLaunch `efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e`, periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). -Indexer schema `zero-seed-launch-index/v3` (migration 00139). Live instance -and quote lists are empty. +Indexer schema `zero-seed-launch-index/v3` (migration 00140, after main's +`00139_mev_capture_v21`). Live instance and quote lists are empty. Changes from v3: @@ -136,7 +136,7 @@ regenerates `openapi.json`. For actual emitted-state/Postgres/handler replay, provide the exact pinned indexer checkout (EkuboProtocol/indexer -`91a70364ecb06a2aef107261aee323f65890c0f5`, fixture +`03e95fd8a457a8e5aa24799f4d5242a5fff66a22`, fixture `tests/fixtures/zero-seed-launch/local-chain.json` sha256 `ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9`), a disposable loopback Postgres 18 and a scratch directory: From 06fc548f544c88a16b878212ec0a489f03a2cde0 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sat, 24 Oct 2026 17:44:24 -0400 Subject: [PATCH 14/19] Serve the tick-rate launch fee model as response contract v5 The replacement ZeroSeedLaunch revision (evm-contracts 3d6378f) replaces the fee duration with a uint64 Q32.32 decayRateX32 toward a long-run floor. fee_schedule becomes a union on model, the fee model of the launch's deployment pin: fixed_base_duration_v1 keeps the v4 schedule and math unchanged; tick_rate_v2 serves decay_rate_x32 as a decimal string, a display half-life from an exact integer constant, the saturation second and phases before_trading_start, decaying, at_floor and constant, with no end time. Instance pins carry the model and the list/detail SQL screens it. Malformed indexed schedules fail as 500. The TypeScript mirror is checked against the contract's frozen v7 vectors, and the emitted-state replay runner now runs the indexer's harness for each fee model and writes a golden response set for each. --- openapi.json | 448 ++-- scripts/launchReplayCases.txt | 10 +- scripts/launchReplayCasesV2.txt | 152 ++ scripts/verifyLaunchReplay.ts | 106 +- src/contract/cases/launches.ts | 28 +- src/routes/launches/README.md | 58 +- ...o-seed-launch-api-v5-tick-rate.golden.json | 1697 +++++++++++++++ ...on => zero-seed-launch-api-v5.golden.json} | 79 +- src/routes/launches/golden.test.ts | 68 +- src/routes/launches/policy.test.ts | 247 ++- src/routes/launches/policy.ts | 190 +- src/routes/launches/queries.ts | 29 +- src/routes/launches/schema.ts | 61 +- .../vectors/zero-seed-launch-v2-vectors.json | 1867 +++++++++++++++++ 14 files changed, 4787 insertions(+), 253 deletions(-) create mode 100644 scripts/launchReplayCasesV2.txt create mode 100644 src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json rename src/routes/launches/fixtures/{zero-seed-launch-api-v4.golden.json => zero-seed-launch-api-v5.golden.json} (97%) create mode 100644 src/routes/launches/vectors/zero-seed-launch-v2-vectors.json diff --git a/openapi.json b/openapi.json index 3eae868..da3daab 100644 --- a/openapi.json +++ b/openapi.json @@ -10027,7 +10027,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "data": { @@ -10038,7 +10038,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "chain_id": { @@ -10226,85 +10226,177 @@ "type": "integer" }, "fee_schedule": { - "type": "object", - "properties": { - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "fee_duration": { - "type": "integer", - "exclusiveMinimum": 0 - }, - "fee_end": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + "oneOf": [ + { + "type": "object", + "properties": { + "model": { + "type": "string", + "enum": [ + "fixed_base_duration_v1" + ] + }, + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "fee_duration": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "fee_end": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "decay": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "fixed_base_q128" + ] + }, + "half_life_seconds": { + "type": "number", + "enum": [ + 600 + ] + } + }, + "required": [ + "kind", + "half_life_seconds" + ], + "additionalProperties": false + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "fee_decay", + "final_fee" + ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "model", + "trading_start", + "fee_duration", + "fee_end", + "initial_fee", + "final_fee", + "decay", + "phase", + "fee_rate", + "evaluated_at" + ], + "additionalProperties": false }, - "decay": { + { "type": "object", "properties": { - "kind": { + "model": { "type": "string", "enum": [ - "fixed_base_q128" + "tick_rate_v2" ] }, + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee" + }, "half_life_seconds": { - "type": "number", + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only" + }, + "saturation_elapsed_seconds": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end" + }, + "phase": { + "type": "string", "enum": [ - 600 + "before_trading_start", + "decaying", + "at_floor", + "constant" ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 } }, "required": [ - "kind", - "half_life_seconds" + "model", + "trading_start", + "initial_fee", + "final_fee", + "decay_rate_x32", + "half_life_seconds", + "saturation_elapsed_seconds", + "phase", + "fee_rate", + "evaluated_at" ], "additionalProperties": false - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "fee_decay", - "final_fee" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 } - }, - "required": [ - "trading_start", - "fee_duration", - "fee_end", - "initial_fee", - "final_fee", - "decay", - "phase", - "fee_rate", - "evaluated_at" - ], - "additionalProperties": false + ] }, "created": { "type": "object", @@ -10548,7 +10640,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "chain_id": { @@ -10795,7 +10887,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "chain_id": { @@ -11030,7 +11122,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "chain_id": { @@ -11277,7 +11369,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v4" + "zero-seed-launch-api/v5" ] }, "chain_id": { @@ -11465,85 +11557,177 @@ "type": "integer" }, "fee_schedule": { - "type": "object", - "properties": { - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "fee_duration": { - "type": "integer", - "exclusiveMinimum": 0 - }, - "fee_end": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + "oneOf": [ + { + "type": "object", + "properties": { + "model": { + "type": "string", + "enum": [ + "fixed_base_duration_v1" + ] + }, + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "fee_duration": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "fee_end": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "decay": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "fixed_base_q128" + ] + }, + "half_life_seconds": { + "type": "number", + "enum": [ + 600 + ] + } + }, + "required": [ + "kind", + "half_life_seconds" + ], + "additionalProperties": false + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "fee_decay", + "final_fee" + ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "model", + "trading_start", + "fee_duration", + "fee_end", + "initial_fee", + "final_fee", + "decay", + "phase", + "fee_rate", + "evaluated_at" + ], + "additionalProperties": false }, - "decay": { + { "type": "object", "properties": { - "kind": { + "model": { "type": "string", "enum": [ - "fixed_base_q128" + "tick_rate_v2" ] }, + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee" + }, "half_life_seconds": { - "type": "number", + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only" + }, + "saturation_elapsed_seconds": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end" + }, + "phase": { + "type": "string", "enum": [ - 600 + "before_trading_start", + "decaying", + "at_floor", + "constant" ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 } }, "required": [ - "kind", - "half_life_seconds" + "model", + "trading_start", + "initial_fee", + "final_fee", + "decay_rate_x32", + "half_life_seconds", + "saturation_elapsed_seconds", + "phase", + "fee_rate", + "evaluated_at" ], "additionalProperties": false - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "fee_decay", - "final_fee" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 } - }, - "required": [ - "trading_start", - "fee_duration", - "fee_end", - "initial_fee", - "final_fee", - "decay", - "phase", - "fee_rate", - "evaluated_at" - ], - "additionalProperties": false + ] }, "created": { "type": "object", diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt index 9a7d0ab..51da2db 100644 --- a/scripts/launchReplayCases.txt +++ b/scripts/launchReplayCases.txt @@ -30,8 +30,9 @@ const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); const { creator, newOwner, feeRecipient, trader } = full.addresses; // The served local instance is the fixture's, field for field. - expect(LOCAL_INSTANCES).toEqual(full.instances.map((i: any) => ({ - chainId: String(CHAIN_ID), address: lower(i.extension), runtime: i.extensionRuntimeKeccak, + // This fixture is the fixed-base (43d6628) instance; the tick-rate one has its own harness. + expect(LOCAL_INSTANCES.filter((p) => p.feeModel === "fixed_base_duration_v1")).toEqual(full.instances.map((i: any) => ({ + chainId: String(CHAIN_ID), feeModel: "fixed_base_duration_v1", address: lower(i.extension), runtime: i.extensionRuntimeKeccak, periphery: lower(i.periphery), peripheryRuntime: i.peripheryRuntimeKeccak, core: lower(full.addresses.core), abiSha256: full.provenance.launchAbiSha256, peripheryAbiSha256: full.provenance.peripheryAbiSha256, }))); @@ -45,7 +46,7 @@ const launch = full.launches[label]; const instance = full.instances.find((i: any) => i.label === install.instance); const launchIs1 = body.launch_token_is_token1; - expect(body.schema_version).toBe("zero-seed-launch-api/v4"); + expect(body.schema_version).toBe("zero-seed-launch-api/v5"); // requested = totalSupply, installed = Core's position delta, sink = balanceOf(0) expect(body.supply).toEqual({ requested: launch.requestedSupply, installed: launch.installedSupply, sink: launch.sinkSupply, @@ -77,6 +78,7 @@ const tick = decodePoolState(chain.poolStateWord).tick; expect(body.inventory.price_tick).toBe(launchIs1 ? -tick : tick); expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); + expect(body.fee_schedule.model).toBe("fixed_base_duration_v1"); expect(body.fee_schedule.decay).toEqual({ kind: "fixed_base_q128", half_life_seconds: 600 }); expect(body.fee_schedule).not.toHaveProperty("decay_factor"); // The API's TypeScript schedule equals the indexer's SQL feeAt and the contract's feeAt. @@ -286,7 +288,7 @@ const l2Claims = await get(`${path("l2")}/claims`); const l4 = await get(path("l4")); await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ - schema_version: "zero-seed-launch-api/v4", + schema_version: "zero-seed-launch-api/v5", source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain.json", chain_id: String(CHAIN_ID) }, list, second, canonicalList, nativeDetail: detail, nativeSwaps: swaps, nativeClaims: claims, nativeOwners: owners, erc20Detail: l2, erc20Swaps: l2Swaps, erc20Claims: l2Claims, erc20Owners: l2Owners, erc20TraderOwnedDetail: l4, diff --git a/scripts/launchReplayCasesV2.txt b/scripts/launchReplayCasesV2.txt new file mode 100644 index 0000000..c13a426 --- /dev/null +++ b/scripts/launchReplayCasesV2.txt @@ -0,0 +1,152 @@ + + // ----- API cases for the tick-rate fee model, appended by scripts/verifyLaunchReplay.ts ----- + + async function apiHead(db: Db) { + await db.sql`UPDATE indexer_cursor SET + head_block_number = b.block_number, head_block_hash = b.block_hash, + head_block_time = b.block_time, last_updated = NOW() + FROM (SELECT * FROM blocks WHERE chain_id = ${CHAIN_ID} ORDER BY block_number DESC LIMIT 1) b + WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; + } + + test("API actual handlers, tick-rate model: schedules, rates, feeAt, swaps, claims, model screening and malformed rows", async () => { + const db = await freshDb(); + const reader = new LaunchQueries(db.sql as never, { + localHarness: true, instances: LOCAL_INSTANCES, quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, + }); + const spies = [ + spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), + spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), + spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), + spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), + spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), + ]; + const get = async (path: string, status = 200) => { + const response = await app.fetch(new Request(`http://localhost${path}`), { PG_CONNECTION_STRING: urlFor(db.name) }); + const body = await response.json() as any; + expect(response.status).toBe(status); + if (response.status !== status) throw new Error(JSON.stringify(body)); + return body; + }; + const lower = (a: string) => a.toLowerCase(); + const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain-v2.json"), "utf8")); + expect(full.feeModel).toBe("tick_rate_v2"); + // The served tick-rate instance is the fixture's, field for field. + expect(LOCAL_INSTANCES.filter((p) => p.feeModel === "tick_rate_v2")).toEqual(full.instances.map((i: any) => ({ + chainId: String(CHAIN_ID), feeModel: "tick_rate_v2", address: lower(i.extension), runtime: i.extensionRuntimeKeccak, + periphery: lower(i.periphery), peripheryRuntime: i.peripheryRuntimeKeccak, core: lower(full.addresses.core), + abiSha256: full.provenance.launchAbiSha256, peripheryAbiSha256: full.provenance.peripheryAbiSha256, + }))); + const path = (label: string) => `/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`; + const MAX = 2n ** 64n - 1n; + /** One launch at one block: the served schedule versus the chain's feeAt and the indexer's SQL. */ + const expectLaunch = async (pool: any) => { + const body = await get(path(pool.label)); + const schedule = body.fee_schedule; + expect(body.schema_version).toBe("zero-seed-launch-api/v5"); + expect(schedule.model).toBe("tick_rate_v2"); + expect(schedule).not.toHaveProperty("fee_end"); + expect(schedule).not.toHaveProperty("fee_duration"); + expect(schedule).not.toHaveProperty("decay"); + expect(schedule.evaluated_at).toBe(body.as_of.block_time); + const [row] = await db.sql<{ rate: string; fee: string; start: string }[]>` + SELECT decay_rate_x32::TEXT AS rate, trading_start::TEXT AS start, + zero_seed_launch_model_fee_at(fee_model, trading_start, fee_duration, decay_rate_x32, + initial_fee, final_fee, ${body.as_of.block_time}::int8)::TEXT AS fee + FROM zero_seed_launch_created WHERE pool_id = ${num(pool.poolId)}::numeric`; + expect(schedule.decay_rate_x32).toBe(row!.rate); + // TypeScript schedule = indexer SQL = the contract's feeAt at the block + expect(schedule.fee_rate).toBe(row!.fee); + expect(schedule.fee_rate).toBe(pool.feeAtBlockTime); + const rate = BigInt(schedule.decay_rate_x32); + expect(schedule.half_life_seconds).toBe(halfLifeSeconds(rate)); + expect(schedule.saturation_elapsed_seconds).toBe(rate === 0n ? null : ((((88722835n + 1n) << 32n) - 1n) / rate).toString()); + const expectedPhase = body.as_of.block_time < schedule.trading_start ? "before_trading_start" + : schedule.fee_rate === schedule.final_fee ? "at_floor" : rate === 0n ? "constant" : "decaying"; + expect(schedule.phase).toBe(expectedPhase); + expect(body.owner).toBe(lower(pool.launchOwner)); + expect(body.controller).toBe(lower(pool.controller)); + const launchIs1 = body.launch_token_is_token1; + expect(body.fee_ledger.launch_token.unclaimed).toBe(pool.creatorFees[launchIs1 ? 1 : 0]); + expect(body.fee_ledger.quote_token.unclaimed).toBe(pool.creatorFees[launchIs1 ? 0 : 1]); + expect([body.supply.requested, body.supply.sink]).toEqual([pool.totalSupply, pool.sinkBalance]); + return body; + }; + const golden: Record = {}; + try { + for (const branch of [prefix, canonical]) + await replay(db, branch, async (state) => { + await apiHead(db); + for (const pool of state.pools) await expectLaunch(pool); + }); + const list = await get(`/launches?chainId=${CHAIN_ID}`); + expect(list.data.every((l: any) => l.fee_schedule.model === "tick_rate_v2")).toBe(true); + // rates 0, 1, the default, the 600 s rate and 2^64 - 1, served as decimal strings + expect(new Set(list.data.map((l: any) => l.fee_schedule.decay_rate_x32))).toEqual( + new Set(["0", "1", "68913100933", "9923486534471", MAX.toString()])); + golden.list = list; + for (const label of ["l1", "l2", "l4", "l5", "l6"]) { + const detail = await get(path(label)); + const swaps = await get(`${path(label)}/swaps`); + const claims = await get(`${path(label)}/claims`); + expect(swaps.data.length).toBe(detail.fee_ledger.swap_count); + // every swap's served rate is the indexed LaunchSwapped.feeRate, the + // contract's feeAt at its block, which the SQL mirror reproduces + const rows = await db.sql<{ ok: boolean }[]>` + SELECT w.fee_rate = zero_seed_launch_model_fee_at(c.fee_model, c.trading_start, c.fee_duration, c.decay_rate_x32, + c.initial_fee, c.final_fee, EXTRACT(EPOCH FROM b.block_time)::int8) AS ok + FROM zero_seed_launch_swapped w JOIN zero_seed_launch_created c USING (pool_key_id) + JOIN blocks b ON b.chain_id = w.chain_id AND b.block_number = w.block_number + WHERE c.pool_id = ${num(fixture.launches[label]!.poolId)}::numeric`; + expect(rows.every((r) => r.ok)).toBe(true); + for (const asset of ["launch_token", "quote_token"]) { + expect(swaps.data.filter((s: any) => s.fee_asset === asset).reduce((n: bigint, s: any) => n + BigInt(s.fee_amount), 0n).toString()) + .toBe(detail.fee_ledger[asset].accrued); + expect(claims.data.reduce((n: bigint, c: any) => n + BigInt(c[`${asset}_amount`]), 0n).toString()) + .toBe(detail.fee_ledger[asset].claimed); + } + golden[label] = { detail, swaps, claims }; + } + // l4 (rate 0) never decays; l5 (2^64 - 1) is at the floor after its start + const l4 = golden.l4 as any; + expect(l4.detail.fee_schedule).toMatchObject({ decay_rate_x32: "0", phase: "constant", half_life_seconds: null, saturation_elapsed_seconds: null }); + expect(l4.swaps.data.every((s: any) => s.fee_rate === l4.detail.fee_schedule.initial_fee)).toBe(true); + const l5 = golden.l5 as any; + expect(l5.detail.fee_schedule).toMatchObject({ decay_rate_x32: MAX.toString(), phase: "at_floor", saturation_elapsed_seconds: "0" }); + // A reader whose pin has the other fee model for this deployment serves nothing. + const wrongModel = new LaunchQueries(db.sql as never, { + localHarness: true, + instances: LOCAL_INSTANCES.map((p) => ({ ...p, feeModel: p.feeModel === "tick_rate_v2" ? "fixed_base_duration_v1" as const : "tick_rate_v2" as const })), + quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, + }); + await expect(wrongModel.detail(CHAIN_ID, BigInt(fixture.launches.l1!.poolId))).rejects.toThrow(/disabled|not found/); + // A malformed indexed schedule fails as 500, never served: a v2 row with + // a duration (the CHECK is lifted to simulate corruption). + const before = await get(path("l1")); + const [check] = await db.sql<{ name: string; def: string }[]>` + SELECT conname AS name, pg_get_constraintdef(oid) AS def FROM pg_constraint + WHERE conrelid = 'zero_seed_launch_created'::regclass AND contype = 'c' + AND pg_get_constraintdef(oid) LIKE '%decay_rate_x32%'`; + await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created DROP CONSTRAINT "${check!.name}"`); + await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; + const l1Pool = num(fixture.launches.l1!.poolId); + for (const [duration, rate] of [["3600", before.fee_schedule.decay_rate_x32], [null, (2n ** 64n).toString()], [null, null]] as const) { + await db.sql`UPDATE zero_seed_launch_created SET fee_duration = ${duration}::int8, decay_rate_x32 = ${rate}::numeric WHERE pool_id = ${l1Pool}::numeric`; + await get(path("l1"), 500); + await get(`/launches?chainId=${CHAIN_ID}`, 500); + } + await db.sql`UPDATE zero_seed_launch_created SET fee_duration = NULL, decay_rate_x32 = ${before.fee_schedule.decay_rate_x32}::numeric WHERE pool_id = ${l1Pool}::numeric`; + await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; + await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created ADD CONSTRAINT "${check!.name}" ${check!.def}`); + expect(await get(path("l1"))).toEqual(before); + if (process.env.LAUNCH_GOLDEN_DIR) + await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden-tick-rate.json"), JSON.stringify({ + schema_version: "zero-seed-launch-api/v5", + source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain-v2.json", chain_id: String(CHAIN_ID), fee_model: "tick_rate_v2" }, + ...golden, + }, null, 2)); + } finally { + spies.forEach((s) => s.mockRestore()); + await db.sql.end(); + } + }, 300_000); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts index b3fe920..2e8984c 100644 --- a/scripts/verifyLaunchReplay.ts +++ b/scripts/verifyLaunchReplay.ts @@ -1,6 +1,8 @@ -// Reuse the exact reviewed indexer replay harness without editing its checkout. +// Reuse the exact reviewed indexer replay harnesses without editing their checkout. // No RPC/provider call or production database is involved. Generated test files // belong to the run's scratch directory and are evidence, not source overrides. +// One run per fee model: the fixed-base harness and fixture (evm-contracts +// 43d6628) and the tick-rate ones (3d6378f), each with its API cases. import { resolve, dirname } from "node:path"; import { mkdir } from "node:fs/promises"; @@ -16,7 +18,7 @@ if (!["127.0.0.1", "localhost"].includes(url.hostname)) throw new Error("Only loopback test Postgres is permitted"); const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); if ( - head.stdout.toString().trim() !== "03e95fd8a457a8e5aa24799f4d5242a5fff66a22" + head.stdout.toString().trim() !== "7f373e59ca79eadb5d7ce47a29eb7f02decf31da" ) throw new Error("Indexer source pin mismatch"); const clean = Bun.spawnSync([ @@ -30,50 +32,66 @@ const clean = Bun.spawnSync([ ]); if (clean.exitCode !== 0) throw new Error("Pinned indexer tracked source is modified"); -const fixturePath = resolve( - root, - "tests/fixtures/zero-seed-launch/local-chain.json", -); -const fixtureBytes = await Bun.file(fixturePath).arrayBuffer(); -if ( - new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - "ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9" -) - throw new Error("Emitted fixture pin mismatch"); -const original = resolve(root, "src/evm/zeroSeedLaunchReplay.postgres.test.ts"); -let source = await Bun.file(original).text(); -source = source.replace(/from "([^"]+)"/g, (match, name: string) => { - if (name.startsWith("node:") || name === "bun:test") return match; - const target = name.startsWith(".") - ? resolve(dirname(original), name) - : Bun.resolveSync(name, root); - return `from ${JSON.stringify(target)}`; -}); -source = source.replace( - 'const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");', - `const ROOT = ${JSON.stringify(root)};`, -); + +const RUNS = [ + { + harness: "src/evm/zeroSeedLaunchReplay.postgres.test.ts", + fixture: "tests/fixtures/zero-seed-launch/local-chain.json", + sha256: "ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9", + cases: "launchReplayCases.txt", + target: "api-emitted-replay.test.ts", + }, + { + harness: "src/evm/zeroSeedLaunchReplayV2.postgres.test.ts", + fixture: "tests/fixtures/zero-seed-launch/local-chain-v2.json", + sha256: "52377ccd26930d893aa656d7835375536c7e13375faa437d1b8d220f1b1d1d2f", + cases: "launchReplayCasesV2.txt", + target: "api-emitted-replay-tick-rate.test.ts", + }, +]; + const api = resolve(import.meta.dir, "../src"); -const extra = await Bun.file( - resolve(import.meta.dir, "launchReplayCases.txt"), -).text(); -source = `import { spyOn } from "bun:test"; +await mkdir(scratch, { recursive: true }); +let failed = 0; +for (const run of RUNS) { + const fixtureBytes = await Bun.file(resolve(root, run.fixture)).arrayBuffer(); + if ( + new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== + run.sha256 + ) + throw new Error(`Emitted fixture pin mismatch: ${run.fixture}`); + const original = resolve(root, run.harness); + let source = await Bun.file(original).text(); + source = source.replace(/from "([^"]+)"/g, (match, name: string) => { + if (name.startsWith("node:") || name === "bun:test") return match; + const target = name.startsWith(".") + ? resolve(dirname(original), name) + : Bun.resolveSync(name, root); + return `from ${JSON.stringify(target)}`; + }); + source = source.replace( + 'const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");', + `const ROOT = ${JSON.stringify(root)};`, + ); + const extra = await Bun.file(resolve(import.meta.dir, run.cases)).text(); + source = `import { spyOn } from "bun:test"; import { LaunchQueries } from ${JSON.stringify(resolve(api, "routes/launches/queries"))}; -import { LOCAL_INSTANCES, LOCAL_QUOTES } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; +import { LOCAL_INSTANCES, LOCAL_QUOTES, halfLifeSeconds } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; import { Queries } from ${JSON.stringify(resolve(api, "queries"))}; import { app } from ${JSON.stringify(resolve(api, "router"))}; ${source}`; -// The harness ends by closing its describe callback, as `});` or, formatted -// across lines, ` },\n);`. The API cases go inside that callback. -const ending = source.search(/\n(?:\}\);| \},\n\);)\s*$/); -if (ending < 0) throw new Error("Unrecognized pinned indexer harness"); -source = source.slice(0, ending + 1) + extra + source.slice(ending + 1); -await mkdir(scratch, { recursive: true }); -const target = resolve(scratch, "api-emitted-replay.test.ts"); -await Bun.write(target, source); -const run = Bun.spawn(["bun", "test", target, "--timeout", "300000"], { - stdout: "inherit", - stderr: "inherit", - env: process.env, -}); -process.exit(await run.exited); + // The harness ends by closing its describe callback, as `});` or, formatted + // across lines, ` },\n);`. The API cases go inside that callback. + const ending = source.search(/\n(?:\}\);| \},\n\);)\s*$/); + if (ending < 0) throw new Error("Unrecognized pinned indexer harness"); + source = source.slice(0, ending + 1) + extra + source.slice(ending + 1); + const target = resolve(scratch, run.target); + await Bun.write(target, source); + const child = Bun.spawn(["bun", "test", target, "--timeout", "300000"], { + stdout: "inherit", + stderr: "inherit", + env: process.env, + }); + if ((await child.exited) !== 0) failed += 1; +} +process.exit(failed === 0 ? 0 : 1); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index b2b0134..5ada1f0 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -8,7 +8,7 @@ const owner = `0x${"2".repeat(40)}`; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { - schema_version: "zero-seed-launch-api/v4" as const, + schema_version: "zero-seed-launch-api/v5" as const, chain_id: "31337", pool_id: zero, pool_key: { @@ -46,6 +46,7 @@ const summary = { ask_tick: 0, upper_tick: 100, fee_schedule: { + model: "fixed_base_duration_v1" as const, trading_start: 1000, fee_duration: 3600, fee_end: 4600, @@ -68,9 +69,28 @@ const summary = { }, as_of, }; +// The same launch shape on the tick-rate instance: a floor, never an end. +const tickRatePin = LOCAL_INSTANCES[1]; +const tickRateSummary = { + ...summary, + pool_key: { ...summary.pool_key, extension: tickRatePin.address }, + controller: tickRatePin.periphery, + fee_schedule: { + model: "tick_rate_v2" as const, + trading_start: 1000, + initial_fee: "9223372036854775808", + final_fee: "55340232221128654", + decay_rate_x32: "68913100933", + half_life_seconds: "86400.000001", + saturation_elapsed_seconds: "5529597", + phase: "decaying" as const, + fee_rate: "9223372036854775808", + evaluated_at: 1000, + }, +}; const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; const identity = { - schema_version: "zero-seed-launch-api/v4" as const, + schema_version: "zero-seed-launch-api/v5" as const, chain_id: "31337", pool_id: zero, as_of, @@ -83,8 +103,8 @@ export const launchesCases: ContractCase[] = [ request: "/launches?chainId=31337", stub: () => stubQuery("readZeroSeedList", { - schema_version: "zero-seed-launch-api/v4", - data: [summary], + schema_version: "zero-seed-launch-api/v5", + data: [tickRateSummary, summary], as_of, next_cursor: null, }), diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 3248ab8..24a0e95 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,14 +1,48 @@ # Zero-seed launch reads (draft/local) -Draft response contract: `zero-seed-launch-api/v4`, for the forward-only +Draft response contract: `zero-seed-launch-api/v5`, for the forward-only `ZeroSeedLaunch` and its separate `ZeroSeedLaunchPeriphery` of evm-contracts `43d6628841b3ce91dafa048b7ceef64bed55fc8e` (ABI sha256 of `jq -cS .abi`: ZeroSeedLaunch `efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e`, periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). -Indexer schema `zero-seed-launch-index/v3` (migration 00140, after main's -`00139_mev_capture_v21`). Live instance and quote lists are empty. - -Changes from v3: +Since v5 it also serves the tick-rate revision, +`3d6378f585591dc7eabeea6976932c30524bb8ea` (ABI sha256 `89ff4d15…aadd` and +`a6d43b36…db07`). Indexer schema `zero-seed-launch-index/v4` (migration 00140, +after main's `00139_mev_capture_v21`). Live instance and quote lists are empty. + +Changes from v4: + +- `fee_schedule` is a union on `model`, the fee model of the launch's + deployment, bound to the instance pin (`LaunchInstance.feeModel`) and never + inferred from a row. List and detail SQL screen `fee_model` with the rest of + the instance, so a reader with the other model for a deployment serves none + of its launches. +- `model: "fixed_base_duration_v1"` (43d6628, the earlier revision): the v4 + schedule unchanged (`fee_duration`, `fee_end`, `decay`, phases + `before_trading_start | fee_decay | final_fee`). +- `model: "tick_rate_v2"` (3d6378f): `trading_start`, `initial_fee`, + `final_fee` (the long-run floor), `decay_rate_x32` (canonical decimal + uint64, unsigned Q32.32 ticks per second, never a JSON number), + `half_life_seconds` (display: 6 fractional digits, floored, from the exact + integer `HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626`; + `null` for rate 0), `saturation_elapsed_seconds` (`floor(381061679028371455 / + rate)`, after which the fee is exactly the floor by numerical saturation; + `null` for rate 0; not an end), `phase` (`before_trading_start | decaying | + at_floor | constant`), `fee_rate` (the contract's `feeAt` at the indexed block + time) and `evaluated_at`. There is no `fee_end`, `fee_duration` or `decay`. + `fee_rate = final + floor((initial - final) * toFixed(tickToSqrtRatio(-m)) / + 2^128)`, `m = floor(rate * elapsed / 2^32)`, with saturation checked first; + `policy.test.ts` checks it against the contract's frozen vectors v7 + (`vectors/zero-seed-launch-v2-vectors.json`, verbatim: fee rows, curve + tables, tick steps, saturation seconds and the five SHA-256 chains). +- Malformed indexed schedules fail as 500: an unknown model, a v2 row with a + duration or without a rate, a rate outside uint64, a v1 row with a rate. +- Golden examples: `fixtures/zero-seed-launch-api-v5.golden.json` (fixed-base + fixture, previously the v4 golden) and + `fixtures/zero-seed-launch-api-v5-tick-rate.golden.json` (tick-rate fixture: + rates 0, 1, one day, 600 s and 2^64 - 1). + +Changes from v3 (v4): - Every `LaunchSummary` (list and detail) has `supply: { requested, installed, sink, sink_holder }`, decimal strings of @@ -54,7 +88,7 @@ transferable holder of unclaimed and future fees) and `controller` (the periphery), periphery claim payouts linked by `ledger_event_id` and counted once, and the `/owners` history and `owner`/`creator` list filters. -`fixtures/zero-seed-launch-api-v4.golden.json` is a machine-readable example of +`fixtures/zero-seed-launch-api-v5.golden.json` is a machine-readable example of every response, written by the actual handlers over the replayed local fixture: a native-ETH (launch token1, sink 578) and two ERC-20 (launch token0, sink 790) launches on one extension, ownership transfers, claims, swaps and a reorg, @@ -136,9 +170,11 @@ regenerates `openapi.json`. For actual emitted-state/Postgres/handler replay, provide the exact pinned indexer checkout (EkuboProtocol/indexer -`03e95fd8a457a8e5aa24799f4d5242a5fff66a22`, fixture +`7f373e59ca79eadb5d7ce47a29eb7f02decf31da`, fixtures `tests/fixtures/zero-seed-launch/local-chain.json` sha256 -`ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9`), a +`ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9` and +`local-chain-v2.json` sha256 +`52377ccd26930d893aa656d7835375536c7e13375faa437d1b8d220f1b1d1d2f`), a disposable loopback Postgres 18 and a scratch directory: ```sh @@ -153,8 +189,10 @@ bun scripts/verifyLaunchReplay.ts docker stop launch-replay-pg ``` -With `LAUNCH_GOLDEN_DIR` set, the run writes `actual-handler-golden.json`; -copy it to `fixtures/zero-seed-launch-api-v4.golden.json` and run +The runner runs both fee models' harnesses. With `LAUNCH_GOLDEN_DIR` set, it +writes `actual-handler-golden.json` and `actual-handler-golden-tick-rate.json`; +copy them to `fixtures/zero-seed-launch-api-v5.golden.json` and +`fixtures/zero-seed-launch-api-v5-tick-rate.golden.json` and run `bun run format`. The runner checks source/fixture pins, creates a scratch copy of the reviewed diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json new file mode 100644 index 0000000..52cc43a --- /dev/null +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json @@ -0,0 +1,1697 @@ +{ + "schema_version": "zero-seed-launch-api/v5", + "source": { + "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain-v2.json", + "chain_id": "31337", + "fee_model": "tick_rate_v2" + }, + "list": { + "schema_version": "zero-seed-launch-api/v5", + "data": [ + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x29595065df87855bacc2f4179d99eeb807c399ab331ca954aaacef214b58e39e", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x3c77e927fa2c58c5a0db186896b1e189e9ed7c19", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000001", + "fee": "0", + "tick_spacing": 1, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x3c77e927fa2c58c5a0db186896b1e189e9ed7c19", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "170141183460469231694794709727778658247", + "installed": "170141183460469231694794709727778658247", + "sink": "0", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -88722835, + "upper_tick": 88722835, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0xa36617d0b2108090723abd4a735681fcc39fe2f5fb9ff51deda8d47acd5142aa", + "block_time": 1800005100, + "event_id": "-9223371968135233522", + "transaction_index": 1, + "event_index": 13, + "transaction_hash": "0xae4a7c761089548fce3b1f4d0d81a6dc2e0054afa06a6f86df1705a8be3b6170" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x78584afa19c71aadb11a78c8c7c7c5176868f6f8384b6fd75d6bc1c28ca98ee7", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xe51554275bef011ccb7648b1a0b5fc080bd78915", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080080000", + "fee": "0", + "tick_spacing": 524288, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0xe51554275bef011ccb7648b1a0b5fc080bd78915", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -88080384, + "upper_tick": -85983232, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "16", + "block_hash": "0xa36617d0b2108090723abd4a735681fcc39fe2f5fb9ff51deda8d47acd5142aa", + "block_time": 1800005100, + "event_id": "-9223371968135299065", + "transaction_index": 0, + "event_index": 6, + "transaction_hash": "0xb2e095b629cac40fce28c5062e93df91a506b6374816e7e84d2f70c3de7d6d72" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", + "name": "Zero Seed l6", + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay_rate_x32": "1", + "half_life_seconds": "5954091920683119.788732", + "saturation_elapsed_seconds": "381061679028371455", + "phase": "at_floor", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494775266", + "transaction_index": 4, + "event_index": 29, + "transaction_hash": "0xf1b11401ab1b29b9ea159a52ee41d4596615bf8c0b36378ea4bfcdd9dda04be3" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", + "name": "Zero Seed l5", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay_rate_x32": "18446744073709551615", + "half_life_seconds": "0.000322", + "saturation_elapsed_seconds": "0", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494840810", + "transaction_index": 3, + "event_index": 21, + "transaction_hash": "0xfb8a73d444fe116725219fdf0baa4189238f39591f79bbcc294509fafe1304a2" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x77cd1963269215c42f374efcf48022b890dfc5fc", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x77cd1963269215c42f374efcf48022b890dfc5fc", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "0", + "half_life_seconds": null, + "saturation_elapsed_seconds": null, + "phase": "constant", + "fee_rate": "922337203685477580", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494906354", + "transaction_index": 2, + "event_index": 13, + "transaction_hash": "0x93a1efb9f3c33ae3481826d1c4c0d3f553fdbf1b06b9b9e5322ee728c30e6ab5" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "3", + "block_hash": "0xbebd0fe88adcee7372dc5d0419b91c2ad80857d487d2e7df40d6c116368aedd0", + "block_time": 1800000020, + "event_id": "-9223372023969873913", + "transaction_index": 0, + "event_index": 6, + "transaction_hash": "0x257e44a4017f805ed4d37d0eefd62524566bea81c795639a05ece3aca4eccca5" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001000, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "9923486534471", + "half_life_seconds": "600.000000", + "saturation_elapsed_seconds": "38399", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "2", + "block_hash": "0x8c5adbc2d0283c6baf44f585cb424483f85690a15c71d68ac3b5627947c0e068", + "block_time": 1800000010, + "event_id": "-9223372028264710136", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x8ed8c19664e464cbea5f81952f478d29b0f5023f93c05f032a3161c313c164ae" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + } + ], + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + } + }, + "l1": { + "detail": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001000, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "9923486534471", + "half_life_seconds": "600.000000", + "saturation_elapsed_seconds": "38399", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "2", + "block_hash": "0x8c5adbc2d0283c6baf44f585cb424483f85690a15c71d68ac3b5627947c0e068", + "block_time": 1800000010, + "event_id": "-9223372028264710136", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x8ed8c19664e464cbea5f81952f478d29b0f5023f93c05f032a3161c313c164ae" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", + "tick_lower": 9210300, + "tick_upper": 13815500, + "liquidity": "1111119116089565312898036", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "984239888128788178301567600", + "quote_token": "15987150838889926474" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "transferable": true, + "launch_token": { + "accrued": "1476890214302222101466582", + "claimed": "1476890214302222101466582", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "9323380801075558014", + "claimed": "9323380801075558014", + "unclaimed": "0" + }, + "swap_count": 10, + "claim_count": 4, + "owner_change_count": 3, + "last_event_id": "-9223371959545233400" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13786928 + } + }, + "swaps": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", + "block_time": 1800001000, + "event_id": "-9223372015379939327", + "transaction_index": 0, + "event_index": 0, + "transaction_hash": "0xb740e721a93924ade13abaa5c1bd9fe4874b5f8fe97320b39368683b64276718" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "zero_fill", + "launch_token_delta": "0", + "quote_token_delta": "0", + "fee_rate": "9223372036854775808", + "fee_amount": "0", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "5", + "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", + "block_time": 1800001000, + "event_id": "-9223372015379873789", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x07377cf406760f8d87ef013be1c43bc5744edb633f2347c0264bfe334c63cf13" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-499541686868404598624947", + "quote_token_delta": "1000000000000000000", + "fee_rate": "9223372036854775808", + "fee_amount": "499541686868404598624948", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", + "block_time": 1800001060, + "event_id": "-9223372011084972030", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x33c300d33e2a9c7689025d6e365f0107920b78fe881091a86596dbcc646de604" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000000000000000000", + "quote_token_delta": "18956647379605107490", + "fee_rate": "8609418535131466691", + "fee_amount": "8847399338429899765", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", + "block_time": 1800001060, + "event_id": "-9223372011084906491", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0xf48a29f8f803a7c9e1786654b21208e3cd008e713ccde7c84c26097c3cb6d113" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "1000000000000000000000000", + "quote_token_delta": "-543514932567036288", + "fee_rate": "8609418535131466691", + "fee_amount": "475672734038619372", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002495037438", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x66d6057deddc325eb595f0f7ce06b1b41ad7f4f7dcab36cc66a81f1874cdfd72" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-1273636192113237800407828", + "quote_token_delta": "2000000000000000000", + "fee_rate": "6463658066361875532", + "fee_amount": "686997392968332076936929", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "11", + "block_hash": "0xea823a1624c421642c343588291bd967f268db38e038f0809c0f8705b1983ca1", + "block_time": 1800001756, + "event_id": "-9223371989610135550", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x163513f8082eda07081632f98c943eafc988bb96237f730011cdbdfb171d083e" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-771875785591178897799846", + "quote_token_delta": "1000000000000000000", + "fee_rate": "3883398005406982927", + "fee_amount": "205825012475042142878670", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", + "block_time": 1800002800, + "event_id": "-9223371985315168254", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x1540407af5aac9d170164a1d32a5cd5a59225d90db43f39495e03494fd4cfd55" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-912403406016545423674081", + "quote_token_delta": "1000000000000000000", + "fee_rate": "1201344301085512086", + "fee_amount": "63559595402884915790647", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "13", + "block_hash": "0x76483275a4dfbc11e23bca9aa51c92a1e3ea83bd33c6b59c2b873ab1fb56bdb5", + "block_time": 1800004599, + "event_id": "-9223371981020200958", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xf8e1689d52a97cc7cb223992d412dfeb115e02d46c4b9096d34671b269739926" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-963732892605571480403853", + "quote_token_delta": "1000000000000000000", + "fee_rate": "198756374052614459", + "fee_amount": "10496941276057644149158", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "14", + "block_hash": "0x50feaeec18c38bc28549e91998d45490e0c9d8bfd16f22cc21fc689741814179", + "block_time": 1800004600, + "event_id": "-9223371976725233662", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x26203ced636676caa5cd62a3b898a27db36f8c0807d2544f4e97559b45ba16fc" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-962031693714661396054685", + "quote_token_delta": "1000000000000000000", + "fee_rate": "198590752489824724", + "fee_amount": "10469585311500723086230", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000, + "event_id": "-9223371959545364478", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xd43bd9849a86b3449b90992ef3804c2b2f4fa5ab543a3a0d03903656fbd25e7f" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "100000000000000000000000", + "quote_token_delta": "-102600807072586714", + "fee_rate": "55340232221128654", + "fee_amount": "308728607038877", + "fee_asset": "quote_token" + } + ] + }, + "claims": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "6", + "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", + "block_time": 1800001060, + "event_id": "-9223372011084775411", + "transaction_index": 3, + "event_index": 12, + "transaction_hash": "0x75bd7b0541a3fc2796be5b4c5ee7c72a464df2acb042a159708f93dd0d4dd979" + }, + "ledger_event_id": "-9223372011084775413", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "499541686868404598624948", + "quote_token_amount": "9323072072468519137" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", + "block_time": 1800002800, + "event_id": "-9223371985315037174", + "transaction_index": 2, + "event_index": 9, + "transaction_hash": "0xb7d006f1252f06680349d35b6185d8f8973ed0917c298bdab48f881fcbc504c8" + }, + "ledger_event_id": "-9223371985315037176", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "956382000846259135606246", + "quote_token_amount": "0" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000, + "event_id": "-9223371959545298938", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0x853e87c2ec6e66032bf916d5f786b1339b40cc83149a5b5623bfa658ddf75911" + }, + "ledger_event_id": "-9223371959545298940", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "20966526587558367235388", + "quote_token_amount": "308728607038877" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000, + "event_id": "-9223371959545233400", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x40d82cfe0b5597f9470552c2759a6b9b0068eda77293b4ebfd54fdbf4e0664f1" + }, + "ledger_event_id": "-9223371959545233401", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "0", + "quote_token_amount": "0" + } + ] + } + }, + "l2": { + "detail": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "3", + "block_hash": "0xbebd0fe88adcee7372dc5d0419b91c2ad80857d487d2e7df40d6c116368aedd0", + "block_time": 1800000020, + "event_id": "-9223372023969873913", + "transaction_index": 0, + "event_index": 6, + "transaction_hash": "0x257e44a4017f805ed4d37d0eefd62524566bea81c795639a05ece3aca4eccca5" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "991072549435525463122574599", + "quote_token": "8999918293703352734" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "transferable": true, + "launch_token": { + "accrued": "395582709290336348407848", + "claimed": "248368656637862963393881", + "unclaimed": "147214052652473385013967" + }, + "quote_token": { + "accrued": "52679910194913302", + "claimed": "52679910194913302", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 2, + "last_event_id": "-9223371993905037306" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13799366 + } + }, + "swaps": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", + "block_time": 1800001000, + "event_id": "-9223372015379808250", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0x17bceb9d20d5b0f29fa26b7d9870da1554bd9bec488f54f9cf227b75918f7a66" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-1000000000000000000000000", + "quote_token_delta": "1053598203898266036", + "fee_rate": "922337203685477580", + "fee_amount": "52679910194913302", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", + "block_time": 1800001060, + "event_id": "-9223372011084840952", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x5f23c76ea0a42880df580b44252b368631b183316cc8b905f38de0fdd56d4a6e" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-4720262100638779571525175", + "quote_token_delta": "5000000000000000000", + "fee_rate": "921920278629180106", + "fee_amount": "248316639408682001285794", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "7", + "block_hash": "0xad431b2a1b6c312c0db23e80576cc2f11c20d7283f8f4cadb3e67e27646e1c04", + "block_time": 1800001120, + "event_id": "-9223372006789939197", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x8c9a128abee97b9164113587ee6c7cb46ab9681a5249f74c495690f25efec3a9" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "1041286233626528999764", + "quote_token_delta": "-1000000000000000", + "fee_rate": "921503120984176012", + "fee_amount": "52017229180962108087", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "10", + "block_hash": "0x89c2b4ce768a54f7579e51e8c43b4974ab38c838c5bfa7fe0a10fb64a8d85ebd", + "block_time": 1800001700, + "event_id": "-9223371993905037306", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0x6ed38440160ae3559e16c8fe1257b67a374d99dd9def326eac3869ce764a2d29" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-2812647040779047486491352", + "quote_token_delta": "3000000000000000000", + "fee_rate": "917482228933054387", + "fee_amount": "147214052652473385013967", + "fee_asset": "launch_token" + } + ] + }, + "claims": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494971897", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x70d19d941019367fa98fb0b82c2eba90a04139aab1300b78f2e0ad2f825bac4e" + }, + "ledger_event_id": "-9223372002494971900", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "248368656637862963393881", + "quote_token_amount": "52679910194913302" + } + ] + } + }, + "l4": { + "detail": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x77cd1963269215c42f374efcf48022b890dfc5fc", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x77cd1963269215c42f374efcf48022b890dfc5fc", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "0", + "half_life_seconds": null, + "saturation_elapsed_seconds": null, + "phase": "constant", + "fee_rate": "922337203685477580", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494906354", + "transaction_index": 2, + "event_index": 13, + "transaction_hash": "0x93a1efb9f3c33ae3481826d1c4c0d3f553fdbf1b06b9b9e5322ee728c30e6ab5" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "992953944708816951043107488", + "quote_token": "7091146575104629573" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "transferable": true, + "launch_token": { + "accrued": "347802764559152447542916", + "claimed": "347802764559152447542916", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "5836673075303073", + "claimed": "5836673075303073", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371976725168121" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13802777 + } + }, + "swaps": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998200004606", + "transaction_index": 1, + "event_index": 1, + "transaction_hash": "0x908c6b2456b4a9205e02ece20f19d1863025c94ce19601de8a0aafb31e9fff7a" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "zero_fill", + "launch_token_delta": "0", + "quote_token_delta": "0", + "fee_rate": "922337203685477580", + "fee_amount": "0", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998199939068", + "transaction_index": 2, + "event_index": 3, + "transaction_hash": "0x93b6871f568324d65df1f8d158464a6f11f08fda81319a9bb5124a7b8ee1365a" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-6608252526623896509348806", + "quote_token_delta": "7000000000000000000", + "fee_rate": "922337203685477580", + "fee_amount": "347802764559152447542916", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "10", + "block_hash": "0x89c2b4ce768a54f7579e51e8c43b4974ab38c838c5bfa7fe0a10fb64a8d85ebd", + "block_time": 1800001700, + "event_id": "-9223371993905102846", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x82420f8ee003d082329376306c0cd7b232796347e390864165635526a77d6cea" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-100000000000000000000000", + "quote_token_delta": "106605146979841542", + "fee_rate": "922337203685477580", + "fee_amount": "5330257348992078", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", + "block_time": 1800002800, + "event_id": "-9223371985315102715", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0xff24cfc8d7ac5bfa3c01a2a8fe72feb613d4c524ea9c3bcce6c3231a4947d9b7" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "10000000000000000000000", + "quote_token_delta": "-9621898799908896", + "fee_rate": "922337203685477580", + "fee_amount": "506415726310995", + "fee_asset": "quote_token" + } + ] + }, + "claims": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "14", + "block_hash": "0x50feaeec18c38bc28549e91998d45490e0c9d8bfd16f22cc21fc689741814179", + "block_time": 1800004600, + "event_id": "-9223371976725168121", + "transaction_index": 1, + "event_index": 6, + "transaction_hash": "0x28a1274e0f4a187c38a7f664b3c0e00cb91d28ecb38c4a483537e18d3db6b7f1" + }, + "ledger_event_id": "-9223371976725168124", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "347802764559152447542916", + "quote_token_amount": "5836673075303073" + } + ] + } + }, + "l5": { + "detail": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", + "name": "Zero Seed l5", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "sink": "578", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": true, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay_rate_x32": "18446744073709551615", + "half_life_seconds": "0.000322", + "saturation_elapsed_seconds": "0", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494840810", + "transaction_index": 3, + "event_index": 21, + "transaction_hash": "0xfb8a73d444fe116725219fdf0baa4189238f39591f79bbcc294509fafe1304a2" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", + "tick_lower": 9210300, + "tick_upper": 13815500, + "liquidity": "1111119116089565312898036", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "997008130316076698400737225", + "quote_token": "2999999999999999984" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "transferable": true, + "launch_token": { + "accrued": "1005061732667368674310164", + "claimed": "1005061732667368674310164", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "184817928874495997630716", + "claimed": "184817928874495997630716", + "unclaimed": "0" + }, + "swap_count": 5, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371972430135287" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13810107 + } + }, + "swaps": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998199742454", + "transaction_index": 5, + "event_index": 9, + "transaction_hash": "0xa61e2d30a5d3abd9ebfc40d412eec3177d8dcdcd1c9dd2288967a6aaf1815469" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-54160", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "999083373736809197195735", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998199676915", + "transaction_index": 6, + "event_index": 12, + "transaction_hash": "0xbb6f8443b1a49a5ec7615b27695cb840213bb021abcd37b9e64fe2fc6a03b12c" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "184817928874495997640704", + "fee_rate": "18446744073709551615", + "fee_amount": "184817928874495997630685", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", + "block_time": 1800002800, + "event_id": "-9223371985314971636", + "transaction_index": 3, + "event_index": 11, + "transaction_hash": "0x8f948e22790515db6f88dbe271506af7476641a283898341d11fbf61a3114e1b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-994296418585455187411754", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2991864850307287378526", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", + "block_time": 1800005000, + "event_id": "-9223371972430266366", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x0010589c2f13c2baa1198c925b9c2f0d797d7853dc9552956d17cb8ee4e35b4d" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "10000000000", + "quote_token_delta": "-10004", + "fee_rate": "55340232221128654", + "fee_amount": "31", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", + "block_time": 1800005000, + "event_id": "-9223371972430200827", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0x2f56541851d9db177cb8daca8d3e0406c4e911c2b238c8340440370653a00686" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-992511532670477737486119", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2986494080252189735903", + "fee_asset": "launch_token" + } + ] + }, + "claims": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "15", + "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", + "block_time": 1800005000, + "event_id": "-9223371972430135287", + "transaction_index": 2, + "event_index": 8, + "transaction_hash": "0x31c86889cab5b68293fcf87ab88a7858f1f23839018ca20a1e405177bf91f05f" + }, + "ledger_event_id": "-9223371972430135289", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "1005061732667368674310164", + "quote_token_amount": "184817928874495997630716" + } + ] + } + }, + "l6": { + "detail": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", + "fee": "0", + "tick_spacing": 100, + "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" + }, + "token": { + "address": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", + "name": "Zero Seed l6", + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "sink": "790", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "launch_token_is_token1": false, + "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "ask_tick": -13815500, + "upper_tick": -9210300, + "fee_schedule": { + "model": "tick_rate_v2", + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay_rate_x32": "1", + "half_life_seconds": "5954091920683119.788732", + "saturation_elapsed_seconds": "381061679028371455", + "phase": "at_floor", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "created": { + "block_number": "8", + "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", + "block_time": 1800001310, + "event_id": "-9223372002494775266", + "transaction_index": 4, + "event_index": 29, + "transaction_hash": "0xf1b11401ab1b29b9ea159a52ee41d4596615bf8c0b36378ea4bfcdd9dda04be3" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", + "tick_lower": -13815500, + "tick_upper": -9210300, + "liquidity": "1111119116089565312897934", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999000916626263180802490332", + "quote_token": "1000000000000010019" + }, + "reserves_basis": "core_pool_balance" + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "transferable": true, + "launch_token": { + "accrued": "999083373736809197454718", + "claimed": "999083373736809197454718", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "184817928874495997630685", + "claimed": "184817928874495997630685", + "unclaimed": "0" + }, + "swap_count": 2, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371985314906095" + }, + "inventory": { + "trading_open": true, + "no_bid": false, + "price_tick": -13813701 + } + }, + "swaps": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998199611376", + "transaction_index": 7, + "event_index": 15, + "transaction_hash": "0x5e2d078babc19e43eb6ac6facefed693888d2b3be2801641d5763fba8b4c795a" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-54160", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "999083373736809197454718", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", + "block_time": 1800001400, + "event_id": "-9223371998199545836", + "transaction_index": 8, + "event_index": 19, + "transaction_hash": "0xc899747848e1e0407a0f86124ce268a72ba5e155a225446528f80e0e78ca7e89" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "184817928874495997640704", + "fee_rate": "18446744073709551615", + "fee_amount": "184817928874495997630685", + "fee_asset": "quote_token" + } + ] + }, + "claims": { + "schema_version": "zero-seed-launch-api/v5", + "chain_id": "31337", + "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "12", + "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", + "block_time": 1800002800, + "event_id": "-9223371985314906095", + "transaction_index": 4, + "event_index": 16, + "transaction_hash": "0x1da96d809d89d9b64dc2d110c55cebeef6315ad00e8468ccae6fd771db4dc3db" + }, + "ledger_event_id": "-9223371985314906098", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "999083373736809197454718", + "quote_token_amount": "184817928874495997630685" + } + ] + } + } +} diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json similarity index 97% rename from src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json rename to src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json index 7143023..def794c 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v4.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json @@ -1,14 +1,14 @@ { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "source": { "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain.json", "chain_id": "31337" }, "list": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "data": [ { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4b680c35b2badd8287a7bd61a44c5105c047e06496b6aa83cc95255893c7f5b3", "pool_key": { @@ -46,6 +46,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001300, "fee_duration": 3600, "fee_end": 1800004900, @@ -83,10 +84,10 @@ } }, "second": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "data": [ { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { @@ -124,6 +125,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 600, "fee_end": 1800001600, @@ -153,7 +155,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { @@ -191,6 +193,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 3600, "fee_end": 1800004600, @@ -228,10 +231,10 @@ } }, "canonicalList": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "data": [ { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", "pool_key": { @@ -269,6 +272,7 @@ "ask_tick": -88722835, "upper_tick": 88722835, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800005200, "fee_duration": 3600, "fee_end": 1800008800, @@ -298,7 +302,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", "pool_key": { @@ -336,6 +340,7 @@ "ask_tick": -88080384, "upper_tick": -85983232, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800005200, "fee_duration": 3600, "fee_end": 1800008800, @@ -365,7 +370,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "pool_key": { @@ -403,6 +408,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 1, "fee_end": 1800001401, @@ -432,7 +438,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "pool_key": { @@ -470,6 +476,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 3600, "fee_end": 1800005000, @@ -499,7 +506,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", "pool_key": { @@ -537,6 +544,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 600, "fee_end": 1800002000, @@ -566,7 +574,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { @@ -604,6 +612,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 600, "fee_end": 1800001600, @@ -633,7 +642,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { @@ -671,6 +680,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 3600, "fee_end": 1800004600, @@ -708,7 +718,7 @@ } }, "nativeDetail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "pool_key": { @@ -746,6 +756,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 3600, "fee_end": 1800004600, @@ -811,7 +822,7 @@ } }, "nativeSwaps": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, @@ -1004,7 +1015,7 @@ ] }, "nativeClaims": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, @@ -1081,7 +1092,7 @@ ] }, "nativeOwners": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", "next_cursor": null, @@ -1133,7 +1144,7 @@ ] }, "erc20Detail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "pool_key": { @@ -1171,6 +1182,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001000, "fee_duration": 600, "fee_end": 1800001600, @@ -1236,7 +1248,7 @@ } }, "erc20Swaps": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, @@ -1321,7 +1333,7 @@ ] }, "erc20Claims": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, @@ -1350,7 +1362,7 @@ ] }, "erc20Owners": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", "next_cursor": null, @@ -1389,7 +1401,7 @@ ] }, "erc20TraderOwnedDetail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", "pool_key": { @@ -1427,6 +1439,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 600, "fee_end": 1800002000, @@ -1492,7 +1505,7 @@ } }, "maxNativeDetail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "pool_key": { @@ -1530,6 +1543,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 3600, "fee_end": 1800005000, @@ -1595,7 +1609,7 @@ } }, "maxNativeSwaps": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "next_cursor": null, @@ -1698,7 +1712,7 @@ ] }, "maxNativeClaims": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", "next_cursor": null, @@ -1727,7 +1741,7 @@ ] }, "maxErc20Detail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "pool_key": { @@ -1765,6 +1779,7 @@ "ask_tick": -13815500, "upper_tick": -9210300, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800001400, "fee_duration": 1, "fee_end": 1800001401, @@ -1830,7 +1845,7 @@ } }, "maxErc20Swaps": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "next_cursor": null, @@ -1879,7 +1894,7 @@ ] }, "maxErc20Claims": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", "next_cursor": null, @@ -1908,7 +1923,7 @@ ] }, "lowPriceErc20Detail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", "pool_key": { @@ -1946,6 +1961,7 @@ "ask_tick": -88080384, "upper_tick": -85983232, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800005200, "fee_duration": 3600, "fee_end": 1800008800, @@ -2011,7 +2027,7 @@ } }, "fullWidthErc20Detail": { - "schema_version": "zero-seed-launch-api/v4", + "schema_version": "zero-seed-launch-api/v5", "chain_id": "31337", "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", "pool_key": { @@ -2049,6 +2065,7 @@ "ask_tick": -88722835, "upper_tick": 88722835, "fee_schedule": { + "model": "fixed_base_duration_v1", "trading_start": 1800005200, "fee_duration": 3600, "fee_end": 1800008800, diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 4812514..4999da0 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -1,5 +1,7 @@ import { expect, test } from "bun:test"; -import golden from "./fixtures/zero-seed-launch-api-v4.golden.json"; +import golden from "./fixtures/zero-seed-launch-api-v5.golden.json"; +import tickRate from "./fixtures/zero-seed-launch-api-v5-tick-rate.golden.json"; +import { halfLifeSeconds, MAX_DECAY_PRODUCT } from "./policy"; import { ClaimPage, LaunchDetail, @@ -10,9 +12,9 @@ import { } from "./schema"; // Written by scripts/verifyLaunchReplay.ts (LAUNCH_GOLDEN_DIR) from the actual -// handlers over the indexer's replayed local fixture; a machine-readable -// example of every v4 response for integrators. -test("the emitted-state golden responses parse with the v4 schemas", () => { +// handlers over the indexer's replayed local fixtures; a machine-readable +// example of every v5 response for integrators, per fee model. +test("the emitted-state golden responses parse with the v5 schemas", () => { expect(golden.schema_version).toBe(SCHEMA_VERSION); for (const list of [golden.list, golden.second, golden.canonicalList]) LaunchList.parse(list); @@ -109,6 +111,7 @@ test("launches created at the maximum rate 2^64 - 1 are served exactly, never as false, ]); expect(l5.fee_schedule).toMatchObject({ + model: "fixed_base_duration_v1", initial_fee: MAX, final_fee: FINAL, fee_duration: 3600, @@ -204,3 +207,60 @@ test("low-price ERC-20 token0 launches serve the exact installation, including a BigInt(detail.supply.requested), ); }); + +test("tick-rate golden: every response parses, rates are decimal strings and schedules have a floor, not an end", () => { + expect(tickRate.schema_version).toBe(SCHEMA_VERSION); + expect(tickRate.source.fee_model).toBe("tick_rate_v2"); + LaunchList.parse(tickRate.list); + const launches = [ + tickRate.l1, + tickRate.l2, + tickRate.l4, + tickRate.l5, + tickRate.l6, + ]; + for (const { detail, swaps, claims } of launches) { + LaunchDetail.parse(detail); + SwapPage.parse(swaps); + ClaimPage.parse(claims); + } + for (const launch of [ + ...tickRate.list.data, + ...launches.map((l) => l.detail), + ]) { + const schedule = launch.fee_schedule; + expect(schedule.model).toBe("tick_rate_v2"); + expect(schedule).not.toHaveProperty("fee_end"); + expect(schedule).not.toHaveProperty("fee_duration"); + if (schedule.model !== "tick_rate_v2") throw new Error("model"); + const rate = BigInt(schedule.decay_rate_x32); + expect(schedule.half_life_seconds).toBe(halfLifeSeconds(rate)); + expect(schedule.saturation_elapsed_seconds).toBe( + rate === 0n ? null : (MAX_DECAY_PRODUCT / rate).toString(), + ); + } + expect( + tickRate.list.data.map((l) => l.fee_schedule.decay_rate_x32).sort(), + ).toEqual( + [ + "0", + "1", + "68913100933", + "68913100933", + "68913100933", + "9923486534471", + MAX, + ].sort(), + ); + // rate 0 is flat at the initial fee; every other launch is at its floor by the head + expect(tickRate.l4.detail.fee_schedule).toMatchObject({ + decay_rate_x32: "0", + phase: "constant", + half_life_seconds: null, + }); + expect(tickRate.l1.detail.fee_schedule).toMatchObject({ + decay_rate_x32: "9923486534471", + half_life_seconds: "600.000000", + phase: "at_floor", + }); +}); diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index c44a346..6b0e8e6 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -7,7 +7,13 @@ import { DECAY_ZERO_FROM, decayQ128, decayedFee, + decayRateForHalfLife, feeSchedule, + halfLifeSeconds, + HALF_LIFE_X32_E30, + MAX_DECAY_PRODUCT, + tickFactorQ128, + tickRateDecayedFee, launchSupply, LOCAL_INSTANCES, LOCAL_QUOTES, @@ -17,6 +23,7 @@ import { } from "./policy"; import { ClaimPage, + FeeSchedule, LaunchDetail, LaunchList, LaunchSummary, @@ -30,7 +37,10 @@ test("local instance manifests bind every runtime/ABI/periphery/Core field and c expect(supportedInstances(LOCAL_INSTANCES, true)).toEqual(LOCAL_INSTANCES); expect(supportedInstances(LOCAL_INSTANCES, false)).toEqual([]); expect(supportedInstances([], true)).toEqual([]); - expect(LOCAL_INSTANCES).toHaveLength(1); + expect(LOCAL_INSTANCES.map((pin) => pin.feeModel)).toEqual([ + "fixed_base_duration_v1", + "tick_rate_v2", + ]); for (const pin of LOCAL_INSTANCES) { expect(Object.keys(pin).some((key) => /quote/i.test(key))).toBe(false); for (const key of Object.keys(pin) as (keyof typeof pin)[]) { @@ -40,7 +50,13 @@ test("local instance manifests bind every runtime/ABI/periphery/Core field and c } const native = { ...pin, quote: NATIVE, quoteKind: "native" as const }; expect(supportedLaunch(native, LOCAL_INSTANCES, [])).toBe(true); - for (const key of ["chainId", "address", "periphery", "core"] as const) { + for (const key of [ + "chainId", + "feeModel", + "address", + "periphery", + "core", + ] as const) { expect( supportedLaunch( { ...native, [key]: "unreviewed" }, @@ -145,6 +161,7 @@ test("supply: 0 < installed <= requested and sink = requested - installed, else }); const input = { + model: "fixed_base_duration_v1" as const, start: 1000n, duration: 3600n, initial: 9223372036854775808n, @@ -163,6 +180,7 @@ test("fee phases use indexed time only; 50% -> 0.3% over 3600 s", () => { const result = feeSchedule({ ...input, indexedTime }); expect(result.phase).toBe(phase); if (rate) expect(result.fee_rate).toBe(rate); + if (result.model !== "fixed_base_duration_v1") throw new Error("model"); expect(result.decay).toEqual({ kind: "fixed_base_q128", half_life_seconds: 600, @@ -260,6 +278,7 @@ const MAX = (1n << 64n) - 1n; test("the whole uint64 fee range is served exactly; 2^64 - 1 is the maximum, not a cap below it", () => { // l5 of the local fixture: max -> 0.3% over 3600 s, trading from 1800001400 const l5 = { + model: "fixed_base_duration_v1" as const, start: 1800001400n, duration: 3600n, initial: MAX, @@ -284,6 +303,7 @@ test("the whole uint64 fee range is served exactly; 2^64 - 1 is the maximum, not // l6: flat at the maximum expect( feeSchedule({ + model: "fixed_base_duration_v1", start: 1800001400n, duration: 1n, initial: MAX, @@ -309,7 +329,7 @@ test("the whole uint64 fee range is served exactly; 2^64 - 1 is the maximum, not }); test("fee fields are canonical decimal uint64 strings; 2^64 and malformed encodings are never served", () => { - const schedule = LaunchDetail.shape.fee_schedule; + const schedule = FeeSchedule; const ok = feeSchedule({ ...input, indexedTime: 1000n }); expect(schedule.safeParse(ok).success).toBe(true); for (const field of ["initial_fee", "final_fee", "fee_rate"] as const) { @@ -383,7 +403,7 @@ test("invalid indexed domains fail, never coerce or overflow", () => { test("frozen schemas expose no staged status/stats/exhausted or decay-factor fields", () => { const empty = { - schema_version: "zero-seed-launch-api/v4", + schema_version: "zero-seed-launch-api/v5", as_of: { block_number: "1", block_hash: `0x${"a".repeat(64)}`, @@ -396,7 +416,7 @@ test("frozen schemas expose no staged status/stats/exhausted or decay-factor fie expect( LaunchList.safeParse({ ...empty, - schema_version: "zero-seed-launch-api/v3", + schema_version: "zero-seed-launch-api/v4", }).success, ).toBe(false); expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe( @@ -415,9 +435,12 @@ test("frozen schemas expose no staged status/stats/exhausted or decay-factor fie expect(Object.keys(LaunchDetail.shape.inventory.shape)).not.toContain( "exhausted", ); - expect(Object.keys(LaunchDetail.shape.fee_schedule.shape)).not.toContain( - "decay_factor", - ); + for (const option of FeeSchedule.options) + expect(Object.keys(option.shape)).not.toContain("decay_factor"); + // the tick-rate schedule has a floor, never an end + const [, tickRate] = FeeSchedule.options; + for (const field of ["fee_end", "fee_duration", "ends_at", "end"]) + expect(Object.keys(tickRate.shape)).not.toContain(field); }); test("v4 supply is on every summary and replaces principal.installed_supply", () => { @@ -451,3 +474,211 @@ test("v4 supply is on every summary and replaces principal.installed_supply", () ]) expect(supply.safeParse(bad).success).toBe(false); }); + +// ---- tick_rate_v2 (evm-contracts 3d6378f) against the contract's frozen v7 vectors ---- + +const V2 = JSON.parse( + readFileSync( + resolve(import.meta.dir, "vectors/zero-seed-launch-v2-vectors.json"), + "utf8", + ), +); +// Elapsed seconds beyond 2^64 cannot be indexed; every positive rate has +// saturated long before. +const elapsedOf = (t: string) => { + const value = BigInt(t); + return value > 1n << 63n ? 1n << 63n : value; +}; + +test("tick-rate fee: every frozen fee row, table, tick step and saturation second", () => { + expect(V2.version).toBe(7); + const fx = V2.fixture; + for (const row of V2.fee) + expect( + tickRateDecayedFee( + BigInt(fx.fee_initial_q64), + BigInt(fx.fee_final_q64), + BigInt(row.decayRateX32), + elapsedOf(row.t_minus_start), + ).toString(), + ).toBe(row.fee_q64); + const curve = V2.fee_curve; + const r = curve.rates_q64; + const d = curve.decay_rates_x32; + const f03 = BigInt(curve.final_fee_q64); + const tables: [string, bigint, bigint, bigint][] = [ + [ + "default_one_day_50_to_0.3", + BigInt(r["50"]), + f03, + BigInt(d.default_one_day), + ], + [ + "two_ticks_per_second_50_to_0.3", + BigInt(r["50"]), + f03, + BigInt(d.two_ticks_per_second), + ], + [ + "two_ticks_per_second_99.9_to_0", + BigInt(r["99.9"]), + 0n, + BigInt(d.two_ticks_per_second), + ], + ["default_one_day_max_to_0", MAX, 0n, BigInt(d.default_one_day)], + ["min_rate_max_to_0", MAX, 0n, 1n], + ]; + for (const [name, initial, final, rate] of tables) + for (const row of curve.tables[name]) + expect( + tickRateDecayedFee(initial, final, rate, elapsedOf(row.t)).toString(), + ).toBe(row.fee_q64); + for (const steps of curve.tick_steps) { + const initial = + BigInt(steps.rows[0].fee_q64) === f03 + 1n ? f03 + 1n : BigInt(r["50"]); + for (const row of steps.rows) + expect( + tickRateDecayedFee( + initial, + f03, + BigInt(steps.decayRateX32), + BigInt(row.t), + ).toString(), + ).toBe(row.fee_q64); + } + for (const [name, second] of Object.entries(curve.saturation_second)) { + const rate = BigInt(d[name]); + expect((MAX_DECAY_PRODUCT / rate).toString()).toBe(second as string); + expect( + tickRateDecayedFee(MAX, 0n, rate, BigInt(second as string) + 1n), + ).toBe(0n); + } + expect(tickFactorQ128(0n)).toBe(1n << 128n); + expect(tickFactorQ128(88722835n)).toBe(18447191164202170524n); + expect(() => tickFactorQ128(88722836n)).toThrow(); +}); + +test("tick-rate fee: the five SHA-256 chains the contract tests pin", async () => { + for (const chain of V2.fee_curve.digests_sha256) { + let h = new Uint8Array(32); + for (let t = 0n; t <= BigInt(chain.last); t += BigInt(chain.step)) { + const fee = tickRateDecayedFee( + BigInt(chain.initialFee), + BigInt(chain.finalFee), + BigInt(chain.decayRateX32), + t, + ); + const word = new Uint8Array(40); + word.set(h, 0); + new DataView(word.buffer).setBigUint64(32, fee); + h = new Uint8Array(await crypto.subtle.digest("SHA-256", word)); + } + expect(Buffer.from(h).toString("hex")).toBe(chain.digest); + } +}, 120_000); + +test("tick-rate schedule: decimal-string rate, exact half-life, saturation, phases, no end", () => { + const consumer = V2.creator_fee_range.reference_schedules.consumer_default; + expect(decayRateForHalfLife(86400n).toString()).toBe(consumer.decayRateX32); + expect(decayRateForHalfLife(600n)).toBe(9923486534471n); + expect(HALF_LIFE_X32_E30.toString()).toBe( + "5954091920683119788732747343471285361579197626", + ); + for (const [rate, text] of [ + [68913100933n, "86400.000001"], + [9923486534471n, "600.000000"], + [8589934592n, "693147.527133"], + [1073741824n, "5545180.217067"], + [1n, "5954091920683119.788732"], + [MAX, "0.000322"], + ] as const) + expect(halfLifeSeconds(rate)).toBe(text); + expect(halfLifeSeconds(0n)).toBeNull(); + const base = { + model: "tick_rate_v2" as const, + start: 1000n, + rate: BigInt(consumer.decayRateX32), + initial: BigInt(consumer.initialFee), + final: BigInt(consumer.finalFee), + }; + const at = (indexedTime: bigint, change = {}) => + feeSchedule({ ...base, ...change, indexedTime }); + expect(at(999n)).toEqual({ + model: "tick_rate_v2", + trading_start: 1000, + initial_fee: consumer.initialFee, + final_fee: consumer.finalFee, + decay_rate_x32: "68913100933", + half_life_seconds: "86400.000001", + saturation_elapsed_seconds: "5529597", + phase: "before_trading_start", + fee_rate: consumer.initialFee, + evaluated_at: 999, + }); + expect(at(1000n + 86400n)).toMatchObject({ + phase: "decaying", + fee_rate: consumer.fee_after_one_day_q64, + }); + expect(at(1000n + 5529598n)).toMatchObject({ + phase: "at_floor", + fee_rate: consumer.finalFee, + }); + expect(at(5000n, { rate: 0n })).toMatchObject({ + phase: "constant", + fee_rate: consumer.initialFee, + half_life_seconds: null, + saturation_elapsed_seconds: null, + }); + expect(at(1001n, { rate: MAX })).toMatchObject({ + phase: "at_floor", + saturation_elapsed_seconds: "0", + }); + expect(at(5000n, { initial: 7n, final: 7n })).toMatchObject({ + phase: "at_floor", + }); + for (const schedule of [ + at(999n), + at(5000n, { rate: 0n }), + at(2000n, { rate: MAX }), + ]) { + expect(FeeSchedule.safeParse(schedule).success).toBe(true); + expect(schedule).not.toHaveProperty("fee_end"); + } + // The rate is a canonical decimal uint64 string, never a number or 2^64. + const ok = at(2000n); + for (const bad of [ + "18446744073709551616", + "01", + "-1", + "1e3", + "", + 68913100933, + ]) + expect(FeeSchedule.safeParse({ ...ok, decay_rate_x32: bad }).success).toBe( + false, + ); + for (const bad of ["86400", "86400.00000", "-1.000000", 86400]) + expect( + FeeSchedule.safeParse({ ...ok, half_life_seconds: bad }).success, + ).toBe(false); + // A v2 row cannot carry v1 fields, nor v1 a v2 rate. + expect(FeeSchedule.safeParse({ ...ok, fee_duration: 3600 }).success).toBe( + false, + ); + expect( + FeeSchedule.safeParse({ + ...feeSchedule({ ...input, indexedTime: 1000n }), + decay_rate_x32: "1", + }).success, + ).toBe(false); + // Out-of-domain indexed values fail as 500, never coerce. + for (const change of [ + { rate: 1n << 64n }, + { rate: -1n }, + { final: base.initial + 1n }, + ]) + expect(() => at(2000n, change)).toThrow(); + expect(() => + feeSchedule({ ...input, model: "other" as never, indexedTime: 1000n }), + ).toThrow(/fee model/); +}); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 347fefb..dd9bfa3 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -7,6 +7,18 @@ export const ABI_SHA256 = "efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e"; export const PERIPHERY_ABI_SHA256 = "df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305"; +// The same of the tick-rate revision, evm-contracts 3d6378f. +export const V2_ABI_SHA256 = + "89ff4d15e5e8b4f044c4804d460df915e9206bbdbdcef22cf4afe0fbbf4eaadd"; +export const V2_PERIPHERY_ABI_SHA256 = + "a6d43b362d04eeb4e636ee32a031fc06995a2fd8bb71fb63750f27836a0ddb07"; + +/** + * The fee math of a deployment's contract revision, fixed for its life and + * never inferred from a row: fixed_base_duration_v1 is evm-contracts 43d6628 + * (uint32 feeDuration), tick_rate_v2 is 3d6378f (uint64 decayRateX32). + */ +export type FeeModel = "fixed_base_duration_v1" | "tick_rate_v2"; const NATIVE = "0x0000000000000000000000000000000000000000" as const; /** @@ -16,6 +28,7 @@ const NATIVE = "0x0000000000000000000000000000000000000000" as const; */ export interface LaunchInstance { chainId: string; + feeModel: FeeModel; address: string; runtime: string; periphery: string; @@ -32,13 +45,15 @@ export interface LaunchQuote { runtime: string; } -// The constructor-executed LOCAL instance of the indexer replay fixture -// (evm-contracts 43d6628, anvil, no fork), serving native-ETH and ERC-20 -// quote launches. No live instance exists. Never enable it via a hosted -// configuration. +// The constructor-executed LOCAL instances of the indexer replay fixtures +// (anvil, no fork), one per fee model: evm-contracts 43d6628 +// (local-chain.json) and 3d6378f (local-chain-v2.json), each serving native-ETH +// and ERC-20 quote launches. No live instance exists. Never enable them via a +// hosted configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", + feeModel: "fixed_base_duration_v1", address: "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f", runtime: "0xd6b4c7ce8da56e9948fdb004a035d9b52dbd3690bcb03c1269bbf2a9981d8ed5", @@ -49,6 +64,19 @@ export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ abiSha256: ABI_SHA256, peripheryAbiSha256: PERIPHERY_ABI_SHA256, }, + { + chainId: "31337", + feeModel: "tick_rate_v2", + address: "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d", + runtime: + "0xe08ad382497fc9825956c2b4e9ccf463014c4ed65baf50c0a59db8d7b7aedfc3", + periphery: "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", + peripheryRuntime: + "0xf72d3cd6b99e1b64f0b08c0f6933b4f8adb92464ded48c51bfb7d2805c09d667", + core: "0x00000000000014aa86c5d3c41765bb24e11bd701", + abiSha256: V2_ABI_SHA256, + peripheryAbiSha256: V2_PERIPHERY_ABI_SHA256, + }, ]; // The fixture's plain 18-decimal test ERC-20 (evm-contracts @@ -107,7 +135,10 @@ export function supportedQuote( } export function supportedLaunch( - row: Pick & { + row: Pick< + LaunchInstance, + "chainId" | "feeModel" | "address" | "periphery" | "core" + > & { quote: string; quoteKind: "native" | "erc20"; }, @@ -118,6 +149,7 @@ export function supportedLaunch( enabled.some( (pin) => pin.chainId === row.chainId && + pin.feeModel === row.feeModel && pin.address === row.address && pin.periphery === row.periphery && pin.core === row.core, @@ -233,16 +265,124 @@ export function decayedFee( ); } -export function feeSchedule(input: { +/** Largest tick magnitude of the contracts' tick helper. */ +export const MAX_TICK_MAGNITUDE = 88722835n; +/** (MAX_TICK_MAGNITUDE + 1) * 2^32 - 1 (math/feeDecay.sol at 3d6378f). */ +export const MAX_DECAY_PRODUCT = ((MAX_TICK_MAGNITUDE + 1n) << 32n) - 1n; +/** + * floor(2 * ln 2 / ln 1.000001 * 2^32 * 10^30): ticks per half-life of the + * excess, Q32.32, scaled by 10^30. The normative integer for every half-life + * conversion; never a float. + */ +export const HALF_LIFE_X32_E30 = + 5954091920683119788732747343471285361579197626n; +/** The bit constants of tickToSqrtRatio for negative ticks (math/ticks.sol). */ +const TICK_MASKS: readonly bigint[] = [ + 340282196779882608775400081051345954875n, + 340282026638911824551550055881712329744n, + 340281686357225467326082729798982530761n, + 340281005794873596573094710012989410072n, + 340279644674253220331048947518555486588n, + 340276922449345852680114346161748269428n, + 340271478064864046934932689706268169160n, + 340260589557227275536970734808027446368n, + 340238813587222069206590352882444122789n, + 340195265827972829906238800850533898084n, + 340108187030021964395011332102514443192n, + 339934096296338836553381438728885759063n, + 339586182118148867994722769619739863337n, + 338891421642114786711725022995822495130n, + 337506162020136007223375792700736547955n, + 334752606878476170032608184381010470119n, + 329312708224965356314372077522414179198n, + 318696677643769341895946499718036526719n, + 298480268784467865547185399201474874621n, + 261813363001402740542020888190605903085n, + 201439286044552466954512953679502885114n, + 119247395418425876229630149779831629514n, + 41788651709309085859755118322591422179n, + 5131889223303998677939799486543748348n, + 77395391476111124436543048938586576n, + 17603164912545197391926702422886n, + 910630244353091011850553n, +]; + +/** + * toFixed(tickToSqrtRatio(-magnitude)): from 2^128, multiply by the constant + * of every set bit in ascending order and floor after each multiply, then + * round down to the compact 94-bit mantissa of its region. + */ +export function tickFactorQ128(magnitude: bigint): bigint { + if (magnitude < 0n || magnitude > MAX_TICK_MAGNITUDE) + throw new Error("Tick magnitude out of range"); + let ratio = Q128; + TICK_MASKS.forEach((mask, bit) => { + if ((magnitude >> BigInt(bit)) & 1n) ratio = (ratio * mask) >> 128n; + }); + const shift = ratio >= Q128 ? 66n : ratio >= 1n << 96n ? 34n : 2n; + return (ratio >> shift) << shift; +} + +/** + * decayedFee of the tick-rate contract (evm-contracts 3d6378f) in its + * normative order: rate 0 is initial; elapsed > MAX_DECAY_PRODUCT / rate + * (integer division first) is final, the floor; otherwise + * final + floor((initial - final) * tickFactorQ128(floor(rate * elapsed / 2^32)) / 2^128). + */ +export function tickRateDecayedFee( + initial: bigint, + final: bigint, + rate: bigint, + elapsed: bigint, +): bigint { + if (elapsed <= 0n || rate === 0n) return initial; + if (elapsed > MAX_DECAY_PRODUCT / rate) return final; + return ( + final + + (((initial - final) * tickFactorQ128((rate * elapsed) >> 32n)) >> 128n) + ); +} + +/** decayRateX32 for a half-life of `seconds` (> 0): floor(HALF_LIFE_X32_E30 / (seconds * 10^30)). */ +export function decayRateForHalfLife(seconds: bigint): bigint { + if (seconds <= 0n) throw new Error("Half-life must be positive"); + return HALF_LIFE_X32_E30 / (seconds * 10n ** 30n); +} + +/** The half-life of the excess, 6 fractional digits, floored; null for rate 0. Display only. */ +export function halfLifeSeconds(rate: bigint): string | null { + if (rate === 0n) return null; + const micros = HALF_LIFE_X32_E30 / (rate * 10n ** 24n); + const fraction = (micros % 1_000_000n).toString().padStart(6, "0"); + return `${micros / 1_000_000n}.${fraction}`; +} + +type FeeScheduleInput = { start: bigint; - duration: bigint; initial: bigint; final: bigint; indexedTime: bigint; -}): z.infer { - const { start, duration, initial, final, indexedTime } = input; - [start, duration, initial, final, indexedTime].forEach(uint64); - if (duration === 0n || duration > 0xffffffffn || final > initial) { +} & ( + | { model: "fixed_base_duration_v1"; duration: bigint } + | { model: "tick_rate_v2"; rate: bigint } +); + +/** + * The served schedule of an indexed launch, by its deployment's fee model. + * Anything outside the model's domain is malformed indexed data (500). + */ +export function feeSchedule( + input: FeeScheduleInput, +): z.infer { + const { start, initial, final, indexedTime } = input; + [start, initial, final, indexedTime].forEach(uint64); + if (final > initial) throw new Error("Invalid indexed fee schedule"); + if (input.model === "tick_rate_v2") return tickRateSchedule(input); + if (input.model !== "fixed_base_duration_v1") + throw new Error("Invalid indexed fee model"); + const { duration } = input; + uint64(duration); + if (duration === 0n || duration > 0xffffffffn) { throw new Error("Invalid indexed fee schedule"); } const end = uint64(start + duration); @@ -250,6 +390,7 @@ export function feeSchedule(input: { const elapsed = indexedTime > start ? indexedTime - start : 0n; const rate = decayedFee(initial, final, duration, elapsed); return { + model: "fixed_base_duration_v1", trading_start: seconds(start), fee_duration: seconds(duration), fee_end: seconds(end), @@ -262,6 +403,33 @@ export function feeSchedule(input: { }; } +/** v2: final_fee is a floor that is approached; there is no end time. */ +function tickRateSchedule( + input: FeeScheduleInput & { model: "tick_rate_v2" }, +): z.infer { + const { start, initial, final, indexedTime, rate } = input; + uint64(rate); + const fee = tickRateDecayedFee(initial, final, rate, indexedTime - start); + let phase: "before_trading_start" | "at_floor" | "constant" | "decaying"; + if (indexedTime < start) phase = "before_trading_start"; + else if (fee === final) phase = "at_floor"; + else if (rate === 0n) phase = "constant"; + else phase = "decaying"; + return { + model: "tick_rate_v2", + trading_start: seconds(start), + initial_fee: initial.toString(), + final_fee: final.toString(), + decay_rate_x32: rate.toString(), + half_life_seconds: halfLifeSeconds(rate), + saturation_elapsed_seconds: + rate === 0n ? null : (MAX_DECAY_PRODUCT / rate).toString(), + phase, + fee_rate: fee.toString(), + evaluated_at: seconds(indexedTime), + }; +} + function feePhase(start: bigint, end: bigint, time: bigint) { if (time < start) return "before_trading_start"; if (time < end) return "fee_decay"; diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index 4eec21c..c1ca6a8 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -57,7 +57,9 @@ interface LaunchRow extends EventRow { upper_tick: number; tick_spacing: bigint; trading_start: bigint; - fee_duration: bigint; + fee_model: string; + fee_duration: bigint | null; + decay_rate_x32: Numeric | null; initial_fee: Numeric; final_fee: Numeric; position_id: Numeric; @@ -139,8 +141,8 @@ function summary(row: LaunchRow, asOf: z.infer) { ask_tick: row.ask_tick, upper_tick: row.upper_tick, fee_schedule: feeSchedule({ + ...feeDecay(row), start: row.trading_start, - duration: row.fee_duration, initial: BigInt(row.initial_fee), final: BigInt(row.final_fee), indexedTime: BigInt(asOf.block_time), @@ -149,6 +151,28 @@ function summary(row: LaunchRow, asOf: z.infer) { as_of: asOf, }; } +// Exactly the row's model parameter; anything else is malformed indexed data. +function feeDecay(row: LaunchRow) { + if ( + row.fee_model === "fixed_base_duration_v1" && + row.fee_duration !== null && + row.decay_rate_x32 === null + ) + return { + model: "fixed_base_duration_v1" as const, + duration: BigInt(row.fee_duration), + }; + if ( + row.fee_model === "tick_rate_v2" && + row.fee_duration === null && + row.decay_rate_x32 !== null + ) + return { + model: "tick_rate_v2" as const, + rate: BigInt(row.decay_rate_x32), + }; + throw new Error("Invalid indexed fee model"); +} // Native ETH has no token contract: 18 decimals, no ERC-20 metadata. function quoteToken(row: LaunchRow) { if (row.quote_kind === "native") @@ -303,6 +327,7 @@ export class LaunchQueries { .map( (p) => tx` (c.emitter = ${BigInt(p.address).toString()}::numeric + AND c.fee_model = ${p.feeModel} AND c.controller = ${BigInt(p.periphery).toString()}::numeric AND pk.core_address = ${BigInt(p.core).toString()}::numeric) `, diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index 0f24351..d46abd8 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,7 +1,9 @@ import { z } from "@hono/zod-openapi"; +// v5: fee_schedule is a union on `model`, the deployment's fee model +// (fixed_base_duration_v1, evm-contracts 43d6628; tick_rate_v2, 3d6378f). // v4: requested/installed/sink supply, per-launch quote. -export const SCHEMA_VERSION = "zero-seed-launch-api/v4" as const; +export const SCHEMA_VERSION = "zero-seed-launch-api/v5" as const; export const SchemaVersion = z.literal(SCHEMA_VERSION); const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); @@ -48,13 +50,16 @@ const PoolKey = z.strictObject({ tick_spacing: z.number().int().positive(), extension: Address, }); -export const FeeSchedule = z.strictObject({ +// fixed_base_duration_v1 (evm-contracts 43d6628): unchanged from v4 apart from +// the discriminator. 2^(-t/600) from a fixed Q128 table, rescaled to reach +// final_fee at fee_end. +const FixedBaseDurationSchedule = z.strictObject({ + model: z.literal("fixed_base_duration_v1"), trading_start: Seconds, fee_duration: z.number().int().positive(), fee_end: Seconds, initial_fee: Q64, final_fee: Q64, - // 2^(-t/600) from a fixed Q128 table, rescaled to reach final_fee at fee_end. decay: z.strictObject({ kind: z.literal("fixed_base_q128"), half_life_seconds: z.literal(600), @@ -63,6 +68,56 @@ export const FeeSchedule = z.strictObject({ fee_rate: Q64, evaluated_at: Seconds, }); +// A canonical decimal uint64 that is not a fee: the Q32.32 decay rate. +const Uint64 = U.max(20) + .refine( + (value) => /^(0|[1-9][0-9]*)$/.test(value) && BigInt(value) <= UINT64_MAX, + ) + .openapi({ + description: + "Canonical decimal uint64 (0 to 18446744073709551615), never a JSON number", + }); +// tick_rate_v2 (evm-contracts 3d6378f): final_fee is the long-run floor the +// fee approaches, reached only by rounding or numerical saturation; there is +// no end time. fee = final + floor((initial - final) * +// toFixed(tickToSqrtRatio(-m)) / 2^128), m = floor(rate * elapsed / 2^32). +const TickRateSchedule = z.strictObject({ + model: z.literal("tick_rate_v2"), + trading_start: Seconds, + initial_fee: Q64, + final_fee: Q64.openapi({ + description: + "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled", + }), + decay_rate_x32: Uint64.openapi({ + description: + "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee", + }), + // floor(HALF_LIFE_X32_E30 * 10^6 / (rate * 10^30)) microseconds, display + // only; null for rate 0. + half_life_seconds: z + .string() + .regex(/^(0|[1-9][0-9]*)\.[0-9]{6}$/) + .nullable() + .openapi({ + description: + "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only", + }), + // floor(381061679028371455 / rate): the last elapsed second the curve is + // evaluated at; from the next one the fee is exactly final_fee. Not an end + // of fees. + saturation_elapsed_seconds: U.nullable().openapi({ + description: + "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end", + }), + phase: z.enum(["before_trading_start", "decaying", "at_floor", "constant"]), + fee_rate: Q64, + evaluated_at: Seconds, +}); +export const FeeSchedule = z.discriminatedUnion("model", [ + FixedBaseDurationSchedule, + TickRateSchedule, +]); // Raw launch-token units. requested is LaunchParameters.supply and the // token's totalSupply; installed is what Core took into the position at // creation (all circulating inventory); sink = requested - installed was diff --git a/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json b/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json new file mode 100644 index 0000000..cb0fc0a --- /dev/null +++ b/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json @@ -0,0 +1,1867 @@ +{ + "version": 7, + "fixture": { + "core": "Locally compiled src/Core.sol deployed at the test's canonical Core address; no fork or live-chain validation", + "router": "Locally compiled test/LaunchTestRouter.sol; standard-ABI single-hop forwarding and settlement, with snapshot/revert quotes in the test harness; no deployed-router bytecode fixture", + "supply_requested": "1000000000000000000000000000", + "tick_spacing": 100, + "width_ticks": 4605200, + "ask_tick_18_18": -13815500, + "ask_tick_18_6": -41446500, + "trading_start": 1000, + "fee_initial_q64": "922337203685477580", + "fee_final_q64": "55340232221128654", + "max_creator_fee_q64": "18446744073709551615", + "creator_fee_range": "every uint64 0..18446744073709551615 inclusive for initialFee and finalFee (Q0.64; 2**64 - 1 is just under 100%, never exactly 100%); finalFee <= initialFee, else InvalidFeeSchedule; exactly 100% = 2**64 is not a uint64 and fails ABI decoding with empty revert data; see creator_fee_range", + "label": "LOCAL FIXTURE VECTORS - not live evidence; numeric start margin is a local-reference proposal, not a live parameter; the creator fee ceiling is the uint64 maximum", + "quote_18": "t0 order: plain 18-decimal test ERC-20 selected per launch at the top of the address space. t1 order: native ETH, address(0), the reference Base configuration; always token0, no code, no decimals(), no approvals.", + "quote_6": "plain 6-decimal solady ERC-20 (DecimalsToken), selected per launch for math vectors only", + "fee_decay_rates_x32": ["68913100933", "9923486534471"], + "fee_decay_rate_x32_default": "68913100933", + "fee_decay_rate_encoding": "decayRateX32: uint64, unsigned Q32.32 ticks per second (2**32 = 1 tick/s). Any value is accepted; 0 is a flat fee. Half-life in seconds of the excess over finalFee = 2 * ln(2) / ln(1.000001) * 2**32 / decayRateX32 = 1386295.05426695565 * 2**32 / decayRateX32. rate for a half-life H: floor(1386295.05426695565... * 2**32 / H), computed with exact high-precision arithmetic (not binary floating point).", + "fee_schedule": "initialFee for t <= 0 or decayRateX32 == 0; finalFee for t > MAX_DECAY_PRODUCT / decayRateX32 (integer division; MAX_DECAY_PRODUCT = (88722835 + 1) * 2**32 - 1 = 381061679028371455); otherwise m = floor(decayRateX32 * t / 2**32) and fee = finalFee + floor((initialFee - finalFee) * R / 2**128) with R = toFixed(tickToSqrtRatio(-m)), the compact (94-bit mantissa, rounded down) sqrt ratio of src/math/ticks.sol as a 64.128 integer. t = block timestamp - tradingStart in whole seconds. The operation order is normative; the saturation check must precede the helper call." + }, + "create": [ + { + "order": "t0", + "dec": "18/18", + "ask": "-13815500", + "upper": "-9210300", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999210", + "liquidity": "1111119116089565312897934", + "tickLower": "-13815500", + "tickUpper": "-9210300", + "initialTick": "-13815500", + "withdrawable": "999999999999999999999999209", + "ideal_liquidity": "1111119116089565312897934", + "relative_error": "6.356e-27" + }, + { + "order": "t0", + "dec": "18/6", + "ask": "-41446500", + "upper": "-36841300", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999302656786", + "liquidity": "1111138522742929536", + "tickLower": "-41446500", + "tickUpper": "-36841300", + "initialTick": "-41446500", + "withdrawable": "999999999999999999302656785", + "ideal_liquidity": "1111138522742929536", + "relative_error": "2.037e-20" + }, + { + "order": "t1", + "dec": "18/18", + "ask": "-13815500", + "upper": "-9210300", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999422", + "liquidity": "1111119116089565312898036", + "tickLower": "9210300", + "tickUpper": "13815500", + "initialTick": "13815500", + "withdrawable": "999999999999999999999999421", + "ideal_liquidity": "1111119116089565312897934", + "relative_error": "9.158e-23" + }, + { + "order": "t1", + "dec": "18/6", + "ask": "-41446500", + "upper": "-36841300", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999282285400", + "liquidity": "1111138522742929536", + "tickLower": "36841300", + "tickUpper": "41446500", + "initialTick": "41446500", + "withdrawable": "999999999999999999282285399", + "ideal_liquidity": "1111138522742929536", + "relative_error": "2.875e-29" + } + ], + "installation": { + "token0_liquidity": "floor(supply * lowerFixed * upperFixed / (2**128 * (upperFixed - lowerFixed))), the exact inverse of Core's token0 debit ceil(ceil(liquidity * 2**128 * (upperFixed - lowerFixed) / upperFixed) / lowerFixed); computed on chain as floor(supply * mantissaLower * mantissaUpper / (((upperFixed - lowerFixed) >> shiftLower) << (128 - shiftUpper))) from the compact sqrt ratios (mantissa = low 94 bits, shift = 2 + 32 * top two bits); saturates at 2**128 - 1", + "token1_liquidity": "floor(supply * 2**128 / (upperFixed - lowerFixed)), unchanged shared helper; the exact inverse of ceil(liquidity * (upperFixed - lowerFixed) / 2**128)", + "note": "Installed liquidity is the exact inverse of Core's rounded-up debit in both orders: debit(liquidity) <= supply < debit(liquidity + 1). The token0 order no longer uses math/liquidity.sol maxLiquidityForToken0, whose first floor under-installed at low prices; the fixture creation rows above are unchanged by this. Full pinned rows, refusals and pins: zero-seed-launch-installation.json (oracle: zero-seed-launch-installation.py).", + "vectors": [ + { + "label": "low price, 1e30", + "order": "t0", + "ask": "-88080384", + "upper": "-85983232", + "spacing": "524288", + "supply": "1000000000000000000000000000000", + "liquidity": "115073586432", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "debit_at_liquidity_plus_one": "1000000000000100854972818930129", + "previous_liquidity": "106052588672", + "previous_installed": "921606703670075887283160121070" + }, + { + "label": "low price, two liquidity units", + "order": "t0", + "ask": "-88080384", + "upper": "-85983232", + "spacing": "524288", + "supply": "17380183081064167375", + "liquidity": "2", + "installed": "17380183081064167375", + "sink": "0", + "debit_at_liquidity_plus_one": "26070274621596251062", + "previous_liquidity": "1", + "previous_installed": "8690091540532083688" + }, + { + "label": "full width, int128 budget", + "order": "t0", + "ask": "-88722835", + "upper": "88722835", + "spacing": "1", + "supply": "170141183460469231694794709727778658247", + "liquidity": "9223595582101085260", + "installed": "170141183460469231694794709727778658247", + "sink": "0", + "debit_at_liquidity_plus_one": "170141183460469231713241006721831381988", + "previous_liquidity": "9223595582101085259", + "previous_installed": "170141183460469231676348412733725934507" + } + ] + }, + "trade": [ + { + "order": "t0", + "decimals": "18/18", + "step": "buyExactIn", + "quoteIn": "1000000000000000000", + "gross": "999083373736809197508878", + "fee": "49954168686840459832116", + "net": "949129205049968737676762" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "buyExactOut", + "tokenOut": "949129205049968737676762", + "calcQuote": "951668650101832044", + "fee": "50087823689570108", + "paid": "1001756473791402152" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "dustExhausted", + "token": "191", + "quote": "90" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "dust", + "token": "102", + "quote": "2" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "exhaust", + "requested": "999950046831313159540166992", + "got": "999950045831313159540166903", + "principalToken": "0", + "principalQuote": "10000312219037842827169" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "reverse", + "leftHeld": "0", + "principalQuote": "49957287200176383", + "principalToken": "999950045831313159540166992" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "sellExactIn", + "tokenIn": "949129205049968737676762", + "quoteOut": "904085217596740440" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "buyExactIn", + "quoteIn": "1000000", + "gross": "999048506325137632526244", + "fee": "49952425316256881582986", + "net": "949096081008880750943258" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "buyExactOut", + "tokenOut": "949096081008880750943258", + "calcQuote": "951669", + "fee": "50088", + "paid": "1001757" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "dustExhausted", + "token": "94478106", + "quote": "100" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "dust", + "token": "94478017", + "quote": "2" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "exhaust", + "requested": "999950048574683742326595783", + "got": "999950047574683742326595694", + "principalToken": "0", + "principalQuote": "10000661550" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "reverse", + "leftHeld": "0", + "principalQuote": "49957", + "principalToken": "999950047574683742326595783" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "sellExactIn", + "tokenIn": "949096081008880750943258", + "quoteOut": "904084" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "buyExactIn", + "quoteIn": "1000000000000000000", + "gross": "999083373736809197249895", + "fee": "49954168686840459819167", + "net": "949129205049968737430728" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "buyExactOut", + "tokenOut": "949129205049968737430728", + "calcQuote": "951668650101832044", + "fee": "50087823689570108", + "paid": "1001756473791402152" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "dustExhausted", + "token": "428919", + "quote": "86" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "dust", + "token": "428825", + "quote": "3" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "exhaust", + "requested": "999950046831313159539751430", + "got": "999950045831313159539751336", + "principalToken": "0", + "principalQuote": "10000312219037842827184" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "reverse", + "leftHeld": "0", + "principalQuote": "49957287200176384", + "principalToken": "999950045831313159539751430" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "sellExactIn", + "tokenIn": "949129205049968737430728", + "quoteOut": "904085217596740439" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "buyExactIn", + "quoteIn": "1000000", + "gross": "999048148471968146393535", + "fee": "49952407423598407276350", + "net": "949095741048369739117185" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "buyExactOut", + "tokenOut": "949095741048369739117185", + "calcQuote": "951669", + "fee": "50088", + "paid": "1001757" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "dustExhausted", + "token": "91", + "quote": "90" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "dust", + "token": "1", + "quote": "2" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "exhaust", + "requested": "999950048592576400875009049", + "got": "999950047592576400875008959", + "principalToken": "0", + "principalQuote": "10000661550" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "reverse", + "leftHeld": "0", + "principalQuote": "49957", + "principalToken": "999950047592576400875009049" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "sellExactIn", + "tokenIn": "949095741048369739117185", + "quoteOut": "904084" + } + ], + "fee": [ + { + "decayRateX32": "68913100933", + "t_minus_start": "-1", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "0", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "1", + "tick_magnitude": "16", + "fee_q64": "922330267740917652" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "10", + "tick_magnitude": "160", + "fee_q64": "922267846736753839" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "60", + "tick_magnitude": "962", + "fee_q64": "921920278629180106" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "600", + "tick_magnitude": "9627", + "fee_q64": "918173943809149156" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "1800", + "tick_magnitude": "28881", + "fee_q64": "909907303121253143" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "3600", + "tick_magnitude": "57762", + "fee_q64": "897655606672062181" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "43200", + "tick_magnitude": "693147", + "fee_q64": "668399831593786699" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86399", + "tick_magnitude": "1386279", + "fee_q64": "488842197715736884" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86400", + "tick_magnitude": "1386295", + "fee_q64": "488838729715618946" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86401", + "tick_magnitude": "1386311", + "fee_q64": "488835261743244884" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "172800", + "tick_magnitude": "2772590", + "fee_q64": "272089486849531874" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "604800", + "tick_magnitude": "9704065", + "fee_q64": "62113647347197278" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "2592000", + "tick_magnitude": "41588851", + "fee_q64": "55340233028582759" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529596", + "tick_magnitude": "88722819", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529597", + "tick_magnitude": "88722835", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529598", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "18446744073709551615", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "-1", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "0", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "1", + "tick_magnitude": "2310", + "fee_q64": "921336400758785753" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "10", + "tick_magnitude": "23104", + "fee_q64": "912379287313991670" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "60", + "tick_magnitude": "138629", + "fee_q64": "864277214588907100" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "600", + "tick_magnitude": "1386295", + "fee_q64": "488838729715618946" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "1800", + "tick_magnitude": "4158885", + "fee_q64": "163714862475909380" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "3600", + "tick_magnitude": "8317770", + "fee_q64": "68887062105693473" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "43200", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86399", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86400", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86401", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "172800", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "604800", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "2592000", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38398", + "tick_magnitude": "88718262", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38399", + "tick_magnitude": "88720572", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38400", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "18446744073709551615", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + } + ], + "router": [ + { + "order": "t0", + "paid": "1000000000000000000", + "got": "949129205049968737676762" + }, + { + "order": "t1", + "paid": "1000000000000000000", + "got": "949129205049968737430728" + } + ], + "gas": [ + { + "order": "t0", + "create": 1368087, + "firstBuy": 102485, + "secondBuy": 34267, + "sell": 34271, + "claim": 70154 + }, + { + "order": "t1", + "create": 1326935, + "firstBuy": 84663, + "secondBuy": 35163, + "sell": 38609, + "claim": 79676 + } + ], + "other": [ + { + "kind": "dust", + "quoteIn": "1", + "net": "949971", + "fee": "49999", + "raw": "VEC|dust|t0|quoteIn=1|net=949971|fee=49999" + }, + { + "kind": "recapture", + "quoteIn": "10e18", + "netOfFee": "9415099531871718329641155", + "afterClaim": "9910631086180756136011948", + "raw": "VEC|recapture|t0|quoteIn=10e18|netOfFee=9415099531871718329641155|afterClaim=9910631086180756136011948" + } + ], + "gas_note": "In-test warm gas, t0 on the plain 18-decimal test ERC-20 quote and t1 on native ETH, through the periphery (create, claim) and the forwarding test locker (swaps), as logged by test_gas_vectors at this revision (forge 1.8.3, solc 0.8.33, via_ir, optimizer 9999999, evm osaka).", + "residue_metrics": { + "definitions": { + "core_unbooked_dust": "Per token: Core balance - position amount (rounded down) - creator fee ledger, accumulated over all swaps; raw units of that token. Unowned: not creator principal, not creator fees, unreachable by claims.", + "upper_bound_residue": "Raw launch-token units the position held before the exhausting buy that the buy did not deliver when it stopped at the upper bound. Becomes Core unbooked dust: dust_after.token = dust_before.token + residue (asserted)." + }, + "scenarios": [ + { + "order": "t0", + "decimals": "18/18", + "upper_residue_raw_token": "89", + "position_token_before_exhaust": "999950045831313159540166992", + "delivered_raw_token": "999950045831313159540166903", + "core_dust_before_exhaust": { + "raw_token": "102", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "191", + "raw_quote": "90" + } + }, + { + "order": "t0", + "decimals": "18/6", + "upper_residue_raw_token": "89", + "position_token_before_exhaust": "999950047574683742326595783", + "delivered_raw_token": "999950047574683742326595694", + "core_dust_before_exhaust": { + "raw_token": "94478017", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "94478106", + "raw_quote": "100" + } + }, + { + "order": "t1", + "decimals": "18/18", + "upper_residue_raw_token": "94", + "position_token_before_exhaust": "999950045831313159539751430", + "delivered_raw_token": "999950045831313159539751336", + "core_dust_before_exhaust": { + "raw_token": "428825", + "raw_quote": "3" + }, + "core_dust_after_exhaust": { + "raw_token": "428919", + "raw_quote": "86" + } + }, + { + "order": "t1", + "decimals": "18/6", + "upper_residue_raw_token": "90", + "position_token_before_exhaust": "999950047592576400875009049", + "delivered_raw_token": "999950047592576400875008959", + "core_dust_before_exhaust": { + "raw_token": "1", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "91", + "raw_quote": "90" + } + } + ] + }, + "start": { + "contract": [ + { + "block_time": 5000, + "tradingStart": "4999", + "create": "InvalidTradingStart (create and preview)", + "trade_at_block_time": null, + "trade_at_start": null + }, + { + "block_time": 5000, + "tradingStart": "5000", + "create": "ok", + "trade_at_block_time": "ok", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "5001", + "create": "ok", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "91400", + "create": "ok", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "18446744073709551615", + "create": "ok (contract has no cap; effectively indefinite self-lock)", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "not exercised (astronomically distant timestamp)" + } + ], + "contract_mined_late": { + "previewed_at": 5000, + "tradingStart": 5000, + "mined_at": 5001, + "create": "InvalidTradingStart, no token and no pool left" + }, + "producer_proposal": { + "status": "local reference proposal for CTO freeze; not a live/hosted selection", + "rule": "buildTime + minMargin <= tradingStart <= buildTime + maxDelay and buildTime < expiry <= tradingStart; never rewrite a signed start", + "minMargin_s": 300, + "maxDelay_s": 86400, + "vectors": [ + { + "buildTime": 1000, + "tradingStart": 1000, + "expiry": 1000, + "verdict": "refuse: start-now (start <= buildTime)" + }, + { + "buildTime": 1000, + "tradingStart": 1299, + "expiry": 1299, + "verdict": "refuse: start below margin" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1300, + "verdict": "accept: minimum start, expiry at start" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1301, + "verdict": "refuse: expiry after start" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1000, + "verdict": "refuse: expiry not after buildTime" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1001, + "verdict": "accept" + }, + { + "buildTime": 1000, + "tradingStart": 87400, + "expiry": 1300, + "verdict": "accept: maximum start" + }, + { + "buildTime": 1000, + "tradingStart": 87400, + "expiry": 87400, + "verdict": "accept" + }, + { + "buildTime": 1000, + "tradingStart": 87401, + "expiry": 1300, + "verdict": "refuse: start above cap" + }, + { + "buildTime": 1000, + "tradingStart": 18446744073709551615, + "expiry": 1300, + "verdict": "refuse: start above cap (contract alone would accept and self-lock)" + } + ], + "why": "Bounds the requested start to maxDelay after buildTime. Submission before expiry does not guarantee mining before start: late mining reverts atomically. No inclusion-latency guarantee." + } + }, + "claim_recipient_refusals": { + "error": "InvalidRecipient() (periphery)", + "refused": [ + "address(0)", + "Core", + "extension", + "periphery", + "poolKey.token0", + "poolKey.token1" + ], + "accepted_examples": [ + "owner", + "EOA", + "other contract (ETH-receiving for a native quote)" + ], + "atomicity": "both assets or neither; a reverting native recipient or a refused ERC-20 transfer rolls the ledger, principal and Core balances back; retry to another recipient pays both in full", + "reentrancy": "the ledger is moved onto the lock before Core pays; an owner contract re-entering claimFees during the payment claims zero" + }, + "metadata": { + "max_bytes": 31, + "unit": "UTF-8 bytes", + "error": "MetadataTooLong()", + "checked_in": ["create", "previewInstallation"], + "vectors": [ + { + "field": "name", + "bytes": 31, + "verdict": "accept" + }, + { + "field": "name", + "bytes": 32, + "verdict": "MetadataTooLong" + }, + { + "field": "symbol", + "bytes": 32, + "verdict": "MetadataTooLong" + }, + { + "field": "name", + "bytes": 33, + "note": "11 x U+20AC", + "verdict": "MetadataTooLong" + } + ] + }, + "collision": { + "error": "LaunchTokenExists(address token)", + "checked_in": ["create", "previewInstallation"], + "rule": "token address = CREATE2(extension, keccak256(abi.encode(controller, salt)), MintableERC20 init code with name/symbol/decimals). The controller is the locker that forwards the creation; the periphery passes salt = keccak256(abi.encode(owner, userSalt)). Refused when the address already has code. Same salt with other metadata, another salt, another owner or another controller is independent." + }, + "fee_curve": { + "note": "Tick-rate schedule. Digests: SHA-256 chain h = sha256(h || fee as 8-byte big-endian) over the sampled seconds t = 0, step, 2*step, ..., last inclusive; all from the independent Python oracle (exact port of the tick table).", + "rates_q64": { + "5": "922337203685477580", + "50": "9223372036854775808", + "75": "13835058055282163712", + "90": "16602069666338596454", + "99": "18262276632972456099", + "99.9": "18428297329635842064", + "max": "18446744073709551615" + }, + "final_fee_q64": "55340232221128654", + "decay_rates_x32": { + "default_one_day": "68913100933", + "600s": "9923486534471", + "two_ticks_per_second": "8589934592", + "quarter_tick_per_second": "1073741824", + "tenth_tick_per_second_floor": "429496729", + "min": "1", + "max": "18446744073709551615" + }, + "half_life_s": { + "default_one_day": "86400.00000104363", + "600s": "600.00000000005238", + "two_ticks_per_second": "693151.52713347783 (8.0225408233 days)", + "quarter_tick_per_second": "5545180.2170678226 (64.18 days)", + "min": "5954091920683119 (about 1.89e8 years)", + "max": "0.0000000003229 (saturates within the first second)" + }, + "saturation_second": { + "default_one_day": "5529597", + "600s": "38399", + "two_ticks_per_second": "44361417", + "quarter_tick_per_second": "354891343", + "min": "381061679028371455", + "max": "0" + }, + "saturation_note": "saturation_second = MAX_DECAY_PRODUCT / decayRateX32 is the last elapsed second computed through the helper; from the next second the fee is exactly finalFee. On the 50% -> 0.3% fixtures the floored excess is already zero earlier (rounding), so the two mechanisms coincide at the floor; a full-span excess (2**64 - 1 -> 0) still leaves 1 raw unit when the magnitude is exactly 88,722,835 at saturation_second, which is why the guard rather than a clamp to the minimum tick yields finalFee.", + "digests_sha256": [ + { + "label": "default 50% -> 0.3%, every second 0..3600", + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "last": 3600, + "step": 1, + "digest": "c3de4d91d476acc61f73455efe7fb8c750b89de3759a0c500bbde0bdf9bcd062" + }, + { + "label": "default 50% -> 0.3%, every minute 0..30 days", + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "last": 2592000, + "step": 60, + "digest": "b2c022f5cbbc90cedca9834bcf64f636b97c45dee08c730ab4dbf17d8d5acc74" + }, + { + "label": "600 s half-life 5% -> 0.3%, every second 0..3600", + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "9923486534471", + "last": 3600, + "step": 1, + "digest": "c4db35cd45f48222463cb9a940c506599715162db6c37b01428331ff5263bdc6" + }, + { + "label": "2 ticks/s 99.9% -> 0, every hour 0..30 days", + "initialFee": "18428297329635842064", + "finalFee": "0", + "decayRateX32": "8589934592", + "last": 2592000, + "step": 3600, + "digest": "8fdefff5fe4cfb98c43bbbf6ed6f0b9a0ae398f1f810ffea0522d5504e1321da" + }, + { + "label": "1/4 tick/s max -> 0, every second 0..3600", + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "1073741824", + "last": 3600, + "step": 1, + "digest": "11ff0a905ab22707579e714aba31b895ed3234788a0dab10b0daa0881a9ccf36" + } + ], + "tables": { + "default_one_day_50_to_0.3": [ + { + "t": "0", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000" + }, + { + "t": "1", + "fee_q64": "9223298692930386783", + "pct": "49.9996024018", + "tick_magnitude": "16" + }, + { + "t": "60", + "fee_q64": "9218963276153076996", + "pct": "49.9761000603", + "tick_magnitude": "962" + }, + { + "t": "3600", + "fee_q64": "8962377276946957658", + "pct": "48.5851445715", + "tick_magnitude": "57762" + }, + { + "t": "43200", + "fee_q64": "6538119400055406284", + "pct": "35.4432162875", + "tick_magnitude": "693147" + }, + { + "t": "86400", + "fee_q64": "4639356258918185573", + "pct": "25.1500006743", + "tick_magnitude": "1386295" + }, + { + "t": "172800", + "fee_q64": "2347348307759775472", + "pct": "12.7250006743", + "tick_magnitude": "2772590" + }, + { + "t": "604800", + "fee_q64": "126965494298918151", + "pct": "0.6882813237", + "tick_magnitude": "9704065" + }, + { + "t": "2592000", + "fee_q64": "55340240759526319", + "pct": "0.3000000463", + "tick_magnitude": "41588851" + }, + { + "t": "5529597", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "88722835" + }, + { + "t": "5529598", + "fee_q64": "55340232221128654", + "pct": "0.3000000000" + } + ], + "two_ticks_per_second_50_to_0.3": [ + { + "t": "0", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000" + }, + { + "t": "1", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "60", + "fee_q64": "9222821971723649262", + "pct": "49.9970180909", + "tick_magnitude": "120" + }, + { + "t": "3600", + "fee_q64": "9190426476420833039", + "pct": "49.8214017590", + "tick_magnitude": "7200" + }, + { + "t": "43200", + "fee_q64": "8835746255246636721", + "pct": "47.8986764273", + "tick_magnitude": "86400" + }, + { + "t": "86400", + "fee_q64": "8464509350746382498", + "pct": "45.8861971355", + "tick_magnitude": "172800" + }, + { + "t": "172800", + "fee_q64": "7768459773126180960", + "pct": "42.1129048144", + "tick_magnitude": "345600" + }, + { + "t": "604800", + "fee_q64": "5062770843274302024", + "pct": "27.4453357354", + "tick_magnitude": "1209600" + }, + { + "t": "2592000", + "fee_q64": "741753037726786584", + "pct": "4.0210512748", + "tick_magnitude": "5184000" + }, + { + "t": "44361417", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "88722834" + }, + { + "t": "44361418", + "fee_q64": "55340232221128654", + "pct": "0.3000000000" + } + ], + "two_ticks_per_second_99.9_to_0": [ + { + "t": "0", + "fee_q64": "18428297329635842064", + "pct": "99.9000000000" + }, + { + "t": "1", + "fee_q64": "18428278901356940707", + "pct": "99.8999001001", + "tick_magnitude": "2" + }, + { + "t": "60", + "fee_q64": "18427191665519151079", + "pct": "99.8940061828", + "tick_magnitude": "120" + }, + { + "t": "3600", + "fee_q64": "18362074764497997343", + "pct": "99.5410067550", + "tick_magnitude": "7200" + }, + { + "t": "43200", + "fee_q64": "17649146110668978989", + "pct": "95.6762127785", + "tick_magnitude": "86400" + }, + { + "t": "86400", + "fee_q64": "16902937523957200381", + "pct": "91.6310079243", + "tick_magnitude": "172800" + }, + { + "t": "172800", + "fee_q64": "15503835857875547793", + "pct": "84.0464625948", + "tick_magnitude": "345600" + }, + { + "t": "604800", + "fee_q64": "10065237787609899792", + "pct": "54.5637633795", + "tick_magnitude": "1209600" + }, + { + "t": "2592000", + "fee_q64": "1379731172434914028", + "pct": "7.4795376730", + "tick_magnitude": "5184000" + } + ], + "default_one_day_max_to_0": [ + { + "t": "0", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000" + }, + { + "t": "1", + "fee_q64": "18446596500421042511", + "pct": "99.9992000036", + "tick_magnitude": "16" + }, + { + "t": "60", + "fee_q64": "18437873327830881974", + "pct": "99.9519115902", + "tick_magnitude": "962" + }, + { + "t": "3600", + "fee_q64": "17921603711721989946", + "pct": "97.1532083934", + "tick_magnitude": "57762" + }, + { + "t": "43200", + "fee_q64": "13043821263248043520", + "pct": "70.7106967556", + "tick_magnitude": "693147" + }, + { + "t": "86400", + "fee_q64": "9223372287116814725", + "pct": "50.0000013567", + "tick_magnitude": "1386295" + }, + { + "t": "172800", + "fee_q64": "4611686268689430216", + "pct": "25.0000013567", + "tick_magnitude": "2772590" + }, + { + "t": "604800", + "fee_q64": "144115215448268606", + "pct": "0.7812501484", + "tick_magnitude": "9704065" + }, + { + "t": "2592000", + "fee_q64": "17179874578", + "pct": "9.31E-8", + "tick_magnitude": "41588851" + }, + { + "t": "5529597", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "5529598", + "fee_q64": "0", + "pct": "0E-10" + } + ], + "min_rate_max_to_0": [ + { + "t": "0", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000" + }, + { + "t": "4294967295", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000", + "tick_magnitude": "0" + }, + { + "t": "4294967296", + "fee_q64": "18446734850344432283", + "pct": "99.9999500000", + "tick_magnitude": "1" + }, + { + "t": "381061679028371454", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "381061679028371455", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "381061679028371456", + "fee_q64": "0", + "pct": "0E-10" + }, + { + "t": "18446744073709551615", + "fee_q64": "0", + "pct": "0E-10" + } + ] + }, + "tick_steps": [ + { + "decayRateX32": "1073741824", + "note": "1/4 tick/s: the fee steps every 4 s", + "rows": [ + { + "t": "1", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "3", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "4", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "5", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "7", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "8", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "9", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + } + ] + }, + { + "decayRateX32": "429496729", + "note": "floor(0.1 * 2**32), slightly below one tick per 10 s: first step at 11 s", + "rows": [ + { + "t": "9", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "10", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "11", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "20", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "21", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "30", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "31", + "fee_q64": "9223358284824258897", + "pct": "49.9999254501", + "tick_magnitude": "3" + } + ] + }, + { + "decayRateX32": "68913100933", + "note": "default: 16 whole ticks per second, 1,386,295 after one day", + "rows": [ + { + "t": "1", + "fee_q64": "9223298692930386783", + "pct": "49.9996024018", + "tick_magnitude": "16" + }, + { + "t": "2", + "fee_q64": "9223225349592746513", + "pct": "49.9992048068", + "tick_magnitude": "32" + }, + { + "t": "86400", + "fee_q64": "4639356258918185573", + "pct": "25.1500006743", + "tick_magnitude": "1386295" + } + ] + }, + { + "decayRateX32": "68913100933", + "note": "a one-unit excess is gone after the first whole tick", + "rows": [ + { + "t": "0", + "fee_q64": "55340232221128655", + "pct": "0.3000000000" + }, + { + "t": "1", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "16" + } + ] + } + ], + "oracle_error_bounds": { + "note": "Integer result minus the real-number F + (I - F) * 1.000001**(-(decayRateX32 / 2**32) * t / 2) at 150 digits, default 50% -> 0.3%, every second of the first hour then every 10 minutes to 30 days. The error is dominated by the whole-tick floor of m (one tick = 5e-7 of the excess), not by the helper (which is within a few units of 2**-128 of the real sqrt ratio) or the final floor.", + "max_abs_q64": "4574692217333.041", + "at_t": "244", + "max_abs_fraction_of_excess": "4.98983E-7", + "one_tick_of_excess_fraction": "5.00000E-7" + } + }, + "creator_fee_range": { + "type": "uint64, Q0.64 (2**64 = 100%)", + "min_q64": "0", + "max_q64": "18446744073709551615", + "max_fraction": "1 - 2**-64: just under 100%, not 100%", + "constant": "MAX_CREATOR_FEE = type(uint64).max (src/extensions/ZeroSeedLaunch.sol); no ceiling comparison in _validate", + "schedule_rules": ["finalFee <= initialFee"], + "error": "InvalidFeeSchedule() 0xf75a271a", + "checked_in": ["create", "previewInstallation", "forwarded create payload"], + "unencodable": { + "value": "18446744073709551616", + "meaning": "exactly 100% = 2**64", + "verdict": "ABI decoding failure with empty revert data in create(LaunchParameters), previewInstallation(address,LaunchParameters) and the forwarded create payload (abi.encode(ZERO_SEED_LAUNCH_CREATE, LaunchParameters)), for either fee word; nothing is truncated or wrapped to zero", + "dirty_high_bits": { + "value": "36893488147419103231", + "note": "2**64 + (2**64 - 1)", + "verdict": "same ABI decoding failure" + } + }, + "removed": { + "error": "FeeTooHigh()", + "selector": "0xcd4e6167", + "note": "removed from IZeroSeedLaunch and the derived ABI; the previous ceiling floor(2**64 * 99 / 100) = 18262276632972456099 no longer exists" + }, + "vectors": [ + { + "initialFee": "18446744073709551615", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "verdict": "accept", + "note": "max -> 0.3% at the default one-day half-life" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551615", + "decayRateX32": "1", + "verdict": "accept", + "note": "flat at the maximum, slowest rate" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551615", + "decayRateX32": "18446744073709551615", + "verdict": "accept", + "note": "flat at the maximum, fastest rate" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "18446744073709551615", + "verdict": "accept", + "note": "max -> 0 at the fastest rate: saturated to 0 from t = 1" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "1", + "verdict": "accept", + "note": "max -> 0 at the slowest rate: one raw unit at t = MAX_DECAY_PRODUCT (minimum tick residual), 0 from t = MAX_DECAY_PRODUCT + 1" + }, + { + "initialFee": "0", + "finalFee": "0", + "decayRateX32": "68913100933", + "verdict": "accept", + "note": "zero schedule" + }, + { + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "0", + "verdict": "accept", + "note": "zero rate: flat at initialFee forever" + }, + { + "initialFee": "18446744073709551614", + "finalFee": "18446744073709551615", + "decayRateX32": "68913100933", + "verdict": "InvalidFeeSchedule", + "note": "final above initial at the top of the range" + }, + { + "initialFee": "0", + "finalFee": "1", + "decayRateX32": "68913100933", + "verdict": "InvalidFeeSchedule" + }, + { + "initialFee": "922337203685477580", + "finalFee": "922337203685477581", + "decayRateX32": "0", + "verdict": "InvalidFeeSchedule", + "note": "shape is checked even at a zero rate" + }, + { + "initialFee": "18446744073709551616", + "finalFee": "18446744073709551615", + "decayRateX32": "68913100933", + "verdict": "abi_decoding_failure", + "note": "2**64 in initialFee" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551616", + "decayRateX32": "68913100933", + "verdict": "abi_decoding_failure", + "note": "2**64 in finalFee" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "55340232221128654", + "decayRateX32": "18446744073709551616", + "verdict": "abi_decoding_failure", + "note": "2**64 in decayRateX32" + } + ], + "reference_schedules": { + "fixture": { + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "note": "5% -> 0.3% at the default one-day half-life: this fixture's local reference parameters" + }, + "consumer_default": { + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "half_life_s": "86400.0000010436", + "fee_after_one_day_q64": "4639356258918185573", + "fee_after_one_day_pct": "25.1500006743", + "note": "50% -> 0.3% with a one-day half-life of the excess: the consumer-side default; a creation parameter choice, not a contract constant. About 25.15% after one day, 12.73% after two, 0.69% after seven; the floor is approached, not scheduled." + } + }, + "decay_rate_rules": [ + "any uint64 decayRateX32 is accepted, including 0 (flat at initialFee) and 2**64 - 1 (finalFee from the first second)" + ] + }, + "max_rate": { + "note": "Launches created through ZeroSeedLaunchPeriphery.create at initialFee = 2**64 - 1 (no storage injection), finalFee = 0.3%, default one-day half-life rate 68913100933, traded at tradingStart. Exact input: net = floor(gross / 2**64), fee = gross - net = ceil(gross * (2**64 - 1) / 2**64). Exact output: paid = calc * 2**64, fee = paid - calc. Settled by the local test lockers against locally compiled Core; not live evidence.", + "first_buy_note": "The first whole-quote-unit buy is identical for the max -> 0.3% / default rate, max -> max / rate 1 and max -> 0 / rate 2**64 - 1 schedules (the rate at tradingStart is the initial rate in every case).", + "trades": [ + { + "order": "t0", + "decimals": "18/18", + "step": "oneUnit", + "quoteIn": "1", + "gross": "999970", + "fee": "999970", + "net": "0" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "buyExactIn", + "quoteIn": "1000000000000000000", + "gross": "999083373736809197507080", + "fee": "999083373736809197452920", + "net": "54160" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "buyExactOut", + "tokenOut": "54160", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616" + }, + { + "order": "t0", + "decimals": "18/18", + "step": "sellAll", + "tokenIn": "108320", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "oneUnit", + "quoteIn": "1", + "gross": "999947603316768497", + "fee": "999947603316768497", + "net": "0" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "buyExactIn", + "quoteIn": "1000000", + "gross": "999048504527750217302727", + "fee": "999048504527750217248569", + "net": "54158" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "buyExactOut", + "tokenOut": "54158", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616" + }, + { + "order": "t0", + "decimals": "18/6", + "step": "sellAll", + "tokenIn": "108316", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "oneUnit", + "quoteIn": "1", + "gross": "722806", + "fee": "722806", + "net": "0" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "buyExactIn", + "quoteIn": "1000000000000000000", + "gross": "999083373736809196527088", + "fee": "999083373736809196472928", + "net": "54160" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "buyExactOut", + "tokenOut": "54160", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616" + }, + { + "order": "t1", + "decimals": "18/18", + "step": "sellAll", + "tokenIn": "108320", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "oneUnit", + "quoteIn": "1", + "gross": "641450041727074607", + "fee": "641450041727074607", + "net": "0" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "buyExactIn", + "quoteIn": "1000000", + "gross": "999047507021926419318928", + "fee": "999047507021926419264770", + "net": "54158" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "buyExactOut", + "tokenOut": "54158", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616" + }, + { + "order": "t1", + "decimals": "18/6", + "step": "sellAll", + "tokenIn": "108316", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0" + } + ], + "trades_note": "t1 18/18 is the native ETH quote. A single raw quote unit nets nothing (the whole gross output is the fee); a whole quote unit nets 54,160 (18/18) or 54,158 (18/6) raw launch-token units; the immediate sell of everything held grosses under one raw quote unit, so it pays nothing and charges nothing.", + "exact_output_bounds": { + "note": "Bisection over the requested launch-token output on the pinned fixture: the last request that executes, the first that fails the signed int128 conversion of the fee-inclusive input (calc * 2**64 >= 2**127), and the first that fails the uint128 gross-up helper (calc >= 2**64). The last executable request has calc = 2**63 - 1 exactly. Every failed probe rolls back pool state, fee ledger, position liquidity and Core balances.", + "signed_bound_calc": "9223372036854775807", + "helper_bound_calc": "18446744073709551615", + "errors": { + "signed": "SafeCastLib.Overflow() 0x35278d12", + "helper": "AmountBeforeFeeOverflow() 0x0d88f526" + }, + "vectors": [ + { + "order": "t0", + "decimals": "18/18", + "lastValidTokenOut": "9147280263567190862134907", + "calc": "9223372036854775807", + "paid": "170141183460469231713240559642174554112", + "firstSignedOverflowTokenOut": "9147280263567190862134908", + "lastSignedOverflowTokenOut": "18145179163502972233172123", + "firstHelperOverflowTokenOut": "18145179163502972233172124" + }, + { + "order": "t1", + "decimals": "18/18", + "quote": "native", + "lastValidTokenOut": "9147280263567190861982364", + "calc": "9223372036854775807", + "paid": "170141183460469231713240559642174554112", + "firstSignedOverflowTokenOut": "9147280263567190861982365", + "lastSignedOverflowTokenOut": "18145179163502972233028470", + "firstHelperOverflowTokenOut": "18145179163502972233028471" + } + ] + } + }, + "forward_schema": { + "swap": { + "payload": "abi.encode(PoolKey, SwapParameters)", + "length": 128, + "returns": "abi.encode(PoolBalanceUpdate, PoolState)", + "first_word": "PoolKey.token0 < 2**160" + }, + "create": { + "tag": "0x224a5a98239f599168b798c4205db155bfff4332b4931a7fb5bc60c2e2eeee30", + "tag_source": "keccak256(\"IZeroSeedLaunch#CREATE\")", + "payload": "abi.encode(tag, LaunchParameters)", + "returns": "abi.encode(Installation)", + "controller": "Locker.addr() of the forwarding locker", + "lock_debt_after": "zero" + }, + "claim_fees": { + "tag": "0xe52d8a05fd0f2d32b280675736d8117fee83069a0eac7ab9279aa52ff05a614c", + "tag_source": "keccak256(\"IZeroSeedLaunch#CLAIM_FEES\")", + "payload": "abi.encode(tag, PoolKey)", + "returns": "abi.encode(uint128 amount0, uint128 amount1)", + "authorization": "launch.controller == Locker.addr()", + "lock_debt_after": "credit of (amount0, amount1) for the locker to withdraw" + }, + "unknown": "any first word >= 2**160 that is not a tag, or fewer than 32 bytes, reverts UnknownForwardAction(); a first word < 2**160 is decoded as a swap" + }, + "abi": { + "ZeroSeedLaunch": { + "selectors": { + "creatorFees((address,address,bytes32))": "0xb4ce9a2e", + "feeAt(bytes32,uint256)": "0xe81084ec", + "forwarded_2374103877(bytes32)": "0x00000001", + "getLaunch(bytes32)": "0x7d1d589c", + "previewInstallation(address,(string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xb665ec4d" + }, + "topics": { + "LaunchCreated": "0x8f81ce1beb182cad759aa1c797ef027d374f535fd0c08696441c32797c7a5888", + "LaunchSwapped": "0x3e58795f52a8e88efdec2da89ea3798175f8764145ccd7bf5e40633a7d571f6d", + "CreatorFeesClaimed": "0xf0ffdfa84530baef2e9f8bbb160545a59bfef8da319fa27aa69e4f3c8f05a41d" + }, + "errors": { + "ControllerOnly()": "0xac8ff76a", + "InitializeThroughCreateOnly()": "0x028eb443", + "InvalidFeeSchedule()": "0xf75a271a", + "InvalidParameters()": "0xe5239090", + "InvalidTradingStart()": "0xe0839259", + "LaunchTokenExists(address)": "0xf68475cd", + "LiquidityAboveTickCap(uint128,uint128)": "0x20ea795f", + "MetadataTooLong()": "0x85b8e2f4", + "PositionsThroughCreateOnly()": "0x74a99003", + "SwapsThroughForwardOnly()": "0xf17f54b3", + "TradingNotStarted()": "0xacf75be8", + "UnknownForwardAction()": "0xe5c78d9a", + "UnknownLaunch()": "0xf6806bf8", + "UnsupportedQuoteToken()": "0x658d8331", + "ZeroLiquidity()": "0x10074548" + } + }, + "ZeroSeedLaunchPeriphery": { + "selectors": { + "LAUNCH()": "0x096e3c3e", + "claimFees((address,address,bytes32),address)": "0x6906b4c0", + "create((string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xf91810d2", + "launchOwner(bytes32)": "0x60ac0b66", + "previewInstallation(address,(string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xb665ec4d", + "transferLaunch(bytes32,address)": "0x8b1cb36f" + }, + "topics": { + "LaunchOwnerChanged": "0x478004cf03e8c3b648608ce0259ce479c813aa671a383230ee7763fe5da22391", + "LaunchFeesClaimed": "0x908c6b2c19765fe47aef218b4f71ea1a10a9e156d9d5c3500c3a298e01428805" + } + }, + "LaunchParameters": "(string name,string symbol,uint8 decimals,uint128 supply,address quoteToken,int32 askTick,int32 upperTick,uint32 tickSpacing,uint64 tradingStart,uint64 decayRateX32,uint64 initialFee,uint64 finalFee,bytes32 salt)", + "Launch": "(address controller,uint64 tradingStart,uint64 decayRateX32,uint64 initialFee,uint64 finalFee,int32 tickLower,int32 tickUpper)", + "changed_from_previous_revision": { + "previewInstallation(address,LaunchParameters)": { + "previous": "0x917c1163", + "current": "0xb665ec4d" + }, + "create(LaunchParameters)": { + "previous": "0x662af306", + "current": "0xf91810d2" + }, + "LaunchCreated topic": { + "previous": "0x3a4e811187f4a8c0b72396e9d7ca13aeed62eb97a599a24af1ed99066041098f", + "current": "0x8f81ce1beb182cad759aa1c797ef027d374f535fd0c08696441c32797c7a5888" + }, + "LaunchParameters": "uint32 feeDuration (10th field) replaced by uint64 decayRateX32 at the same position", + "Launch": "uint32 feeDuration (3rd field) replaced by uint64 decayRateX32 at the same position; getLaunch(bytes32) selector unchanged, return tuple changed", + "unchanged": [ + "feeAt(bytes32,uint256) 0xe81084ec", + "getLaunch(bytes32) 0x7d1d589c", + "creatorFees 0xb4ce9a2e", + "claimFees 0x6906b4c0", + "transferLaunch 0x8b1cb36f", + "launchOwner 0x60ac0b66", + "LAUNCH 0x096e3c3e", + "LaunchSwapped topic", + "CreatorFeesClaimed topic", + "LaunchOwnerChanged topic", + "LaunchFeesClaimed topic", + "forward tags ZERO_SEED_LAUNCH_CREATE / ZERO_SEED_LAUNCH_CLAIM_FEES", + "swap payload", + "InvalidFeeSchedule selector 0xf75a271a (now only finalFee > initialFee)" + ] + }, + "storage_layout_Launch": { + "slots": 2, + "slot0": "controller address @0, tradingStart uint64 @20", + "slot1": "decayRateX32 uint64 @0, initialFee uint64 @8, finalFee uint64 @16, tickLower int32 @24, tickUpper int32 @28", + "source": "forge inspect ZeroSeedLaunch storage-layout" + } + }, + "pins": { + "runtime": { + "ZeroSeedLaunch": { + "keccak256": "0xe08ad382497fc9825956c2b4e9ccf463014c4ed65baf50c0a59db8d7b7aedfc3", + "bytes": 19694 + }, + "ZeroSeedLaunchPeriphery": { + "keccak256": "0xfa8ef7dfd8f5f7454366b471449f8a771f1cef34c764022c971e9394a05f0444", + "bytes": 5534 + } + }, + "initcode": { + "ZeroSeedLaunch": { + "keccak256": "0xa76b1de0bfc2d72b32ce963cbc7598aa67fa60ae6b8874ec176e3630ec965f2c", + "bytes": 20184 + }, + "ZeroSeedLaunchPeriphery": { + "keccak256": "0x7a4c7a556485b2005cb824b11641adc4d766a5c501e3a65adaa72646654ec0c4", + "bytes": 5754 + } + }, + "note": "forge 1.8.3 (cae51ad), solc 0.8.33, via_ir, optimizer 9999999, evm osaka; immutables resolved with Core at the canonical address and the extension at its callpoint address; LOCAL compilation from test_bytecodeSizes, not a deployment." + } +} From dd4481f309750070acf65925c86cdf37df3604d3 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sun, 25 Oct 2026 19:40:58 -0400 Subject: [PATCH 15/19] Serve only the final zero-seed launch as response contract v6 Drop the fixed-base and earlier tick-rate fee models, their pins, vectors and goldens. Launches now carry the logo, every LaunchParameters field as exact decimal strings, the derived spacing and aligned bounds, a trading status and market data (price, market cap, raised, sold share, trade count). The list spans chains with status, owner and creator filters, market-cap ordering, offset paging and totals. Claims are one row per extension claim with the periphery owner and recipient attached. Tests use the frozen 853b608 vectors. --- .oxfmtrc.json | 2 +- openapi.json | 1236 ++++++----- scripts/launchReplayCases.txt | 393 ---- scripts/launchReplayCasesV2.txt | 152 -- scripts/verifyLaunchReplay.ts | 97 - src/contract/cases/launches.ts | 137 +- src/queries.ts | 5 +- src/router.test.ts | 2 +- src/routes/launches/index.ts | 33 +- src/routes/launches/policy.test.ts | 734 +++---- src/routes/launches/policy.ts | 331 ++- src/routes/launches/queries.ts | 578 +++-- src/routes/launches/routes.test.ts | 50 +- src/routes/launches/schema.ts | 214 +- src/routes/launches/vectors/PROVENANCE.md | 16 + .../vectors/fee-curve-all-seconds.json.gz | Bin 355072 -> 0 bytes .../launches/vectors/fee-decay-vectors.json | 1510 ------------- .../vectors/zero-seed-launch-events.json | 130 ++ .../zero-seed-launch-installation.json | 1720 +++++++++++++++ .../vectors/zero-seed-launch-v2-vectors.json | 1867 ---------------- .../launches/vectors/zero-seed-launch.json | 1956 +++++++++++++++++ 21 files changed, 5559 insertions(+), 5604 deletions(-) delete mode 100644 scripts/launchReplayCases.txt delete mode 100644 scripts/launchReplayCasesV2.txt delete mode 100644 scripts/verifyLaunchReplay.ts create mode 100644 src/routes/launches/vectors/PROVENANCE.md delete mode 100644 src/routes/launches/vectors/fee-curve-all-seconds.json.gz delete mode 100644 src/routes/launches/vectors/fee-decay-vectors.json create mode 100644 src/routes/launches/vectors/zero-seed-launch-events.json create mode 100644 src/routes/launches/vectors/zero-seed-launch-installation.json delete mode 100644 src/routes/launches/vectors/zero-seed-launch-v2-vectors.json create mode 100644 src/routes/launches/vectors/zero-seed-launch.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json index f77d99b..c6c2c4e 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -3,5 +3,5 @@ "trailingComma": "all", "printWidth": 80, "sortPackageJson": false, - "ignorePatterns": ["openapi.json"] + "ignorePatterns": ["openapi.json", "src/routes/launches/vectors/**"] } diff --git a/openapi.json b/openapi.json index da3daab..56f6730 100644 --- a/openapi.json +++ b/openapi.json @@ -9941,34 +9941,29 @@ "Launches" ], "operationId": "get_ListLaunches", - "summary": "List supported zero-seed launches at one indexed head", + "summary": "List supported zero-seed launches across chains, with status, owner and creator filters", "parameters": [ { "schema": { "type": "integer", "format": "int64" }, - "required": true, - "name": "chainId", - "in": "query" - }, - { - "schema": { - "type": "string" - }, "required": false, - "name": "before_event_id", + "name": "chainId", "in": "query" }, { "schema": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 + "type": "string", + "enum": [ + "all", + "trading", + "upcoming" + ], + "default": "all" }, "required": false, - "name": "limit", + "name": "status", "in": "query" }, { @@ -9990,7 +9985,7 @@ }, "required": false, "description": "An address on the specified blockchain network", - "name": "creator", + "name": "owner", "in": "query" }, { @@ -10012,7 +10007,45 @@ }, "required": false, "description": "An address on the specified blockchain network", - "name": "owner", + "name": "creator", + "in": "query" + }, + { + "schema": { + "type": "string", + "enum": [ + "market_cap", + "created" + ], + "default": "market_cap" + }, + "required": false, + "name": "order_by", + "in": "query" + }, + { + "schema": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 100000, + "default": 0 + }, + "required": false, + "name": "offset", + "in": "query" + }, + { + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "required": false, + "name": "limit", "in": "query" } ], @@ -10027,7 +10060,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "data": { @@ -10038,7 +10071,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "chain_id": { @@ -10114,12 +10147,10 @@ ] }, "decimals": { - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 255 + "type": "number", + "enum": [ + 18 + ] } }, "required": [ @@ -10174,6 +10205,126 @@ ], "additionalProperties": false }, + "launch_token_is_token1": { + "type": "boolean" + }, + "controller": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "creator": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "owner": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "logo_url": { + "type": [ + "string", + "null" + ], + "description": "LaunchCreated.logoUrl exactly as emitted; null when empty or not valid UTF-8. Untrusted: never fetched or rewritten here" + }, + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "symbol": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "supply": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "quote_token": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "ask_tick": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "upper_tick": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" + }, + "trading_start": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$" + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "salt": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + } + }, + "required": [ + "name", + "symbol", + "supply", + "quote_token", + "ask_tick", + "upper_tick", + "trading_start", + "decay_rate_x32", + "initial_fee", + "final_fee", + "salt" + ], + "additionalProperties": false + }, + "range": { + "type": "object", + "properties": { + "tick_spacing": { + "type": "integer", + "exclusiveMinimum": 0 + }, + "aligned_ask_tick": { + "type": "integer" + }, + "aligned_upper_tick": { + "type": "integer" + }, + "pool_tick_lower": { + "type": "integer" + }, + "pool_tick_upper": { + "type": "integer" + } + }, + "required": [ + "tick_spacing", + "aligned_ask_tick", + "aligned_upper_tick", + "pool_tick_lower", + "pool_tick_upper" + ], + "additionalProperties": false + }, "supply": { "type": "object", "properties": { @@ -10204,199 +10355,145 @@ ], "additionalProperties": false }, - "launch_token_is_token1": { - "type": "boolean" - }, - "controller": { - "type": "string", - "pattern": "^0x[0-9a-f]{40}$" - }, - "creator": { - "type": "string", - "pattern": "^0x[0-9a-f]{40}$" + "trading_start": { + "type": "integer", + "minimum": 0 }, - "owner": { + "status": { "type": "string", - "pattern": "^0x[0-9a-f]{40}$" - }, - "ask_tick": { - "type": "integer" - }, - "upper_tick": { - "type": "integer" + "enum": [ + "upcoming", + "trading" + ], + "description": "upcoming while evaluated_at < trading_start, else trading" }, "fee_schedule": { - "oneOf": [ - { - "type": "object", - "properties": { - "model": { - "type": "string", - "enum": [ - "fixed_base_duration_v1" - ] - }, - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "fee_duration": { - "type": "integer", - "exclusiveMinimum": 0 - }, - "fee_end": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "decay": { - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "fixed_base_q128" - ] - }, - "half_life_seconds": { - "type": "number", - "enum": [ - 600 - ] - } - }, - "required": [ - "kind", - "half_life_seconds" - ], - "additionalProperties": false - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "fee_decay", - "final_fee" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 - } - }, - "required": [ - "model", - "trading_start", - "fee_duration", - "fee_end", - "initial_fee", - "final_fee", - "decay", - "phase", - "fee_rate", - "evaluated_at" + "type": "object", + "properties": { + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "decayRateX32: unsigned Q32.32 ticks per second as a canonical decimal uint64; 0 keeps initial_fee" + }, + "half_life_seconds": { + "type": [ + "string", + "null" ], - "additionalProperties": false + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored); null for rate 0. Display only" }, - { - "type": "object", - "properties": { - "model": { - "type": "string", - "enum": [ - "tick_rate_v2" - ] - }, - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled" - }, - "decay_rate_x32": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee" - }, - "half_life_seconds": { - "type": [ - "string", - "null" - ], - "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", - "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only" - }, - "saturation_elapsed_seconds": { - "type": [ - "string", - "null" - ], - "pattern": "^(0|[1-9][0-9]*)$", - "description": "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end" - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "decaying", - "at_floor", - "constant" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 - } - }, - "required": [ - "model", - "trading_start", - "initial_fee", - "final_fee", - "decay_rate_x32", - "half_life_seconds", - "saturation_elapsed_seconds", - "phase", - "fee_rate", - "evaluated_at" + "saturation_elapsed_seconds": { + "type": [ + "string", + "null" ], - "additionalProperties": false + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(381061679028371455 / decay_rate_x32): after this many seconds the fee is exactly final_fee; null for rate 0" + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "decaying", + "at_floor", + "constant" + ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 } - ] + }, + "required": [ + "trading_start", + "initial_fee", + "final_fee", + "decay_rate_x32", + "half_life_seconds", + "saturation_elapsed_seconds", + "phase", + "fee_rate", + "evaluated_at" + ], + "additionalProperties": false + }, + "market": { + "type": "object", + "properties": { + "price_tick": { + "type": "integer" + }, + "price_x128": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "market_cap": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(price * totalSupply) in raw quote units; totalSupply includes the sink" + }, + "market_cap_usd": { + "type": [ + "string", + "null" + ], + "description": "market_cap in USD from the indexed quote price, 6 decimals; null without a price" + }, + "raised": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Quote held by the launch pool (buyers' net input)" + }, + "sold": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "installed - launch tokens left in the pool" + }, + "sold_share": { + "type": "string", + "pattern": "^[01]\\.[0-9]{18}$", + "description": "sold / installed, 18 decimals, floored" + }, + "trade_count": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "price_tick", + "price_x128", + "market_cap", + "market_cap_usd", + "raised", + "sold", + "sold_share", + "trade_count" + ], + "additionalProperties": false }, "created": { "type": "object", @@ -10472,55 +10569,102 @@ "pool_key", "token", "quote_token", - "supply", "launch_token_is_token1", "controller", "creator", "owner", - "ask_tick", - "upper_tick", + "logo_url", + "parameters", + "range", + "supply", + "trading_start", + "status", "fee_schedule", + "market", "created", "as_of" ], "additionalProperties": false } }, - "next_cursor": { - "type": [ - "string", - "null" - ] + "total": { + "type": "integer", + "minimum": 0 }, - "as_of": { + "totals": { "type": "object", "properties": { - "block_number": { - "type": "string", - "pattern": "^(0|[1-9][0-9]*)$" + "all": { + "type": "integer", + "minimum": 0 }, - "block_hash": { - "type": "string", - "pattern": "^0x[0-9a-f]{64}$" + "trading": { + "type": "integer", + "minimum": 0 }, - "block_time": { + "upcoming": { "type": "integer", "minimum": 0 } }, "required": [ - "block_number", - "block_hash", - "block_time" + "all", + "trading", + "upcoming" ], "additionalProperties": false + }, + "next_offset": { + "type": [ + "integer", + "null" + ], + "exclusiveMinimum": 0 + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 + }, + "heads": { + "type": "array", + "items": { + "type": "object", + "properties": { + "block_number": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "block_hash": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "block_time": { + "type": "integer", + "minimum": 0 + }, + "chain_id": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "block_number", + "block_hash", + "block_time", + "chain_id" + ], + "additionalProperties": false + } } }, "required": [ "schema_version", "data", - "next_cursor", - "as_of" + "total", + "totals", + "next_offset", + "evaluated_at", + "heads" ], "additionalProperties": false } @@ -10576,7 +10720,7 @@ "Launches" ], "operationId": "get_ListLaunchClaims", - "summary": "List atomic full two-asset creator fee claims, each paid out once by the periphery", + "summary": "List creator fee claims, one row per claim (extension event, periphery owner and recipient)", "parameters": [ { "schema": { @@ -10640,7 +10784,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "chain_id": { @@ -10728,7 +10872,7 @@ ], "additionalProperties": false }, - "ledger_event_id": { + "payout_event_id": { "type": "string", "pattern": "^(0|-?[1-9][0-9]*)$" }, @@ -10751,7 +10895,7 @@ }, "required": [ "event", - "ledger_event_id", + "payout_event_id", "owner", "recipient", "launch_token_amount", @@ -10759,6 +10903,24 @@ ], "additionalProperties": false } + }, + "totals": { + "type": "object", + "properties": { + "launch_token": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "quote_token": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + } + }, + "required": [ + "launch_token", + "quote_token" + ], + "additionalProperties": false } }, "required": [ @@ -10767,7 +10929,8 @@ "pool_id", "next_cursor", "as_of", - "data" + "data", + "totals" ], "additionalProperties": false } @@ -10887,7 +11050,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "chain_id": { @@ -11122,7 +11285,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "chain_id": { @@ -11234,7 +11397,7 @@ "type": "string", "maxLength": 20, "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" }, "fee_amount": { "type": "string", @@ -11369,7 +11532,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v5" + "zero-seed-launch-api/v6" ] }, "chain_id": { @@ -11432,76 +11595,194 @@ "type": "string", "pattern": "^0x[0-9a-f]{40}$" }, - "name": { - "type": [ - "string", - "null" - ] + "name": { + "type": [ + "string", + "null" + ] + }, + "symbol": { + "type": [ + "string", + "null" + ] + }, + "decimals": { + "type": "number", + "enum": [ + 18 + ] + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals" + ], + "additionalProperties": false + }, + "quote_token": { + "type": "object", + "properties": { + "address": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "symbol": { + "type": [ + "string", + "null" + ] + }, + "decimals": { + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 255 + }, + "kind": { + "type": "string", + "enum": [ + "native", + "erc20" + ] + } + }, + "required": [ + "address", + "name", + "symbol", + "decimals", + "kind" + ], + "additionalProperties": false + }, + "launch_token_is_token1": { + "type": "boolean" + }, + "controller": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "creator": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "owner": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "logo_url": { + "type": [ + "string", + "null" + ], + "description": "LaunchCreated.logoUrl exactly as emitted; null when empty or not valid UTF-8. Untrusted: never fetched or rewritten here" + }, + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "symbol": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" + }, + "supply": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "quote_token": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "ask_tick": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" }, - "symbol": { - "type": [ - "string", - "null" - ] + "upper_tick": { + "type": "string", + "pattern": "^(0|-?[1-9][0-9]*)$" }, - "decimals": { - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 255 + "trading_start": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$" + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "salt": { + "type": "string", + "pattern": "^0x[0-9a-f]{64}$" } }, "required": [ - "address", "name", "symbol", - "decimals" + "supply", + "quote_token", + "ask_tick", + "upper_tick", + "trading_start", + "decay_rate_x32", + "initial_fee", + "final_fee", + "salt" ], "additionalProperties": false }, - "quote_token": { + "range": { "type": "object", "properties": { - "address": { - "type": "string", - "pattern": "^0x[0-9a-f]{40}$" + "tick_spacing": { + "type": "integer", + "exclusiveMinimum": 0 }, - "name": { - "type": [ - "string", - "null" - ] + "aligned_ask_tick": { + "type": "integer" }, - "symbol": { - "type": [ - "string", - "null" - ] + "aligned_upper_tick": { + "type": "integer" }, - "decimals": { - "type": [ - "integer", - "null" - ], - "minimum": 0, - "maximum": 255 + "pool_tick_lower": { + "type": "integer" }, - "kind": { - "type": "string", - "enum": [ - "native", - "erc20" - ] + "pool_tick_upper": { + "type": "integer" } }, "required": [ - "address", - "name", - "symbol", - "decimals", - "kind" + "tick_spacing", + "aligned_ask_tick", + "aligned_upper_tick", + "pool_tick_lower", + "pool_tick_upper" ], "additionalProperties": false }, @@ -11535,199 +11816,145 @@ ], "additionalProperties": false }, - "launch_token_is_token1": { - "type": "boolean" - }, - "controller": { - "type": "string", - "pattern": "^0x[0-9a-f]{40}$" - }, - "creator": { - "type": "string", - "pattern": "^0x[0-9a-f]{40}$" + "trading_start": { + "type": "integer", + "minimum": 0 }, - "owner": { + "status": { "type": "string", - "pattern": "^0x[0-9a-f]{40}$" - }, - "ask_tick": { - "type": "integer" - }, - "upper_tick": { - "type": "integer" + "enum": [ + "upcoming", + "trading" + ], + "description": "upcoming while evaluated_at < trading_start, else trading" }, "fee_schedule": { - "oneOf": [ - { - "type": "object", - "properties": { - "model": { - "type": "string", - "enum": [ - "fixed_base_duration_v1" - ] - }, - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "fee_duration": { - "type": "integer", - "exclusiveMinimum": 0 - }, - "fee_end": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "decay": { - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "fixed_base_q128" - ] - }, - "half_life_seconds": { - "type": "number", - "enum": [ - 600 - ] - } - }, - "required": [ - "kind", - "half_life_seconds" - ], - "additionalProperties": false - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "fee_decay", - "final_fee" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 - } - }, - "required": [ - "model", - "trading_start", - "fee_duration", - "fee_end", - "initial_fee", - "final_fee", - "decay", - "phase", - "fee_rate", - "evaluated_at" + "type": "object", + "properties": { + "trading_start": { + "type": "integer", + "minimum": 0 + }, + "initial_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "final_fee": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "decay_rate_x32": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "decayRateX32: unsigned Q32.32 ticks per second as a canonical decimal uint64; 0 keeps initial_fee" + }, + "half_life_seconds": { + "type": [ + "string", + "null" ], - "additionalProperties": false + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored); null for rate 0. Display only" }, - { - "type": "object", - "properties": { - "model": { - "type": "string", - "enum": [ - "tick_rate_v2" - ] - }, - "trading_start": { - "type": "integer", - "minimum": 0 - }, - "initial_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "final_fee": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled" - }, - "decay_rate_x32": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee" - }, - "half_life_seconds": { - "type": [ - "string", - "null" - ], - "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", - "description": "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only" - }, - "saturation_elapsed_seconds": { - "type": [ - "string", - "null" - ], - "pattern": "^(0|[1-9][0-9]*)$", - "description": "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end" - }, - "phase": { - "type": "string", - "enum": [ - "before_trading_start", - "decaying", - "at_floor", - "constant" - ] - }, - "fee_rate": { - "type": "string", - "maxLength": 20, - "pattern": "^(0|[1-9][0-9]*)$", - "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%" - }, - "evaluated_at": { - "type": "integer", - "minimum": 0 - } - }, - "required": [ - "model", - "trading_start", - "initial_fee", - "final_fee", - "decay_rate_x32", - "half_life_seconds", - "saturation_elapsed_seconds", - "phase", - "fee_rate", - "evaluated_at" + "saturation_elapsed_seconds": { + "type": [ + "string", + "null" ], - "additionalProperties": false + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(381061679028371455 / decay_rate_x32): after this many seconds the fee is exactly final_fee; null for rate 0" + }, + "phase": { + "type": "string", + "enum": [ + "before_trading_start", + "decaying", + "at_floor", + "constant" + ] + }, + "fee_rate": { + "type": "string", + "maxLength": 20, + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64" + }, + "evaluated_at": { + "type": "integer", + "minimum": 0 } - ] + }, + "required": [ + "trading_start", + "initial_fee", + "final_fee", + "decay_rate_x32", + "half_life_seconds", + "saturation_elapsed_seconds", + "phase", + "fee_rate", + "evaluated_at" + ], + "additionalProperties": false + }, + "market": { + "type": "object", + "properties": { + "price_tick": { + "type": "integer" + }, + "price_x128": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "market_cap": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "floor(price * totalSupply) in raw quote units; totalSupply includes the sink" + }, + "market_cap_usd": { + "type": [ + "string", + "null" + ], + "description": "market_cap in USD from the indexed quote price, 6 decimals; null without a price" + }, + "raised": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Quote held by the launch pool (buyers' net input)" + }, + "sold": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "installed - launch tokens left in the pool" + }, + "sold_share": { + "type": "string", + "pattern": "^[01]\\.[0-9]{18}$", + "description": "sold / installed, 18 decimals, floored" + }, + "trade_count": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "price_tick", + "price_x128", + "market_cap", + "market_cap_usd", + "raised", + "sold", + "sold_share", + "trade_count" + ], + "additionalProperties": false }, "created": { "type": "object", @@ -11802,12 +12029,6 @@ "type": "string", "pattern": "^0x[0-9a-f]{64}$" }, - "tick_lower": { - "type": "integer" - }, - "tick_upper": { - "type": "integer" - }, "liquidity": { "type": "string", "pattern": "^(0|[1-9][0-9]*)$" @@ -11841,23 +12062,14 @@ "quote_token" ], "additionalProperties": false - }, - "reserves_basis": { - "type": "string", - "enum": [ - "core_pool_balance" - ] } }, "required": [ "position_id", - "tick_lower", - "tick_upper", "liquidity", "locked", "withdrawable", - "pool_reserves", - "reserves_basis" + "pool_reserves" ], "additionalProperties": false }, @@ -11868,12 +12080,6 @@ "type": "string", "pattern": "^0x[0-9a-f]{40}$" }, - "transferable": { - "type": "boolean", - "enum": [ - true - ] - }, "launch_token": { "type": "object", "properties": { @@ -11939,7 +12145,6 @@ }, "required": [ "holder", - "transferable", "launch_token", "quote_token", "swap_count", @@ -11948,26 +12153,6 @@ "last_event_id" ], "additionalProperties": false - }, - "inventory": { - "type": "object", - "properties": { - "trading_open": { - "type": "boolean" - }, - "no_bid": { - "type": "boolean" - }, - "price_tick": { - "type": "integer" - } - }, - "required": [ - "trading_open", - "no_bid", - "price_tick" - ], - "additionalProperties": false } }, "required": [ @@ -11977,19 +12162,22 @@ "pool_key", "token", "quote_token", - "supply", "launch_token_is_token1", "controller", "creator", "owner", - "ask_tick", - "upper_tick", + "logo_url", + "parameters", + "range", + "supply", + "trading_start", + "status", "fee_schedule", + "market", "created", "as_of", "principal", - "fee_ledger", - "inventory" + "fee_ledger" ], "additionalProperties": false } diff --git a/scripts/launchReplayCases.txt b/scripts/launchReplayCases.txt deleted file mode 100644 index 51da2db..0000000 --- a/scripts/launchReplayCases.txt +++ /dev/null @@ -1,393 +0,0 @@ - async function apiHead(db: Db) { - await db.sql`UPDATE indexer_cursor SET - head_block_number = b.block_number, head_block_hash = b.block_hash, - head_block_time = b.block_time, last_updated = NOW() - FROM (SELECT * FROM blocks WHERE chain_id = ${CHAIN_ID} ORDER BY block_number DESC LIMIT 1) b - WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; - } - - test("API actual handlers: emitted-state golden, supply, owners, per-launch quotes, screening, pages, ledgers, reorg and repair", async () => { - const db = await freshDb(); - const processors = LAUNCH_ON(); - const reader = new LaunchQueries(db.sql as never, { - localHarness: true, instances: LOCAL_INSTANCES, quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, - }); - const spies = [ - spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), - spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), - spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), - spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), - spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), - ]; - const get = async (path: string, status = 200) => { - const response = await app.fetch(new Request(`http://localhost${path}`), { PG_CONNECTION_STRING: urlFor(db.name) }); - const body = await response.json() as any; - expect(response.status).toBe(status); - if (response.status !== status) throw new Error(JSON.stringify(body)); - return body; - }; - const lower = (a: string) => a.toLowerCase(); - const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), "utf8")); - const { creator, newOwner, feeRecipient, trader } = full.addresses; - // The served local instance is the fixture's, field for field. - // This fixture is the fixed-base (43d6628) instance; the tick-rate one has its own harness. - expect(LOCAL_INSTANCES.filter((p) => p.feeModel === "fixed_base_duration_v1")).toEqual(full.instances.map((i: any) => ({ - chainId: String(CHAIN_ID), feeModel: "fixed_base_duration_v1", address: lower(i.extension), runtime: i.extensionRuntimeKeccak, - periphery: lower(i.periphery), peripheryRuntime: i.peripheryRuntimeKeccak, core: lower(full.addresses.core), - abiSha256: full.provenance.launchAbiSha256, peripheryAbiSha256: full.provenance.peripheryAbiSha256, - }))); - expect(LOCAL_QUOTES).toEqual([{ chainId: String(CHAIN_ID), address: lower(full.addresses.erc20Quote), runtime: full.provenance.erc20QuoteRuntimeKeccak }]); - const path = (label: string) => `/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`; - /** One launch at one block: API detail versus the chain state and the indexer's SQL feeAt. */ - const expectLaunch = async (state: any, label: string) => { - const chain = state.pools.find((p: any) => p.label === label); - const body = await get(path(label)); - const install = full.installations[label]; - const launch = full.launches[label]; - const instance = full.instances.find((i: any) => i.label === install.instance); - const launchIs1 = body.launch_token_is_token1; - expect(body.schema_version).toBe("zero-seed-launch-api/v5"); - // requested = totalSupply, installed = Core's position delta, sink = balanceOf(0) - expect(body.supply).toEqual({ - requested: launch.requestedSupply, installed: launch.installedSupply, sink: launch.sinkSupply, - sink_holder: "0x0000000000000000000000000000000000000000", - }); - expect([body.supply.requested, body.supply.sink]).toEqual([chain.totalSupply, chain.sinkBalance]); - expect([body.supply.installed, body.supply.installed]).toEqual([install.coreInstalledDelta, install.installation.installedSupply]); - // every launch sinks a remainder except l8, whose int128-scale supply installs exactly - expect(BigInt(body.supply.sink) > 0n).toBe(label !== "l8"); - expect(body.principal).not.toHaveProperty("installed_supply"); - expect(body.principal.liquidity).toBe(install.liquidity); - expect(body.principal.liquidity).toBe(install.coreLiquidityDelta); - expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.find((l: any) => l.pool_id === body.pool_id).supply).toEqual(body.supply); - expect(body.fee_ledger.launch_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 1 : 0]); - expect(body.fee_ledger.quote_token.unclaimed).toBe(chain.creatorFees[launchIs1 ? 0 : 1]); - expect(body.fee_ledger.transferable).toBe(true); - expect([body.owner, body.fee_ledger.holder, body.creator, body.controller]).toEqual( - [lower(chain.launchOwner), lower(chain.launchOwner), lower(install.owner), lower(chain.controller)]); - expect(body.controller).toBe(lower(instance.periphery)); - expect(body.pool_key.extension).toBe(lower(instance.extension)); - // the launch's own quote, LaunchParameters.quoteToken - expect(body.quote_token.kind).toBe(install.quoteKind); - expect(body.quote_token.address).toBe(lower(install.quote)); - expect(launchIs1).toBe(launch.tokenIsToken1); - if (install.quoteKind === "native") { - expect([body.quote_token.decimals, body.quote_token.name, body.quote_token.symbol, launchIs1]).toEqual([18, null, null, true]); - expect(body.pool_key.token0).toBe("0x0000000000000000000000000000000000000000"); - } - const tick = decodePoolState(chain.poolStateWord).tick; - expect(body.inventory.price_tick).toBe(launchIs1 ? -tick : tick); - expect(body.fee_schedule.evaluated_at).toBe(body.as_of.block_time); - expect(body.fee_schedule.model).toBe("fixed_base_duration_v1"); - expect(body.fee_schedule.decay).toEqual({ kind: "fixed_base_q128", half_life_seconds: 600 }); - expect(body.fee_schedule).not.toHaveProperty("decay_factor"); - // The API's TypeScript schedule equals the indexer's SQL feeAt and the contract's feeAt. - const [sql] = await db.sql<{ rate: string }[]>` - SELECT zero_seed_launch_fee_at(trading_start, fee_duration, initial_fee, final_fee, - ${body.as_of.block_time}::int8)::text AS rate - FROM zero_seed_launch_created WHERE pool_id = ${num(fixture.launches[label]!.poolId)}::numeric`; - expect(body.fee_schedule.fee_rate).toBe(sql!.rate); - expect(body.fee_schedule.fee_rate).toBe(chain.feeAtBlockTime); - expect(body).not.toHaveProperty("status"); - expect(body.inventory).not.toHaveProperty("exhausted"); - return body; - }; - /** Claims are counted once: payouts equal the ledger's claimed amounts. */ - const expectClaimsOnce = async (label: string) => { - const detail = await get(path(label)); - const claims = await get(`${path(label)}/claims`); - expect(claims.data.length).toBe(detail.fee_ledger.claim_count); - const sum = (side: string) => claims.data.reduce((n: bigint, c: any) => n + BigInt(c[side]), 0n).toString(); - expect(sum("launch_token_amount")).toBe(detail.fee_ledger.launch_token.claimed); - expect(sum("quote_token_amount")).toBe(detail.fee_ledger.quote_token.claimed); - return claims; - }; - try { - await replay(db, prefix, processors, async (state) => { - await expectChainParity(db, state); - await apiHead(db); - for (const pool of state.pools) await expectLaunch(state, pool.label); - }); - // A launch in the deployment block is served. - expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); - await replay(db, alternate, processors, async (state) => { - await apiHead(db); - for (const pool of state.pools) await expectLaunch(state, pool.label); - }); - const list = await get(`/launches?chainId=${CHAIN_ID}&limit=1`); - expect(list.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l3!.poolId]); - const second = await get(`/launches?chainId=${CHAIN_ID}&limit=2&before_event_id=${list.next_cursor}`); - expect(second.data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); - expect(second.next_cursor).toBeNull(); - const alternateL1 = await get(path("l1")); - expect([alternateL1.owner, alternateL1.creator]).toEqual([lower(newOwner), lower(creator)]); - const nativeAlternate = await get(path("l3")); - expect([nativeAlternate.creator, nativeAlternate.owner, nativeAlternate.quote_token.kind]).toEqual([lower(newOwner), lower(newOwner), "native"]); - - // Reorg onto the canonical branch: the alternate transfer, claims and l3 disappear. - await invalidateFrom(db.dao, fixture.forkPoint + 1); - await replay(db, canonical, processors, async (state) => { - await apiHead(db); - for (const pool of state.pools) await expectLaunch(state, pool.label); - }); - const canonicalList = await get(`/launches?chainId=${CHAIN_ID}`); - expect(canonicalList.data.map((l: any) => l.pool_id)).toEqual( - ["l8", "l7", "l6", "l5", "l4", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); - expect(canonicalList.next_cursor).toBeNull(); - // Low-price token0 launches serve the exact installation the chain recorded: l7 sinks the - // remainder of the largest affordable liquidity, l8 installs its whole supply. - const l7 = await get(path("l7")); - const l8 = await get(path("l8")); - expect([l7.supply, l7.principal.liquidity, l7.launch_token_is_token1, l7.quote_token.kind]).toEqual([ - { requested: "1000000000000000000000000000000", installed: "999999999991410763432286846442", - sink: "8589236567713153558", sink_holder: "0x0000000000000000000000000000000000000000" }, - "115073586432", false, "erc20"]); - expect([l8.supply, l8.principal.liquidity, l8.launch_token_is_token1]).toEqual([ - { requested: "170141183460469231694794709727778658247", installed: "170141183460469231694794709727778658247", - sink: "0", sink_holder: "0x0000000000000000000000000000000000000000" }, - "9223595582101085260", false]); - for (const suffix of ["", "/swaps", "/claims", "/owners"]) - await get(`${path("l3")}${suffix}`, 404); - // creator (owner at creation) and current owner filters - expect((await get(`/launches?chainId=${CHAIN_ID}&creator=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( - ["l7", "l5", "l2", "l1"].map((l) => fixture.launches[l]!.poolId)); - expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${feeRecipient}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l1!.poolId]); - expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${BigInt(trader).toString()}`)).data.map((l: any) => l.pool_id)).toEqual( - ["l8", "l6", "l4"].map((l) => fixture.launches[l]!.poolId)); - // the creator still owns only l7 and l5 (l1 and l2 were transferred) - expect((await get(`/launches?chainId=${CHAIN_ID}&owner=${creator}`)).data.map((l: any) => l.pool_id)).toEqual( - ["l7", "l5"].map((l) => fixture.launches[l]!.poolId)); - const owners = await get(`${path("l1")}/owners`); - expect(owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ - [null, lower(creator)], [lower(creator), lower(newOwner)], [lower(newOwner), lower(feeRecipient)]]); - const firstOwner = await get(`${path("l1")}/owners?limit=1`); - const laterOwners = await get(`${path("l1")}/owners?after_event_id=${firstOwner.next_cursor}`); - expect([...firstOwner.data, ...laterOwners.data]).toEqual(owners.data); - const detail = await get(path("l1")); - expect(detail.fee_ledger.owner_change_count).toBe(3); - const swaps = await get(`${path("l1")}/swaps`); - expect(swaps.data.length).toBe(detail.fee_ledger.swap_count); - const claims = await expectClaimsOnce("l1"); - await expectClaimsOnce("l2"); - await expectClaimsOnce("l4"); - // Launches created at the maximum rate 2^64 - 1 (just under 100%) are - // served exactly: decimal strings, never rounded, never 2^64. - const MAX = (2n ** 64n - 1n).toString(); - const l5 = await get(path("l5")); - const l6 = await get(path("l6")); - expect(l5.fee_schedule).toMatchObject({ initial_fee: MAX, final_fee: "55340232221128654", fee_duration: 3600 }); - expect(l6.fee_schedule).toMatchObject({ initial_fee: MAX, final_fee: MAX, fee_duration: 1 }); - // the canonical head is past both fee windows - expect([l5.fee_schedule.phase, l5.fee_schedule.fee_rate]).toEqual(["final_fee", "55340232221128654"]); - expect([l6.fee_schedule.phase, l6.fee_schedule.fee_rate]).toEqual(["final_fee", MAX]); - expect([l5.quote_token.kind, l5.launch_token_is_token1, l6.quote_token.kind, l6.launch_token_is_token1]).toEqual( - ["native", true, "erc20", false]); - expect([l5.creator, l5.owner, l6.creator, l6.owner]).toEqual([lower(creator), lower(creator), lower(trader), lower(trader)]); - const l5Swaps = await get(`${path("l5")}/swaps`); - const l6Swaps = await get(`${path("l6")}/swaps`); - const l5Claims = await expectClaimsOnce("l5"); - const l6Claims = await expectClaimsOnce("l6"); - // the exact-input fee differs by launch with the pool price it met - for (const [launch, page, exactInFee] of [[l5, l5Swaps, 999083373736809197195735n], [l6, l6Swaps, 999083373736809197454718n]] as const) { - expect(page.data.length).toBe(launch.fee_ledger.swap_count); - // every swap fee is accrued once to the creator ledger of its asset - for (const asset of ["launch_token", "quote_token"]) - expect(page.data.filter((s: any) => s.fee_asset === asset).reduce((n: bigint, s: any) => n + BigInt(s.fee_amount), 0n).toString()) - .toBe(launch.fee_ledger[asset].accrued); - // at the maximum: exact input nets floor(gross / 2^64) of the output; exact output pays calculated * 2^64 - const atMax = page.data.filter((s: any) => s.fee_rate === MAX); - expect(atMax.map((s: any) => [s.direction, s.fee_asset])).toEqual([["buy", "launch_token"], ["buy", "quote_token"]]); - const [exactIn, exactOut] = atMax; - const net = -BigInt(exactIn.launch_token_delta); - expect([net, BigInt(exactIn.quote_token_delta)]).toEqual([54160n, 10n ** 18n]); - expect(BigInt(exactIn.fee_amount)).toBe(exactInFee); - expect((net + BigInt(exactIn.fee_amount)) / 2n ** 64n).toBe(net); - expect([BigInt(exactOut.launch_token_delta), BigInt(exactOut.quote_token_delta)]).toEqual( - [-(10n ** 10n), 184817928874495997640704n]); - expect(BigInt(exactOut.quote_token_delta)).toBe(10019n * 2n ** 64n); - expect(BigInt(exactOut.fee_amount)).toBe(184817928874495997630685n); - expect(BigInt(exactOut.quote_token_delta) - BigInt(exactOut.fee_amount)).toBe(10019n); - } - // l5 decays from the maximum: one swap mid-window, then at the final fee - expect(l5Swaps.data.map((s: any) => s.fee_rate)).toEqual( - [MAX, MAX, "3470655575073783216", "55340232221128654", "55340232221128654"]); - expect(l6Swaps.data.map((s: any) => s.fee_rate)).toEqual([MAX, MAX]); - // l1 claims: by the creator, by the recipient-owner after two transfers, then twice at zero - expect(claims.data.map((c: any) => c.owner)).toEqual( - [lower(creator), lower(feeRecipient), lower(feeRecipient), lower(feeRecipient)]); - expect(claims.data.at(-1).launch_token_amount).toBe("0"); - const firstSwap = await get(`${path("l1")}/swaps?limit=1`); - const later = await get(`${path("l1")}/swaps?after_event_id=${firstSwap.next_cursor}`); - expect([...firstSwap.data, ...later.data]).toEqual(swaps.data); - const firstClaim = await get(`${path("l1")}/claims?limit=1`); - const laterClaims = await get(`${path("l1")}/claims?after_event_id=${firstClaim.next_cursor}`); - expect([...firstClaim.data, ...laterClaims.data]).toEqual(claims.data); - for (const row of [...swaps.data, ...claims.data, ...owners.data]) { - const [block] = await db.sql`SELECT block_hash, block_time FROM blocks WHERE chain_id=${CHAIN_ID} AND block_number=${row.event.block_number}`; - expect(row.event.block_hash).toBe(`0x${block!.block_hash.toString(16).padStart(64, "0")}`); - expect(row.event.block_time).toBe(Math.floor(block!.block_time.getTime() / 1000)); - } - // native buys pay ETH in: quote delta positive on buys - expect(swaps.data.filter((s: any) => s.direction === "buy").every((s: any) => BigInt(s.quote_token_delta) > 0n)).toBe(true); - // native token1 and ERC-20 token0 launches share the one extension - const l2 = await get(path("l2")); - expect([detail.pool_key.extension, detail.launch_token_is_token1, detail.quote_token.kind, detail.supply.sink]).toEqual( - [lower(full.instances[0].extension), true, "native", "578"]); - expect([l2.pool_key.extension, l2.launch_token_is_token1, l2.quote_token.kind, l2.supply.sink]).toEqual( - [lower(full.instances[0].extension), false, "erc20", "790"]); - // no ERC-20 metadata is indexed for the local quote - expect(l2.quote_token).toEqual({ address: "0xffffffffffffffffffffffffffffffffffffffff", name: null, symbol: null, decimals: null, kind: "erc20" }); - // l2 ownership: creator, transferred to newOwner on the prefix branch - const l2Owners = await get(`${path("l2")}/owners`); - expect(l2Owners.data.map((o: any) => [o.previous_owner, o.new_owner])).toEqual([ - [null, lower(creator)], [lower(creator), lower(newOwner)]]); - expect([l2.creator, l2.owner]).toEqual([lower(creator), lower(newOwner)]); - const l2Swaps = await get(`${path("l2")}/swaps`); - expect(l2Swaps.data.length).toBe(l2.fee_ledger.swap_count); - // ERC-20 quote launches are served only while their quote is reviewed - const nativeOnly = (quotes: unknown[]) => new LaunchQueries(db.sql as never, { - localHarness: true, instances: LOCAL_INSTANCES, quotes: quotes as never, maxIndexAgeSeconds: 60, - }); - for (const quotes of [[], LOCAL_QUOTES.map((q) => ({ ...q, runtime: `0x${"0".repeat(64)}` })), LOCAL_QUOTES.map((q) => ({ ...q, chainId: "1" }))]) { - const restricted = nativeOnly(quotes); - const spy = [ - spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => restricted.list(input)), - spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => restricted.detail(chain, pool)), - spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => restricted.swaps(input)), - spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => restricted.claims(input)), - spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => restricted.owners(input)), - ]; - try { - // screened before pagination: the native launch fills the page - const page = await get(`/launches?chainId=${CHAIN_ID}&limit=2`); - expect(page.data.map((l: any) => l.pool_id)).toEqual(["l5", "l1"].map((l) => fixture.launches[l]!.poolId)); - expect(page.next_cursor).toBeNull(); - for (const label of ["l2", "l4", "l6", "l7", "l8"]) - for (const suffix of ["", "/swaps", "/claims", "/owners"]) { - const body = await get(`${path(label)}${suffix}`, 404); - expect(body).toEqual({ status: 404, error: "Launch not found" }); - } - expect(await get(path("l1"))).toEqual(detail); - expect(await get(path("l5"))).toEqual(l5); - } finally { - spy.forEach((s) => s.mockRestore()); - } - } - spies.forEach((s) => s.mockRestore()); - spies.splice(0, spies.length, - spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), - spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), - spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), - spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), - spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), - ); - expect(await get(path("l2"))).toEqual(l2); - if (process.env.LAUNCH_GOLDEN_DIR) { - const l2Claims = await get(`${path("l2")}/claims`); - const l4 = await get(path("l4")); - await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden.json"), JSON.stringify({ - schema_version: "zero-seed-launch-api/v5", - source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain.json", chain_id: String(CHAIN_ID) }, - list, second, canonicalList, nativeDetail: detail, nativeSwaps: swaps, nativeClaims: claims, nativeOwners: owners, - erc20Detail: l2, erc20Swaps: l2Swaps, erc20Claims: l2Claims, erc20Owners: l2Owners, erc20TraderOwnedDetail: l4, - maxNativeDetail: l5, maxNativeSwaps: l5Swaps, maxNativeClaims: l5Claims, - maxErc20Detail: l6, maxErc20Swaps: l6Swaps, maxErc20Claims: l6Claims, - lowPriceErc20Detail: l7, fullWidthErc20Detail: l8, - }, null, 2)); - } - const before = await get(path("l1")); - await replay(db, canonical, processors); - await apiHead(db); - expect(await get(path("l1"))).toEqual(before); - await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; - expect(await get(path("l1"))).toEqual(before); - await db.sql`UPDATE zero_seed_launch_states SET claimed0 = accrued0 + 1 WHERE pool_key_id = (SELECT pool_key_id FROM pool_keys WHERE chain_id=${CHAIN_ID} AND pool_id=${num(fixture.launches.l1!.poolId)}::numeric)`; - await get(path("l1"), 500); - await db.sql`SELECT recompute_zero_seed_launch_state(pool_key_id) FROM zero_seed_launch_created`; - expect(await get(path("l1"))).toEqual(before); - // Malformed indexed supply fails as 500 on detail and list, never served. - const [check] = await db.sql<{ name: string; def: string }[]>` - SELECT conname AS name, pg_get_constraintdef(oid) AS def FROM pg_constraint - WHERE conrelid = 'zero_seed_launch_created'::regclass AND contype = 'c' - AND pg_get_constraintdef(oid) LIKE '%installed_supply%'`; - await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created DROP CONSTRAINT "${check!.name}"`); - await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; - const l1Pool = num(fixture.launches.l1!.poolId); - for (const installed of ["0", "1000000000000000000000000001"]) { - await db.sql`UPDATE zero_seed_launch_created SET installed_supply = ${installed}::numeric WHERE pool_id = ${l1Pool}::numeric`; - await get(path("l1"), 500); - await get(`/launches?chainId=${CHAIN_ID}`, 500); - } - await db.sql`UPDATE zero_seed_launch_created SET installed_supply = ${before.supply.installed}::numeric WHERE pool_id = ${l1Pool}::numeric`; - await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; - await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created ADD CONSTRAINT "${check!.name}" ${check!.def}`); - expect(await get(path("l1"))).toEqual(before); - // A stored fee above uint64 (2^64, exactly 100%) fails as 500 on every - // read that serves it, never wrapped, truncated or served. - const l6Before = await get(path("l6")); - const l6SwapsBefore = await get(`${path("l6")}/swaps`); - const l6Pool = num(fixture.launches.l6!.poolId); - const Q64 = (2n ** 64n).toString(); - await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; - await db.sql`UPDATE zero_seed_launch_created SET initial_fee = ${Q64}::numeric WHERE pool_id = ${l6Pool}::numeric`; - await get(path("l6"), 500); - await get(`/launches?chainId=${CHAIN_ID}`, 500); - await db.sql`UPDATE zero_seed_launch_created SET initial_fee = ${MAX}::numeric WHERE pool_id = ${l6Pool}::numeric`; - await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; - await db.sql`ALTER TABLE zero_seed_launch_swapped DISABLE TRIGGER no_updates_zero_seed_launch_swapped`; - const l6Swap = BigInt(l6SwapsBefore.data[0].event.event_id); - await db.sql`UPDATE zero_seed_launch_swapped SET fee_rate = ${Q64}::numeric WHERE chain_id = ${CHAIN_ID} AND event_id = ${l6Swap}`; - await get(`${path("l6")}/swaps`, 500); - await db.sql`UPDATE zero_seed_launch_swapped SET fee_rate = ${MAX}::numeric WHERE chain_id = ${CHAIN_ID} AND event_id = ${l6Swap}`; - await db.sql`ALTER TABLE zero_seed_launch_swapped ENABLE TRIGGER no_updates_zero_seed_launch_swapped`; - expect(await get(path("l6"))).toEqual(l6Before); - expect(await get(`${path("l6")}/swaps`)).toEqual(l6SwapsBefore); - await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes' WHERE chain_id=${CHAIN_ID}`; - await get(path("l1"), 503); - await apiHead(db); - await db.sql`UPDATE indexer_cursor SET head_block_hash = 0 WHERE chain_id=${CHAIN_ID}`; - await get(path("l1"), 503); - await apiHead(db); - // A reader without the periphery-bound pins serves nothing. - const unpinned = new LaunchQueries(db.sql as never, { - localHarness: true, - instances: LOCAL_INSTANCES.map((p) => ({ ...p, periphery: "0x0000000000000000000000000000000000000001" })), - quotes: LOCAL_QUOTES, - maxIndexAgeSeconds: 60, - }); - await expect(unpinned.detail(CHAIN_ID, BigInt(fixture.launches.l1!.poolId))).rejects.toThrow(/disabled/); - const writerSql = postgres(urlFor(db.name), { max: 1, onnotice: () => {}, types: { bigint: postgres.BigInt, numeric: NumericIntegerType } }) as unknown as Db["sql"]; - const writer = { sql: writerSql, dao: Reflect.construct(DAO, [writerSql, CHAIN_ID]) as DAO, name: db.name }; - try { - await Reflect.apply(Reflect.get(reader, "snapshot"), reader, [CHAIN_ID, async (tx: any, asOf: any) => { - const [old] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; - await invalidateFrom(writer.dao, fixture.forkPoint + 1); - await apiHead(writer); - const [same] = await tx`SELECT COUNT(*)::int AS n FROM zero_seed_launch_created`; - const [sameHead] = await tx`SELECT head_block_number FROM indexer_cursor WHERE chain_id=${CHAIN_ID}`; - expect(same.n).toBe(old.n); - expect(sameHead.head_block_number.toString()).toBe(asOf.block_number); - }]); - } finally { - await writerSql.end(); - } - expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual( - [fixture.launches.l2!.poolId, fixture.launches.l1!.poolId]); - // Back to the deployment block: only the launch created in it remains. - const l2Block = Number((await get(path("l2"))).created.block_number); - await invalidateFrom(db.dao, l2Block); - await apiHead(db); - expect((await get(`/launches?chainId=${CHAIN_ID}`)).data.map((l: any) => l.pool_id)).toEqual([fixture.launches.l1!.poolId]); - await get(path("l2"), 404); - // Before the deployment block nothing is indexed: no coherent head. - await invalidateFrom(db.dao, Number(before.created.block_number)); - await apiHead(db); - await get(`/launches?chainId=${CHAIN_ID}`, 503); - expect((await db.sql`SELECT COUNT(*)::int AS n FROM zero_seed_launch_states`)[0]!.n).toBe(0); - } finally { - spies.forEach((s) => s.mockRestore()); - await db.sql.end(); - } - }, 300_000); diff --git a/scripts/launchReplayCasesV2.txt b/scripts/launchReplayCasesV2.txt deleted file mode 100644 index c13a426..0000000 --- a/scripts/launchReplayCasesV2.txt +++ /dev/null @@ -1,152 +0,0 @@ - - // ----- API cases for the tick-rate fee model, appended by scripts/verifyLaunchReplay.ts ----- - - async function apiHead(db: Db) { - await db.sql`UPDATE indexer_cursor SET - head_block_number = b.block_number, head_block_hash = b.block_hash, - head_block_time = b.block_time, last_updated = NOW() - FROM (SELECT * FROM blocks WHERE chain_id = ${CHAIN_ID} ORDER BY block_number DESC LIMIT 1) b - WHERE indexer_cursor.chain_id = ${CHAIN_ID}`; - } - - test("API actual handlers, tick-rate model: schedules, rates, feeAt, swaps, claims, model screening and malformed rows", async () => { - const db = await freshDb(); - const reader = new LaunchQueries(db.sql as never, { - localHarness: true, instances: LOCAL_INSTANCES, quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, - }); - const spies = [ - spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((input) => reader.list(input)), - spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation((chain, pool) => reader.detail(chain, pool)), - spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((input) => reader.swaps(input)), - spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((input) => reader.claims(input)), - spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((input) => reader.owners(input)), - ]; - const get = async (path: string, status = 200) => { - const response = await app.fetch(new Request(`http://localhost${path}`), { PG_CONNECTION_STRING: urlFor(db.name) }); - const body = await response.json() as any; - expect(response.status).toBe(status); - if (response.status !== status) throw new Error(JSON.stringify(body)); - return body; - }; - const lower = (a: string) => a.toLowerCase(); - const full = JSON.parse(readFileSync(resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain-v2.json"), "utf8")); - expect(full.feeModel).toBe("tick_rate_v2"); - // The served tick-rate instance is the fixture's, field for field. - expect(LOCAL_INSTANCES.filter((p) => p.feeModel === "tick_rate_v2")).toEqual(full.instances.map((i: any) => ({ - chainId: String(CHAIN_ID), feeModel: "tick_rate_v2", address: lower(i.extension), runtime: i.extensionRuntimeKeccak, - periphery: lower(i.periphery), peripheryRuntime: i.peripheryRuntimeKeccak, core: lower(full.addresses.core), - abiSha256: full.provenance.launchAbiSha256, peripheryAbiSha256: full.provenance.peripheryAbiSha256, - }))); - const path = (label: string) => `/launches/${CHAIN_ID}/${fixture.launches[label]!.poolId}`; - const MAX = 2n ** 64n - 1n; - /** One launch at one block: the served schedule versus the chain's feeAt and the indexer's SQL. */ - const expectLaunch = async (pool: any) => { - const body = await get(path(pool.label)); - const schedule = body.fee_schedule; - expect(body.schema_version).toBe("zero-seed-launch-api/v5"); - expect(schedule.model).toBe("tick_rate_v2"); - expect(schedule).not.toHaveProperty("fee_end"); - expect(schedule).not.toHaveProperty("fee_duration"); - expect(schedule).not.toHaveProperty("decay"); - expect(schedule.evaluated_at).toBe(body.as_of.block_time); - const [row] = await db.sql<{ rate: string; fee: string; start: string }[]>` - SELECT decay_rate_x32::TEXT AS rate, trading_start::TEXT AS start, - zero_seed_launch_model_fee_at(fee_model, trading_start, fee_duration, decay_rate_x32, - initial_fee, final_fee, ${body.as_of.block_time}::int8)::TEXT AS fee - FROM zero_seed_launch_created WHERE pool_id = ${num(pool.poolId)}::numeric`; - expect(schedule.decay_rate_x32).toBe(row!.rate); - // TypeScript schedule = indexer SQL = the contract's feeAt at the block - expect(schedule.fee_rate).toBe(row!.fee); - expect(schedule.fee_rate).toBe(pool.feeAtBlockTime); - const rate = BigInt(schedule.decay_rate_x32); - expect(schedule.half_life_seconds).toBe(halfLifeSeconds(rate)); - expect(schedule.saturation_elapsed_seconds).toBe(rate === 0n ? null : ((((88722835n + 1n) << 32n) - 1n) / rate).toString()); - const expectedPhase = body.as_of.block_time < schedule.trading_start ? "before_trading_start" - : schedule.fee_rate === schedule.final_fee ? "at_floor" : rate === 0n ? "constant" : "decaying"; - expect(schedule.phase).toBe(expectedPhase); - expect(body.owner).toBe(lower(pool.launchOwner)); - expect(body.controller).toBe(lower(pool.controller)); - const launchIs1 = body.launch_token_is_token1; - expect(body.fee_ledger.launch_token.unclaimed).toBe(pool.creatorFees[launchIs1 ? 1 : 0]); - expect(body.fee_ledger.quote_token.unclaimed).toBe(pool.creatorFees[launchIs1 ? 0 : 1]); - expect([body.supply.requested, body.supply.sink]).toEqual([pool.totalSupply, pool.sinkBalance]); - return body; - }; - const golden: Record = {}; - try { - for (const branch of [prefix, canonical]) - await replay(db, branch, async (state) => { - await apiHead(db); - for (const pool of state.pools) await expectLaunch(pool); - }); - const list = await get(`/launches?chainId=${CHAIN_ID}`); - expect(list.data.every((l: any) => l.fee_schedule.model === "tick_rate_v2")).toBe(true); - // rates 0, 1, the default, the 600 s rate and 2^64 - 1, served as decimal strings - expect(new Set(list.data.map((l: any) => l.fee_schedule.decay_rate_x32))).toEqual( - new Set(["0", "1", "68913100933", "9923486534471", MAX.toString()])); - golden.list = list; - for (const label of ["l1", "l2", "l4", "l5", "l6"]) { - const detail = await get(path(label)); - const swaps = await get(`${path(label)}/swaps`); - const claims = await get(`${path(label)}/claims`); - expect(swaps.data.length).toBe(detail.fee_ledger.swap_count); - // every swap's served rate is the indexed LaunchSwapped.feeRate, the - // contract's feeAt at its block, which the SQL mirror reproduces - const rows = await db.sql<{ ok: boolean }[]>` - SELECT w.fee_rate = zero_seed_launch_model_fee_at(c.fee_model, c.trading_start, c.fee_duration, c.decay_rate_x32, - c.initial_fee, c.final_fee, EXTRACT(EPOCH FROM b.block_time)::int8) AS ok - FROM zero_seed_launch_swapped w JOIN zero_seed_launch_created c USING (pool_key_id) - JOIN blocks b ON b.chain_id = w.chain_id AND b.block_number = w.block_number - WHERE c.pool_id = ${num(fixture.launches[label]!.poolId)}::numeric`; - expect(rows.every((r) => r.ok)).toBe(true); - for (const asset of ["launch_token", "quote_token"]) { - expect(swaps.data.filter((s: any) => s.fee_asset === asset).reduce((n: bigint, s: any) => n + BigInt(s.fee_amount), 0n).toString()) - .toBe(detail.fee_ledger[asset].accrued); - expect(claims.data.reduce((n: bigint, c: any) => n + BigInt(c[`${asset}_amount`]), 0n).toString()) - .toBe(detail.fee_ledger[asset].claimed); - } - golden[label] = { detail, swaps, claims }; - } - // l4 (rate 0) never decays; l5 (2^64 - 1) is at the floor after its start - const l4 = golden.l4 as any; - expect(l4.detail.fee_schedule).toMatchObject({ decay_rate_x32: "0", phase: "constant", half_life_seconds: null, saturation_elapsed_seconds: null }); - expect(l4.swaps.data.every((s: any) => s.fee_rate === l4.detail.fee_schedule.initial_fee)).toBe(true); - const l5 = golden.l5 as any; - expect(l5.detail.fee_schedule).toMatchObject({ decay_rate_x32: MAX.toString(), phase: "at_floor", saturation_elapsed_seconds: "0" }); - // A reader whose pin has the other fee model for this deployment serves nothing. - const wrongModel = new LaunchQueries(db.sql as never, { - localHarness: true, - instances: LOCAL_INSTANCES.map((p) => ({ ...p, feeModel: p.feeModel === "tick_rate_v2" ? "fixed_base_duration_v1" as const : "tick_rate_v2" as const })), - quotes: LOCAL_QUOTES, maxIndexAgeSeconds: 60, - }); - await expect(wrongModel.detail(CHAIN_ID, BigInt(fixture.launches.l1!.poolId))).rejects.toThrow(/disabled|not found/); - // A malformed indexed schedule fails as 500, never served: a v2 row with - // a duration (the CHECK is lifted to simulate corruption). - const before = await get(path("l1")); - const [check] = await db.sql<{ name: string; def: string }[]>` - SELECT conname AS name, pg_get_constraintdef(oid) AS def FROM pg_constraint - WHERE conrelid = 'zero_seed_launch_created'::regclass AND contype = 'c' - AND pg_get_constraintdef(oid) LIKE '%decay_rate_x32%'`; - await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created DROP CONSTRAINT "${check!.name}"`); - await db.sql`ALTER TABLE zero_seed_launch_created DISABLE TRIGGER no_updates_zero_seed_launch_created`; - const l1Pool = num(fixture.launches.l1!.poolId); - for (const [duration, rate] of [["3600", before.fee_schedule.decay_rate_x32], [null, (2n ** 64n).toString()], [null, null]] as const) { - await db.sql`UPDATE zero_seed_launch_created SET fee_duration = ${duration}::int8, decay_rate_x32 = ${rate}::numeric WHERE pool_id = ${l1Pool}::numeric`; - await get(path("l1"), 500); - await get(`/launches?chainId=${CHAIN_ID}`, 500); - } - await db.sql`UPDATE zero_seed_launch_created SET fee_duration = NULL, decay_rate_x32 = ${before.fee_schedule.decay_rate_x32}::numeric WHERE pool_id = ${l1Pool}::numeric`; - await db.sql`ALTER TABLE zero_seed_launch_created ENABLE TRIGGER no_updates_zero_seed_launch_created`; - await db.sql.unsafe(`ALTER TABLE zero_seed_launch_created ADD CONSTRAINT "${check!.name}" ${check!.def}`); - expect(await get(path("l1"))).toEqual(before); - if (process.env.LAUNCH_GOLDEN_DIR) - await Bun.write(resolve(process.env.LAUNCH_GOLDEN_DIR, "actual-handler-golden-tick-rate.json"), JSON.stringify({ - schema_version: "zero-seed-launch-api/v5", - source: { fixture: "indexer tests/fixtures/zero-seed-launch/local-chain-v2.json", chain_id: String(CHAIN_ID), fee_model: "tick_rate_v2" }, - ...golden, - }, null, 2)); - } finally { - spies.forEach((s) => s.mockRestore()); - await db.sql.end(); - } - }, 300_000); diff --git a/scripts/verifyLaunchReplay.ts b/scripts/verifyLaunchReplay.ts deleted file mode 100644 index 2e8984c..0000000 --- a/scripts/verifyLaunchReplay.ts +++ /dev/null @@ -1,97 +0,0 @@ -// Reuse the exact reviewed indexer replay harnesses without editing their checkout. -// No RPC/provider call or production database is involved. Generated test files -// belong to the run's scratch directory and are evidence, not source overrides. -// One run per fee model: the fixed-base harness and fixture (evm-contracts -// 43d6628) and the tick-rate ones (3d6378f), each with its API cases. -import { resolve, dirname } from "node:path"; -import { mkdir } from "node:fs/promises"; - -const root = process.env.ZERO_SEED_INDEXER_WORKTREE; -const scratch = process.env.LAUNCH_REPLAY_SCRATCH_DIR; -const pg = process.env.TEST_PG_CONNECTION_STRING; -if (!root || !scratch || !pg) - throw new Error( - "Indexer worktree, run scratch and local Postgres are required", - ); -const url = new URL(pg); -if (!["127.0.0.1", "localhost"].includes(url.hostname)) - throw new Error("Only loopback test Postgres is permitted"); -const head = Bun.spawnSync(["git", "-C", root, "rev-parse", "HEAD"]); -if ( - head.stdout.toString().trim() !== "7f373e59ca79eadb5d7ce47a29eb7f02decf31da" -) - throw new Error("Indexer source pin mismatch"); -const clean = Bun.spawnSync([ - "git", - "-C", - root, - "diff", - "--quiet", - "HEAD", - "--", -]); -if (clean.exitCode !== 0) - throw new Error("Pinned indexer tracked source is modified"); - -const RUNS = [ - { - harness: "src/evm/zeroSeedLaunchReplay.postgres.test.ts", - fixture: "tests/fixtures/zero-seed-launch/local-chain.json", - sha256: "ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9", - cases: "launchReplayCases.txt", - target: "api-emitted-replay.test.ts", - }, - { - harness: "src/evm/zeroSeedLaunchReplayV2.postgres.test.ts", - fixture: "tests/fixtures/zero-seed-launch/local-chain-v2.json", - sha256: "52377ccd26930d893aa656d7835375536c7e13375faa437d1b8d220f1b1d1d2f", - cases: "launchReplayCasesV2.txt", - target: "api-emitted-replay-tick-rate.test.ts", - }, -]; - -const api = resolve(import.meta.dir, "../src"); -await mkdir(scratch, { recursive: true }); -let failed = 0; -for (const run of RUNS) { - const fixtureBytes = await Bun.file(resolve(root, run.fixture)).arrayBuffer(); - if ( - new Bun.CryptoHasher("sha256").update(fixtureBytes).digest("hex") !== - run.sha256 - ) - throw new Error(`Emitted fixture pin mismatch: ${run.fixture}`); - const original = resolve(root, run.harness); - let source = await Bun.file(original).text(); - source = source.replace(/from "([^"]+)"/g, (match, name: string) => { - if (name.startsWith("node:") || name === "bun:test") return match; - const target = name.startsWith(".") - ? resolve(dirname(original), name) - : Bun.resolveSync(name, root); - return `from ${JSON.stringify(target)}`; - }); - source = source.replace( - 'const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");', - `const ROOT = ${JSON.stringify(root)};`, - ); - const extra = await Bun.file(resolve(import.meta.dir, run.cases)).text(); - source = `import { spyOn } from "bun:test"; -import { LaunchQueries } from ${JSON.stringify(resolve(api, "routes/launches/queries"))}; -import { LOCAL_INSTANCES, LOCAL_QUOTES, halfLifeSeconds } from ${JSON.stringify(resolve(api, "routes/launches/policy"))}; -import { Queries } from ${JSON.stringify(resolve(api, "queries"))}; -import { app } from ${JSON.stringify(resolve(api, "router"))}; -${source}`; - // The harness ends by closing its describe callback, as `});` or, formatted - // across lines, ` },\n);`. The API cases go inside that callback. - const ending = source.search(/\n(?:\}\);| \},\n\);)\s*$/); - if (ending < 0) throw new Error("Unrecognized pinned indexer harness"); - source = source.slice(0, ending + 1) + extra + source.slice(ending + 1); - const target = resolve(scratch, run.target); - await Bun.write(target, source); - const child = Bun.spawn(["bun", "test", target, "--timeout", "300000"], { - stdout: "inherit", - stderr: "inherit", - env: process.env, - }); - if ((await child.exited) !== 0) failed += 1; -} -process.exit(failed === 0 ? 0 : 1); diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 5ada1f0..95179ca 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -2,13 +2,14 @@ import { ContractCase, stubQuery } from "../harness"; import { LOCAL_INSTANCES } from "../../routes/launches/policy"; const NATIVE = "0x0000000000000000000000000000000000000000" as const; +const VERSION = "zero-seed-launch-api/v6" as const; const pin = LOCAL_INSTANCES[0]; const owner = `0x${"2".repeat(40)}`; const zero = `0x${"0".repeat(64)}`; const as_of = { block_number: "1", block_hash: zero, block_time: 1000 }; const summary = { - schema_version: "zero-seed-launch-api/v5" as const, + schema_version: VERSION, chain_id: "31337", pool_id: zero, pool_key: { @@ -17,14 +18,14 @@ const summary = { token1: `0x${"1".repeat(40)}`, config: zero, fee: "0" as const, - tick_spacing: 100, + tick_spacing: 17990, extension: pin.address, }, token: { address: `0x${"1".repeat(40)}`, - name: "Local", - symbol: "LOCAL", - decimals: 18, + name: "Zero Seed", + symbol: "ZERO", + decimals: 18 as const, }, quote_token: { address: NATIVE, @@ -33,50 +34,41 @@ const summary = { decimals: 18, kind: "native" as const, }, - supply: { - requested: "1000000000000000000000000000", - installed: "999999999999999999999999422", - sink: "578", - sink_holder: NATIVE, - }, launch_token_is_token1: true, controller: pin.periphery, creator: owner, owner, - ask_tick: 0, - upper_tick: 100, - fee_schedule: { - model: "fixed_base_duration_v1" as const, - trading_start: 1000, - fee_duration: 3600, - fee_end: 4600, - initial_fee: "10", - final_fee: "3", - decay: { - kind: "fixed_base_q128" as const, - half_life_seconds: 600 as const, - }, - phase: "fee_decay" as const, - fee_rate: "10", - evaluated_at: 1000, + logo_url: null, + parameters: { + name: "0x095a65726f205365656400000000000000000000000000000000000000000000", + symbol: + "0x045a45524f000000000000000000000000000000000000000000000000000000", + supply: "1000000000000000000000000000", + quote_token: NATIVE, + ask_tick: "-13815500", + upper_tick: "-9210300", + trading_start: "1000", + decay_rate_x32: "68913100933", + initial_fee: "9223372036854775808", + final_fee: "55340232221128654", + salt: zero, }, - created: { - ...as_of, - event_id: "-1", - transaction_index: 0, - event_index: 0, - transaction_hash: zero, + range: { + tick_spacing: 17990, + aligned_ask_tick: -13798330, + aligned_upper_tick: -9210880, + pool_tick_lower: 9210880, + pool_tick_upper: 13798330, }, - as_of, -}; -// The same launch shape on the tick-rate instance: a floor, never an end. -const tickRatePin = LOCAL_INSTANCES[1]; -const tickRateSummary = { - ...summary, - pool_key: { ...summary.pool_key, extension: tickRatePin.address }, - controller: tickRatePin.periphery, + supply: { + requested: "1000000000000000000000000000", + installed: "999999999999999999999999508", + sink: "492", + sink_holder: NATIVE, + }, + trading_start: 1000, + status: "trading" as const, fee_schedule: { - model: "tick_rate_v2" as const, trading_start: 1000, initial_fee: "9223372036854775808", final_fee: "55340232221128654", @@ -87,10 +79,28 @@ const tickRateSummary = { fee_rate: "9223372036854775808", evaluated_at: 1000, }, + market: { + price_tick: -13798330, + price_x128: "340282366920938463463374607431768", + market_cap: "1000000000000000000000", + market_cap_usd: null, + raised: "0", + sold: "0", + sold_share: "0.000000000000000000", + trade_count: 0, + }, + created: { + ...as_of, + event_id: "-1", + transaction_index: 0, + event_index: 0, + transaction_hash: zero, + }, + as_of, }; const ledger = { accrued: "0", claimed: "0", unclaimed: "0" }; const identity = { - schema_version: "zero-seed-launch-api/v5" as const, + schema_version: VERSION, chain_id: "31337", pool_id: zero, as_of, @@ -100,13 +110,23 @@ const path = `/launches/31337/${zero}`; export const launchesCases: ContractCase[] = [ { operation: "/launches", - request: "/launches?chainId=31337", + request: "/launches?status=trading", stub: () => stubQuery("readZeroSeedList", { - schema_version: "zero-seed-launch-api/v5", - data: [tickRateSummary, summary], - as_of, - next_cursor: null, + schema_version: VERSION, + data: [ + summary, + { + ...summary, + logo_url: "https://example.invalid/logo.svg", + status: "upcoming", + }, + ], + total: 2, + totals: { all: 2, trading: 1, upcoming: 1 }, + next_offset: null, + evaluated_at: 1000, + heads: [{ chain_id: "31337", ...as_of }], }), }, { @@ -117,17 +137,13 @@ export const launchesCases: ContractCase[] = [ ...summary, principal: { position_id: zero, - tick_lower: 0, - tick_upper: 100, liquidity: "100", locked: true, withdrawable: false, pool_reserves: { launch_token: "1000", quote_token: "0" }, - reserves_basis: "core_pool_balance", }, fee_ledger: { holder: owner, - transferable: true, launch_token: ledger, quote_token: ledger, swap_count: 0, @@ -135,7 +151,6 @@ export const launchesCases: ContractCase[] = [ owner_change_count: 1, last_event_id: "-1", }, - inventory: { trading_open: true, no_bid: true, price_tick: 0 }, }), }, { @@ -146,7 +161,21 @@ export const launchesCases: ContractCase[] = [ { operation: "/launches/{chainId}/{poolId}/claims", request: `${path}/claims`, - stub: () => stubQuery("readZeroSeedClaims", { ...identity, data: [] }), + stub: () => + stubQuery("readZeroSeedClaims", { + ...identity, + data: [ + { + event: summary.created, + payout_event_id: "1", + owner, + recipient: owner, + launch_token_amount: "0", + quote_token_amount: "5", + }, + ], + totals: { launch_token: "0", quote_token: "5" }, + }), }, { operation: "/launches/{chainId}/{poolId}/owners", diff --git a/src/queries.ts b/src/queries.ts index 632cb32..774af81 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -2,6 +2,7 @@ import postgres, { type Sql } from "postgres"; import { Env } from "./env"; import { LaunchQueries, + type LaunchListOptions, type LaunchReadOptions, } from "./routes/launches/queries"; @@ -201,9 +202,7 @@ export class Queries { this.sql = sql; } - public readZeroSeedList( - input: LaunchReadOptions & { creator?: bigint; owner?: bigint }, - ) { + public readZeroSeedList(input: LaunchListOptions) { return new LaunchQueries(this.sql).list(input); } diff --git a/src/router.test.ts b/src/router.test.ts index 524a60b..7360f97 100644 --- a/src/router.test.ts +++ b/src/router.test.ts @@ -31,7 +31,7 @@ describe("Hono app integration", () => { (count, operation) => count + (operation.parameters?.length ?? 0), 0, ), - ).toBe(217); + ).toBe(219); const getToken = operations.find( (operation) => diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts index a8c166e..4f7b5fe 100644 --- a/src/routes/launches/index.ts +++ b/src/routes/launches/index.ts @@ -76,30 +76,35 @@ export const ListLaunches = defineRoute( path: "/launches", tags: ["Launches"], operationId: "get_ListLaunches", - summary: "List supported zero-seed launches at one indexed head", + summary: + "List supported zero-seed launches across chains, with status, owner and creator filters", request: { query: z.object({ - chainId, - before_event_id: cursor.optional(), - limit, - // owner at creation - creator: address.optional(), + // omitted: every chain with launch support + chainId: chainId.optional(), + status: z.enum(["all", "trading", "upcoming"]).default("all"), // current owner owner: address.optional(), + // owner at creation + creator: address.optional(), + order_by: z.enum(["market_cap", "created"]).default("market_cap"), + offset: z.coerce.number().int().min(0).max(100_000).default(0), + limit, }), }, responses: { 200: response(LaunchList), ...errors }, }), async (c) => { - const { chainId, before_event_id, limit, creator, owner } = - c.req.valid("query"); + const query = c.req.valid("query"); const q = await launchQueries(c.env); const data = await q.readZeroSeedList({ - chainId, - cursor: before_event_id, - limit, - creator: creator === undefined ? undefined : BigInt(creator), - owner: owner === undefined ? undefined : BigInt(owner), + chainId: query.chainId, + status: query.status, + orderBy: query.order_by, + offset: query.offset, + limit: query.limit, + creator: query.creator === undefined ? undefined : BigInt(query.creator), + owner: query.owner === undefined ? undefined : BigInt(query.owner), }); return c.json(data, 200, headers); }, @@ -160,7 +165,7 @@ export const ListLaunchClaims = defineRoute( tags: ["Launches"], operationId: "get_ListLaunchClaims", summary: - "List atomic full two-asset creator fee claims, each paid out once by the periphery", + "List creator fee claims, one row per claim (extension event, periphery owner and recipient)", request: { params, query: page }, responses: { 200: response(ClaimPage), ...errors }, }), diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index 6b0e8e6..bac30bf 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -1,508 +1,244 @@ import { expect, test } from "bun:test"; +import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; -import { gunzipSync } from "node:zlib"; import { - DECAY_TABLE_Q128, - DECAY_ZERO_FROM, - decayQ128, - decayedFee, + alignedRange, decayRateForHalfLife, + feeAt, feeSchedule, halfLifeSeconds, HALF_LIFE_X32_E30, - MAX_DECAY_PRODUCT, - tickFactorQ128, - tickRateDecayedFee, + launchStatus, launchSupply, LOCAL_INSTANCES, LOCAL_QUOTES, + marketData, + MAX_DECAY_PRODUCT, + priceX128, + share18, supportedInstances, - supportedLaunch, + supportedQuote, supportedQuotes, + tickFactorQ128, + unpackString, + utf8OrNull, } from "./policy"; -import { - ClaimPage, - FeeSchedule, - LaunchDetail, - LaunchList, - LaunchSummary, - OwnerPage, - SwapPage, -} from "./schema"; +import { FeeSchedule, LaunchDetail, LaunchList, LaunchSummary } from "./schema"; const NATIVE = "0x0000000000000000000000000000000000000000"; +const MAX = (1n << 64n) - 1n; +const vectors = resolve(import.meta.dir, "vectors"); +const read = (name: string) => readFileSync(resolve(vectors, name)); +const V = JSON.parse(read("zero-seed-launch.json").toString("utf8")); +const INSTALL = JSON.parse( + read("zero-seed-launch-installation.json").toString("utf8"), +); -test("local instance manifests bind every runtime/ABI/periphery/Core field and carry no quote", () => { +test("vendored vectors are the freeze's bytes", () => { + const sha = (name: string) => + createHash("sha256").update(read(name)).digest("hex"); + expect(sha("zero-seed-launch.json")).toBe( + "e5444bf6b1f7e422238331b50b64cf0400597bd13421e281615735511274190b", + ); + expect(sha("zero-seed-launch-installation.json")).toBe( + "5cf44854cbf00abb700936b2e22782c27dac5e68cf892edc8f5aa7090067a555", + ); + expect(sha("zero-seed-launch-events.json")).toBe( + "24dd5883ce242ec523517c9f38cfd5e8ca29740974aeded5740ac3608e1e7066", + ); + expect(V.version).toBe(8); +}); + +test("local instance manifests bind every field, carry no quote and are refused when hosted", () => { expect(supportedInstances(LOCAL_INSTANCES, true)).toEqual(LOCAL_INSTANCES); expect(supportedInstances(LOCAL_INSTANCES, false)).toEqual([]); expect(supportedInstances([], true)).toEqual([]); - expect(LOCAL_INSTANCES.map((pin) => pin.feeModel)).toEqual([ - "fixed_base_duration_v1", - "tick_rate_v2", - ]); for (const pin of LOCAL_INSTANCES) { - expect(Object.keys(pin).some((key) => /quote/i.test(key))).toBe(false); - for (const key of Object.keys(pin) as (keyof typeof pin)[]) { + expect(Object.keys(pin).some((key) => /quote|model/i.test(key))).toBe( + false, + ); + for (const key of Object.keys(pin) as (keyof typeof pin)[]) expect( supportedInstances([{ ...pin, [key]: "unreviewed" }], true), ).toEqual([]); - } - const native = { ...pin, quote: NATIVE, quoteKind: "native" as const }; - expect(supportedLaunch(native, LOCAL_INSTANCES, [])).toBe(true); - for (const key of [ - "chainId", - "feeModel", - "address", - "periphery", - "core", - ] as const) { - expect( - supportedLaunch( - { ...native, [key]: "unreviewed" }, - LOCAL_INSTANCES, - LOCAL_QUOTES, - ), - ).toBe(false); - } - expect(supportedLaunch(native, [], LOCAL_QUOTES)).toBe(false); } }); test("quote support is per launch: native, or a reviewed ERC-20 by chain/address/runtime", () => { - const [pin] = LOCAL_INSTANCES; const [token] = LOCAL_QUOTES; - expect([token.chainId, token.address, token.runtime]).toEqual([ - "31337", - "0xffffffffffffffffffffffffffffffffffffffff", - "0xe8ccd7c4eb891208d5e04a27a31dfaf2b9bce01e083d7b36f10b6dd6ea643e63", - ]); expect(supportedQuotes(LOCAL_QUOTES, true)).toEqual(LOCAL_QUOTES); - // hosted: no ERC-20 quote is ever supported expect(supportedQuotes(LOCAL_QUOTES, false)).toEqual([]); for (const key of Object.keys(token) as (keyof typeof token)[]) expect(supportedQuotes([{ ...token, [key]: "unreviewed" }], true)).toEqual( [], ); - const erc20 = { - ...pin, - quote: token.address, - quoteKind: "erc20" as const, - }; - expect(supportedLaunch(erc20, LOCAL_INSTANCES, LOCAL_QUOTES)).toBe(true); - expect(supportedLaunch(erc20, LOCAL_INSTANCES, [])).toBe(false); - expect( - supportedLaunch( - { ...erc20, quote: `0x${"e".repeat(40)}` }, - LOCAL_INSTANCES, - LOCAL_QUOTES, - ), - ).toBe(false); + const row = { chainId: token.chainId, quote: token.address }; + expect(supportedQuote({ ...row, quoteKind: "erc20" }, LOCAL_QUOTES)).toBe( + true, + ); + expect(supportedQuote({ ...row, quoteKind: "erc20" }, [])).toBe(false); expect( - supportedLaunch( - { ...erc20, chainId: "1" }, - [{ ...pin, chainId: "1" }], - LOCAL_QUOTES, - ), + supportedQuote({ ...row, chainId: "1", quoteKind: "erc20" }, LOCAL_QUOTES), ).toBe(false); - // relabelling either kind is refused + expect(supportedQuote({ ...row, quoteKind: "native" }, LOCAL_QUOTES)).toBe( + false, + ); expect( - supportedLaunch( - { ...erc20, quoteKind: "native" }, - LOCAL_INSTANCES, - LOCAL_QUOTES, - ), - ).toBe(false); + supportedQuote({ ...row, quote: NATIVE, quoteKind: "native" }, []), + ).toBe(true); expect( - supportedLaunch( - { ...pin, quote: NATIVE, quoteKind: "erc20" }, - LOCAL_INSTANCES, - [{ ...token, address: NATIVE }], - ), + supportedQuote({ ...row, quote: NATIVE, quoteKind: "erc20" }, [ + { ...token, address: NATIVE }, + ]), ).toBe(false); }); -test("supply: 0 < installed <= requested and sink = requested - installed, else refused", () => { - const requested = 10n ** 27n; +test("supply: 0 < installed <= requested <= 10^30 and sink = requested - installed", () => { + const [first] = INSTALL.vectors; + const requested = BigInt(first.supply); expect( launchSupply({ requested, - installed: 999999999999999999999999422n, - sink: 578n, + installed: BigInt(first.installedSupply), + sink: BigInt(first.sink), }), ).toEqual({ - requested: "1000000000000000000000000000", - installed: "999999999999999999999999422", - sink: "578", + requested: first.supply, + installed: first.installedSupply, + sink: first.sink, sink_holder: NATIVE, }); - expect(launchSupply({ requested, installed: requested, sink: 0n }).sink).toBe( + for (const v of INSTALL.vectors) + if (v.installedSupply !== undefined && v.sink !== undefined) + expect( + launchSupply({ + requested: BigInt(v.supply), + installed: BigInt(v.installedSupply), + sink: BigInt(v.sink), + }).sink, + ).toBe(v.sink); + const cap = 10n ** 30n; + expect(launchSupply({ requested: cap, installed: cap, sink: 0n }).sink).toBe( "0", ); - const max = (1n << 127n) - 1n; - expect( - launchSupply({ requested: max, installed: 1n, sink: max - 1n }), - ).toEqual({ - requested: max.toString(), - installed: "1", - sink: (max - 1n).toString(), - sink_holder: NATIVE, - }); for (const bad of [ { requested, installed: 0n, sink: requested }, - { requested, installed: -1n, sink: requested + 1n }, { requested, installed: requested + 1n, sink: -1n }, - { requested, installed: requested - 578n, sink: 577n }, - { requested, installed: requested - 578n, sink: 579n }, - { requested: max + 1n, installed: 1n, sink: max }, - { requested: 0n, installed: 0n, sink: 0n }, + { requested, installed: requested - 492n, sink: 491n }, + { requested: cap + 1n, installed: cap + 1n, sink: 0n }, ]) expect(() => launchSupply(bad)).toThrow(/Invalid indexed launch supply/); }); -const input = { - model: "fixed_base_duration_v1" as const, - start: 1000n, - duration: 3600n, - initial: 9223372036854775808n, - final: 55340232221128654n, - indexedTime: 999n, -}; -test("fee phases use indexed time only; 50% -> 0.3% over 3600 s", () => { - const cases = [ - [999n, "before_trading_start", "9223372036854775808"], - [1000n, "fee_decay", "9223372036854775808"], - [4599n, "fee_decay", null], - [4600n, "final_fee", "55340232221128654"], - [1000000000n, "final_fee", "55340232221128654"], - ] as const; - for (const [indexedTime, phase, rate] of cases) { - const result = feeSchedule({ ...input, indexedTime }); - expect(result.phase).toBe(phase); - if (rate) expect(result.fee_rate).toBe(rate); - if (result.model !== "fixed_base_duration_v1") throw new Error("model"); - expect(result.decay).toEqual({ - kind: "fixed_base_q128", - half_life_seconds: 600, +test("spacing and signed alignment reproduce every frozen installation vector", () => { + expect(INSTALL.vectors.length).toBe(64); + // eight vectors are tick refusals and have no geometry + const geometric = INSTALL.vectors.filter( + (v: { tickSpacing?: number }) => v.tickSpacing !== undefined, + ); + expect(geometric.length).toBe(56); + for (const v of geometric) { + const range = alignedRange(v.askTick, v.upperTick); + expect(range).toEqual({ + tick_spacing: v.tickSpacing, + aligned_ask_tick: v.alignedAskTick, + aligned_upper_tick: v.alignedUpperTick, }); - expect(result.evaluated_at).toBe(Number(indexedTime)); - } - // halvings: 600 s past the start the unscaled decay is exactly 2^127 - 1 - expect(decayQ128(600n)).toBe((1n << 127n) - 1n); -}); - -const vectorsDir = resolve(import.meta.dir, "vectors"); - -// The protocol's independent vectors (250-digit Decimal generator, not this -// code): every second 0..D of 12 curves, rates 5/50/75/90/99/max% and -// durations 600/3600 s, [initial, final, duration, elapsed, fee]. These are -// study rates, not limits: every uint64 rate is valid and max is 2^64 - 1. -test("decayedFee reproduces all 25,212 independent all-second vectors and the manifest digests", () => { - const rows = JSON.parse( - gunzipSync( - readFileSync(resolve(vectorsDir, "fee-curve-all-seconds.json.gz")), - ).toString("utf8"), - ) as [string, string, string, string, string][]; - expect(rows.length).toBe(25_212); - const chains = new Map(); - for (const [initial, final, duration, elapsed, fee] of rows) { - const actual = decayedFee( - BigInt(initial), - BigInt(final), - BigInt(duration), - BigInt(elapsed), - ); - if (actual.toString() !== fee) - throw new Error(`${initial} ${duration} ${elapsed}: ${actual} != ${fee}`); - // h = sha256(h || fee as 8-byte big-endian), in elapsed order - const key = `${duration}:${initial}`; - const word = new Uint8Array(40); - word.set(chains.get(key) ?? new Uint8Array(32)); - new DataView(word.buffer).setBigUint64(32, actual); - chains.set( - key, - new Uint8Array(new Bun.CryptoHasher("sha256").update(word).digest()), - ); + const [lower, upper] = + v.order === "t1" + ? [-range.aligned_upper_tick, -range.aligned_ask_tick] + : [range.aligned_ask_tick, range.aligned_upper_tick]; + expect([lower, upper]).toEqual([v.tickLower, v.tickUpper]); } - const vectors = JSON.parse( - readFileSync(resolve(vectorsDir, "fee-decay-vectors.json"), "utf8"), - ) as { - table_q128: string[]; - zero_at_or_above: number; - decay_samples: { t: number; decay_q128: string }[]; - fee_rows: { - initial: string; - final: string; - duration: number; - elapsed: number; - fee: string; - }[]; - all_second_digests_sha256: Record>; - }; - const rates: Record = { - "5": "922337203685477580", - "50": "9223372036854775808", - "75": "13835058055282163712", - "90": "16602069666338596454", - "99": "18262276632972456099", - max: "18446744073709551615", - }; - // the manifest lists 12 digests; this file carries the five above 5% - let digests = 0; - for (const [duration, byRate] of Object.entries( - vectors.all_second_digests_sha256, - )) - for (const [label, digest] of Object.entries(byRate)) { - expect( - Buffer.from(chains.get(`${duration}:${rates[label]}`)!).toString("hex"), - ).toBe(digest); - digests++; - } - expect(digests).toBeGreaterThanOrEqual(10); - expect(DECAY_TABLE_Q128.map(String)).toEqual(vectors.table_q128); - expect(DECAY_ZERO_FROM).toBe(BigInt(vectors.zero_at_or_above)); - for (const { t, decay_q128 } of vectors.decay_samples) - expect(decayQ128(BigInt(t)).toString()).toBe(decay_q128); - for (const r of vectors.fee_rows) - expect( - decayedFee( - BigInt(r.initial), - BigInt(r.final), - BigInt(r.duration), - BigInt(r.elapsed), - ).toString(), - ).toBe(r.fee); -}); - -const MAX = (1n << 64n) - 1n; -test("the whole uint64 fee range is served exactly; 2^64 - 1 is the maximum, not a cap below it", () => { - // l5 of the local fixture: max -> 0.3% over 3600 s, trading from 1800001400 - const l5 = { - model: "fixed_base_duration_v1" as const, - start: 1800001400n, - duration: 3600n, - initial: MAX, - final: 55340232221128654n, - }; - const at = (indexedTime: bigint) => feeSchedule({ ...l5, indexedTime }); - expect(at(1800001399n)).toMatchObject({ - phase: "before_trading_start", - initial_fee: "18446744073709551615", - fee_rate: "18446744073709551615", + // the spec's table, including no snapping to 1024 + expect(alignedRange(-13815500, -9210300)).toEqual({ + tick_spacing: 17990, + aligned_ask_tick: -13798330, + aligned_upper_tick: -9210880, }); - expect(at(1800001400n).fee_rate).toBe("18446744073709551615"); - // the rate the contract applied to the fixture swap 1400 s in - expect(at(1800002800n)).toMatchObject({ - phase: "fee_decay", - fee_rate: "3470655575073783216", + expect(alignedRange(-5000001, 4999999)).toEqual({ + tick_spacing: 39063, + aligned_ask_tick: -4961001, + aligned_upper_tick: 4961001, }); - expect(at(1800005000n)).toMatchObject({ - phase: "final_fee", - fee_rate: "55340232221128654", - }); - // l6: flat at the maximum - expect( - feeSchedule({ - model: "fixed_base_duration_v1", - start: 1800001400n, - duration: 1n, - initial: MAX, - final: MAX, - indexedTime: 2115361000n, - }), - ).toMatchObject({ - initial_fee: MAX.toString(), - final_fee: MAX.toString(), - phase: "final_fee", - fee_rate: MAX.toString(), - }); - // rates above floor(2^64 * 99 / 100) are ordinary rates; zero is valid too - for (const [initial, final] of [ - [MAX, 0n], - [18262276632972456100n, 18262276632972456099n], - [0n, 0n], - ]) - expect( - feeSchedule({ ...l5, initial, final, indexedTime: 1800001400n }) - .initial_fee, - ).toBe(initial.toString()); + expect(alignedRange(-88722835, 88722835).tick_spacing).toBe(693148); + expect(() => alignedRange(5, 5)).toThrow(); }); -test("fee fields are canonical decimal uint64 strings; 2^64 and malformed encodings are never served", () => { - const schedule = FeeSchedule; - const ok = feeSchedule({ ...input, indexedTime: 1000n }); - expect(schedule.safeParse(ok).success).toBe(true); - for (const field of ["initial_fee", "final_fee", "fee_rate"] as const) { - expect(schedule.safeParse({ ...ok, [field]: MAX.toString() }).success).toBe( - true, +test("names and symbols decode from canonical packOne words; non-canonical words are refused", () => { + const { examples } = V.metadata; + expect(unpackString(BigInt(examples["Zero Seed"]))).toBe("Zero Seed"); + expect(unpackString(BigInt(examples.ZERO))).toBe("ZERO"); + const pack = (bytes: number[]) => + BigInt( + `0x${[bytes.length, ...bytes] + .map((b) => b.toString(16).padStart(2, "0")) + .join("") + .padEnd(64, "0")}`, ); - for (const bad of [ - "18446744073709551616", - "36893488147419103231", - "100000000000000000000", - "018446744073709551615", - "01", - "-1", - "+1", - " 1", - "1 ", - "0x1", - "1e18", - "1.0", - "", - Number(MAX), - 1, - ]) - expect(schedule.safeParse({ ...ok, [field]: bad }).success).toBe(false); - } - const swap = SwapPage.shape.data.element; - const row = { - event: { - block_number: "1", - block_hash: `0x${"a".repeat(64)}`, - block_time: 1000, - event_id: "1", - transaction_index: 0, - event_index: 0, - transaction_hash: `0x${"b".repeat(64)}`, - }, - forwarding_locker: `0x${"c".repeat(40)}`, - direction: "buy", - launch_token_delta: "-54160", - quote_token_delta: "1000000000000000000", - fee_rate: MAX.toString(), - fee_amount: "999083373736809197195735", - fee_asset: "launch_token", - }; - expect(swap.safeParse(row).success).toBe(true); - for (const fee_rate of ["18446744073709551616", "01", "1e3", Number(MAX)]) - expect(swap.safeParse({ ...row, fee_rate }).success).toBe(false); - // fee amounts are token units, not rates: they may exceed 2^64 - expect( - swap.safeParse({ ...row, fee_amount: (1n << 100n).toString() }).success, - ).toBe(true); -}); - -test("invalid indexed domains fail, never coerce or overflow", () => { - for (const change of [ - { start: -1n }, - { duration: 0n }, - { duration: 1n << 32n }, - { final: input.initial + 1n }, - { initial: 1n << 64n }, - { initial: 1n << 64n, final: 1n << 64n }, - { initial: 1n << 64n, final: MAX }, - { indexedTime: -1n }, - { start: (1n << 64n) - 1n }, - { indexedTime: BigInt(Number.MAX_SAFE_INTEGER) + 1n }, - ]) { - expect(() => feeSchedule({ ...input, ...change })).toThrow(); - } - expect(() => decayQ128(-1n)).toThrow(); -}); - -test("frozen schemas expose no staged status/stats/exhausted or decay-factor fields", () => { - const empty = { - schema_version: "zero-seed-launch-api/v5", - as_of: { - block_number: "1", - block_hash: `0x${"a".repeat(64)}`, - block_time: 1000, - }, - data: [], - next_cursor: null, - }; - expect(LaunchList.safeParse(empty).success).toBe(true); - expect( - LaunchList.safeParse({ - ...empty, - schema_version: "zero-seed-launch-api/v4", - }).success, - ).toBe(false); - expect(LaunchList.safeParse({ ...empty, status: "ended" }).success).toBe( - false, + // multi-byte UTF-8, a NUL byte and 31 bytes round-trip; invalid UTF-8 is null + expect(unpackString(pack([...new TextEncoder().encode("Zéro 🚀")]))).toBe( + "Zéro 🚀", ); - for (const schema of [SwapPage, ClaimPage, OwnerPage]) { - expect( - schema.safeParse({ - ...empty, - chain_id: "31337", - pool_id: `0x${"b".repeat(64)}`, - }).success, - ).toBe(true); - } - expect(Object.keys(LaunchDetail.shape)).not.toContain("status"); - expect(Object.keys(LaunchDetail.shape.inventory.shape)).not.toContain( - "exhausted", - ); - for (const option of FeeSchedule.options) - expect(Object.keys(option.shape)).not.toContain("decay_factor"); - // the tick-rate schedule has a floor, never an end - const [, tickRate] = FeeSchedule.options; - for (const field of ["fee_end", "fee_duration", "ends_at", "end"]) - expect(Object.keys(tickRate.shape)).not.toContain(field); + expect(unpackString(pack([0x41, 0x00, 0x42]))).toBe("A\u0000B"); + expect(unpackString(pack(Array(31).fill(0x61)))).toBe("a".repeat(31)); + expect(unpackString(pack([0xff, 0xfe]))).toBeNull(); + for (const bad of [ + 0n, + 32n << 248n, + pack([0x41]) | 1n, + (1n << 248n) | (0x41n << 240n) | (0x42n << 232n), + ]) + expect(() => unpackString(bad)).toThrow(/packed/); }); -test("v4 supply is on every summary and replaces principal.installed_supply", () => { - expect(Object.keys(LaunchSummary.shape)).toContain("supply"); - expect(Object.keys(LaunchSummary.shape.supply.shape)).toEqual([ - "requested", - "installed", - "sink", - "sink_holder", - ]); - expect(Object.keys(LaunchDetail.shape.principal.shape)).not.toContain( - "installed_supply", +test("logos: empty and invalid UTF-8 are null, everything else is served byte for byte", () => { + const events = JSON.parse( + read("zero-seed-launch-events.json").toString("utf8"), ); - expect(Object.keys(LaunchDetail.shape.principal.shape)).toContain( - "liquidity", + const logos = events.LaunchCreated.map((e: { logo: string }) => e.logo); + expect(logos).toEqual(["", "https://example.invalid/logo.svg"]); + // The last field of LaunchCreated.data: after poolKey (3 words), + // positionId, liquidity and the 11-word parameters, the offset of an ABI + // string (length, then bytes). + for (const created of events.LaunchCreated) { + const data = Buffer.from(created.data.slice(2), "hex"); + const offset = Number( + BigInt(`0x${data.subarray(16 * 32, 17 * 32).toString("hex")}`), + ); + const size = Number( + BigInt(`0x${data.subarray(offset, offset + 32).toString("hex")}`), + ); + const bytes = data.subarray(offset + 32, offset + 32 + size); + expect(utf8OrNull(bytes)).toBe(created.logo === "" ? null : created.logo); + } + expect(utf8OrNull(new Uint8Array([0x68, 0xc3]))).toBeNull(); + expect(utf8OrNull(new TextEncoder().encode("javascript:alert(1)"))).toBe( + "javascript:alert(1)", ); - const supply = LaunchSummary.shape.supply; - const ok = { - requested: "1000000000000000000000000000", - installed: "999999999999999999999999210", - sink: "790", - sink_holder: NATIVE, - }; - expect(supply.safeParse(ok).success).toBe(true); - for (const bad of [ - { ...ok, sink: 790 }, - { ...ok, requested: "1e27" }, - { ...ok, installed: "-1" }, - { ...ok, sink_holder: `0x${"1".repeat(40)}` }, - { ...ok, burned: "790" }, - ]) - expect(supply.safeParse(bad).success).toBe(false); }); -// ---- tick_rate_v2 (evm-contracts 3d6378f) against the contract's frozen v7 vectors ---- - -const V2 = JSON.parse( - readFileSync( - resolve(import.meta.dir, "vectors/zero-seed-launch-v2-vectors.json"), - "utf8", - ), -); -// Elapsed seconds beyond 2^64 cannot be indexed; every positive rate has +// Elapsed seconds beyond 2^63 cannot be indexed; every positive rate has // saturated long before. const elapsedOf = (t: string) => { const value = BigInt(t); return value > 1n << 63n ? 1n << 63n : value; }; -test("tick-rate fee: every frozen fee row, table, tick step and saturation second", () => { - expect(V2.version).toBe(7); - const fx = V2.fixture; - for (const row of V2.fee) +test("fee: every frozen fee row, table, tick step and saturation second", () => { + const fx = V.fixture; + expect(V.fee.length).toBe(40); + for (const row of V.fee) expect( - tickRateDecayedFee( + feeAt( BigInt(fx.fee_initial_q64), BigInt(fx.fee_final_q64), BigInt(row.decayRateX32), elapsedOf(row.t_minus_start), ).toString(), ).toBe(row.fee_q64); - const curve = V2.fee_curve; + const curve = V.fee_curve; const r = curve.rates_q64; const d = curve.decay_rates_x32; const f03 = BigInt(curve.final_fee_q64); @@ -530,15 +266,15 @@ test("tick-rate fee: every frozen fee row, table, tick step and saturation secon ]; for (const [name, initial, final, rate] of tables) for (const row of curve.tables[name]) - expect( - tickRateDecayedFee(initial, final, rate, elapsedOf(row.t)).toString(), - ).toBe(row.fee_q64); + expect(feeAt(initial, final, rate, elapsedOf(row.t)).toString()).toBe( + row.fee_q64, + ); for (const steps of curve.tick_steps) { const initial = BigInt(steps.rows[0].fee_q64) === f03 + 1n ? f03 + 1n : BigInt(r["50"]); for (const row of steps.rows) expect( - tickRateDecayedFee( + feeAt( initial, f03, BigInt(steps.decayRateX32), @@ -549,20 +285,18 @@ test("tick-rate fee: every frozen fee row, table, tick step and saturation secon for (const [name, second] of Object.entries(curve.saturation_second)) { const rate = BigInt(d[name]); expect((MAX_DECAY_PRODUCT / rate).toString()).toBe(second as string); - expect( - tickRateDecayedFee(MAX, 0n, rate, BigInt(second as string) + 1n), - ).toBe(0n); + expect(feeAt(MAX, 0n, rate, BigInt(second as string) + 1n)).toBe(0n); } expect(tickFactorQ128(0n)).toBe(1n << 128n); expect(tickFactorQ128(88722835n)).toBe(18447191164202170524n); expect(() => tickFactorQ128(88722836n)).toThrow(); }); -test("tick-rate fee: the five SHA-256 chains the contract tests pin", async () => { - for (const chain of V2.fee_curve.digests_sha256) { +test("fee: the five SHA-256 chains the contract tests pin", async () => { + for (const chain of V.fee_curve.digests_sha256) { let h = new Uint8Array(32); for (let t = 0n; t <= BigInt(chain.last); t += BigInt(chain.step)) { - const fee = tickRateDecayedFee( + const fee = feeAt( BigInt(chain.initialFee), BigInt(chain.finalFee), BigInt(chain.decayRateX32), @@ -577,8 +311,8 @@ test("tick-rate fee: the five SHA-256 chains the contract tests pin", async () = } }, 120_000); -test("tick-rate schedule: decimal-string rate, exact half-life, saturation, phases, no end", () => { - const consumer = V2.creator_fee_range.reference_schedules.consumer_default; +test("schedule: decimal-string rate, exact half-life, saturation, phases, no end", () => { + const consumer = V.creator_fee_range.reference_schedules.consumer_default; expect(decayRateForHalfLife(86400n).toString()).toBe(consumer.decayRateX32); expect(decayRateForHalfLife(600n)).toBe(9923486534471n); expect(HALF_LIFE_X32_E30.toString()).toBe( @@ -587,24 +321,20 @@ test("tick-rate schedule: decimal-string rate, exact half-life, saturation, phas for (const [rate, text] of [ [68913100933n, "86400.000001"], [9923486534471n, "600.000000"], - [8589934592n, "693147.527133"], - [1073741824n, "5545180.217067"], [1n, "5954091920683119.788732"], [MAX, "0.000322"], ] as const) expect(halfLifeSeconds(rate)).toBe(text); expect(halfLifeSeconds(0n)).toBeNull(); const base = { - model: "tick_rate_v2" as const, start: 1000n, rate: BigInt(consumer.decayRateX32), initial: BigInt(consumer.initialFee), final: BigInt(consumer.finalFee), }; - const at = (indexedTime: bigint, change = {}) => - feeSchedule({ ...base, ...change, indexedTime }); + const at = (time: bigint, change = {}) => + feeSchedule({ ...base, ...change, time }); expect(at(999n)).toEqual({ - model: "tick_rate_v2", trading_start: 1000, initial_fee: consumer.initialFee, final_fee: consumer.finalFee, @@ -633,52 +363,104 @@ test("tick-rate schedule: decimal-string rate, exact half-life, saturation, phas phase: "at_floor", saturation_elapsed_seconds: "0", }); - expect(at(5000n, { initial: 7n, final: 7n })).toMatchObject({ - phase: "at_floor", - }); - for (const schedule of [ - at(999n), - at(5000n, { rate: 0n }), - at(2000n, { rate: MAX }), - ]) { - expect(FeeSchedule.safeParse(schedule).success).toBe(true); - expect(schedule).not.toHaveProperty("fee_end"); - } - // The rate is a canonical decimal uint64 string, never a number or 2^64. const ok = at(2000n); - for (const bad of [ - "18446744073709551616", - "01", - "-1", - "1e3", - "", - 68913100933, - ]) + expect(FeeSchedule.safeParse(ok).success).toBe(true); + for (const bad of ["18446744073709551616", "01", "-1", "1e3", "", 6891]) expect(FeeSchedule.safeParse({ ...ok, decay_rate_x32: bad }).success).toBe( false, ); - for (const bad of ["86400", "86400.00000", "-1.000000", 86400]) - expect( - FeeSchedule.safeParse({ ...ok, half_life_seconds: bad }).success, - ).toBe(false); - // A v2 row cannot carry v1 fields, nor v1 a v2 rate. - expect(FeeSchedule.safeParse({ ...ok, fee_duration: 3600 }).success).toBe( - false, - ); - expect( - FeeSchedule.safeParse({ - ...feeSchedule({ ...input, indexedTime: 1000n }), - decay_rate_x32: "1", - }).success, - ).toBe(false); - // Out-of-domain indexed values fail as 500, never coerce. + for (const extra of [ + { model: "tick_rate_v2" }, + { fee_end: 1 }, + { fee_duration: 3600 }, + ]) + expect(FeeSchedule.safeParse({ ...ok, ...extra }).success).toBe(false); for (const change of [ { rate: 1n << 64n }, { rate: -1n }, + { initial: 1n << 64n, final: 0n }, { final: base.initial + 1n }, ]) expect(() => at(2000n, change)).toThrow(); - expect(() => - feeSchedule({ ...input, model: "other" as never, indexedTime: 1000n }), - ).toThrow(/fee model/); +}); + +test("status is upcoming strictly before trading_start, trading from it on", () => { + expect(launchStatus(1000n, 999n)).toBe("upcoming"); + expect(launchStatus(1000n, 1000n)).toBe("trading"); + expect(launchStatus(1000n, 1001n)).toBe("trading"); +}); + +test("market data: exact price, market cap, raised and sold share", () => { + const Q = 1n << 128n; + // sqrt ratio 1.0: one raw quote per raw launch token in either order + expect(priceX128(Q, false)).toBe(Q); + expect(priceX128(Q, true)).toBe(Q); + // token1 per token0 = 4: a token0 launch costs 4, a token1 launch 1/4 + expect(priceX128(2n * Q, false)).toBe(4n * Q); + expect(priceX128(2n * Q, true)).toBe(Q / 4n); + expect(() => priceX128(0n, false)).toThrow(); + expect(share18(1n, 3n)).toBe("0.333333333333333333"); + expect(share18(0n, 3n)).toBe("0.000000000000000000"); + expect(share18(3n, 3n)).toBe("1.000000000000000000"); + const market = marketData({ + sqrtRatio: 2n * Q, + tick: 1386295, + launchIsToken1: true, + totalSupply: 1000n, + installed: 900n, + tokenReserve: 600n, + quoteReserve: 77n, + marketCapUsd: null, + tradeCount: 3n, + }); + expect(market).toEqual({ + price_tick: -1386295, + price_x128: (Q / 4n).toString(), + market_cap: "250", + market_cap_usd: null, + raised: "77", + sold: "300", + sold_share: "0.333333333333333333", + trade_count: 3, + }); + // dust above the installed amount never makes sold negative + expect( + marketData({ + ...{ sqrtRatio: Q, tick: 0, launchIsToken1: false, totalSupply: 10n }, + installed: 9n, + tokenReserve: 10n, + quoteReserve: 0n, + marketCapUsd: "1.000000", + tradeCount: 0n, + }).sold, + ).toBe("0"); +}); + +test("v6 schemas are final-only and closed", () => { + const keys = Object.keys(LaunchSummary.shape); + for (const key of [ + "logo_url", + "parameters", + "range", + "status", + "market", + "creator", + "owner", + "controller", + ]) + expect(keys).toContain(key); + for (const key of ["fee_model", "ask_tick", "upper_tick", "inventory"]) + expect(keys).not.toContain(key); + expect(Object.keys(LaunchDetail.shape)).toContain("fee_ledger"); + expect(Object.keys(LaunchList.shape).sort()).toEqual( + [ + "schema_version", + "data", + "total", + "totals", + "next_offset", + "evaluated_at", + "heads", + ].sort(), + ); }); diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index dd9bfa3..3ed1bf5 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -1,34 +1,22 @@ import { z } from "@hono/zod-openapi"; import { FeeSchedule } from "./schema"; -// sha256 of `jq -cS .abi` (no trailing newline) of the evm-contracts 43d6628 -// build, as in the protocol manifest. +// sha256 of the compiled ABI JSON files of the final source freeze, +// evm-contracts 853b608ec26d4c80bdad6e394cb8ba686e1ae615 (SHA256SUMS.json of +// the freeze; copies in ./vectors/PROVENANCE.md). +export const SOURCE_COMMIT = "853b608ec26d4c80bdad6e394cb8ba686e1ae615"; export const ABI_SHA256 = - "efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e"; + "6a131c2d2f70f85a5e2a54395b9d77c4dd5028a7a4bbf632f3d3364ac61e56eb"; export const PERIPHERY_ABI_SHA256 = - "df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305"; -// The same of the tick-rate revision, evm-contracts 3d6378f. -export const V2_ABI_SHA256 = - "89ff4d15e5e8b4f044c4804d460df915e9206bbdbdcef22cf4afe0fbbf4eaadd"; -export const V2_PERIPHERY_ABI_SHA256 = - "a6d43b362d04eeb4e636ee32a031fc06995a2fd8bb71fb63750f27836a0ddb07"; - -/** - * The fee math of a deployment's contract revision, fixed for its life and - * never inferred from a row: fixed_base_duration_v1 is evm-contracts 43d6628 - * (uint32 feeDuration), tick_rate_v2 is 3d6378f (uint64 decayRateX32). - */ -export type FeeModel = "fixed_base_duration_v1" | "tick_rate_v2"; + "5d05426f8190cb0bcd6a021e7aceb46f468708cb36bd5a5d818548650499c839"; const NATIVE = "0x0000000000000000000000000000000000000000" as const; /** * An extension and the periphery that forwards to it. An instance has no - * quote: every launch names its own (LaunchParameters.quoteToken), and one - * instance serves native ETH and ERC-20 quotes alike. + * quote: every launch names its own (LaunchParameters.quoteToken). */ export interface LaunchInstance { chainId: string; - feeModel: FeeModel; address: string; runtime: string; periphery: string; @@ -45,38 +33,23 @@ export interface LaunchQuote { runtime: string; } -// The constructor-executed LOCAL instances of the indexer replay fixtures -// (anvil, no fork), one per fee model: evm-contracts 43d6628 -// (local-chain.json) and 3d6378f (local-chain-v2.json), each serving native-ETH -// and ERC-20 quote launches. No live instance exists. Never enable them via a +// The final contract has no deployment, so no hosted instance exists. These +// are the LOCAL instances of the indexer's replay fixture (anvil, no fork), +// accepted only by the explicit local harness. Never enable them through a // hosted configuration. export const LOCAL_INSTANCES: readonly LaunchInstance[] = [ { chainId: "31337", - feeModel: "fixed_base_duration_v1", - address: "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f", + address: "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c", runtime: - "0xd6b4c7ce8da56e9948fdb004a035d9b52dbd3690bcb03c1269bbf2a9981d8ed5", - periphery: "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", + "0xa66e365089d80b017edeb98237831260bf9e1b7d5f3965d42be5672de6b0d70e", + periphery: "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", peripheryRuntime: - "0x2a9329dbb064a2c031115c7c120c8b6a1f31de167f33374180e4dc73d975974b", + "0x208c314d799d9898e145881eefdf715d0620f13cfb343951987aba8fb8ba8363", core: "0x00000000000014aa86c5d3c41765bb24e11bd701", abiSha256: ABI_SHA256, peripheryAbiSha256: PERIPHERY_ABI_SHA256, }, - { - chainId: "31337", - feeModel: "tick_rate_v2", - address: "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d", - runtime: - "0xe08ad382497fc9825956c2b4e9ccf463014c4ed65baf50c0a59db8d7b7aedfc3", - periphery: "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - peripheryRuntime: - "0xf72d3cd6b99e1b64f0b08c0f6933b4f8adb92464ded48c51bfb7d2805c09d667", - core: "0x00000000000014aa86c5d3c41765bb24e11bd701", - abiSha256: V2_ABI_SHA256, - peripheryAbiSha256: V2_PERIPHERY_ABI_SHA256, - }, ]; // The fixture's plain 18-decimal test ERC-20 (evm-contracts @@ -110,16 +83,18 @@ function reviewed( export function supportedInstances( requested: readonly LaunchInstance[], localHarness: boolean, + pins: readonly LaunchInstance[] = LOCAL_INSTANCES, ): readonly LaunchInstance[] { - return reviewed(requested, LOCAL_INSTANCES, localHarness); + return reviewed(requested, pins, localHarness); } /** ERC-20 quotes bound by chain, address and runtime; empty when hosted. */ export function supportedQuotes( requested: readonly LaunchQuote[], localHarness: boolean, + pins: readonly LaunchQuote[] = LOCAL_QUOTES, ): readonly LaunchQuote[] { - return reviewed(requested, LOCAL_QUOTES, localHarness); + return reviewed(requested, pins, localHarness); } /** Native ETH, or an ERC-20 in the enabled quote list of the chain. */ @@ -134,37 +109,14 @@ export function supportedQuote( ); } -export function supportedLaunch( - row: Pick< - LaunchInstance, - "chainId" | "feeModel" | "address" | "periphery" | "core" - > & { - quote: string; - quoteKind: "native" | "erc20"; - }, - enabled: readonly LaunchInstance[], - quotes: readonly LaunchQuote[], -): boolean { - return ( - enabled.some( - (pin) => - pin.chainId === row.chainId && - pin.feeModel === row.feeModel && - pin.address === row.address && - pin.periphery === row.periphery && - pin.core === row.core, - ) && supportedQuote(row, quotes) - ); -} - -const INT128_MAX = (1n << 127n) - 1n; export const SINK_HOLDER = NATIVE; +const MAX_SUPPLY = 10n ** 30n; /** - * The launch-token supply split: requested (LaunchParameters.supply, the - * token's totalSupply), installed (in the position) and sink (minted to - * address 0). Anything outside 0 < installed <= requested <= int128 max with - * sink = requested - installed is malformed indexed data. + * requested (LaunchParameters.supply, totalSupply), installed (in the + * position) and sink (minted to address 0). Anything outside + * 0 < installed <= requested <= 10^30 with sink = requested - installed is + * malformed indexed data. */ export function launchSupply(input: { requested: bigint; @@ -175,7 +127,7 @@ export function launchSupply(input: { if ( installed <= 0n || installed > requested || - requested > INT128_MAX || + requested > MAX_SUPPLY || sink !== requested - installed ) { throw new Error("Invalid indexed launch supply"); @@ -188,6 +140,48 @@ export function launchSupply(input: { }; } +/** + * The content of a canonical LibString.packOne word: byte 0 is the length + * (1..31), then the content, then zeros. Returns the UTF-8 text, or null + * when the content is not valid UTF-8. A non-canonical word is malformed + * indexed data: the contract refuses it. + */ +export function unpackString(word: bigint): string | null { + if (word < 0n || word >= 1n << 256n) throw new Error("Invalid packed word"); + const length = Number(word >> 248n); + const tailBits = BigInt(8 * (31 - length)); + if (length === 0 || length > 31 || (word & ((1n << tailBits) - 1n)) !== 0n) { + throw new Error("Invalid indexed packed string"); + } + const bytes = new Uint8Array(length); + for (let i = 0; i < length; i++) + bytes[i] = Number((word >> BigInt(8 * (30 - i))) & 0xffn); + return utf8OrNull(bytes); +} + +/** UTF-8 text of `bytes`, null when empty or not valid UTF-8. Never rewritten. */ +export function utf8OrNull(bytes: Uint8Array): string | null { + if (bytes.length === 0) return null; + try { + return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode( + bytes, + ); + } catch { + return null; + } +} + +/** Derived spacing and signed-aligned economic bounds of the installation. */ +export function alignedRange(ask: number, upper: number) { + if (!(ask < upper)) throw new Error("Invalid indexed ticks"); + const spacing = Math.floor((upper - ask + 255) / 256); + return { + tick_spacing: spacing, + aligned_ask_tick: Math.ceil(ask / spacing) * spacing, + aligned_upper_tick: Math.floor(upper / spacing) * spacing, + }; +} + const UINT64_MAX = (1n << 64n) - 1n; function uint64(value: bigint) { if (value < 0n || value > UINT64_MAX) { @@ -197,7 +191,7 @@ function uint64(value: bigint) { } // JSON Unix seconds must be exactly representable; never round indexed time. -function seconds(value: bigint) { +export function seconds(value: bigint) { uint64(value); if (value > BigInt(Number.MAX_SAFE_INTEGER)) { throw new Error("Indexed time exceeds exact JSON integer domain"); @@ -206,68 +200,9 @@ function seconds(value: bigint) { } const Q128 = 1n << 128n; -/** Elapsed seconds from which the decay is exactly zero. */ -export const DECAY_ZERO_FROM = 131072n; -/** round(2^128 * 2^(-2^i/600)) for bits i = 0..16 (math/feeDecay.sol). */ -export const DECAY_TABLE_Q128: readonly bigint[] = [ - 339889484297159900493648602831395569057n, - 339497055287118257930533248299650400736n, - 338713556013920184336129943844147573651n, - 337151977828609613753562640265082287810n, - 334050386396171404885545237344599070716n, - 327932539264834336949184815807600758235n, - 316030922441737935988095136387330274498n, - 293507844215098455952964317090465751204n, - 253162852354938356992240649094357060196n, - 188347784201757897225807540435664088841n, - 104251325552682051605813724741512712010n, - 31939177389160626453527783486618815759n, - 2997836947964109484105344435429137776n, - 26410496811510722015611852309540293n, - 2049810421099131403277778219901n, - 12347753427443481862030n, - 448060n, -]; - -/** - * decayQ128 of the contract: 2^(-elapsed/600) in Q128. Starting at 2^128, - * multiply by the constant of each set bit in ascending order and floor after - * every multiply; exactly zero from 2^17 seconds. BigInt division floors for - * these non-negative operands. - */ -export function decayQ128(elapsed: bigint): bigint { - if (elapsed < 0n) throw new Error("Negative elapsed time"); - if (elapsed >= DECAY_ZERO_FROM) return 0n; - let result = Q128; - DECAY_TABLE_Q128.forEach((constant, bit) => { - if ((elapsed >> BigInt(bit)) & 1n) result = (result * constant) / Q128; - }); - return result; -} - -/** - * decayedFee of the contract: initial at zero elapsed, then - * final + floor((initial - final) * (decay(t) - decay(D)) / (2^128 - decay(D))), - * then final from the duration on. - */ -export function decayedFee( - initial: bigint, - final: bigint, - duration: bigint, - elapsed: bigint, -): bigint { - if (elapsed === 0n) return initial; - if (elapsed >= duration) return final; - const endpoint = decayQ128(duration); - return ( - final + - ((initial - final) * (decayQ128(elapsed) - endpoint)) / (Q128 - endpoint) - ); -} - /** Largest tick magnitude of the contracts' tick helper. */ export const MAX_TICK_MAGNITUDE = 88722835n; -/** (MAX_TICK_MAGNITUDE + 1) * 2^32 - 1 (math/feeDecay.sol at 3d6378f). */ +/** (MAX_TICK_MAGNITUDE + 1) * 2^32 - 1: the saturation numerator. */ export const MAX_DECAY_PRODUCT = ((MAX_TICK_MAGNITUDE + 1n) << 32n) - 1n; /** * floor(2 * ln 2 / ln 1.000001 * 2^32 * 10^30): ticks per half-life of the @@ -324,12 +259,12 @@ export function tickFactorQ128(magnitude: bigint): bigint { } /** - * decayedFee of the tick-rate contract (evm-contracts 3d6378f) in its - * normative order: rate 0 is initial; elapsed > MAX_DECAY_PRODUCT / rate - * (integer division first) is final, the floor; otherwise + * feeAt of the contract in its normative order: initial before and at the + * start and for rate 0; elapsed > MAX_DECAY_PRODUCT / rate (integer division + * first) is final, the floor; otherwise * final + floor((initial - final) * tickFactorQ128(floor(rate * elapsed / 2^32)) / 2^128). */ -export function tickRateDecayedFee( +export function feeAt( initial: bigint, final: bigint, rate: bigint, @@ -357,66 +292,24 @@ export function halfLifeSeconds(rate: bigint): string | null { return `${micros / 1_000_000n}.${fraction}`; } -type FeeScheduleInput = { +/** The served schedule; anything outside the contract's domain is malformed (500). */ +export function feeSchedule(input: { start: bigint; initial: bigint; final: bigint; - indexedTime: bigint; -} & ( - | { model: "fixed_base_duration_v1"; duration: bigint } - | { model: "tick_rate_v2"; rate: bigint } -); - -/** - * The served schedule of an indexed launch, by its deployment's fee model. - * Anything outside the model's domain is malformed indexed data (500). - */ -export function feeSchedule( - input: FeeScheduleInput, -): z.infer { - const { start, initial, final, indexedTime } = input; - [start, initial, final, indexedTime].forEach(uint64); + rate: bigint; + time: bigint; +}): z.infer { + const { start, initial, final, rate, time } = input; + [start, initial, final, rate, time].forEach(uint64); if (final > initial) throw new Error("Invalid indexed fee schedule"); - if (input.model === "tick_rate_v2") return tickRateSchedule(input); - if (input.model !== "fixed_base_duration_v1") - throw new Error("Invalid indexed fee model"); - const { duration } = input; - uint64(duration); - if (duration === 0n || duration > 0xffffffffn) { - throw new Error("Invalid indexed fee schedule"); - } - const end = uint64(start + duration); - const phase = feePhase(start, end, indexedTime); - const elapsed = indexedTime > start ? indexedTime - start : 0n; - const rate = decayedFee(initial, final, duration, elapsed); - return { - model: "fixed_base_duration_v1", - trading_start: seconds(start), - fee_duration: seconds(duration), - fee_end: seconds(end), - initial_fee: initial.toString(), - final_fee: final.toString(), - decay: { kind: "fixed_base_q128", half_life_seconds: 600 }, - phase, - fee_rate: rate.toString(), - evaluated_at: seconds(indexedTime), - }; -} - -/** v2: final_fee is a floor that is approached; there is no end time. */ -function tickRateSchedule( - input: FeeScheduleInput & { model: "tick_rate_v2" }, -): z.infer { - const { start, initial, final, indexedTime, rate } = input; - uint64(rate); - const fee = tickRateDecayedFee(initial, final, rate, indexedTime - start); - let phase: "before_trading_start" | "at_floor" | "constant" | "decaying"; - if (indexedTime < start) phase = "before_trading_start"; + const fee = feeAt(initial, final, rate, time - start); + let phase: z.infer["phase"]; + if (time < start) phase = "before_trading_start"; else if (fee === final) phase = "at_floor"; else if (rate === 0n) phase = "constant"; else phase = "decaying"; return { - model: "tick_rate_v2", trading_start: seconds(start), initial_fee: initial.toString(), final_fee: final.toString(), @@ -426,12 +319,62 @@ function tickRateSchedule( rate === 0n ? null : (MAX_DECAY_PRODUCT / rate).toString(), phase, fee_rate: fee.toString(), - evaluated_at: seconds(indexedTime), + evaluated_at: seconds(time), }; } -function feePhase(start: bigint, end: bigint, time: bigint) { - if (time < start) return "before_trading_start"; - if (time < end) return "fee_decay"; - return "final_fee"; +/** upcoming before trading_start, trading from it on. */ +export function launchStatus(start: bigint, time: bigint) { + return time < start ? ("upcoming" as const) : ("trading" as const); +} + +/** + * Raw quote per raw launch token times 2^128, floored, from Core's Q128 + * sqrt ratio (token1 per token0). + */ +export function priceX128(sqrtRatio: bigint, launchIsToken1: boolean) { + if (sqrtRatio <= 0n) throw new Error("Invalid indexed sqrt ratio"); + const square = sqrtRatio * sqrtRatio; + return launchIsToken1 ? (1n << 384n) / square : square >> 128n; +} + +/** floor(fraction * 10^18) of numerator / denominator as "d.dddddddddddddddddd". */ +export function share18(numerator: bigint, denominator: bigint) { + if (denominator <= 0n || numerator < 0n || numerator > denominator) + throw new Error("Invalid indexed share"); + const scaled = (numerator * 10n ** 18n) / denominator; + return `${scaled / 10n ** 18n}.${(scaled % 10n ** 18n).toString().padStart(18, "0")}`; +} + +/** + * Market data from the indexed Core state: price, market cap (price times + * totalSupply), raised quote and the sold launch tokens. + */ +export function marketData(input: { + sqrtRatio: bigint; + tick: number; + launchIsToken1: boolean; + totalSupply: bigint; + installed: bigint; + tokenReserve: bigint; + quoteReserve: bigint; + marketCapUsd: string | null; + tradeCount: bigint; +}) { + const price = priceX128(input.sqrtRatio, input.launchIsToken1); + const left = + input.tokenReserve > input.installed ? input.installed : input.tokenReserve; + if (input.tokenReserve < 0n || input.quoteReserve < 0n) + throw new Error("Invalid indexed reserves"); + const sold = input.installed - left; + return { + price_tick: input.launchIsToken1 ? -input.tick : input.tick, + price_x128: price.toString(), + market_cap: ((price * input.totalSupply) >> 128n).toString(), + market_cap_usd: input.marketCapUsd, + raised: input.quoteReserve.toString(), + sold: sold.toString(), + sold_share: share18(sold, input.installed), + trade_count: Number(input.tradeCount), + }; } diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index c1ca6a8..6c04ed2 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -3,10 +3,16 @@ import type { Sql } from "postgres"; import { z } from "@hono/zod-openapi"; import { StatusError } from "../../shared/errors"; import { + alignedRange, feeSchedule, + launchStatus, launchSupply, + marketData, + seconds, supportedInstances, supportedQuotes, + unpackString, + utf8OrNull, type LaunchInstance, type LaunchQuote, } from "./policy"; @@ -22,6 +28,7 @@ import { type DB = Sql<{ bigint: bigint }>; type Numeric = string | bigint; +type Head = z.infer; interface EventRow { event_id: bigint; block_number: bigint; @@ -46,9 +53,9 @@ interface LaunchRow extends EventRow { initial_owner: Numeric; current_owner: Numeric; owner_change_count: bigint; - name: string; - symbol: string; - decimals: number; + name_word: Numeric; + symbol_word: Numeric; + logo_url_bytes: Uint8Array; quote_name: string | null; quote_symbol: string | null; quote_decimals: number | null; @@ -56,21 +63,23 @@ interface LaunchRow extends EventRow { ask_tick: number; upper_tick: number; tick_spacing: bigint; - trading_start: bigint; - fee_model: string; - fee_duration: bigint | null; - decay_rate_x32: Numeric | null; + aligned_ask_tick: number; + aligned_upper_tick: number; + tick_lower: number; + tick_upper: number; + trading_start: Numeric; + decay_rate_x32: Numeric; initial_fee: Numeric; final_fee: Numeric; + salt: Numeric; position_id: Numeric; - tick_lower: number; - tick_upper: number; liquidity: Numeric; requested_supply: Numeric; installed_supply: Numeric; sink_supply: Numeric; balance0: Numeric; balance1: Numeric; + sqrt_ratio: Numeric; tick: number; accrued0: Numeric; accrued1: Numeric; @@ -79,13 +88,14 @@ interface LaunchRow extends EventRow { swap_count: bigint; claim_count: bigint; last_event_id: bigint; + market_cap_usd: string | null; } const decimal = (n: Numeric) => n.toString(); const hex = (n: Numeric, width: number) => `0x${BigInt(n) .toString(16) .padStart(width * 2, "0")}`; -const seconds = (date: Date) => Math.floor(date.getTime() / 1000); +const epoch = (date: Date) => Math.floor(date.getTime() / 1000); function parseIndexed( schema: T, value: unknown, @@ -99,13 +109,39 @@ function event(row: EventRow) { event_id: decimal(row.event_id), block_number: decimal(row.block_number), block_hash: hex(row.block_hash, 32), - block_time: seconds(row.block_time), + block_time: epoch(row.block_time), transaction_index: row.transaction_index, event_index: row.event_index, transaction_hash: hex(row.transaction_hash, 32), }; } -function summary(row: LaunchRow, asOf: z.infer) { +// The installed range must be the one the contract derives from the +// requested ticks; anything else is malformed indexed data. +function range(row: LaunchRow) { + const derived = alignedRange(row.ask_tick, row.upper_tick); + const [lower, upper] = row.token_is_token1 + ? [-derived.aligned_upper_tick, -derived.aligned_ask_tick] + : [derived.aligned_ask_tick, derived.aligned_upper_tick]; + if ( + derived.tick_spacing !== Number(row.tick_spacing) || + derived.aligned_ask_tick !== row.aligned_ask_tick || + derived.aligned_upper_tick !== row.aligned_upper_tick || + lower !== row.tick_lower || + upper !== row.tick_upper + ) + throw new Error("Invalid indexed launch range"); + return { ...derived, pool_tick_lower: lower, pool_tick_upper: upper }; +} +function summary(row: LaunchRow, asOf: Head, time: bigint) { + const start = BigInt(row.trading_start); + const [tokenReserve, quoteReserve] = row.token_is_token1 + ? [row.balance1, row.balance0] + : [row.balance0, row.balance1]; + const supply = launchSupply({ + requested: BigInt(row.requested_supply), + installed: BigInt(row.installed_supply), + sink: BigInt(row.sink_supply), + }); return { schema_version: SCHEMA_VERSION, chain_id: decimal(row.chain_id), @@ -124,55 +160,55 @@ function summary(row: LaunchRow, asOf: z.infer) { }, token: { address: hex(row.token, 20), - name: row.name, - symbol: row.symbol, - decimals: row.decimals, + name: unpackString(BigInt(row.name_word)), + symbol: unpackString(BigInt(row.symbol_word)), + decimals: 18 as const, }, quote_token: quoteToken(row), - supply: launchSupply({ - requested: BigInt(row.requested_supply), - installed: BigInt(row.installed_supply), - sink: BigInt(row.sink_supply), - }), launch_token_is_token1: row.token_is_token1, controller: hex(row.controller, 20), creator: hex(row.initial_owner, 20), owner: hex(row.current_owner, 20), - ask_tick: row.ask_tick, - upper_tick: row.upper_tick, + logo_url: utf8OrNull(row.logo_url_bytes), + parameters: { + name: hex(row.name_word, 32), + symbol: hex(row.symbol_word, 32), + supply: supply.requested, + quote_token: hex(row.quote_token, 20), + ask_tick: row.ask_tick.toString(), + upper_tick: row.upper_tick.toString(), + trading_start: decimal(row.trading_start), + decay_rate_x32: decimal(row.decay_rate_x32), + initial_fee: decimal(row.initial_fee), + final_fee: decimal(row.final_fee), + salt: hex(row.salt, 32), + }, + range: range(row), + supply, + trading_start: seconds(start), + status: launchStatus(start, time), fee_schedule: feeSchedule({ - ...feeDecay(row), - start: row.trading_start, + start, initial: BigInt(row.initial_fee), final: BigInt(row.final_fee), - indexedTime: BigInt(asOf.block_time), + rate: BigInt(row.decay_rate_x32), + time, + }), + market: marketData({ + sqrtRatio: BigInt(row.sqrt_ratio), + tick: row.tick, + launchIsToken1: row.token_is_token1, + totalSupply: BigInt(row.requested_supply), + installed: BigInt(row.installed_supply), + tokenReserve: BigInt(tokenReserve), + quoteReserve: BigInt(quoteReserve), + marketCapUsd: row.market_cap_usd, + tradeCount: row.swap_count, }), created: event(row), as_of: asOf, }; } -// Exactly the row's model parameter; anything else is malformed indexed data. -function feeDecay(row: LaunchRow) { - if ( - row.fee_model === "fixed_base_duration_v1" && - row.fee_duration !== null && - row.decay_rate_x32 === null - ) - return { - model: "fixed_base_duration_v1" as const, - duration: BigInt(row.fee_duration), - }; - if ( - row.fee_model === "tick_rate_v2" && - row.fee_duration === null && - row.decay_rate_x32 !== null - ) - return { - model: "tick_rate_v2" as const, - rate: BigInt(row.decay_rate_x32), - }; - throw new Error("Invalid indexed fee model"); -} // Native ETH has no token contract: 18 decimals, no ERC-20 metadata. function quoteToken(row: LaunchRow) { if (row.quote_kind === "native") @@ -198,17 +234,18 @@ function ledger(accrued: Numeric, claimed: Numeric) { unclaimed: (BigInt(accrued) - BigInt(claimed)).toString(), }; } -function detail(row: LaunchRow, asOf: z.infer) { - const launch = summary(row, asOf); +function detail(row: LaunchRow, asOf: Head, time: bigint) { + const launch = summary(row, asOf, time); const [tokenBalance, quoteBalance] = row.token_is_token1 ? [row.balance1, row.balance0] : [row.balance0, row.balance1]; + const [tokenSide, quoteSide] = row.token_is_token1 + ? [ledger(row.accrued1, row.claimed1), ledger(row.accrued0, row.claimed0)] + : [ledger(row.accrued0, row.claimed0), ledger(row.accrued1, row.claimed1)]; return parseIndexed(LaunchDetail, { ...launch, principal: { position_id: hex(row.position_id, 32), - tick_lower: row.tick_lower, - tick_upper: row.tick_upper, liquidity: decimal(row.liquidity), locked: true, withdrawable: false, @@ -216,118 +253,153 @@ function detail(row: LaunchRow, asOf: z.infer) { launch_token: decimal(tokenBalance), quote_token: decimal(quoteBalance), }, - reserves_basis: "core_pool_balance", }, fee_ledger: { holder: launch.owner, - transferable: true, - launch_token: row.token_is_token1 - ? ledger(row.accrued1, row.claimed1) - : ledger(row.accrued0, row.claimed0), - quote_token: row.token_is_token1 - ? ledger(row.accrued0, row.claimed0) - : ledger(row.accrued1, row.claimed1), + launch_token: tokenSide, + quote_token: quoteSide, swap_count: Number(row.swap_count), claim_count: Number(row.claim_count), owner_change_count: Number(row.owner_change_count), last_event_id: decimal(row.last_event_id), }, - inventory: { - trading_open: asOf.block_time >= Number(row.trading_start), - no_bid: BigInt(quoteBalance) === 0n, - price_tick: row.token_is_token1 ? -row.tick : row.tick, - }, }); } +export type LaunchStatusFilter = "all" | "trading" | "upcoming"; +export type LaunchOrder = "market_cap" | "created"; + export interface LaunchReadOptions { chainId: bigint; cursor?: bigint; limit: number; } +export interface LaunchListOptions { + chainId?: bigint; + status: LaunchStatusFilter; + owner?: bigint; + creator?: bigint; + orderBy: LaunchOrder; + offset: number; + limit: number; +} + +interface ReaderOptions { + localHarness: boolean; + instances: readonly LaunchInstance[]; + quotes: readonly LaunchQuote[]; + maxIndexAgeSeconds: number; + /** Seconds since the epoch at which status and fee rate are evaluated. */ + now?: () => bigint; + /** The reviewed manifests the requested lists must match (tests only). */ + pins?: { + instances: readonly LaunchInstance[]; + quotes: readonly LaunchQuote[]; + }; +} + +const HOSTED: ReaderOptions = { + localHarness: false, + instances: [], + quotes: [], + maxIndexAgeSeconds: 60, +}; +const wallClock = () => BigInt(Math.floor(Date.now() / 1000)); + /** All constituent reads share a read-only repeatable-read snapshot. */ export class LaunchQueries { private readonly enabled: readonly LaunchInstance[]; private readonly quotes: readonly LaunchQuote[]; + private readonly maxAge: number; + private readonly now: () => bigint; constructor( private readonly sql: DB, - options: { - localHarness: boolean; - instances: readonly LaunchInstance[]; - quotes: readonly LaunchQuote[]; - maxIndexAgeSeconds: number; - } = { - localHarness: false, - instances: [], - quotes: [], - maxIndexAgeSeconds: 60, - }, + options: ReaderOptions = HOSTED, ) { - this.enabled = supportedInstances(options.instances, options.localHarness); - this.quotes = supportedQuotes(options.quotes, options.localHarness); + this.enabled = supportedInstances( + options.instances, + options.localHarness, + options.pins?.instances, + ); + this.quotes = supportedQuotes( + options.quotes, + options.localHarness, + options.pins?.quotes, + ); this.maxAge = options.maxIndexAgeSeconds; + this.now = options.now ?? wallClock; if (!Number.isFinite(this.maxAge) || this.maxAge <= 0) { throw new Error("Invalid local index freshness bound"); } } - private readonly maxAge: number; + + /** The enabled chains a request covers; none is a disabled launch read. */ + private chains(chainId?: bigint): string[] { + const chains = [...new Set(this.enabled.map((p) => p.chainId))] + .filter((c) => chainId === undefined || c === chainId.toString()) + .sort((a, b) => (BigInt(a) < BigInt(b) ? -1 : 1)); + if (chains.length === 0) + throw new StatusError(404, "Launch support is disabled"); + return chains; + } + + private async head(db: DB, chainId: string): Promise { + const [head] = await db< + { + block_number: bigint; + block_hash: Numeric; + block_time: Date; + last_updated: Date; + }[] + >` + SELECT b.block_number, b.block_hash, b.block_time, ic.last_updated + FROM indexer_cursor ic JOIN blocks b + ON b.chain_id = ic.chain_id AND b.block_number = ic.head_block_number + AND b.block_hash = ic.head_block_hash + WHERE ic.chain_id = ${chainId} + `; + if (!head || Date.now() - head.last_updated.getTime() > this.maxAge * 1000) + throw new StatusError( + 503, + `Launch index for chain ${chainId} is stale or has no coherent head`, + ); + return parseIndexed(BlockRef, { + block_number: decimal(head.block_number), + block_hash: hex(head.block_hash, 32), + block_time: epoch(head.block_time), + }); + } private async snapshot( - chainId: bigint, - read: (tx: DB, asOf: z.infer) => Promise, + chains: string[], + read: (tx: DB, heads: Map, time: bigint) => Promise, ): Promise { - if (!this.enabled.some((p) => p.chainId === chainId.toString())) { - throw new StatusError(404, "Launch support is disabled"); - } + const time = this.now(); return this.sql.begin( "isolation level repeatable read read only", async (tx) => { const db = tx as unknown as DB; - const [head] = await db< - { - block_number: bigint; - block_hash: Numeric; - block_time: Date; - last_updated: Date; - }[] - >` - SELECT b.block_number, b.block_hash, b.block_time, ic.last_updated - FROM indexer_cursor ic JOIN blocks b - ON b.chain_id = ic.chain_id AND b.block_number = ic.head_block_number - AND b.block_hash = ic.head_block_hash - WHERE ic.chain_id = ${chainId} - `; - if ( - !head || - Date.now() - head.last_updated.getTime() > this.maxAge * 1000 - ) { - throw new StatusError( - 503, - "Launch index is stale or has no coherent head", - ); - } - const asOf = parseIndexed(BlockRef, { - block_number: decimal(head.block_number), - block_hash: hex(head.block_hash, 32), - block_time: seconds(head.block_time), - }); - return read(db, asOf); + const heads = new Map(); + for (const chain of chains) + heads.set(chain, await this.head(db, chain)); + return read(db, heads, time); }, ) as Promise; } - private rows(tx: DB, chainId: bigint, filter: postgres.Fragment) { - // Screening occurs in SQL BEFORE cursor/limit; no unsupported quote can - // occupy a page or be served by detail/history. - const chain = chainId.toString(); - const pins = this.enabled.filter((p) => p.chainId === chain); + /** + * Supported launches of the given chains. Screening happens in SQL before + * any order, offset or limit, so an unsupported launch never takes a slot. + */ + private rows(tx: DB, chains: string[], filter: postgres.Fragment) { + const pins = this.enabled.filter((p) => chains.includes(p.chainId)); // Only launches created through the instance's periphery have an owner. const instance = pins .map( (p) => tx` - (c.emitter = ${BigInt(p.address).toString()}::numeric - AND c.fee_model = ${p.feeModel} + (c.chain_id = ${p.chainId}::int8 + AND c.emitter = ${BigInt(p.address).toString()}::numeric AND c.controller = ${BigInt(p.periphery).toString()}::numeric AND pk.core_address = ${BigInt(p.core).toString()}::numeric) `, @@ -335,59 +407,108 @@ export class LaunchQueries { .reduce((a, b) => tx`${a} OR ${b}`); // The launch's own quote: native ETH, or a reviewed ERC-20 of the chain. const erc20 = this.quotes - .filter((q) => q.chainId === chain) - .map((q) => tx`c.quote_token = ${BigInt(q.address).toString()}::numeric`) + .filter((q) => chains.includes(q.chainId)) + .map( + (q) => + tx`(c.chain_id = ${q.chainId}::int8 AND c.quote_token = ${BigInt(q.address).toString()}::numeric)`, + ) .reduce((a, b) => tx`${a} OR ${b}`, tx`FALSE`); + const chainList = chains + .map((chain) => tx`${chain}::int8`) + .reduce((a, b) => tx`${a}, ${b}`); const quote = tx` ((c.quote_kind = 'native' AND c.quote_token = 0) OR (c.quote_kind = 'erc20' AND c.quote_token <> 0 AND (${erc20}))) `; + // price_x128 = raw quote per raw launch token * 2^128 (Core's sqrt ratio + // is Q128 token1 per token0); market cap = price * totalSupply, in USD + // when the quote has an indexed price. return tx` - SELECT c.*, b.block_hash, b.block_time, pk.core_address, pk.token0, pk.token1, - qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, - qt.token_decimals AS quote_decimals, s.accrued0, s.accrued1, - s.claimed0, s.claimed1, s.swap_count, s.claim_count, s.last_event_id, - s.initial_owner, s.current_owner, s.owner_change_count, - pt.balance0, pt.balance1, ps.tick - FROM zero_seed_launch_created c - JOIN zero_seed_launch_pool_keys registration USING (pool_key_id) - JOIN pool_keys pk USING (pool_key_id) - JOIN blocks b ON b.chain_id = c.chain_id AND b.block_number = c.block_number - JOIN zero_seed_launch_states s USING (pool_key_id) - JOIN pool_states ps USING (pool_key_id) - JOIN pool_tvl pt USING (pool_key_id) - LEFT JOIN erc20_tokens qt ON qt.chain_id = c.chain_id AND qt.token_address = c.quote_token - AND c.quote_kind = 'erc20' - WHERE c.chain_id = ${chainId} AND pk.chain_id = c.chain_id - AND pk.pool_extension = c.emitter AND pk.fee = 0 - AND pk.token0 = LEAST(c.token, c.quote_token) - AND pk.token1 = GREATEST(c.token, c.quote_token) - AND pk.tick_spacing = c.tick_spacing - AND s.current_owner IS NOT NULL AND s.initial_owner IS NOT NULL - AND (${instance}) AND ${quote} AND ${filter} + SELECT l.*, + CASE WHEN l.quote_price IS NULL THEN NULL + ELSE ROUND(DIV(l.price_x128 * l.requested_supply, 340282366920938463463374607431768211456) + / POWER(10::numeric, l.quote_decimals_effective) + * l.quote_price::numeric, 6)::text END AS market_cap_usd + FROM ( + SELECT c.*, b.block_hash, b.block_time, pk.core_address, pk.token0, pk.token1, + qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, + qt.token_decimals AS quote_decimals, + CASE WHEN c.quote_kind = 'native' THEN 18 ELSE qt.token_decimals END AS quote_decimals_effective, + qp.value AS quote_price, + s.accrued0, s.accrued1, s.claimed0, s.claimed1, s.swap_count, s.claim_count, + s.last_event_id, s.initial_owner, s.current_owner, s.owner_change_count, + pt.balance0, pt.balance1, ps.tick, ps.sqrt_ratio, + CASE WHEN c.token_is_token1 + THEN DIV(POWER(2::numeric, 384), ps.sqrt_ratio * ps.sqrt_ratio) + ELSE DIV(ps.sqrt_ratio * ps.sqrt_ratio, 340282366920938463463374607431768211456) END AS price_x128 + FROM zero_seed_launch_created c + JOIN zero_seed_launch_pool_keys registration USING (pool_key_id) + JOIN pool_keys pk USING (pool_key_id) + JOIN blocks b ON b.chain_id = c.chain_id AND b.block_number = c.block_number + JOIN zero_seed_launch_states s USING (pool_key_id) + JOIN pool_states ps USING (pool_key_id) + JOIN pool_tvl pt USING (pool_key_id) + LEFT JOIN erc20_tokens qt ON qt.chain_id = c.chain_id AND qt.token_address = c.quote_token + AND c.quote_kind = 'erc20' + LEFT JOIN erc20_tokens_latest_price qp ON qp.chain_id = c.chain_id AND qp.token_address = c.quote_token + WHERE c.chain_id IN (${chainList}) AND pk.chain_id = c.chain_id + AND pk.pool_extension = c.emitter AND pk.fee = 0 + AND pk.token0 = LEAST(c.token, c.quote_token) + AND pk.token1 = GREATEST(c.token, c.quote_token) + AND pk.tick_spacing = c.tick_spacing + AND s.current_owner IS NOT NULL AND s.initial_owner IS NOT NULL + AND (${instance}) AND ${quote} AND ${filter} + ) l `; } - async list(input: LaunchReadOptions & { creator?: bigint; owner?: bigint }) { - return this.snapshot(input.chainId, async (tx, asOf) => { + async list(input: LaunchListOptions) { + const chains = this.chains(input.chainId); + return this.snapshot(chains, async (tx, heads, time) => { const creator = input.creator?.toString() ?? null; const owner = input.owner?.toString() ?? null; const filter = tx` - (${input.cursor ?? null}::int8 IS NULL OR c.event_id < ${input.cursor ?? null}) - AND (${creator}::numeric IS NULL OR s.initial_owner = ${creator}) - AND (${owner}::numeric IS NULL OR s.current_owner = ${owner}) + (${creator}::numeric IS NULL OR s.initial_owner = ${creator}::numeric) + AND (${owner}::numeric IS NULL OR s.current_owner = ${owner}::numeric) + `; + const now = time.toString(); + const status = { + all: tx`TRUE`, + trading: tx`l.trading_start <= ${now}::numeric`, + upcoming: tx`l.trading_start > ${now}::numeric`, + }[input.status]; + const order = + input.orderBy === "market_cap" + ? tx`(market_cap_usd)::numeric DESC NULLS LAST, block_time DESC, chain_id, event_id DESC` + : tx`block_time DESC, chain_id, event_id DESC`; + const [counts] = await tx< + { all: number; trading: number; upcoming: number }[] + >` + SELECT COUNT(*)::int AS all, + (COUNT(*) FILTER (WHERE l.trading_start <= ${now}::numeric))::int AS trading, + (COUNT(*) FILTER (WHERE l.trading_start > ${now}::numeric))::int AS upcoming + FROM (${this.rows(tx, chains, filter)}) l `; const rows = await tx` - SELECT * FROM (${this.rows(tx, input.chainId, filter)}) l - ORDER BY event_id DESC LIMIT ${input.limit + 1} + SELECT * FROM (${this.rows(tx, chains, filter)}) l + WHERE ${status} + ORDER BY ${order} OFFSET ${input.offset} LIMIT ${input.limit + 1} `; const page = rows.slice(0, input.limit); return parseIndexed(LaunchList, { schema_version: SCHEMA_VERSION, - as_of: asOf, - data: page.map((r) => summary(r, asOf)), - next_cursor: - rows.length > input.limit ? page.at(-1)!.event_id.toString() : null, + data: page.map((r) => + summary(r, heads.get(r.chain_id.toString())!, time), + ), + total: counts[input.status], + totals: counts, + next_offset: + rows.length > input.limit ? input.offset + input.limit : null, + evaluated_at: seconds(time), + heads: chains.map((chain) => ({ + chain_id: chain, + ...heads.get(chain)!, + })), }); }); } @@ -395,7 +516,7 @@ export class LaunchQueries { private async launch(tx: DB, chainId: bigint, poolId: bigint) { const [row] = await this.rows( tx, - chainId, + [chainId.toString()], tx`c.pool_id = ${poolId.toString()}::numeric`, ); if (!row) throw new StatusError(404, "Launch not found"); @@ -403,13 +524,37 @@ export class LaunchQueries { } async detail(chainId: bigint, poolId: bigint) { - return this.snapshot(chainId, async (tx, asOf) => - detail(await this.launch(tx, chainId, poolId), asOf), + return this.snapshot(this.chains(chainId), async (tx, heads, time) => + detail( + await this.launch(tx, chainId, poolId), + heads.get(chainId.toString())!, + time, + ), ); } + private page( + schema: T, + input: LaunchReadOptions & { poolId: bigint }, + asOf: Head, + data: { event: { event_id: string } }[], + more: boolean, + extra: object = {}, + ) { + return parseIndexed(schema, { + schema_version: SCHEMA_VERSION, + chain_id: input.chainId.toString(), + pool_id: hex(input.poolId, 32), + as_of: asOf, + data, + next_cursor: more ? data.at(-1)!.event.event_id : null, + ...extra, + }); + } + async swaps(input: LaunchReadOptions & { poolId: bigint }) { - return this.snapshot(input.chainId, async (tx, asOf) => { + const chains = this.chains(input.chainId); + return this.snapshot(chains, async (tx, heads) => { const launch = await this.launch(tx, input.chainId, input.poolId); const rows = await tx< (EventRow & { @@ -445,68 +590,83 @@ export class LaunchQueries { : "quote_token", }; }); - return parseIndexed(SwapPage, { - schema_version: SCHEMA_VERSION, - chain_id: input.chainId.toString(), - pool_id: hex(input.poolId, 32), - as_of: asOf, + return this.page( + SwapPage, + input, + heads.get(chains[0])!, data, - next_cursor: - rows.length > input.limit ? data.at(-1)!.event.event_id : null, - }); + rows.length > input.limit, + ); }); } async claims(input: LaunchReadOptions & { poolId: bigint }) { - return this.snapshot(input.chainId, async (tx, asOf) => { + const chains = this.chains(input.chainId); + return this.snapshot(chains, async (tx, heads) => { const launch = await this.launch(tx, input.chainId, input.poolId); - // The periphery payout, joined to the extension claim it pays out; the - // ledger moved once, by the latter. + // One row per claim, keyed on the extension event; the periphery + // payout of the same transaction only names owner and recipient. A + // periphery launch always has its payout; one without is malformed. const rows = await tx< (EventRow & { - claimed_event_id: bigint; - owner: Numeric; - recipient: Numeric; + payout_event_id: bigint | null; + owner: Numeric | null; + recipient: Numeric | null; amount0: Numeric; amount1: Numeric; })[] >` - SELECT p.*, b.block_hash, b.block_time FROM zero_seed_launch_fees_paid p - JOIN zero_seed_launch_fees_claimed cl - ON cl.chain_id = p.chain_id AND cl.event_id = p.claimed_event_id - AND cl.pool_key_id = p.pool_key_id - AND (cl.amount0, cl.amount1) = (p.amount0, p.amount1) - JOIN blocks b ON b.chain_id = p.chain_id AND b.block_number = p.block_number - WHERE p.pool_key_id = ${launch.pool_key_id} - AND (${input.cursor ?? null}::int8 IS NULL OR p.event_id > ${input.cursor ?? null}) - ORDER BY p.event_id LIMIT ${input.limit + 1} + SELECT cl.event_id, cl.block_number, cl.transaction_index, cl.event_index, + cl.transaction_hash, cl.payout_event_id, cl.owner, cl.recipient, + cl.amount0, cl.amount1, b.block_hash, b.block_time + FROM zero_seed_launch_claims cl + JOIN blocks b ON b.chain_id = cl.chain_id AND b.block_number = cl.block_number + WHERE cl.pool_key_id = ${launch.pool_key_id} + AND (${input.cursor ?? null}::int8 IS NULL OR cl.event_id > ${input.cursor ?? null}) + ORDER BY cl.event_id LIMIT ${input.limit + 1} `; - const data = rows.slice(0, input.limit).map((row) => ({ - event: event(row), - ledger_event_id: decimal(row.claimed_event_id), - owner: hex(row.owner, 20), - recipient: hex(row.recipient, 20), - launch_token_amount: decimal( - launch.token_is_token1 ? row.amount1 : row.amount0, - ), - quote_token_amount: decimal( - launch.token_is_token1 ? row.amount0 : row.amount1, - ), - })); - return parseIndexed(ClaimPage, { - schema_version: SCHEMA_VERSION, - chain_id: input.chainId.toString(), - pool_id: hex(input.poolId, 32), - as_of: asOf, - data, - next_cursor: - rows.length > input.limit ? data.at(-1)!.event.event_id : null, + const data = rows.slice(0, input.limit).map((row) => { + if ( + row.payout_event_id === null || + row.owner === null || + row.recipient === null + ) + throw new Error("Invalid indexed claim without its payout"); + return { + event: event(row), + payout_event_id: decimal(row.payout_event_id), + owner: hex(row.owner, 20), + recipient: hex(row.recipient, 20), + launch_token_amount: decimal( + launch.token_is_token1 ? row.amount1 : row.amount0, + ), + quote_token_amount: decimal( + launch.token_is_token1 ? row.amount0 : row.amount1, + ), + }; }); + const [tokenClaimed, quoteClaimed] = launch.token_is_token1 + ? [launch.claimed1, launch.claimed0] + : [launch.claimed0, launch.claimed1]; + return this.page( + ClaimPage, + input, + heads.get(chains[0])!, + data, + rows.length > input.limit, + { + totals: { + launch_token: decimal(tokenClaimed), + quote_token: decimal(quoteClaimed), + }, + }, + ); }); } async owners(input: LaunchReadOptions & { poolId: bigint }) { - return this.snapshot(input.chainId, async (tx, asOf) => { + const chains = this.chains(input.chainId); + return this.snapshot(chains, async (tx, heads) => { const launch = await this.launch(tx, input.chainId, input.poolId); const rows = await tx< (EventRow & { previous_owner: Numeric; new_owner: Numeric })[] @@ -525,15 +685,13 @@ export class LaunchQueries { : hex(row.previous_owner, 20), new_owner: hex(row.new_owner, 20), })); - return parseIndexed(OwnerPage, { - schema_version: SCHEMA_VERSION, - chain_id: input.chainId.toString(), - pool_id: hex(input.poolId, 32), - as_of: asOf, + return this.page( + OwnerPage, + input, + heads.get(chains[0])!, data, - next_cursor: - rows.length > input.limit ? data.at(-1)!.event.event_id : null, - }); + rows.length > input.limit, + ); }); } } diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index 4240b34..8dec25e 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -15,7 +15,9 @@ const request = (path: string, env = {}) => test("hosted launch entrypoints stay disabled without database or live pins", async () => { for (const path of [ + "/launches", "/launches?chainId=31337", + "/launches?status=upcoming&owner=0x1", base, `${base}/swaps`, `${base}/claims`, @@ -38,10 +40,12 @@ test("the hosted reader enables no instance and no ERC-20 quote", async () => { }) as never); expect(Reflect.get(hosted, "enabled")).toEqual([]); expect(Reflect.get(hosted, "quotes")).toEqual([]); - const refused = await hosted.list({ chainId: 31337n, limit: 1 }).then( - () => undefined, - (error: unknown) => error, - ); + const refused = await hosted + .list({ status: "all", orderBy: "market_cap", offset: 0, limit: 1 }) + .then( + () => undefined, + (error: unknown) => error, + ); expect(refused).toBeInstanceOf(StatusError); expect(refused).toMatchObject({ status: 404 }); // even a harness that passes the reviewed lists is refused when not local @@ -55,22 +59,32 @@ test("the hosted reader enables no instance and no ERC-20 quote", async () => { expect(Reflect.get(notLocal, "quotes")).toEqual([]); }); -test("invalid arguments return 400 and the removed stats route returns 404", async () => { - for (const path of [ - "/launches", - "/launches?chainId=-1", - "/launches?chainId=31337&limit=0", - "/launches?chainId=31337&before_event_id=9223372036854775808", - `${base}/swaps?after_event_id=-9223372036854775809`, - `${base}/owners?limit=201`, - `/launches?chainId=31337&owner=0x1${"0".repeat(40)}`, - "/launches?chainId=31337&creator=owner", - `/launches/31337/0x1${"0".repeat(64)}`, - "/launches/31337/NaN", - ]) { +test("invalid arguments return 400 naming the parameter", async () => { + for (const [path, parameter] of [ + ["/launches?chainId=-1", "chainId"], + ["/launches?chainId=abc", "chainId"], + ["/launches?limit=0", "limit"], + ["/launches?limit=201", "limit"], + ["/launches?offset=-1", "offset"], + ["/launches?offset=1.5", "offset"], + ["/launches?status=live", "status"], + ["/launches?status=", "status"], + ["/launches?order_by=volume", "order_by"], + [`/launches?owner=0x1${"0".repeat(40)}`, "owner"], + ["/launches?owner=alice", "owner"], + ["/launches?creator=owner", "creator"], + [`${base}/swaps?after_event_id=-9223372036854775809`, "after_event_id"], + [`${base}/claims?after_event_id=01`, "after_event_id"], + [`${base}/owners?limit=201`, "limit"], + [`/launches/31337/0x1${"0".repeat(64)}`, "poolId"], + ["/launches/31337/NaN", "poolId"], + [`/launches/x/${pool}`, "chainId"], + ] as const) { const response = await request(path); expect(response.status).toBe(400); - expect(await response.json()).toMatchObject({ status: 400 }); + const body = (await response.json()) as { status: number; error: string }; + expect(body.status).toBe(400); + expect(body.error).toContain(parameter); } expect((await request(`${base}/stats`)).status).toBe(404); }); diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index d46abd8..83d9ead 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,46 +1,59 @@ import { z } from "@hono/zod-openapi"; -// v5: fee_schedule is a union on `model`, the deployment's fee model -// (fixed_base_duration_v1, evm-contracts 43d6628; tick_rate_v2, 3d6378f). -// v4: requested/installed/sink supply, per-launch quote. -export const SCHEMA_VERSION = "zero-seed-launch-api/v5" as const; +// v6: the final zero-seed launch (evm-contracts 853b608) only. One fee model, +// so the schedule has no discriminator; launches carry their logo, every +// LaunchParameters field, the derived spacing and aligned bounds, a status +// and market data, and the list spans chains with status/owner filters, +// market-cap ordering and totals. +export const SCHEMA_VERSION = "zero-seed-launch-api/v6" as const; export const SchemaVersion = z.literal(SCHEMA_VERSION); +const UINT64_MAX = (1n << 64n) - 1n; const U = z.string().regex(/^(0|[1-9][0-9]*)$/); const I = z.string().regex(/^(0|-?[1-9][0-9]*)$/); -// A Q0.64 fee rate: every uint64, 0 to 2^64 - 1. The fee fraction is -// value / 2^64, so the maximum is just under 100%, never exactly 100%. -const UINT64_MAX = (1n << 64n) - 1n; -const Q64 = U.max(20) - .refine( - (value) => /^(0|[1-9][0-9]*)$/.test(value) && BigInt(value) <= UINT64_MAX, - ) - .openapi({ - description: - "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64, so the maximum is just under 100%", - }); +const Uint64 = U.max(20).refine( + (value) => /^(0|[1-9][0-9]*)$/.test(value) && BigInt(value) <= UINT64_MAX, +); +// A Q0.64 fee rate: every uint64. The fee fraction is value / 2^64, so the +// maximum 2^64 - 1 is just under 100%, never exactly 100%. +const Q64 = Uint64.openapi({ + description: + "Q0.64 fee rate as a canonical decimal uint64 (0 to 18446744073709551615): the fee fraction is value / 2^64", +}); const Address = z.string().regex(/^0x[0-9a-f]{40}$/); const Bytes32 = z.string().regex(/^0x[0-9a-f]{64}$/); const Seconds = z.number().int().nonnegative(); +const Tick = z.number().int(); export const BlockRef = z.strictObject({ block_number: U, block_hash: Bytes32, block_time: Seconds, }); +export const ChainHead = BlockRef.extend({ chain_id: U }); export const EventRef = BlockRef.extend({ event_id: I, transaction_index: z.number().int().nonnegative(), event_index: z.number().int().nonnegative(), transaction_hash: Bytes32, }); -const Token = z.strictObject({ +// name and symbol are decoded from the canonical LibString.packOne words in +// parameters (length byte, then content); null when the content is not +// valid UTF-8. Untrusted creator input: escape before display. +const LaunchToken = z.strictObject({ + address: Address, + name: z.string().nullable(), + symbol: z.string().nullable(), + decimals: z.literal(18), +}); +// Native ETH is address 0, always token0, 18 decimals, and has no token +// contract, so it has no ERC-20 metadata. ERC-20 metadata is null when the +// token is not in the indexed token list. +const QuoteToken = z.strictObject({ address: Address, name: z.string().nullable(), symbol: z.string().nullable(), decimals: z.number().int().min(0).max(255).nullable(), + kind: z.enum(["native", "erc20"]), }); -// Native ETH is address 0, always token0, 18 decimals and has no token -// contract, so no ERC-20 metadata is read for it (name and symbol are null). -const QuoteToken = Token.extend({ kind: z.enum(["native", "erc20"]) }); const PoolKey = z.strictObject({ core_address: Address, token0: Address, @@ -50,103 +63,121 @@ const PoolKey = z.strictObject({ tick_spacing: z.number().int().positive(), extension: Address, }); -// fixed_base_duration_v1 (evm-contracts 43d6628): unchanged from v4 apart from -// the discriminator. 2^(-t/600) from a fixed Q128 table, rescaled to reach -// final_fee at fee_end. -const FixedBaseDurationSchedule = z.strictObject({ - model: z.literal("fixed_base_duration_v1"), - trading_start: Seconds, - fee_duration: z.number().int().positive(), - fee_end: Seconds, +// LaunchParameters exactly as emitted in LaunchCreated: numbers as exact +// decimal strings, words as 32-byte hex. ask_tick and upper_tick are the +// requested economic ticks; salt is the owner-namespaced salt the extension +// received. +const Parameters = z.strictObject({ + name: Bytes32, + symbol: Bytes32, + supply: U, + quote_token: Address, + ask_tick: I, + upper_tick: I, + trading_start: Uint64, + decay_rate_x32: Uint64, initial_fee: Q64, final_fee: Q64, - decay: z.strictObject({ - kind: z.literal("fixed_base_q128"), - half_life_seconds: z.literal(600), - }), - phase: z.enum(["before_trading_start", "fee_decay", "final_fee"]), - fee_rate: Q64, - evaluated_at: Seconds, + salt: Bytes32, }); -// A canonical decimal uint64 that is not a fee: the Q32.32 decay rate. -const Uint64 = U.max(20) - .refine( - (value) => /^(0|[1-9][0-9]*)$/.test(value) && BigInt(value) <= UINT64_MAX, - ) - .openapi({ - description: - "Canonical decimal uint64 (0 to 18446744073709551615), never a JSON number", - }); -// tick_rate_v2 (evm-contracts 3d6378f): final_fee is the long-run floor the -// fee approaches, reached only by rounding or numerical saturation; there is -// no end time. fee = final + floor((initial - final) * -// toFixed(tickToSqrtRatio(-m)) / 2^128), m = floor(rate * elapsed / 2^32). -const TickRateSchedule = z.strictObject({ - model: z.literal("tick_rate_v2"), +// spacing = ceil((upper - ask) / 256); the installed economic bounds are +// ceil(ask / s) * s and floor(upper / s) * s; pool ticks are those bounds, +// negated and swapped when the launch token is token1. +const Range = z.strictObject({ + tick_spacing: z.number().int().positive(), + aligned_ask_tick: Tick, + aligned_upper_tick: Tick, + pool_tick_lower: Tick, + pool_tick_upper: Tick, +}); +// final_fee is the long-run floor the fee approaches; there is no end time. +// fee = final + floor((initial - final) * toFixed(tickToSqrtRatio(-m)) / +// 2^128), m = floor(rate * elapsed / 2^32), initial before and at the start. +export const FeeSchedule = z.strictObject({ trading_start: Seconds, initial_fee: Q64, - final_fee: Q64.openapi({ - description: - "Q0.64 long-run floor as a canonical decimal uint64: approached, not scheduled", - }), + final_fee: Q64, decay_rate_x32: Uint64.openapi({ description: - "decayRateX32: unsigned Q32.32 ticks per second (2^32 = 1 tick/s) as a canonical decimal uint64; 0 keeps initial_fee", + "decayRateX32: unsigned Q32.32 ticks per second as a canonical decimal uint64; 0 keeps initial_fee", }), - // floor(HALF_LIFE_X32_E30 * 10^6 / (rate * 10^30)) microseconds, display - // only; null for rate 0. half_life_seconds: z .string() .regex(/^(0|[1-9][0-9]*)\.[0-9]{6}$/) .nullable() .openapi({ description: - "Half-life of the excess over the floor, seconds with 6 decimals (floored), from the exact integer HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626; null for rate 0. Display only", + "Half-life of the excess over the floor, seconds with 6 decimals (floored); null for rate 0. Display only", }), - // floor(381061679028371455 / rate): the last elapsed second the curve is - // evaluated at; from the next one the fee is exactly final_fee. Not an end - // of fees. saturation_elapsed_seconds: U.nullable().openapi({ description: - "floor(381061679028371455 / decay_rate_x32): after this many elapsed seconds the fee is exactly final_fee by numerical saturation; null for rate 0. Not a schedule end", + "floor(381061679028371455 / decay_rate_x32): after this many seconds the fee is exactly final_fee; null for rate 0", }), phase: z.enum(["before_trading_start", "decaying", "at_floor", "constant"]), fee_rate: Q64, evaluated_at: Seconds, }); -export const FeeSchedule = z.discriminatedUnion("model", [ - FixedBaseDurationSchedule, - TickRateSchedule, -]); // Raw launch-token units. requested is LaunchParameters.supply and the -// token's totalSupply; installed is what Core took into the position at -// creation (all circulating inventory); sink = requested - installed was -// minted to address 0 and can never move. It is not a burn. +// token's totalSupply; installed went into the position; sink = requested - +// installed was minted to address 0. The sink is not a burn. const Supply = z.strictObject({ requested: U, installed: U, sink: U, sink_holder: z.literal("0x0000000000000000000000000000000000000000"), }); +// Computed from the indexed Core pool state and balances. Raw units; price +// is raw quote per raw launch token, times 2^128 and floored. +const Market = z.strictObject({ + price_tick: Tick, + price_x128: U, + market_cap: U.openapi({ + description: + "floor(price * totalSupply) in raw quote units; totalSupply includes the sink", + }), + market_cap_usd: z.string().nullable().openapi({ + description: + "market_cap in USD from the indexed quote price, 6 decimals; null without a price", + }), + raised: U.openapi({ + description: "Quote held by the launch pool (buyers' net input)", + }), + sold: U.openapi({ + description: "installed - launch tokens left in the pool", + }), + sold_share: z + .string() + .regex(/^[01]\.[0-9]{18}$/) + .openapi({ description: "sold / installed, 18 decimals, floored" }), + trade_count: z.number().int().nonnegative(), +}); export const LaunchSummary = z.strictObject({ schema_version: SchemaVersion, chain_id: U, pool_id: Bytes32, pool_key: PoolKey, - token: Token, - // The launch's own quote, LaunchParameters.quoteToken. + token: LaunchToken, quote_token: QuoteToken, - supply: Supply, launch_token_is_token1: z.boolean(), // The locker that forwarded the creation: the instance's periphery. controller: Address, - // Owner at creation (LaunchOwnerChanged from the zero address); never changes. + // Owner at creation (LaunchOwnerChanged from the zero address). creator: Address, - // Current owner: holds the unclaimed and future creator fees; transferable. + // Current owner: holds the unclaimed and future creator fees. owner: Address, - ask_tick: z.number().int(), - upper_tick: z.number().int(), + logo_url: z.string().nullable().openapi({ + description: + "LaunchCreated.logoUrl exactly as emitted; null when empty or not valid UTF-8. Untrusted: never fetched or rewritten here", + }), + parameters: Parameters, + range: Range, + supply: Supply, + trading_start: Seconds, + status: z.enum(["upcoming", "trading"]).openapi({ + description: "upcoming while evaluated_at < trading_start, else trading", + }), fee_schedule: FeeSchedule, + market: Market, created: EventRef, as_of: BlockRef, }); @@ -155,17 +186,13 @@ const LedgerSide = z.strictObject({ accrued: U, claimed: U, unclaimed: U }); export const LaunchDetail = LaunchSummary.extend({ principal: z.strictObject({ position_id: Bytes32, - tick_lower: z.number().int(), - tick_upper: z.number().int(), liquidity: U, locked: z.literal(true), withdrawable: z.literal(false), pool_reserves: SideAmounts, - reserves_basis: z.literal("core_pool_balance"), }), fee_ledger: z.strictObject({ holder: Address, - transferable: z.literal(true), launch_token: LedgerSide, quote_token: LedgerSide, swap_count: z.number().int().nonnegative(), @@ -173,17 +200,21 @@ export const LaunchDetail = LaunchSummary.extend({ owner_change_count: z.number().int().positive(), last_event_id: I, }), - inventory: z.strictObject({ - trading_open: z.boolean(), - no_bid: z.boolean(), - price_tick: z.number().int(), - }), +}); +const Totals = z.strictObject({ + all: z.number().int().nonnegative(), + trading: z.number().int().nonnegative(), + upcoming: z.number().int().nonnegative(), }); export const LaunchList = z.strictObject({ schema_version: SchemaVersion, data: z.array(LaunchSummary), - next_cursor: z.string().nullable(), - as_of: BlockRef, + // launches matching every filter, and by status matching the others + total: z.number().int().nonnegative(), + totals: Totals, + next_offset: z.number().int().positive().nullable(), + evaluated_at: Seconds, + heads: z.array(ChainHead), }); export const Swap = z.strictObject({ event: EventRef, @@ -195,11 +226,11 @@ export const Swap = z.strictObject({ fee_amount: U, fee_asset: z.enum(["launch_token", "quote_token"]), }); -// One claim: the periphery payout event, linked to the extension ledger -// event of the same transaction. The amounts are counted once. +// One claim, keyed on the extension's CreatorFeesClaimed; the periphery's +// LaunchFeesClaimed of the same transaction supplies owner and recipient. export const Claim = z.strictObject({ event: EventRef, - ledger_event_id: I, + payout_event_id: I, owner: Address, recipient: Address, launch_token_amount: U, @@ -219,5 +250,8 @@ const PageIdentity = z.strictObject({ as_of: BlockRef, }); export const SwapPage = PageIdentity.extend({ data: z.array(Swap) }); -export const ClaimPage = PageIdentity.extend({ data: z.array(Claim) }); +export const ClaimPage = PageIdentity.extend({ + data: z.array(Claim), + totals: SideAmounts, +}); export const OwnerPage = PageIdentity.extend({ data: z.array(OwnerChange) }); diff --git a/src/routes/launches/vectors/PROVENANCE.md b/src/routes/launches/vectors/PROVENANCE.md new file mode 100644 index 0000000..da1170d --- /dev/null +++ b/src/routes/launches/vectors/PROVENANCE.md @@ -0,0 +1,16 @@ +# Frozen zero-seed launch vectors + +Copied byte for byte from the source freeze of EkuboProtocol/evm-contracts +commit `853b608ec26d4c80bdad6e394cb8ba686e1ae615` (`test/vectors/`). Local +Forge execution and Python oracles, never deployed: untrusted input, not +live-chain evidence. `policy.test.ts` checks these hashes. + +| File | sha256 | +|---|---| +| `zero-seed-launch.json` | `e5444bf6b1f7e422238331b50b64cf0400597bd13421e281615735511274190b` | +| `zero-seed-launch-installation.json` | `5cf44854cbf00abb700936b2e22782c27dac5e68cf892edc8f5aa7090067a555` | +| `zero-seed-launch-events.json` | `24dd5883ce242ec523517c9f38cfd5e8ca29740974aeded5740ac3608e1e7066` | + +ABI file hashes used by `policy.ts` (`SHA256SUMS.json` of the freeze): +`ZeroSeedLaunch.abi.json` `6a131c2d2f70f85a5e2a54395b9d77c4dd5028a7a4bbf632f3d3364ac61e56eb`, +`ZeroSeedLaunchPeriphery.abi.json` `5d05426f8190cb0bcd6a021e7aceb46f468708cb36bd5a5d818548650499c839`. diff --git a/src/routes/launches/vectors/fee-curve-all-seconds.json.gz b/src/routes/launches/vectors/fee-curve-all-seconds.json.gz deleted file mode 100644 index 39be40980ca71df25e480c43a6fe9eb1fee4f63c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 355072 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 z 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 AMdXx23#I5L3<@{Jj7#e8xok#fH^h>mF;LL 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îJ{V)mZSz{N>6 zuHh)I12$VXqUu(#5}o6@R={xkMQ9?CrkKC5bh<$-wZrKA688(hGOkJ%dE5H$>2^n1 zHJ#v}#t2yr!_bBW>udA#AGovAE#;Whz^C-nh0h_Xh462Z$P3nI4m3JLFm?y7!?zd# z2Td^c#SU#<7-sGpztu}kE!LZ7hy^#XGj%GfHeC`{Md;~J%J5zD#uZEZTLm3!vXED9 zIaK;Qx zU$fcxyp2N!UB5~QkBTE$E_b0DEB_l`|cGh&gh zvlUXlwi6tD`P#C!D{*Y^yo=WOU+#srK z0ui=w_Hr9T@4#EsNLp5{1HPpq-|k1iNPt?lteO<>Q#2a4l7aHIcQnOHO7xDq2&a9L zHG3h7(Vro&U~r;~`4Nxrksi0#GZ!u=eTDltAFMwAY1dL7lRE(j*jn79QmZPNdt%cP z7Bz4;_`GkOGA?KeNM?)?o(MG(H=+lem<&?)NH)0LV?Fu*T$8vqRW@zpsk4wV=_(DH+#>{FM?XokIkxJ9|}8=Mxr@LzXAxBqN=Wd5^^0X=nvPdefPM z$*BwUoP1=zDWsJzwN7eFcc`11X0kE&LAMa~6&a##ek_~GCLmgFlH*%6@BL%%T^3-7 z2;Ia49&`8BbB<7w_(ya6lIc*lfxxNlRF>Oqm6ey_P)F2tjvC1<k>gk#iP*(qWh_ zG;#^AlGECTJgW89DZAes{WA}sF#Enpz^5T}x2a> z4jAv*U%N`y1nKB`RudTko%n&(6$Ry2l}6)!rSICQd%isdPqTWuIKK@!zI$3D-N6JU z=oM+*I^g8*M{ug)0eY!`L~=afQy_~H+J@{7fIx$8YDB?6bWZZ*R~lq zMmRHb^(-k9;Brg=P$7lJ(%j~KW7kesDIm-*+2GCZemC`r$F0^iz3-*YjL5n>`Y)qiEh0`iq)~mbEcH^HwwZm-g9;m7qd3yK78L zlC<2-6o1CQ0JjL_=FQ;UzP}vZ^wkg6HE?)n52H&8M5~SoYIZenS`;z&W>!)fm`Xsn z2$;3l&F$AkABY0{&fy{yb8$}8*IjTHpf#d4#q|cFqt4i;{3_1&@E$b+NrP2f3m1prj6CI$T}Df5W?mg*J1Tj+%GGmg-6J^zj0BT*hC zPx;-tL3_JH(U9)Ibv$3urgy%=iw~Ct&={+?p1ebGV8(d53$&Coo;ea)&lRm0f$01*$M}f* zFbh>GsZL2a3MQ#FF2eHzYn;XLW{g8Fzo|$iRHNE6d@lwB#V&`}GOr#C5w;NJ`-FwO zO~JH6i>Z%at{8fPU0#J0=ca8M`u8=As2UY>xcE#)aXHt(y^)VLNv`E~NWbRHY(q|) zzHDZ^?rKLc;tyTLF4sKDs$3PIO{D^&emt7x8%-juuEhWAu#}qNJqw43avr^*8n86o zK&B=QeR}GLZ~ls(um)Fr@hjLREguV_`iw*B(@r>zj4tJ52m=%gg`Ft#o%cNFyx`g2;F7`~73ty=UjlnV#7h zi|*1Ei|U>x#4RF=)_xdoa1L$Nc!%U0{u`y|TubIE?3Yr%xf4pC zQbI3g;}PV4VVBv00pU*h49^>{DA3&WbQ80kI4SjLK2D&kM$Z!fL^Gb|vfMTX7$nzQ z1Copz4awxEMY>}!JU$>UMKW8?HwkRz!b$J}9P_O#f)mGg>J4_c!vQhcU$dD(4bkdK zzP43oa1K3)Eu=4=pIb)oovUBbhQ+r)zBhNxsK&G*z+sC7(EEyBNI?a|1cOMJcWGS) z?f3RJgu^l?EDP(ezwQyc#9IaDZ`vyU%kVhJq$&VDJWD8L!pF-$YE+r-uPEzNk?-Fc%df=&z>aVSdvp7 z+XA9HPakhfC9mCp8XHX0^^A{%Zw)j7oITU4L}5YM_=ilI_nXFHlf6gw*3L+hBT4!^ zOCe4~vl(htH4yF<{&Bv;_hR%C@S}6s-%N!qF$`*WS!M|lpTE%VNlz_AzMUQI-8-gn z;K+6gkuUif6^rcXq1j_s{R$y%KEQYfH4Uo%B)e>Vl(Xq`zj0te(RcF=J8t)5^G>q0 z>xMC$^9(~wi=lk2&xeVsY^P}~RQ=UB&U@$RF*KAAjOc;&EP-u}e-wW|fk#;{F8m%i z@8kZCI|?jG{I(z{SwESE`(VL zEMHQqrj_sg@Am<*cmGqGHL`eviE=b$@l0Q%bSOr_EXQ}YGjNh;#@qfr+UVFg+<=;I zTGiXI`~B?bqJ6;Yj?&AS*nGx^m6VBFHjKm6-c-a5IJc*~gi9_?@EyWUF$~(VH!vnk z9~}@I8{L06(9Fe4J%)PmaS=bg8AR1A6Dj}=tQdCmOVX;qs<*68@J*9MTo}Br!{LSt zWA>IN^UOD?c4tgz*+VGavTpVhPHteU6b`t|ge<%ME{=*iO3s5Z7wSU(6WU>>br!E9v3y z58G%Jy{j|T4rf%A!0LfZ+vHYsjjSmKWk9f478-vp``CVOGJ_+I!IbvJ^R~d}>VL|% z6xP(opA}tb?gNmc(+xVdBPrUjSF%|^{-E0xL<*9YGn#p_EC40X>W389npapz9C&%2 zv;>d)VOA*4mVqity6`4u;0zLG0yRr%m=&cP0|*$H%`0k^bP}g`TOn zNfBdgcse`|b*dH^$#6mq7FTBYJOHp&PW)wQ#4>Vv*?{;&tdpgRtE2veQ!sxhm;;5Dv`m{q$`8~$32ZIq=J%J?{F`--r zNTyHX|0pJet&MpJS$O`z48VujLiC@eSmvu_W(Dw^l%~x;JiA{+BXQv5R$Jfp->x!h zBQ#dXp0B8Dk&Duts zLvG>ZUg(BPe>i`ezgW}KNdmJ_uK){=xGiS{Jr^M+ImnQU|AA)5kLu-^KNSnD-;|rXJ;PAg^00;DUJcg#<=UX+E`S ztLj&pw%`$G2!48Q5HYC*H3mjT*fMX13!UrZ4?<1Lo# z=?~5{=){qBJkg@_6B-BDydcS)Ph~rP@6r(5+ zDnRvoQKJi2mqJ6G928ADPrsRrsi^Rt=_=L7;mP6j!%`V!g2^;biGw4~P#5mp&EXAknGBnL}?m1g~)3wgsqnK5#CD zOd_Zil}}aMyaTqRfr@%g09R3rNY)!b3iB+JHPk-4Wk#vT&fv|`XdjSI=jt@jq2>^T z=-8^kEFe-w^j)S?h!P==LTLka-|JJEK4Um-hD`5of=`{p{vP07%0JRy5@QuCmNIBw z=T&P3OrtL zs)%POGo4bOl+^Xr`;5aNZfMEN4%1<&KLsals)u+Pef)`j?oDFrC?kM=B3Im{#u+$vyg2KrS1I6XSf!CuFkslqtF3 zPPK|n$LF4kd_!Dtap+|6iCbA7nfrf!8i`ih>fWNIOoJ9c&7k+~R9Z`X1h(W&e>}M> z<4tAj2h!KiK+8}0Z0ie?=t*)382~F{KHxUSZY~oyW#k+063j9CDEBY5L>aVtpcC01 z^?sS8vJ{7#aF6r7c6Kk`%J0r(EBFAgtDQnsz>+^_p7?h%vJC-#;N6e5G)FX~aK+*M zq%)zK`S|btY25(Tj>z8#|5BVCjZ9o~F6HOvD4l##e&9~kas>BEMc8ts*j9YK0gq~6 zB=Ddz6<|^=%Pssyyi;JgP*R@*-5zz?~L6?B7RM#1??QBSF0EUV-I zkb`Z8HvxH(auzEO09Jc~u0H}wu0n&{o8&fx2h<~&^Lx4PGHRIM!PVn9yZPTSS8{m3 zy&9dFNsQ=%ozFR? zx7O9KNPoDQy6#5JNniPm^L0Z=4_9G_i-bf3wW)&#<6)IzmkL;cAmYrZkJIs&n(0cR23u*Bk!0Sq7wiEo?wgFbi4lR$?kNv<|mhA!SYAY0e~`qC%-qY+v; zc_@#4RVd&6FAZBIhl}V;Y42`umw3LSqSKyLAnv&^_s|0!7t|c}KQQv1;fT$EuoGmd zjZMp3^smA6XIcQtzT<*z`y=ixv|}&%x^$t>qhJDBdQPB-*b%#N`z==#C|Sn~>G3 zuLzc#>g18OKQ(6PJ!}CCEv|C)FMiQ{KJ-r)r5TF@BgYO^PnRWR;tWRPB>c>mlax-)SHeJ?vt|!#lD6tv_YK55RMCQO@ z!FKZ`p829%(#5(h7v$zzHLb&ZEeMO!jt_F-Yf9q7;dxpN#{+_)957ggC-OpeN-*gH?jeGVNVZoVXt15;gjJ`93Ex= zK=S;emRG7H4#Jj!o)VTC%z}{l|Gt1*r_!xD8zqw78oWi{t|d|9>m&CaZwBJY9fx`X z&;L~WDFbhs z9Zjzo4U{&|U$q2zsK_C3HIRtzxyIdR7|8(_c_p6wj0o|HeQyhYB6q^|MncfVFZbx^ zl!NKgP5{!X!i>Y?n~uvSJoJ4g%$aV28ZQ|SUju;PU)9`Bl(WR2lmCrGR5l=)T>RKw zI4VW1|5Pq9q-d&SLr+uP+Cl95j8{iAV$poAs-R(p9Hw4d6#q$PX$vhOg&d~kM}>zY zoGYCF`1VNUFjWQU%RI2;{NEt|nfA&Qdh;my#`#C`P4vMovG!;{mHPp%LSm=2i5SrR zB-hNFOY+@u|NE^4-8;dRA36OH8CPV0>El^8%$z>y@^Z$o<9bApf+_4PoC!cBv_cb7AnVX^SI&#>AV>Xb&{}1kX_jHo=^wb4BxDn@!^V_P z{On}DcnZ_9v3kEKe~F`}qsoxJGrbh5&_m}QfvzEfbr^4B79EBcA%Zc|^gzfc;5as( z2cnep-$*OHa`Kd=Gz|}kohRKzJ=06VNIo-ySoqgtW%#aH{^zt2kLGc{WyAzMBf;Vm zaUd!|ESAa5EhAl?HxzMjWM{Z~(xl|8=pTVe+o4#VxMjl0 z91GHqb}YXoveOHiYv}3zaWBCnBF?)Zd`_opKLjslY+9||oZ1hNm3an^CH2Va5tt-+D)(5}(&aIK0q@Q82Eqrf-=Zq1tAaU8Zx?XOvN~xemZcDmvbGd_O;{ z@|ZCOk^}h4db1EmHa5p&Rxk8s{L-HdOxmM5At-<%4^!^=*yk*N%OHZtPQKpnR{=!9 z4>(x|Y+mU6B{P1T@5t~OX0c5GSQGj!*m~^?So%;xR{D48`)~4WogP+j%s4D?ZZr(H z_~&b(b?~#hrddnnpUE+KGg1UwQcPJxFrk`UKxLcoUo)^=pPUx#ebZau+njTrrUS{7-8SWF@v=r zyPVsL1u23y*;dv1_*;X*$X2lL+RMx>X@#^%Kd|IcCek_zu~GghP-4p|1#-&DvC-dw z@amf`F^w9E7%dN9f&PT~Y821(aOubYGAGWBB_8D+z9tL>j9@44!rmEj2!(KM9LAD} zy37o$Rn2&^Adgsr!1ZgLCEua#8ndYnAl?%eKnnNXuXega4PfTJzT0Q#SC}}&Mn}5P5Mr&+wD#9hcD;qA6y0Y)1LFCKbDG^VG2$ay z0!!vgZ${}K@@KR7r~w22ew*&v<=leZ2G0(F>d?WB=FE~W%x(hEe-!P})q6IsIflFj zu{hyDNkQJ&JgBdJ`XM4#Ef2mz2U!o`;#DkJL42sLYVSvYPv^??NbK+dG%MPW2C&2^ zn#Mx^aECMo6naj5uEf>^_BS5RfD&2DaqJyhU$|#Jg^OSy2N2-tELSFb{sEM|FZj3b zwq(Gu7Xp1u&W^H+@(4znAK8EmJL2C5``vy@hF8D}M75=%XJQV|uVR3PC%wCU?j(e& znKg>UfZ5BO&uNR02~yVut2Wi4zxrPH6S zsO;vVx^6&e84QOK)F+WyHudlz6sfn>5#M`8<(!n|1mmgU+{JeBkB;X(07Le=;PB-& zQP;cE7xJs8FSy%b@5zXZq>N>pymh%)l92V*rMjoL z63LQ6jFhfEXB~vE*_rGmchG7C7+N0IjKlbjP4-_~pb$hsCDVo2H%v6hwhU|m2Tk!*kRtN+j#L=$_?B3og?HRQLpPy~GNt;O@M+R1K%^1#h zDEZE^&^l+B&DL0@MvdCgxCwtN4x9InP)8{wHo z%yh3l-K+!Xjq#4lc#i5dWmLqzwjm5#?B?(XP>e3%V2txGNx+Lm3WnjC8SVu&fm2Il zTv=whB}oGrL@&3=Gy}(AlKH3qnE|@I;yRWg6Ni_Cq91Rzz6I!Z2svS271%l69*QQF zB4m4j!XT&aB=*K0F0P`FDsljdMw8b;!uDbL3l!nJAftwPU(Uzd7NK%Z1@5J4(Q5SSZdN(^~c9Ntd)$}{;D<&4HE5#;qSNgn&X5em&p z-~fSTBG%*zVW1=DO=i)@nzjsklF*g`960YM0c$P@#xa;67PVo~k;`5M6aIlB&F#vp_( zkb_^?5mY|#gHrdm6)PZ-cDGq5;7HM<%m#JpuRUSCWsHMA;N!gqg#Mw|62CLGc`G+5 z7`zlmjA4f{XNQ6M7?4{DHae|Ml8nWJkPrx-W};3lKC><0`cct^0IXI!?FL<|Ea%Sb zazW^4b9gQ3W0H`+*k?Pr>loC%HW$8d(hLFuZr(78dW+%UB)#@9Qt0!Sk3uvJL0mu`w1Ribm5PO#8s4RlxKdh>{G?Bz*{z;1p`1LN)NKknD#uXjp;F7T}Zds;-Lk zbvHPkGAPC}{`7s-mY-*bN;C6!&mU9~Yw9hkM#GIe!A*TwrWwQXNk?11T5#1mL18Od z)BK)0s{_bKy6~#qe64mpZp${n=tVu~i;uL|3iFQOgdVB5Lih2pTYi_;fS9$;%Jihs zAXa~uHLn#w{mSe66Lpb)?;1bGk(YOjl@p;|%Cc#Cp&vL!dE&LH#m6lH@+p-+^8Gu# zYK}&^9G&no`aaFHuaQUQr1;mnalTd*bv)U`Xk&eDUvOp47qskI`O_U2<6R*LE;qA4 zW_*YKV$mu9?dkXLcr&aj{-^|MW~|qUFR1(>m4|Yt{WXA zLD)p$spXyE{t?8#pFtRewQ3H}?i#h#mOlCc63AEVIBYuV;Polo^b{_hReEN+<#B%A zIu!zWKerwyYbj@DJ$0or06AK2q(9QgqUp%O0Z6uV^v3IksO2vx_jWrZp&z>HZI+(>lcD2y!zp!ACfq(FKYBPxsyRd$7Dh=v&dC{j6GO z4oG-Go~5kxz~NaYap=%s-)R4*q^DU9?=W;%C=)^EnpMPk087SfJj1WnVQjF;!HK6^ zKR0{hYt_uS9D(z3Xzgimh3hJ}{``>X#Cb5m*%pl(`9Gfv?n#e(YxSv(cwG{Bh^l0J z>%{Rr3YAyjG41`r-N@wb?hziai*YC0j}KEVlM3wyV0E3gQEi=)^~ioDLaZUEqs83`+Q8dIdALzx{X=q>UHJq7t&zJf_IKzb})YxrcfzA3TQR@FU(WDHdi z{WbMf;ZV|>jMCo=o6@CkXO29S7#3_GzLj8(acCE1_A|R zX?m#0yfM+x&)JdDnE>b9=uh85(Z4M(Qg0iu%pR#?B1SxIjmB*@Gu1EZ-T@Nup` z%>d3*h=dF=C(+Qf6$t_&oOBD%5+;~>(>5*!7A)D5P0Zm(JLrdoujm37X9O;%)Ns`S z`HvU$y_ebc7rS#Af@bFts3^C$DYAJI=R$>>g4%5%9>XTCKijo$Wrnia5O&{jE_FM~f8j0&00@q} zi+!^u?dpEA33V)*7kr5gmCPpC?Z){uY-LT#DXxs{Woe;m8p$^!(ZIW%(*TI@o^F>q z(>m+SqW@*#LmsN_!cE#W(o~3nus}E;iHh}oPfhdb5Y#tl2zvSQw?v|*6;@s0|2KD|n97x5RW13#-zUiYn*f4={C)4*Ow-sl2#KSLh3!jtC5BLq z!Ggxj+k9L)s%+^USRW<3S;X91T2N}(A+;IAbG<>`5kCd026D)XVImmmxJ_R`odB~Y zVSq*?9a`Xs=$>7*WK{bYyw)yh0_nLJNHXIJG`chYtdZ~h`URx3*Zrv^u{Rgf4=aKJ z$cg#52Uc?1W3I~LAXQCEgQ|TuL&&TSwYkRQ++(Qb8=Q)3{$)8$W0F%nV%g0o8%nMH z14$F4qei@{kna)y4XuERgMY?|;``-cQ8V2@h&PBI*l^x1uK7xZLP4u_)OaJ@Qm!yt zIR!GgONiK@D@f*M8Lao&;7d@%V6OZ@+%M!|I~_}e1PzkKV&A*NYlv$lK;hk5*nA=in%Il{&9jt$ z%>vucQ?05}5At7!6%~vF-BivmQFdHwu=;bFzjFtPwp z*WSsa79A(ePv!z;Any;mu81b`Ba*X&PXGm8G}fX&ado{@uK&C7{vc2h!o|N-b6+Ag#rVI1llzhrt6a-~PrSo`a)d>LX;a#+ zcZ2AW9I7ROJJ#M1TF9LR&BEWD&{fC~jvUPtvMz&)jJ1%*%Ly?%pqw??WIufs@#4?* z7-?3vZv@Is$`Pgn_`t&2~^&4B}y zsC!+MI>ueqb>s$b@`Dur42?z14rlZfyd^kKi{^-ybj)_OJ0nm9G=A}Uh&6nzVXpg-5dP*A~&-L=Qh0%6f`$Z9r7sixC24aYr* z(Y7Qe+BW$f>>F6!((_=I>a5{^DQ$NS zQ54aSgUKW}99$}13USC{3CQ~fqO}@tLBgSctmHp@dX|n8s9>$8CLdVA9uE^T=~zAt zEsxjzC7I1zm*WiT^Q(rWReKPvUsFQkUl;g?(v>r9Hz=RCAayf5T+(NxxE~1+G9ph~ zFh=B}5kMKkr-$650l7pioxy-RVn!Alv-i(Z%*yuOC?lI{P)Vx6Y!aD=8Wlb}makq+ zKq6|miRQn1+9T);+?hP&3ex)|r+iW}+})@=C%Fj|e&lzJ!1JFTl#h-L`|qcn%v0%%Ve~<4-Qn@&?n|`4d|0o7hBDP&~Jfpfpf<29h zssvH}u5~8Z_o+> z7l#d25*gf1@Y{9|Iss((y`(d3z|YZFFap1+VVcU|LbF?&(tqcGSVB;i-I*K@_K*V_ z2uhg0BlliXcY9@)Ye61mjiea-L+1;M{0InZNuDs;l{xb520S;UHAB;dpGobWfUubv2u4bRixu8? z*rx7KcD{*kgUN)8{$>7IA9xSO$ig;3Rj~!bfcD-l`M6kG8pz8@R22L^NrJ^(eet_s zvWBKffjLxn!qe*?tECG{uso;azn5C&V5L8R_3C}_@Z^tPL-3IYfpV50l3(6Vr#Y$k zLIF7+7fDO!@xf-W1IFBRIZI$6rmZFFa0&>cq*Vka+M}vGly-m;)C$FD%Euf#Ph)xj zkmw(C8%c5SvQk&?_`vHVew_ymK6RsUB+R^bxSN&~8 z_)7DtY%}F43#T>@TAI#Wf)T4=CyOc$YP}T&8yOOYI*Uxfm}CtUgx=WbC`mhBkPYqg z`4?J?VfC7-jCRj^IPAxQ)O>3=feD}=x;c*04C;;$C?TM~?~oGp?z){!LZUhJbtb-PCBB%W$5r6jbztyo0yakxESQ z!=d5)mGodqO{D9Mkr4vK*9H}rSlLmSB7&4qMW+ zhGl3^fTV2Xa|p=@;J`}`2A^m!&QVkOl#v7?aPR)*i8>XWRh2X0{HcNu!sMleR$ofI zwDjG21VjI2&3M(G9Fyd$SMQ}HiQmASV%kN*xriFb+(zYTRlj3jK{)aOqI<>9jPFYI z{0a`yW|S*R`>t?22~q5@33_tNLb7fr#$}CAOCDxURsYc|wOgj@5tNnvt9QpbBF*{y zQ4J`DB0MJRxjvHNEgA5eK*9eY3gIks@1c71&0uILtP$#31MS}5h##Jh-g0fMsu(~m zr*-g#Ben8AtHn3R-$|=mNW7}6jmI{sugG?iK-~Gxlik{OU)*1FK&*XeUtY+GvlYZ_ z07~LGg+J);_M7WwL$3Nkah}@wGLpTM6HnHgzNCACdiQ_YdpYW=XAR#A@~`U&bqjzx(H++Z%}Fu)`lm4!vwf7K=qP=g&V0)@@grl*79(*X+f z2>TmfH1&mMfK&YOa$eemSMK#Vush3vc2si)!2|Mc09jhr6YI`Y8Ta7JbHDgc#PN^Y>azq#d#sG z-Xzr;778%ABivX6KbiC4Wp{2j-;cKkP)UMn zhM-^kc)#oU3nxIaKQ5WD*ZYX`$podcXKBo^^C(wQeCiQ2vkmc^v>yApnL)0m%oON1 zPIBf2aBoHXHWGkRD~brp#dv3tMxFHW?eH_IL8kJiA8rnw4>~9b~u1rY8?tMP z2Jln9RP9EjIfKgAY5rvr0@`*uhE;wL@e?uH()@z6bYZ>r;DMfLD<%5CN;}jrWOXxt z0y`iR)U*G_AVa+R6$$<4FS#8>bflS&yTtBaIopQ5*z+Tx1idCE!+w(w@yT0G#EyGu zMPM!v{x?}6k*;c+x0dMS9?Q5-yz7pdRGYV&=mZlGvl8c~ZoK!wXC=ru>`hd)R=N8V z7G2mGs3dtCh4oTKS&XDOe2E&3irgWBmYgCd^W=dZW4bs~5qhCp53B{vfj&HfjHy7m z6*+L{VqezmOZrb797b1TviiW%ry!*ECdLUz@oLEeDHpVzt>A2!`sY>zH1_fzA(;a| z^I)!q_%!e$`Zn|sh*d{C7RUKuqSrrfIC=ivO#vc%W}tAa^nYbBsT%w?%`e&+EIj~p$8nb9|C8z7nf~e<`MAufecAM|}Vmh)+ z+iJ-fvH9(8iK@`<+;HcvB%MT(g^)f(HFBWI<0cr%{}yvQ2mqm8HfQ$7B@b$bB*8W1m3&+orYVe;z<8fjG)=f6?`Z;AoZ=awqZzd zuoQ5x?;{GvZ>IQjBd7zau{d)zGvzn(jZFyw7|{wcJy>B(X$K>KpCu(3&;kW<1s$WI zwTUQ2qI&wNC4oEKG8rROREO)|FEQ;_tijh3`ANQMs#ypao|-hh#&GSMzoaWV)9&b8 zLxBlUX_U=df}Bsx0DM)r2Ry>Qikw+T;Sp6OBk>!FIW4LFG?XkKs0bC!b;=ydoyYJt zz(R3X5WP-_miTTR28{%197L6@2ny3};m|>=Hc<=+7WI&JDh^|zmUtc822x@rCQ~NB zZxJ`j@!0KgZ?+H+5R__uxV(h-QQ{xY10R;e=Bih7g9!!vA`W`pGN zed22)EYvWWPr#joV~zGXaGY9Cdme#z245bXaZTpMb6Up|TD!bV7wz6_g<*EtakDJk zVT$_jC{|j1$c+AAwkCd~{gvdISEkTTxg=@zU%@xyK%;Tk8Y?HD$B1|<-&{?~-^xhs z07juK4c_-|JUBVkLq>slT9j&eLH zsYlX_LsA{2P?)^;Jlzzl5y|tvWjn!~1*baBf8B%7^7exdVqEItZ#Y#>hJUL7QBa{3 ze8NMx7@gzw0zUcHaF-0K+U79{`?pJM&ow(`U{6)8aNGoxK-DIHYf4x}Eum=MAJj5d zld0ez?YzK81qa#kMQAVsBY~bNvdH>SXvTl5M%2T3KNTAFboX6|_=G5Fn@X7jTZ6$u;SaSwh zB>>X#oOINWI7j7r9H3|#Nd!nEdrvi_Dvp2-`lJ5|vPJU!XSm}!c)=hy{c5@&tE|fp zuq)hAr1LAx&r+I>7vKWz*L}!`HCx`)*FTMWv6bVf>`jRiny_-h@^bS0U7JZFSHDB0|ZOW<3|Rc^eSurl~Mm2MXuI3;1Y0Vp>_lG`&Bo( z?w>F-h?b^Ebq#ml71{FCzX)_`<;5`A4-HrxmeE_w zHF`h71xIcY5-AOdM@SrfROVTOiFI;pBo`-GR8uig!SaWP9r)ZFPA}p3(u()wo z3O|u&eaN>vdBG>1B;{A^pTg2PD=q%LDN#+U$Vy(d1&Wm<^Y1+I)Wbe5V4+$qXQN-o z+24w2_4smxk>v4>zn9DO&w*t`f&BD?4~}b+2mJ-d;-BbU7wK4Jo4MweeQvBA?+c7F zKyxH!a(v8iw!T|_TXQX^lOE}ES=Qpt_$=;f#-~t7Q(^CQ9kQ6Ht*taCW;Zct|NUDw z#_u^9(_JQsA^L%{Pk_9`#hTq{5doqNOW%8RWjS|M)~HSsaT3bzvTOp`BS5eGcIu;i! zf!TGQ_=h4g8O1W+PVC0hs(!>la5_H7bTkEn_MLTz$3-E5h0N!5h}xd$MDPgemHwM>voN4BK!PX%9JWQIGw_dh^B58$V$PKc6~$ z@8fB4=lj`cl0;Vb-}r~>?~{n-s3+f$>q%f0c(5Kz$v{#Wu1jGl2=HHXApE*jFt~1K`V4_KZxe0<+A*GEVj`>9(4P}LX_4w61P>Jrp?-tXjmlOW9VP20OXeCZz<^0D=GZ;N z-aI*-HSi;#7mYBHD4#E|QRgNy`{^S)`Vb8QrA=J^-+tuVQB~-?p259y%{O}oi=0P% ziSno#GUaS?Y3YCBWTYHKKxKhMJ-Kz=?az-%{Ow#`Syrg8+#c~XK~r63d`kmPp&}(O zZ!3gc&kiIGU%8T$kArB9#jsfl`XVz898%JQ*nPG_H^LsoF^%}K`-5y*!LS3~j$55lWRN%6^_Pexeh{ET66xA~bZe5b8w1@Lw? zUx~e^!!%%DF??q#P7iaRbrR#m$%1?26es`RLp64PF2zrph7%&tNw3W0l2zu@Wv@yn zXfT;eB6xl`*%nv^2A!%ziOgND*2)hBI7dhdVNTB7TcX9tx=ZE4VdfqP< zppoM$#7Nq>M&61HjJuhvBIVKiIA+rZnx&QQy!)zkw%9jfvd)Q_znjj_w9~jR2eAi_ zRmb5b+njAd*pahbz+_V$zccSzLd|!{(1!2T=kW4j@6Zy?pfG%2LNQi#S#BQzGwbrn zaQF3-gzMCMg-H%x*{4d3H@SBVof%+z2vU!eSMT8m*mzEY)!d!!5Q*1DI6(tAe);K~ zv5+MSn*h)>&TeJGtdrP(ALAP4p_Y070)NF$#;3p^rg5)aE5?6rxb%P<6NZ^&U1<>` zuNZJtWasUlb6LG;QqoA!q?^S}Y_-cnN<|m+P&`|uU>;y2lZ>|pEib1pVvkAKVNA70 zjzP=5^tLw0DVo*Y9)c$rQTIEcqO!B)Bsc}kN)qIRp%@*k`w&2%V9Ho=LF` zMBTiY5>YxY2Lf2E&(vPmVBQhNhj{(xUJY|P|0%FyW7wAv{stkZJu2vmaY@cLTc{r- zQ{IA+Ikj)5cejO8PY$+nvJPH#?+Hf?$Rx5H)q+ttTU$T-h*x*in2W)i+q^>cHuY9a zdv$XX5#2l0WlzXLHS`fO!ahFBiii-(!4(A3H*|qvF1X45n6qWs2XbAz@3C$L8-01G z?r*ca@eaT|PQECcw~`H4akQzQLA&c^Olp$RudaWHWknJ`O5IR$Ea_%>*MLbZq{Ao> z9_Cxb&~!>L)GVK}oNh(@V}P}`V*l~SWyA{Yp$ThRk=d2Fte{$Ti6XnA{%oL{(_plb ze#O)(dy4abaUUtKmXODyI{KiE4(GXVC5|YLPvDs0>m->CsM>e0K4mx5yyl$xbh-^Dcw|Hy@$?I+ zPt|uo`$*H{J;}%g$xyd}jceoOj?iMHJN;Hn^af)!eeg%>Q{<1U_x?e^#Eek8&Lo7x zXA&Y?a?CAI8|%85k%y@Vb`(WaQX}^BUHSkHc{iAk*_X*12J=X97JH5g5Me*UXh3ev zMoJ%)$^aS_;|r)Y+Pr78q?)oA_s%?lFvDM7_t$w~Sd}=CIr^U7#B?zVkPM>b5afg> z={rS_FOv?c=wMUg20aNA%Ycy>Wtoldm&USkpu>UIV^OR{Pc)fdi#l1!(P5(%ecq%9GXDomrb~aRu>U;++4B^9irc`_(X%=k*gJ?WPrSe9b=O4F5*vl_&^7~8gLD@n z)_iCyAmCWJSy-8P!6{00LA=w1b^ zKdFq~QSNZt67Gw+n03ypx!xae+kV!a*h~0b%ACO25J&W%VDdu=YXW4#i-<=wcxYTW(tvX_XWGfX$T_5BFty%tBAcSoo`yi~W;xC{8g4i-SVcJxK$B}`a9xn0dw&yV;fx7K` zK;fkHyyBex&uZAN+t9;2L~A*5wrBCA&w|c8f=|4Tsp+3==K)jj#Jg5^%SC3)RJfmp z-~>xoHGO*ojQB>2!H^?SL1vTq%Z8MEcM_hZUIjOq2#yMq7Opw2sXo>7e2wJFAux*d zxLO#4o#h9Mv%IJU&Cjv2K9yY7Z8abUOo0(SJoa@oEpZ(0_dI?8&fW5c=ZPTx zF5gmoatWgmvr%fnbF0*rB~zK8f8Rz-TucC10ZdYHSNHIr2v?Zwg$`WtE|QmabCZD% zKPYIo=dLnv$5D6`1!GCL9LzSyW`joFLwmmqMHE?=|IXfL0N#hDc;MLp+H8!zDtL+t zhTCOwTJ*^f9Cg9&s0ouHKixJy81srC9FW<-?)|n1^m7d<5dWQ-%@_D9uhfy_8pggz z`+bkHOIGg@07hhP`I_m=gIpt+qRu%gw=eyBF;bBw5v zlfIT610AVhyqm&F(&`iAiKmMWV!-F!6$lJR9NLMpz z`5GHAmXGE@i{QD4qn^b8^n3k3j;=DGswD^$(j^VjT}oZLq`MoW8w4e#ySpU?r5h1Y zLb~zNCEZ;j4FYfP`^VwL-JPA8o%v?Y`S94R0K11a|2p+6z2-f;3_pQ%)5+G(pxtihLm&v7$ctzI~E@0frOK*P%=Va}@8;#S%Nru&DFAK2_6 z3FSlRfH${Jg85C29kI-UIrv_#;-zTQq2%vZzT7yFf6?Fj8!$# zZ15p_oX<|u*ZWI{P6pLxl;Vq6%~>P8q7pDuI=+j-G;7<(W5SK3`^&!sC z4Bv64gBnHMysc4!7X}@BsbPt22jB$}$9{PErDd%k1?au{_JZ12&9oyR`7+i~F2BS> zkS%~~gCcfjZgazP5hpZkWB^j+BKChJcWYmxfu_%OH6{tw;O!T`gb~0<7E-iymu=Al zwNm_XYnNfSiIN;Wi)Uh~FGLPwwdOb44Aj4x3pvn5Ax||_EX$Yq%{@m|We@Zz`3h1&| zsY+2&8ZX@@nKTbhklIy)#cuoDmIR1Qt_2ShNt2a!lE77i8)faYI}tQ4X`3Jl+&oZ? zJY){T%!H9A5r~o3|D({l`DmpH4$==QLej+$2FpUND8;=IkF;Gceix24Q6x_jb;#Tm zP3${R!Y#ik9z`8zO&p%x+>JtX0L?=!} zkY^4`r@&O(W_Ik`4VWlStNpX^Us$CS14f`fYlKF7%=^hq-Zy}lW|zD6Z+o{tU6Sr=NF$S-V*zjQgBq|^ z3jL@lViOU5kqiuOXJO2&w?J!0>E-tICj1}HStm?>#+*Xi2QN4>rQYUpF8*)C8og(< z8|5Z7ftRFgf*V$co$ICg##Mk>fRk(1p0o!b(ce zw9g3OA$DgpbM=XwZ(L_IssYl<>Guc^QO01IgiXg;OGGqNCIN$&J=%*rnFX#|x1O@g%H@GW?Up+vxPd&3xq5X0b-ce*or@2P4p0lO=H;~CC%HGYS zSD!hH0f=EgN7bfQ>J+`FnlS>WxXd*+qOh@Yyf3WcvZ700wO@To9-*(N>W7LO!cUK5 zC60ERL*s&{iO+)sQf0>$GD(r%(U>-k+TA(eSjs*|4Jsp1JC174{Lior6ov)Ve@l>;nKxPR$4>6aUlvtR` z{33(8nmghrlbO}8JR$q#6<+vK=#3nViDpNtqnNWyk16#7kQGVb*5py^4-Da9U)WwP zY)auDqZ` z+O2Uo54FoUY65z;MLBhbuN2*0fISIxkg&deDEy?1X~X0c7UCC;T$RccX^X6D0n#1u zrJ|l5ol&Fqy}Y-8`i-el^#$cG>j2_-?CCXtaeF)fv?^x+u^ z1nG8M;u{mQEYlp94Z0-L&vdqY7>)?nK!6;R5jS=vkKqmIeQ6zRr(JG%wV&fr?!y?U zUs6;Emc*lO!!WDa^gsBh$!durM8LL~3n)3Ec`-3w+I=7aNFZa?#iVB6rRxGZrhb>< zbNZV`kI^v~$W+LRt6}jjE*zuBDjWp%D>!?KY|l<(eOO=$$JrxD#3v>;^y4uut6Z+< z)nqRX^?{#Xe;jrV!O{a~P1`rO!vDwBSE3nZTl^2cNO=exHZ zQQ*`6Zh`$Z#u=f)SsY9GTvF?I%kOpueyYK!+=#b^3D@GJ32z82k&7YLEU0Fu@-l}6 zmL>bW7VJpf?n*6?QWA)c_j%ZWE>{A^>?TWpT!@a}=3)Ob$6Z`M)Ec_ywXA2sbw?4O zwv3yG)4#~O=Y8(*jf{u z+zZzF&P|~^tbFntkhtviogW;rC&qvFC&Vf)c|kWPx)}mt6>QS1lZhk z(2SJ7{OyM-T?mUooiX2q{qAtBtfAjH_Gmf(q|h0;gKe7 z^gk&XjlO9b5D5}IyVGWnTESBxCO?N`T#Ad?2++~RJqCL))FnqF^bE!N>VFP0j%p-t zX9Gz@ZXoEgD*?N$Lpamq>|Q{mLi?!CIG^`@c<&_-j@gHouB6Y%t-sra=b&(|-_iE@ z5XJ3Np9%t|6Qf{{exj;n2X`3C)Sw%Psd1?~z{Cu0+7Q#@atabmySPdKTLPTUGfVr> zYtI5yF!IjldwWu>;KML#1OP+%$|BJ~d8x`%3$8e3E2We%C%;A|X(bSKfmbQf`ZHudVPHqu8T4?YgZZ`0G!^g|22G+m#d{HdM#zF9 z0C^#AqX<%EX)CZ^0eENwLC-y*-%G8f1}qD77nw0hQI8V^mOE@37gb3blYcS-xD!Uv zHSLXuZY+#ZE#k`Xg8uZmX74MJ9r&dH#yiEiC+_B0z!+nf|Cui=B#iAXjG;GZ;?K$g zg|K^{)%RO)bkS96;j8*ER>cki2$Vuk;>R{aZVYF@kVs*}T^`i%hG!rE#OBJi_D}Ox z$-)&FfGH5a2!Eb!Nk1jJ&yq4Ih2mKBw;PL^y~YU0FPiWz@;|C=(((&}a40CJnJ8O4 z_JbS2nR!W14gS9-tX0VVF$D;x8L832l2nY@hOG`~=?Gpbw zE0CgD^_N-Be(^>wgLQzY&dfDhYO(y(b$#sy#uFd%@RPOKW%1;R5(LZaB6_`2Yho8= zOd#}onD}r>U&5NSrFsrvM8W%JUb69)!nkP>IHYpBc}p90TJ||EoHZXT)-S)A>yDUV zoG9;?)GVD{@00)M{YZ_$>25A4ABr0TvFNZ9D~nv!oe2pF@Wq3oOL?z;1{bo9iohM? zDIS+r6k(6B|3ADtmW~Hiil#f+T>$v%#Z|J`g!}He`vB6+Ar5`ILu3}t19-4Vn1W(d zQD*znaj*)4d*e0vgNdSLAfV;ytJdo)(;{IoRrPd}nsl{!yHf@j z&y0G_>vuh*uk50K4S~T)ouz4Btd>R5vn`3hEf~KwrPE0BOc?{QCGq@Sw-9b*p7Moo zHVT}KPEwa)#U!!ws1jas0~m(> zpj^D}Yp(R-67UI%hpO}m9Y89regMGNnOKL#@HMhY;ZLB>^7o?O~nrSPo{aE9nqF>CoPOp&6%Xo_}X z;MY#jZz22hfP-Y*p)G@DY5qD)<}ct&ab}XB%8Ox$B7+gnJZt^>-Hx!)|+RztFaz%vAG#iW6U? zuR8YJw0Tx6;6k(71G8!Dq5_uYA@Gp0)fHwt6OImfG(g-UuHe?ER)6>;b3F938xXC` ziN*ztZ)qMeZ}ebd*(iS`^88JwG5HAYy0FevYg=01atDuC<3>mEY5R6PsB#EccZ}mm zt-{1;+?jx%J|MIrEMiWsod!cUrgGG4f3R?3#BHV9co3gD8Xl7a=>$^p$Ba4yX_)>D zw`I|NdD|h^YX-#HbmCSB=}WM7bOni0XR&BMgvy2svd;^owI_X(#&tqJlNhbNu+=Ymm8$E1wtV*O~)kcN?D={#w67qYHf(gFq;O7T8~;7vohQ0i0|Rk(JpZS7Aq zgj$97r+^x#LX*nwCEH-s2_evrVHA(rjh90d7JsV%xhYdGZHC3U7)ajSrEQAUiC-9f zVE`%~EqGR(F$0I;HH&E^QuG&c0pU9uTN)jOxY#RgaiS( zBr`@%Vzi#lL_d+gtmV!JXVS7`J7Vb$5cuD_rHzl>9$O&;IxKOuJ9esKQIa+p0D_YK zlWSC#aiDo4+7C4r5^-u1o^%Im!YsAh6?r(y(8>ri->)@H*$_jKO?t=%SfX&xZj|Yq zvv?S2Omf?PsyZMT14!f-%LX9BT~va1v;E3|S^d3SrMonYu5%Bg9QsoEC8ugOSkdQ! zlIH5XzVm3YfC&fg)sJv9I;myI*&a|yW>0kowWZ(=d6#P#-&U@Q_JZ82GYgY7(H zM=@4}SZ5$_(HfR~?KkxlMK07afM_-=mR@Nf;;SK zMlBTavK1DpU5(w7-zP1!!RcetNc#uSt^l6nrgSpF9p2QaW0~o!3Xi6izm%CrAlH*6 z8;eB1YcWb*S@Dd^LMn~+OM5hNJ-wr_j4+fX+w4T$koLXdvN>6{tY%GfNNk#0<+8C6 zb=-Ka_2Gl5S6r405%&AGl?_oKM$W~;#K@guyw6*20{fwB1sjE1k{Kdhm$p#AruOO3 z=*N`R>1`Y-wmre}AN=Zx{X4)$`9=C$t@o5FfojKC=9kr($`#$qx3Uj^#xmCz+9Av} z3!Iauw_=th8VjEnexgVf|Bnj}tXHjPs*p&nf(N&}Chf?!wW25*%Pb!rO1p1}7WVGc zB@l@tCGe}8(iD_3gb`g1R~r()kyCLO1EI@=T+U&@>F8H;5Nc|C_jJE{c>Ckdj%PN07dw0rM24{%?vZBb?g!URH$#~|CG4)QX4=DLZ%l<$$^h}`Sow# zKGO2Z=SSWTr2g^s0o;PqSFAg}6t+V|TZsb2g0$RJMH;ztU9ZGBW&j_qq+(wO|20hg z7-`doi)%hgUjb^Y@$hiMv|2Up>n~v6h=HKCOlD`})>LvyNv;m`hZi#{*0u3iMy$sL z|JXVZcCkDzCWT<0`Wp|d1^K4?9oj%K$y-jYylPv(wi9=7kZ`~t<-+Uyyenf7cfgvE zlj#Ei$s=Cg;a8!s+I!aaZM8OMV-7YUlP3BAEF8=#C>M zy$Hcqg|WhFlccgji@4^I58mz0{$(rT*N91oE~syp^stc>}5Ky_~c(8GXW`25hBv%=u0> z&42WI6T|z;59F20R!LTSJ76N6b?`tIg>tVAo<#*RDDLNxH(8)0rq5p4y+zO~? zGwaO4Vwqh^O(nP?36kk(NbxZC3<7d;)bYczh>A=*%u8;-=A2ArrX_IeTyR%-8HDa zVDQ_bN7V1fY%ctc5p(E*kieKzMOX3Jpdm1A_o|E?B&b{2!bOsVl>hLC>R}nFk^TZ9 zylou5Ga*5pP{C-}iZesFb(R_673G{N$|-Pcgy z>l|+YLtx?12nu%xH!J%UVEc>waihCB_7pDJHb{+<6fxa-H10Ad0F^YHG|o@KtaQmj z2*`|#;77V^%P{$yI!^jTg2GqGf54&KcJFC(;+U;dBgeW^hdy5tU-@_(tDnx-t*tMg z!r%+2wBL5y=$1#PedtaDcfl>axX_}#4EJ#R7MX+cwMk}Q0Lz@5Y#YSJFO}h)M+n>3 zL8K9%6}&~Rs{?wgFRJ3R;6tsbz0QuMM|8mn4UN$D%^qJAKO+J-)7}7TMSm%(KT5d_ z>lwluKi!P*Yh=48v;n?tN;+E%x8RHz4iB{6Y+_ohwr+&D4D-r@5)R)nY4qO7Ob&pw zW?u$9IN*_6^kDUQaTd`~m*?_n7}7sjcHpaGcuKKnKX~^{_<+tPyfaMpf-78|9*{mo zTucgvA}wS4`Ej0lR{*K8bQXXdo z8&y*OtKUkeFVX4;+vHK;hGsbxHW!e+#_O}djP~d)DE|^+W8K@f0=frul1o5ocM8)Fql>Rpv+MB(K_TUpqV364fADm%BNTd4IDSl ztbA-Cw&zMG%@u$kbUrU2X>dX?)k+ggDY|-kt(fS5nTgLg*NOx&MlJ?K^*kcw^Sgp!NaTcw>4KPgC$hYDLF=sNQY@Mp6PS_Br#hy$i;<}XH`YjAb z3d^sX=w|YQ0^l~(RWWCq)jy3H5vZT09?tNKg+v8Wtr5gbRGRfd__y`4^LgHIRd}A4YvF|6jM-x$So^bDto*2JCrO4r`%jf zLBSKOA*VYk>yIba(FL}B6Fra#r!eblMxxEU*tXe$$F$0B4^c@!C<;5*7(o=LB+kej zwwA%zF(z~o+Su|kKhdd0BVZr|Oylk&!rMKlP9dQ0CG*oU=TV2OHMwY3WLB45h4YlK z?CDUoHZKlbNythJ9R0^;1;8DMtd|sLOFeE-5B8vnUmsf6kq-M|y!7Ll6Ksu?T=Ih; zUz2)$aOhNaq0dku2A~t)MRvV9<{lNFBA{QzmsHhy-zlATVL(h1?RQSX#jRdd7{Pdd zk(&H2;YTlSEimRalwOp-WY82F0C!I41~1Iu;(2adPr!s=@hR?oL0`(!i4GV{VkQ-C z-Fa`dAu!vnpSyYv-m9>#5u=Ac!MKw4^inWNTT7O{g^2hZK&1mzGQE_N@2yKsZ=x|+ zYDTQr;EmP&HZxWHW(zrK>}GDp@IThBpaR4Bf}4KVghRYd5onjnB0v18LaMU8HYlDz z-GATvynm}93VdDJAF9hoMM3<%WMF)Sh_bu18|U_(pv-u9^tk?e{7#izdhJa|luY?b ziCT=A$Mh%QED>eV&evPdf5Tw!eJ9I%!3*C4(6*WO_BiC{{=*$8&}BVx@oS$}EExbM z_n?Z4UxFOY8y%gi>BA$q>1yKVea3(3VU7R}eETvrMb#dX=!$+0M`CvG-l@CQuJ(Br z^=S;3$<0wwyZwEDMjz;>(VEj!ep9^80_(vEQVki(q=;YEER0ze2~xci^+w~Xov2O& zZ1OK^^2Oi0Ba2oG2XiK?`!~^yX%OAbQvWQWEc$NKHkZRWqnnKSHx;<k5HE+ z$dx+{TcA}U`|Ic^FITMzC?Z@!xDt!g8l;863G81RmE9-wTMJ{Uwt(8+NeWx{8CyFg za|zh4waCW&X;_=-9xXvm;iI)18gOxy(()D1vA%sv9V)=(t&+(;A_CX{b@o$L=FNay zK7dXQWpC$yq%U35pV|W|+Y`-+z@o_neHC!ovrTnn^IyypOLD+x83@Rh=uFr_)G88f zDBX3{$500bl>ZgfF*h^{18?2vU$+Of2D95)?fjgXrMV5ph{cO<&v_kYZX(HP2PR$2C1+q zOYNc%NuU*w=R^depeHrnRN+ey4eW_V<&Yo$td=pT_lCpz^8OPqS&16SL?(^X4$Ihg zxn2jLMC!yYC`0~kYCKv%1G;)_$@Qg}KfxMy>`Ckck>Cfp>p=~4f{N}09qE(zJ*@7Q zc})HnZd>$EFAM_0@q9lZ$w8xO`CdLt~q+i7FO{cM{g(3 z7%oUF1-*XhD;p%W&k?1{=q=oOeNYrG zuLnyQIzTF)KE$SDG#D_It4ND2XC;tVZXWh81=|ywGD^3bt2@!$chK*NlIdBHk zfw!|SIMO8jk$%Ha39vO;4z`hyhT%LrCcTk6U)k`B^%&7iERPA$J+n)nhVrte|G#oLg(%9gI~^3q8~8-c`%B|oN`ckj?+uScQxB+3(UPN4@$KBd_G91gt) zvUilLsZAmWINBW|$M;`r7*q`{JA=*(ziyxm>rYZ`yPmb(vHM3NxF zFI>oc|9;*%0AT!Wbd0QAxHWz0TYj7%cA4e~zKUgE*V1_a43YKxc1cLV5Y4v&m5iWR z=G_B{HTvwKV^DLS3D4lGRQAk}hHuG15s#RZlhH)LKe8(2#0kQg9p{vMynFVJ3m_9! zzi4#05R6$DM!@1()-ElChVcxIw0Og9w-A|aYweFogYdxMat#yCS}IHCFDZz1O!UWW zuvz=cdRBl-s0***wuDoUC>#SU_km$7p97>ZiGaaErC(Crt^72BIV;(LxGU6l=TaIN z`q#gXr?4o~mnhl=u*L9KM|6!;8QHKN5afDaqiE0LmZ55YKs~-PuXiluRu=~Ak%cQd z6W%4NRbHA>*rqtMxyW&-m&RNLyY9f_02WIE;)IAEKaP0#fE{8N;{pP#oj`nl?k@ro zCh*%&bvY>N4al$GVXzS!gBTIW&>lzU_i+DZ=xo@p88DqyjIP*fUTj+$5Y#OB7?Y{V zMKF|$0SSyTWq^-FG~*3KyaTPFisHKme1{#W0MHgMmuEe}d6Y^d*5CR7lvr70DYq*4*sw!(sGZY{PwzjR{}pINZLRSmPfQ72js$!5Sw z5%2_UN#s=0tG)!$f~Er-bAaa-DFwQ@-pN#KP1pmc*M-!_=}Q%bTprHgnw|!HiI#C}+zMAgYduS*xt42y!FT?6 zQUtV_;X{r5M1YUWa?IgGBVashw<^`|ho0QCo)8&EOsLkyky024ArV<{jm<>R zW)HAUhQ$N}pA+|Fgy3GkC>lf6xnRKnCgffPlVBr_pu< ziqs({udO<0A18(y;-p0bW6Vf3e-5n4eNTkhE;1Up=&b+F7Z=1r1siZmQXw2OaUwh-jDJa%~X~VHrf=X zeSZ9twEF{@_JuENT+K^Ir9-Q!EK9ml3{}c(uIRd;TsEw#jwG~f_=XyUo^hmk5F$aU z+{C^9T@pxoWDkXKHxjN*0#_mabHnU#e?Zc6s3~WHYTqLR`;|2qYm7Y;@a}Fd z#xf-=c84Qpga=@agxb``$z!6kacR4EwovNjQXCZHy*N}*XVyyc8;=h9Y7zTw2cSOX zHA^P<0zVSz0~A1(`Ua_0RiCl%+nY%kAk@(CzB27EC#(oQ{`q&s*V7RMM)xO5m!BQ1 zv_xVk_S^BU8hD^_&fCFhb&h4ngccb$hwh~MnKfsTi=k^v0%%V#jEob7G#jR)9l8Nb z*6LgZ5zczf82>o(%nJ_R)K_-2IhSQ(h&P7Ijdz(dZeD6FIxmi^XNw8%rBTZXXwT@& z)c6^%(I=D{Cws7VS7^&^yOh*1V>+y_FqM$zimM&3**@L@YGxB@ zJ#7AybAL3>0lGH-HLI)&%QOs@w93xps$XY8;=m#+>vR%J!U97Be0`U)Cao=T1551K$HYw&&`j>{wY@c zhy9pXD*YH9vz+d*MW!BOfA(?blQb-Wx{hA6c5C$=LO<tU~7x##?z zG5PuhEYWQcW-4a2U%pHQx4sx=bhMA61-4g62GJ?^x4v3mWQO%+lA`(w^lyG>NihhT zh$p>SZzZqtG^0bojpWjr_d83kaW6?36Nt+)WQs>3$Vo?o?WGc#WD>;OEU&I$evU3; zB2uYdIJ#nyq#yIhxyGbCOSYg}D1u80-_H~&9@z!vW(m9!hy92QRWu$2Kzp)Aq-^~p zq}>OmOuGgQnoycRuK`Bf*K*RD=$`h!vo-vkC_DSdDbKr+YpE+hb)@N&M}y$~rJ#Z; z$jbTl@p~ZQ(J@;@W}r$`;EaRJ)A3`M5kQsbP@lm4)Z2$W&)ft7v6r9k=Aq)UV?Kf# zGh24MZ|Jn1fmbY;vpVp19~`u01wpllCxLM)d`~-LYsWDYMI5)#^7<LIKNnbIQ&Y zy@8U-rW)bDPL$xL`HA4q-0Q;&j4|Ti-3Sp5LU|7IJ#kPsBi#ymfxIoVXb6L%?Ovbl zBKfvTGuULJe03Ri3U`6BF;>8Gf1%(WO+fW=y zXW#0ZJ-BDT)nQqibu?>98xtfMQ;zZ{2lX2ld0_c=rg58|zY>F3B!e@1u~#Djt^0ZZ zN1|lA7tyWVo%!@buv)2Vv_apH0_6XKv+6^|LnM}^O%rH?ldDu^r*}nWV*wEWGtC@h z00|m%^5HcI4kToVa9*T~PGXaR6ro?V)#y-^p?OGko!G@jB|DtF?p3oT6$rCmb&d)o z)kWv3MtZ}wZWajslE^OnzY8@dx(I>pWDg7UAamlt+3eJ0DzUgc7PNsbj%sNHCbBl^ zc`ul7jz`64GegXzTuneDUpk5c>FukkC7?i4vejgey`jg{&*LINzIGJbeyiJC^s4~f zYCFCw8Drzib+!e9YF#1ARv1fSSAjw3>tkl-!4dytbaKH7lSL&F|1RbK>yzOFp5uRP z&A@ANq&x7A-=lSLQx{Nk8NfO*tj|4U^U1{fz=0DyzyB1}+PTTieD#KlWBDA`V6&J} zg$Kl}e!L#AgO1I#+zs9~p&aNP)8i~!Cd`FQiNL+P_})iv!?P_sf~XHYlG+Y2bxv`7 zpN;aBUB|pZY5n~W#XabJ;3MxogW}MYW=ZEJ{7w(g?{6bm3ZdU{?ez&#p2OBI5Wcdf z?3DbYnV2ntbl(r_mKFLH1ulh$J?Q>9goV$e#zUK19r(y$?epS$EGQj4Ri!z>w8MrY_D<- z=loXvZ<&>~M4}R`b{T6(;-SiH%R6D74T*ka z|0!uTr6qEq)=A=cCA&h0Z8nOyfy!{fE;pAZtX{xreB%{6tF7s;3~K~ucI&Nlz1mCg z2l9_t`4Si6GP0b~+GI}&w=?XCX~x0qf`$&H^Z_9fr;U-ZvSwN%EH;K}U zt2e!kh5ta=no%rfg^y2n7uNj{H2Xsr%Rb3rRNnUkm_)^jN~3dHp3GwnP_<_{kpUgL zzbWx`*1(Kt+qI}NP0+(X0L^diZVG=1x4*Fmb_`a?7X%&>45ntm9Dr}kmF>E=A)h+? zg5JW34GHo!sV+|#QXYg+?>&-q_2O05|Cbf^T?&bLi=4nbcSTD9$eRd0+Trlyz!B9{ z)cj43mRmuZ5($z~b8u{$G)?3&1~c9BGtv3cGR~-y7v~LU$jzC=)C+O_3rmQHI6Y^E zWbVjijuHd}ERvAM>s84cfz&V^*wY#sINy8+rGn2+WZ%@YP{*Xj#K6oVipNXuhG)_f z0tP_z(f7K%k7ZTa{WrKLju=x~t7$Rb=L%+P=)ds)E;D6YfSH+%g@+-pd@p49nFdU! z_aJ8o$LoWrA?P_4H-A&)2W`s>>*53GvEFnw0tExRhB4?r`95OB#G|4YTLE69Ya-Wq zRj3z|9cBRRW=#_x19h|(cb^x$33dU^cRb)99K$mLU140oEePYanXrH`maHbxoBUrz zirdaLEd>XO8Ld`1Qr^D=P~6`wUXb2NQ)s1E0zEv0JOni;GEcBnjI&m&@mn6<%NptT zz$L&99T7ng9}TB(fqxyb%@>9X7WIbZ$DM@+zx|odB?wr+o~W!ms%YK9=@y#>z;c}m z&RC+UAo*-qf-r;W4!y{&lTmc%0g|bB``O5pL-$x5_P%6Le%+eP6W1p)9pIFm2?z{D zSzFU`7=ep7`}T^J=F{FNG%OLgs7zE>)f$1?AeR)_`w=ML2tKtWvGReAKuj6TdX;cF z0uRtVF}fg|B~ zv$;x_Kzt^CJI+ld;zBVOXq5Qm*?UVX8K7RQK1vaO^C*JxCs59agSL6kV9y_YEei9& zxA%b-Tjbw`9Fk}B8I@K$xZ#}kgHVx7t%kB{uUU8?hs9#C=|p$TR^RpR(h-O5O>AX% zN9Fg~cZ)UOxAIe$I#6%bwQ7}kAscTwZuA0|{2ytzLgQI=iJBcyD4}_Q?{5Fe3`UX( zgB_%NjPu{r;qn1;%WM3M*_*HK_0>Mp-L9bEneg^2oRO+URO z4HD*u^GHg@$Y#fZm4KBGhR^?gDP>M3SBdq1kDBw;C3+v5L@e4g_EIQ0-X$Pn0#RJ- zW7jP;`v=D@^F3D{WSRktW^8_5t4TeEy|L+<4pc!?`I6FF{)T|r#J@DC%_i~{*WrW0 z_$zN^5^Xz@J{{(}zMt?q3e3a4iA~xKx6=P-q_^4s$<#Gdw5F@MFm_LZa8TP&t83HS z3ek$S017I!RlE;i-agn>QL#D0`&y66!;e5}qLKaOil>cV~)E*NO@<`Jv|B_Ec}eVVJ8u*Q=}_T~Q%j8}M3N|hS9AP}l{ zYlnj3Mc3y3u;6ox^WqUUfadz?1*X^5`;yjabz&RAldt9HzqXOFd$4(nBzgxmA!ncd zp*zu*!s2`^Ly6Jm4>f=ix*5{59BxvQGn9X&Z{>@ZqB|NB{Q}fvypTIhm-@x#z4LC$ zzmGVgjvs$Kl}Z;HH`Y;aNhPjau5-tb2t7tCWMAH(-090KCi1N*WH)xa_Uw5qp!MI= z5b6!^HkO@@$e;q_n((BI+nC{N7U2lITE2rxBG%aRMrCo^D!Bj@cm&8tt;dbLl_asQXA zQmidOo7OIN@RMKc|8gQq7fs`TNO50y6iN646Wnv0GG!;W;G8{fB&7!p+#1bgo0X4f zZQimekFVJgWLM^j^xkXEml7kh9MaKIpN9s{4}3j45aLPsJ|%^7@)yaKse@6hyldxb z57ln?mD79vu4yill$PBy+zX#-H<~xMvJ)G973%`2>_Gk1IJei?g{QcR29I81@g!XK5O1>SEe;Dk`IA11bWW+!SBg z^eN7FnZf8h-}z%b+_rP?2exDXFIpig^`-?134AXB`2NI+LMuePzQ}XLmtWSx{QiYa zarLm&Q}!OF{bTYkkW*J5)hy-17Jd6Oxi+AyBhqhgp9~)F1R@u@QXlH}PlJG2+QomP zw8(ey(NFaMsgpQO&YdVHqwgG#T|@u(3lHjT@*ZmMJtakMUs3sR?!V;sIK*rVK=Ywg zk;qrM?L@)a?IBuI`fxd*CE%%SCmB#C#Jvz$HviOzm6DONBE5J;{R8ra1_{V@;ZuYZ zmo%z{7&Y8hA#0i0?3oIJlx-o64k6IUuKLek7>%Ew-E&-B!Ys!g@NY6oXuPtZrUts- zVclx|oTaW~_|@j34}7cTnu#np`0b2FirYV2qCGq=_Z2Q{d(R^= za9`+Jv7#S?w6bdL)A-+qoJGy|C*c}~#UU8i!{1X<3dAl$v?i8<=d0a6sj#&0`Q0Jy zwgeV{9+9(2R;&*RcLbPrLMja8dNuVc=F9Xuq2t>rYav}cuhCT}PH#D%ou=C-d5`6j z5|&=>ljJJ)1V}t|#poLtecd?J;#OBaQMp7U7xnB9EL=VB+VffRyPjv8pAKa5JXISJ z568j$rB=K6yW`_tcazX5ZDi|grrqB!l4-p>31wf1`Tyv@gYKahiEVVTm~u1?m7j>_ zW(4LX-{fTC$SHo)K-|yF)N_<5SUq1yjnR}dcsQ{9oSN0^C&230&{tP7hsBu>4!T3v zH%)RFSpE62{hg{#7PCfGKng;M7%jp#=<^@X=#K-^&&ud`t}Ql1`ODDcS~c;83=d9p zotF*VO}cfq*adG-5@S0t8MGYbkNzy2+qNZjMB9@~WbIYNC*4|M#Eb3r;Brn%3tv6JMnskw6KWSR)h6keqMeW-wQK5UWIl8!`aw*`o4apcNM50`X=V~D4(kV zQC%CA9K1CZXLKb4pMmH?DwV=IyEprc&frW4n$dVwWn!^7zs4m7lren~+ArGygnrO( zB(%H`x+&^&l5fe~!=3e9_uZVsw5@)7fF-(N zo2(E@%NftRbo%b1ec0K0pJNT7S@gd+hu^5dVBVDp)*N)1$HO#xrV}rqR@A&vhyno@Lxw&VNc248cQ410*hKrcNJSsJ z`?Xt_S};0PgI~vhQCuhukb|Gme#5t`u5+eqJ?WP;maBK_cIgM#KNg?{4b+ zwaDrd<4zCm@T8Qm=xqNI9mBI~pEx>w&P;axhLCc6m`P+Tl}bun?iQG5Kt<-{EIS#3 zDB7o123WnP|m;t(ZM4}xpLzuUKkebD zN1vITy&kq$-4ye!-Dhn1;A;@H>Acn|V@g7y>AO2$Hr z<2K~0EzjAB#q`*!D=I}V*cz4@3qxKUP^?FNT6+6qz;+xbiWl>4bFkwa9!oE!E`kWxVtSV|8rT{X!q%^30rLK7%qpIPZNZi;4y`AY&=(`+%)_8qVXe z^p2HL>>NFC2?8%^1jCZShx}3;o6@c#`Q`vE|4Y`G`ny#N+MdE(gLx@>y1&k0VGibg-%K-B z;A@^FlOIkdU4bQ z#50jP3Iys(Jmh2&%V8J`6%Uih(CVBq{A>g=SKe`<{nX=EqMEoRRwF9bbJrx(^PYlAj+kqr8jxXOA9ESDu*(%6xd*?e!Rp;FS=+Hw5(^ znT2!~>NM}2-i_grgC|+!Sl+44tb1fD-NYQ9krF|AE{)hk2$$Mq56{m?wT)M+-`Uw? z|9+f@a3}nZPhT|qRlK@(UoUhA)05|R!G`5O4n}wvz}mvsR}G8SP0HLMzB+)2i1l=P zhkcT2dg>n*&;SpvnI%D_gBEyNUuKzUyD49+vC?j_T6?(xQ$WpvlbM1qP+P*4PzCn3 zPobDD=2Uw3az9$mWhgfg}xyP`=iVZXX5*(WVlKl$7t zP&e@Z?f)*UjLns6M*j+psX}G5Y_dM&HV)lt8a_wl;j`5G}w-2t{!k%rjQ4Q4Ev)8LyJ!DrWbf1DOJ zAun?keD!?dwB!a-#eh3)U`ofu_B2w?#lMuI%$MM548X=uCS<9}UZQFou+>)-(RoKS z$X8z(GDdJsnZP&PqpV=Oi{y?Sl9@Ob1fdrzw##p4tCjB`6Q~%r)7v!3uNjQ;dAlf3 zfkhY*g2XC2tuf|6CMQ{v@*e#LViw3W_Hah}+++_)^3MN48IK-VVGYIVkDP8}6S--% z0KbSnWOO#tryw>Za{uc{x>t;!y2)sWkwtvMuEA&oJXw{j;#{D<`ANV|3{&|U^RPaA z)sy3oy1l`C%QYGn_C>Wg;P+)p<_s6xvG#V1r+%xQ*X!iAzyS)VzG#3G);5j&DFf6RUJPnoRlWD!Pda zXu_E z(RQY-A+Nf9yun<ABF`GV7{&cr)XCxL{;pkUFa*TFW{Af~0;jKrT+!bR5<;^n5>M0hX%=sBh#dcrbgkkl+ zbuVJfwz=QJw#kRl%8iRR>*SYVIJ00#_71Ybd-^Q(qR9dBfk17_P|wG%X*FZ{x_34q zc4U;hCcE!P8O$MfqErda^A%Jms4r_YQqRl4D7rt&3;)|BUH?m7JvPS0#r|rpbcd}X zE)}+FHS;$+ls;lw&J z1=s7bsgTLn6_TKSY2IXnfo3wE-}dT>vBc#fP3k~`y}WLbi7UPk;WVR!kWR()LqG6& z36W8~lL`Z6rS#1fVDK{}bw4fr2A7^P$WCB!h=Zi+rv(XYqB_HWXna(_C^bUg4)PSx zQ0@P@09)RCY?9FVg`4_tz)<_e4d$Q4NFq^xcM5mmmZ?)=`RfTkPb1uZccKYZ z2t#c8BUf7=ffSyT0uFKjI=(q-q;modBnCX*8Op6Un1#}>RodnzMGIu znF?1#Md1!V;-M1;<6 zh}@JvUt+b1a}_>Pj;fiSl6cuKqN3*^M)kFQe$@HWi0wF^r#I6b%cU6R_ms?jjp~I| z;SD=OxVyUvPyU2=DiT~s3|yC8cW2B z7crF0pAT<<{cPZ(=iqFN(jPoWyM-pCRx~;wvw+_B+bw1sxqs1xDX1As>ss$XzH&Dv zPA8OJ4+=1WSxkPF%5Pmpwq#Kl>&O{X7g%eg!fHyo&Qt?Y0wJYbcx9!f$-RkdJ2>b5 z+*D$Iq*$zkO96VFecEav0ZsFaL&j@Y0Xl_7Q#Ok3lD8!G?bm+dSSMx#HVr0ZNLDuS z&J08-R`9y=U23Yl7S$T6_cxgMY6t78ULf z!-QI|C@%Y%cid>7l0v0W@DCQmSD2!k$?L!M_zQ3@&)-?q`uatbU!Jea^i00Td@nb) zEZ`7%*d%P-%-IE^3Y3c56r5j0sg{8Z@ zyFq+sfA633nLB6Z^!J>*I~S6sdhh#d8k~l>h2<9K*z=Vse{RO!I^-ox_$9-*itb$W z=ub7&@;BUo{T~Z_CQ~jP69I52+P6M}Ib!i|e(5b9da_hSWNLVNrQuJA< zbgv=7A*1j9xe{k+gLir*tb+bwLwUWsSeU*EwJ<@UWFXw#cluRxP*9$X=+g@LHbo;{Qgo;C8kDFvp7LWv%XCLxQ zTR(vYveK`#thV9U)v31A}FU$H% zOlTnaGS=HdN0*^uhj(l&5=q;NCU?AZUj>)4S?Z7zUxN@m*+tsh_z1&Oje!e>)8z>n z7p|{>%a(nHjVWmfqF!P|DWuAA*9(KHj3rVfx4(hAtmwe!vkzL2*3nlbJgGGF5A^q7 zKP5JLbPH%m*ZTLGp+2zvC0;gWfe%G&@|!Iu*u2WSJGV~*6=t}eK4`X%)AxxM9Rj+P z?B6{ptDn$^>u~1TVAMMRo0P<55yBBelbanf>oo@B`YPS))%8PHGr^g4$O_Rc-g8S4 z6Wu{h0(Q-Cz9n( zPj>{GiQ5_g98v4#Nfz?VrNRLt2=Onxa`p|)-H8-M8(C?9c7*jk^z%5Mkd>=gboT;> zE-Ve0oiP6gs>Vp$!Is6Km^ zY#k%;ij+_qRymuv-N}p-_TXErba$N%7YRS?XIF>~pdTf>jN^P;{a6Q$w!aXO$^9asrdf~dq zn6gX>l52Yi%FrC9N4YKnT`h>a`!G4ib*^##{sD@*KCbE0PbF?V_itgA(Ae33q2D;E z66BmeCBYq%*YBv}M_DY@$S8b=kCMKMe5ww6nL#jVxCP-U`e-m8`&bfJEb9UC^M28P z6we#DVt;=J%5w3pq55gidTMq}wFUV&_3PuG4$)l&$=qW*O0MI7v_1;|yk#>Kx_p9z zzmb@U#$yibD7v^M!PVX-MZrGRYs7zhHv?b?MOP>9z$0lyghfKgEtXKQz_!H+u~zl;HcLLiO`L53G-|uw&94!2K0Ori3%{9Z z2NPkIBZXZ=NP8H+Bp6paf#g}p$w?VHk%M~idmbn?dAGi>_|M3GcdFWLhf<}NR_UTq zE*3?mZb7o5qcp5~B5}uacAUv&@roMR*c3>z&rocvQs7^T1c}=c(MsaX&}P!MAUjd) z$T0`8p$wzdDO-?^5<`;x!-AatPb)l`*)a?ill?#L2ksQdeb7W{(Bg4@U(QYT{~f)s zNn*2b3b@rydWB(Wp8|ivC=>G9L2}CXwW$C5&u@PVai%|4*+J6md{CY)81rtxb@`Ba^pe|>x&6{hA zeC>YtC=tOqe~do+$>H*(x$~b=2C$xQ&rJ0rO7Yi8F=S0r;A$1c!_`BqrgeEFJT#fc zhB$qBY8?cq7?1Q@4luvhk&WE#^OhHag_@N4a+ZXz`F3n7b`Cszf*s}Gn7Mb3%IpYw z$;|IA?xAlukJY{-iUEDbel>z$gkiM9u@__j>@(HA^QMw&%E2weOeNMX65*sd{OWx?Q-TwHB5JL3Y0P1OA)zGP3>Rd6(IW^Gkw_W zRnNR&CvBm)Pw{&VA`euUMxmUgkz(!{VENlqS6g{lH8z8CF5HSz+g35q5>kuAtz0-8 zL5F$Ji?{wBOW}3KSv7?$%XhJgu+iL69HLFE^=Z42Y=6s*?dCWp0KQ2a!EN|6Ny%4c z6+U8OftXyeCV&D~*F(V^W>M0@Ylnw$Ny}ql{JTZw9je9T;XX@JJT6QmpU}Sm_X;BP`v~xLtz|3V^I`0wxK}PC@)SZ;&+Q=7p!> zci-ktwOHO=-apjZa7mI8oz01dG5sFpiEKK73?%P+J&D*bvQSws;>Y(QY#kWtioFz! zpv#v_r@Tn>>?y}@QC&w*eRW8C*}-L2jR2zB`CRUI=$R#Yat~@-Jdp$L;CJgJI%wnw z(J;qKJ$=FbGU!kD2MaN=F10Il)(fGeH%!jb@EvEE+it%N7fjo$^IzJ&`(nOxIqgvP zCrZykCJR?39zw4}OS``xx(PsUp<{5{$P6cywODQ%3PgpIbKJ#5P_p%49br8K@+WMx zvJE>BLXkg)Ks;@)rg^4GrL7=ypwz6lRy#6TU&p?XiOUwG?f$!k_R_xrR9JU4;oVn3+i&X~Cm0sr+p3ykYBCCi z)jQ^8YHp)ZJB8hk7e&tZs^^QRq{+>_8w}sOwxwM3XWc1rjU8ifE^wxhMGdjqc$*!} zEPmlFM#Lp#U?4!~g4AvAAy z9h@il&C_p!37S7}K&nR_u7+rlB)GkL+V+b-(>@%UiOAQKELHqDxPemih;9|KDSF8> z)>uO!5uK@jz~Fl}i2d=Y6Q9^>s)HT{>bC|#7Izgp8Q)Y>N^Dyx&PG7e6X|M76i+IcV? zw9fXrvCJLla`oHjMG|hwWKX16+!UhAU3y=)Z(CBff)4}FEC9a=tADHZDkwhnv;uP38HPI3)?R5h zMjpKY?GM?(Qul0#=Sogu8JtENDJi$}*ALcr8oV8&UVzyamk)MAaq#0ms@9+-_)*Dh zff-7b*({^avwup~>$8qu?|uFtfdB|06l4OrP@1$Jkd3;}8+rCTd-`I8$F;+Ahfg0- zI9(&9uN_NCWoz!Q#a^~9+?ImJr0L9xPFP|qOcZ&7bh#q;R)f=oP3;71oQNx}DVgJ0 zg+$&?a_cF)+pi{vkXX^o*qB+z1s}0{&hvWEWxg#c-M!!%30hZhu&V&jIWA&L*OXl% zlP6`}07S38;K~SH(XJ83{@;Kyr!Fj=@=v!P&l!t8sQg`Iq}mR;#IO&05p5*oJ6oEy z{;-KvPQ6UT%Z=+_;2ZO<55dd?zAEcG!#4JxWRlDl`j!5IKfAnS%-z>3ju(AJb!INs zwZfa`QgvSY?zQM77^xL&4_wo#DcZmEmg!ZeD;^!teZ}_?lpzsx2jU)lsbS+(yMNiw z$)Ly|rTcR--%a`i(4CV-KmV{}#(Ok4Eid3b=!4c8AmZqzMRSpvSJ?o1$kw@PYH`y< zesUKqiMLqM$e1JDIF#{K^s0QB*_n$+Jq42X6Q?l#_3U! z?v3uOlM^*)Ory3pYcS~LNBpkaSOB?{BLQ!#)$r9t!pSxuisgqZ;tsuBNweg@uY(S!yV`Iwp*RDR~0skE6sr*rLUa01haAoXYMWolTByUNjez zIzGqT#fkBnsK{L&7YJ~5(A)qYI=ubGc24G%3N7h`{Tq%4H*}H9Dqj{jar$-#6aNhb zqn%&u-AQB;!lJ7O;7OsF(GxaeLA`QyBTF<+|8D5L$YwtkGLLF=VPH!MVvMnMA=x z4x=qdjlGk0%V2r{HKe3=$nxW`7DtECyXEHk9nc%;_!>Ud(ZT+M!DGS_fIvN>Q0~WA zcXDP>gvUx8wr0Mm#koZQAEtbDj8a&vTRuzhfirdAgj@H=mzbToWqG=QyO+W;$QlNW zy+S{5Hh|juMet9RaG8ut=t^F|EWg|oEfbcOl-Mul_(Z4_6f)p`4kn&Fq8xonyZUbZzy->fzxf@=!D$5mOs?u6X($=i9884c< zo0R{Hj>D}m^rH?Uyw>$d5=RiVa~;Z?*!AU6b$$^`2sa-7D25!!?Xtan89mM|=Y)1n zw%3G-TJpkWfy*@GSX1n#&{9}*;)g(FsMSie?Uq_@&l5A#fK{%fos;zH5n1w?G zL3^x7m!+6%!~f+xJ&~9+=?_-l+t;7Y(z_vLs8S_VS7*cd21^JH?tK zG%8Q}&zaiD8b11Gt_QNB8dbKXC<;*w^aVtfH$pfn+%iOq>=`Y#eki=QH4|HT>Gwo& z+=>K_R~WY=6>)qZP~&7`L0ucz%opGcrFk<*;z0r*QTa@Hd#1Vc%yhRKCbD*B)`2RD zskUA`OL%6Hnff333JYG zGyw7JUe*f-o#+4j_eHMta)%lkX2O2U*p4h6 zzxU+Xh0Z>8ZfUFMWdsuVK7Q2nk}+LTexFR6n#oENNy(fzMi=18Klv_&W76?m}!$?zg7cpO%dpa+>Cz^Dw>2d|;?XM_z5hRr}tc#2% z8`aa13^s+jJM?f)yPEKYTB!x<-_IGhijy?&b3g&h$mXzyPpmp zS=xV>ry))-=gPQ!{^n?I+{XwuKqpFx@(nun4;tdO1qWj4s@i_#xRiyK_LGM-DuB;R zv|m9vYihep#6;>(#|P7A!|O1`>NBXc;sH9Bv=&}kikl8~#MIk7Rr@99_i$frYRAN( z@WM{%YN+$D6}&`b>_b4pe?EJO+kahjHeZ;nP7QnHzJAWWim=}-H=6{_>=5yk@pjKW z8m=M$Y|9(OpLvaA=F z1h)01EY~sXlx}BS=$eYrGx#_AQO?)C&$X*V{r!+EIj5b}q>itjOw()WJkj_JeY`}rYP@gMYLlOxqyqkke+o@TIKf=of0^ng7!iYNTjBqQ|J%Ccio zY1;5t-Sr|QiVx>xrfz3nHv6Egr~XXNKv(HYjRzEeb7$b&ElZT-rOWPrGmE@olOmO8 zas+=?@ZFs0`hSEW;x~u-Bt}cus(tm5xzXmRnqZ-FXT#{nwsPOo($AF8zhenmjLX_1 z2JM!cFQ&LLF)S}f!kw&xbv=VN#4I!#4%DXfxmcsx!kAK=VuttNl#21Pgo~?OSnQ6? zUBnr>p3)(ADJw_0Hhhj-8}qGdR>EjgZzJ$eZB=cBdR$-@CU{Em|U7n3c0p_w2(#W!1HSN z*RJq-wFerkro=gM>a(KkH8K1P_25Ls{y1XE*$GU$lLR5`A5*_BTZpOL~^5s}v zE`*J4^sW_d^1=QTD^0mnYvYg=`Z@Qp1+d2ubo{s5O~R%~(|dW1Oj7RJUIEj8?mPsYh-+Nd3+a@N58|tT6Nk~zH z$@P38hn|-pD-$#wX-ft0)!``mgju5ZbFtaOE(S)A^)f;1%eZ**Y#y1!VQXWJ3`#Ph z1W)GI*~uKIa|^Q+KfOTB5EaT;oN9V6(uJit)9dS32pJL71xFphCVTdAW_T|%&AG=w z1i7!RX!H$R-nPc5Ffh&?BQBrU7@uXO0>!PV46aB+$G#+b1fc~pxb#U98G5m(&SXr9Z{_=qGR8r-UpyAIkf z#HQPg0H3%!FzLK9nclr}Fu4kyy^+7{X)lnSNb^eus%Y5BA7}0&yR@{zTfCFC6_#lr zReK}5oK65aM-N#{SNyrvVs)Fmv_@vKr-gbY*@&wtgh)>j=UHJSO#wzWaBUDj3Y|&{ z{ymXM-5dmhSgAV8nl9p3dCVjL7DCv^p2>;w`M!D!zDA}st7KcFJz{M>rg6N`4wN?I z;UX>OFtUC#pB#%2cAWCD9dhI>&b2pybpbq%7B(qz)V2b{dMp{n;)m~2g-r0_XVkz( zvF_Sxzc%V+JBS+p&4`ypPT;$HC1pQs>;lXr_qf&j&ofr+8(RcaDIvR%V&A-%-0NBm zusg*<7DgXZQ1%vpX`~xv8;D40$Q-UrR+M)-^SlE~0~>eJIUaQ9t%lPd*)sf(QU?X^ zgh;Jy_myfr&{%HE8%b|B^7p11W1D&)E@}DN%qMX;e4nSBmx*H8_~O59nY(D0Z~L(@ z=ru72n~&y1jD{wAcE7-z>rZ;|%He3C3&`* zcFGaR!jcln_ep$?3BSBP0p6+Dq~&lZzwpFSmgfN@jV`gcsTVX{d)4nL1DMq@O<|;V z)7}#Obc&S{{cOP}tT*}my^Y~1R%`Cg=izIs34Xf~MD|v48U6FM_hUWj86YzEf@K?8 zNu4+>Y?;B4;XjXl;3ecPmQJ<|&Ju*iuUiln^mb54lPyES;HvR$iomnFGaZ^`){N}> zwnSoq)*{HP;QlCUhX45aSN5(J@yEVKh;I+X2jgi;R`Z``RZHK`J>b(V*&$NxSeU8* zKe@u>EKq#k@s{ht6=WzuhqTQnHKrWLRSuON!MGo|=Vr8ci%D&V{mzNtC#Y~vLbCpl zTfv1n)(naF7&c!yxDQ3Ad{4oMNaU2-+m0xFQ4rau2h!cjkd9L`Gi<)--m*sKpswMM z<$~9_8o6}{PN#_S@*j!tjm2T3(J2-jT<9}uF&B9SaNRHjWkL|6beM=*mh$EOj*Q4KfwA3h%)oS#oM ztdZfZPHog6Td0d}O@so;UDbrldy!BHcwMQe?g0J#L?2g=Lfzdmng8})1o@rFHg9MV zxz^h+v8g=}9&%&;|J);x#Rrl962V8<3%{^P)VyJyXJgnSf@eKQ2n9j(y`q~mz1x8# z_gCGu=El3NZ*{OJ6E1FTF5nTiX&o*?g5RQ1rA*nYT~N&KoiZz zdFSik@ZC*}(PC4*@80{ImOpWRIDMBjaH~bn@`DdweL!6$39;v4kqPF}%JWa7 z#Zr4T6vWAw6W5xHk;5|>=eUH;?N2Um z*<;$z8rG;2>q45OO8ARhg#4{Obnq8Ge_f=%@2o>z4k@zg-ISRcuk>y{Lm$fTJtV_| zTk9w{vqn{M>xEHzDdgB3E~{?7Xf7{SU6iCB+}rmoJc9nN@MHA!L)(g<2HL6)fo=P(&;&x^nu*yrdAgN6Xdt)elzZ@ zQJrPa9#5rSTMIR%9P3bz&Q}SP=kVhY;3BlWUiEfpcIVRJh<~|U#Cnc!-LUg~HMFxI z)SP#|J55~_R@vhVW|MsV5T=5yyx@IyA=Iw16mKxxBB@Xp-q@A>I}D+@cRD ztssCFonx^PTPqXu2I~Mjca|918@dcRG#?`zekJ>FkQ!NSBSvb!;M^)3Bb5PmKKaCTXKeIlWm)km z>GPVtheN()j1u`-@v1aS3GUWnt!X@hE$sn1lD;dU`r-ma^B>&VM?coF6a{Zdk^R6D z&zZrv)0U}Ko6p6ya!;!&3%_H@B`*<)CjOF2D(mDTOg!)km^K#^RnG>?SO!PZ1Z!*!76;dl8a_R6A33`10nC`8h zkHJ!8O%zxA1A08-t}`;gr0q+fDq1peBv|HetD{ySvg@X=uQYYZ4X$O_<;A`{9vPpV zdS4KrBXin8cE|Fl;yu-a{S1XjtCpT~Ohy>4`SoV5D4e{6mWHw?_=YsHED}6wHQ%z6 zpVKed{)InJ#+;BOtmL&?Ll^GILtebP=v|7Gz$wzaw*+gh7VaJxPbcTssdeQQ*N=)c9`bToVhvbco6h@2kj*cw5T7nu5F`BcSM z!!2aKS*t}?&%Bc*o^>{-lwO24%$)m;4=~9PWFI%wYHNJGZUOl@jZuj z=cxTh?%|RBr(>*qhi?&$M8T4Me-MM}n9fkS0PHQd$?10e;~*3M>Y zt;=8pE@8rYnj02?m9$Nnc)d*(ArV$5ek`FaGwO(ZP@=7{dy%GS zi3)5DSKj`=kv~r)rtJ0gx*v&$vp&3hwyyuH>;o%%02+#r{t>o_tEa3Ss!T`M%a0f+>P<8yu-w^-G2;eL60@P zR*v|Jtjo`rMD{*ByYZU$61$T+wqA`V$vSU&7#Yb32P(4Zq@JH!@}96j&ExPt!@Hh|rj$3i2Wd5Q zr?BMQWryl7^m#S!ti6U}PD;rt+&e|BEFP0^{#5&5O42_m7Fvt&ee%24xPP41_7o%3?nr)kDXfOG`&4huto~!ih!}BfWNQ@LP z%d2)c%e!y2nmWHLsfBPz_E+w|Uj7~zPP_|Ee))r%ZB_2Gjl$2mEwGI->1hNs%J1gE zOX35nxn`69?S*dj`^0BpYC(cUP)crFN_gmfPP+-iO!AnZ>^}0e&lJ_NE-Yw|yIduy zFTj-+#T1o_D9#4CB8vQrgL_;tX)Mu2Di8O#SbcQbiQhPkiN5i|gKxy=lEN&Y_e8eCjyw7ixy3(;ryoY(?o}Og>S8^UA%N?^{hAC8&!7cxp z67FU*cuoY#-X+^_nmlAPu64K z?}{}n@4@xH-HMAq^ZYE=)h*qP7fO{)#-7PqWFhut>6jO0S2mtenS^nWhJ$lZmVay~ z8>s00zZJ-?7rJeG6{6Pp_9#KGpTO&Qw2kFWixo%HbtGo*#a1Bi zwhgBc0STu2ib~}LPI-zBmpTjuOQx@gNXqJscWYL>dYa>|`MT9YrKwnv3B!f+S^90( zoJ6}R70jncNt{>Wo5oE|G5Ju+ZCEm?{%ON>TgnsSuT9tTx~R4XRE@mCiL8Rh66P|a zHzgRGN|B>SKlA*fr;D>nLo%ImwF$zgS$E2_(vSL-P4O%= z0!e%e&r3Z(Gw0_;7O9v{?(#4YOD#aIVp&op-sx@1%4zZH=aC}=GSyrM(TM$urv97A z$u?BNUj3eh-9u~drW5-`(2z29nfn832Q1T|7!0m>}Wz;lWdyAwc@A`r@w^Jdk z^%m{mr&e-%WzzRi^)1x+$GBqX7~;rnSscsAR7Za#^}zt|Iy-BFJ!06_CWhU@#DSz< zEq9lRRw*p@F*KR<@KWYUsKV>A|1nN0EF_I)e(|-l4iN)vch<4fcH^{{eDq-`0Wc`| z4nRNduOyuj7!%t~Xn`dU=d-6YzXIAY6{_zVZ+=*}d$AcF0LFvTk5;0jq_xJ&c-SnE ztiA@~lxc_~TYJf-_b9o<;o`*@am2f}X5|K`Y`7#+zi1m=6n@KQ@rqnUnEqeqvWsUU z(D9-_o6m89#m;GqX`=~))~U~LOyt4#$M@Iwz{mb(DOF&J&Lw4M9b*>{Fg?7gh>?#% z@7I6c1~}Ro{T(3B=Q-JGbPmK)QENv#^>j`=u`Ip->|Rr|toS@8&@&ihJxrW;wagh& zREzfe{NS95AG~^~6Q9+{8T3eV5k!-IdSz8-CU0v5;+q5I-H6Sq+nTe;^ls%))_IP7 z2(N^pf*!0J#3?Z&%gWC`b7xrIl!voe8M@Pk6M) zs+J3ICU&82)hOco)jj4~-W^r@_jCThF&KZL>)d!)@{Pa`GJ^9qVXZU+k_B*Dm?}rb z*PTJ<9UF5kE&`gIA2WnZ`!9xSL%$orx}S}4Z*OgdM}3+s1donAmeN)9D^^#Gjx zE{cu&cZyn{ZC{{)7Z(9})azsFywKz>?iRJDHfj*ylT5sv&_qr3}MIKVc5YA)cBEOI`_z zdc4GAUl;4W2l)6v<-#D+;jU?V?H24OLJ{<&ascu->&-8j5fg?s|JR~2uWJl`T_W(!^X)UdXzYSjx`O~MMN~- z0>N1MOV%O%w_k|bBb2z;qvWtg)v~biSoNNd6NI))iHvtYex2T)51gAYfO{^l=={7m zQ|fRmfV5pc9#9i7qox$HO@}3C$;LU;Uz}0gH1ghrC5w{>PRODv3J(}ly#%%ZU0&bu zd4*RE-K-C=kg2_$zf+z8p*|22jCr?g7seMrwuClDwfD9a;%EOA#f3~w0eqeVrFP@J*4BG@t z67C!pk_NJO#@l&^dOcrsFNh)jYY>yMsKcOvw-5lGUQyG}UwCq1)OmRV<|Bwz%BN?B zuv7{W$uG5l^`;`)oWEWK2(T8Te}0AhMRdJWd(!t{>_Zt1C;M`iOxNLM01aR+vofbN z1*{yop~>R1>UUct&=rL7{uf5DLNVz%BNN}K%K@St-C~qju3OPqJbj8%q2&Y zk6b_X*THi~bw;?a!B4pofq$4V;I&NYn2gQ#iWc&pgQ$R}OMyP^ki;3{&SU_SeqLp7 zsPtcF(Fno_vu1T{&YNzCs1Jw`DTW8F4fU(S>^`p{kRSO#;m`e^8oV|*Q2IMo9zQI@ zu09QO5Lp6_SHuI31lWaJK#5Q?PK89%CYW;O@Dl`%Yp+qFLGJR;PH)T*am2VvUiWqO z@|U4ZMEwBCh?@6ZFj;F=0p5TTj|IeLh{{xc^z^i|QJk_1g?s$5JoHD{#>qY0yO^`JF)=lHt`llX;2;$Gi=7FL|G~6yB1P{zkMSY|E1H z-b+5lxRC)|N{CZrD0$`3aLsPkt4mgE@?zC}5~ut#F@sp!KoPHGe$07b$eZZuwD3x9 z1&Xo0Me0J?ohy$I?cCN*y~v*nPumUZ(YsHzWt8V~)vVWzkVY{O_}`7e6uLJqK6zj1 zItT%T`q?rSc+}K|49YMzOT#-KZ$>{?QL~XwiCJF^6!|NC@WRUEBtvBW?5U8j?$Mg4 z&8Ta6fOh|eH8*tGPFViu9BxE5*>$q+V3Wjv=pYpNZLNtjTZmV9B5cgi(I=!Lurbo_ zKalMPm~gyClU9nVv5)zjE(Vw)EmEjs_?WI!7p+aGTDcV){wwKcweJ#em0HmRoV^Jz$Zx6VhGhzGJ~l1a49STfAs zTcIE&!XzvWUi_`nCnXT?o?zi0m$m)*K(%j|OST(!?j z8TVDg$xFGd$Gc&yR;vdY@L4_ZBtdV8TA@*%YmdINu6dloq8O6R=D)z&vch(j+>y5Y z?Sy@tx-lKLT9#ZLhs4x$458L3-wCkr`SXbHQLygyF_V$rlJJx#W7%V~+g;N$AXf15 zUU94&ollW&(YLbZYDtU^`H!yuPMo~EYv#txp280LJ-h3fU_Cw)ukPvbgr%$axa!N* zPu5&Cx_IQu9`;TGeNynCH)BM8g`{@r1WzDHNH8g<>R2eLoTnnGk=0vE#=EnE;jqR+tPdK45or1)$!8>JDb3%RO9l}>$G1n_7jA;ND5rYQ0&7T5+Ptv15?CtTn5&IkfQSQrW&e6NbY7vqWi>Y{$UVop(PLgn=q|lmyC$WjHH?5r`4DZ;I2AYd8fF zX{cCa)-ZYLLg2z}v`6VXU^nY1Kw#^=~+lnp;U-P#ixMV0LG_Xd5wE{zzlpNF$h4%AH%fH`c@?ZIWQsug> zP1t!4>wX`$RaBsiE3}!3S~q0EFicK+hs`#4VL9|r4N6$eP6)959;K|@u*Dx}lRe-a`&wF$66J=qnzi(EoOLam6B3AHFACu?sUkGg zXzJ&}8;doy^CQ^~j!?{f2_nevnLLb5s7N-FHI)k-!1&eQ$6S@L><5HtIDVOMgmv>= zS5G)ta1m6!`j8|l(ExQiZSymayV@XEB1mdKK2%Q!EPY$wc^;HxweZIP#4Ry;xGpaG zGtYN6Vr&*`PM<1tV5D_)`W5=(H zRJH!;+lB+ti#PVwPc5(b7+cW*W5R>B%*EqIj!dUdZT@~^f#gb5L&0-$`L^mF0rMTr zmmDS=a3%805k;5xNtRZj+CR4NytJ$yr33$5-Yi1g4}-;*S)kJFyjeeR$@crc`vU>U z=38+lh1BL|bdc}gSeV$93VSFhX_4aVlWD_(bc*ap@fX(WEDd1YU`OWFD*nkJr}-;- zTENvCb=+8~hr<_0b%-K>XVUq%BFT2(Zk-)Hm=xxD`5RB@A=}-RCoFkJge9Jtl{_U_ z))k?T`I_?C_`vSt^NkOdTm-3*V&PkLtG0^@L9mXrC-zCK9r4^v8E;9__n~;MCC^Wa zjY;*?pa7(xz(!Bx_`^Q2k`CNy1|90(q`RCx*^LM~YD8;%PCRDjqDYR!knGQqL^RsF zfsDx{JP_+uBKf}A(avWfmWW;%)M#l{s)z`6NaHyf@N@^a;OVDV%rn7W`#@~y3K_p= zQFczLmdF%9Kazyx+8`{j(YSaOX2LMFIq|8sL4@2{UlbqchV4?E^iG{*4hkPj0(8m- ze|$n8dRYn0xFUo|c#mNJ#n+;34nZt*i1;1xqt~OX`;Gb}2xdaYRKEno8ogQ(NW8mK z6lL-0$H%|HlO4b>^^CJd8Y22Qyz~x@K{V^JT7a^ujPBO|kNxMOn-y$tuQH4^je+au z(71iNAVymYB}Uk-UXAoQ0W>&!>wn%p@*w);@B=z)%AhtP_i8>iiz1(+%firqKQG!w})5 z`1?DN!%xC01&Bz-N~wBLoe}-7cIvM-tXqFIu7ay%j0Dw;QCXLZ;A0HA>OGIyWE3M> zO=z+nLD_d@RZT-m9t7@F@ES2F_wrIGG}wP+u`mHYCjRknDK8F^T?QJ*b?a|lPg2{K z-g@$jSr|X~{;g$`}0xGp{ z`QA!|u9dA#YJkS_1|bDi8coSzh8-O2m#pK3QzI;eWvACi1tiU&xh7d>c*t006A5_8 z;}lMpq93R>=7NH`Pi)&ON-7^RQ!y+_T0@hQ-VA@kw2!n1X3TE~BNF%VU}o(dY`sNC zn@4Im?aGtS=!cW#e}QI^jMK;MU>Y{Z7K?H~wL2I{aoZiZhuHQJV6O3=(-Skb%jQTW zH2DcJnc*oNN&)_4sTwGJ@0}gi`G4ZIWV*ho2d$H+{Ponj_}f3a|3iHHhX?s_Dv*|M z2urG&&B=<&@H&7a?9qULz?V_~elElzmcZq&k!t{^+Ho#URST^)3?;N(KOGvqw$@^x zwGAxEE?Va>Q!V(L4G#bv6PWLx(4RnCFXJkK;W|4G{(K>pp)6=Fzgr3V_zAsgHwT5d2X02Vp&u^+@wPzJFfeXMmcvh|Jv~)pfIn zs-D2*pNXle`GhY=*>2tSWms}j-k8o9$IKQycH+MtB_@job!9JoC~h4w$b5tt&rsR# z_^!E$7k+^deIot-^Q@{$`DDU!KhUQuq1U$wQNv93FbM;oP&OFlg^hbf3Vz!-B59y0 zoKZBE$mj1NpfDfvrLe-9*U(Rw*9e|!3VaDozCf3}xg7u#D42rDU9k?i>&BuWl{b9F zQN^Krq;_gWcni+RJ>MFt{*#1y(BhPg50ZIUt)e(zfWK-6jzy3u?4`GBocnLsTP9ziFB# zLu_#)Qm%aB(Qpr+KO=;2jWSY=pznObcD6Bs1sRRaUIf#y(U&0VPyKIvBRvClogzm} z7A7J-L%R`;B*^skG$57vsXvS(9r=`~vXM0a{f#(Cs@r8eU07h-jRC`NkoQ$v*fIC- zYZh26UPbp7@@F<|XG|e%IRK`m9l77N%DyxR`e!-qcNt>1UUNIEdJ>kXi(dp{*_qAE zTQz2cdEQ6={$=dSrI9_ zb}l{@PpDm=2bs+Im%!e;XD8S#S_mgt^~lCnU0?g?Z4uX;^xc12%A2GbcTT$OwHFD5 zi*Ep1k1|u>vK9x=or|F8LXto0R&+9JBL?VA8tB{4L?b-?Ef zZgGmt%1FKPe71UkM}rSUw8yK;#T)+jF34IIo$yfMV%DnpQq0Oc?iYHz%v{%{d*-BD zH7E!EkEE-Pi|YB>A_yw=MWkC45Rs6E1tbKNPU%!SC3orW5~W$`PFcEJy1OKoZdd`q zclP)Gc|Z5wxie?Z%&EE0b4m^+(t-~87;q1;o81ozlTPJDeuV|N%!8Zc0om#biWr$i z55HWZKO-K`br5%UbGTlIUU5_Q7Cx%#iB+33_wIPh>tE_B+{riav0`oA5V4x_G(oeFMmhd?*fc-Es^5 zBZA#94KmhP53zAXbxvTRn$UoM8l$<@?@cquNXuW0qO-})3fGBy+>2oXA~k65ZL;yh z-@cqLbfOZPqXy!F+r3MqW(oR(QmqA38B8(Z?bZpDinmpF*zZ4U=Tk zh-tF4?U$$rfBarb%V_k^DxRbDu-F)2I|&D$M@(kyM0=MNziaz2VZu}(S1mT--=JGi z4QmBbYgwlJ=j>#4v##%WXu=dL+Q^Vs!l9Jtdyz`Y-CqCb#pLd{yAid{xo-J$Ox7y| z#AmjLV~-uI77T}h(Ie#{=Y{{JRT1?&!M$Q?+=*_jtr5xF`3$)p0v|@x-mrc81nMAR zgi7q+9C$92lYy+ky=vfgNEROcUCfhG4lN+oc#W+RX_ zoEOv+wy96pv8uK67Ah)wfaF5<)V}gP)+iB0SB1l9OQ;XFM6isTia5EM$5=x9T)KgZ40`C>i}Fu^b-doTTHS1(F*;cA0e>A#t0x#_>{^^vB7lFlLivebh@##s>G zfRB_M2qbt|v^R~M2?~RZ%NPIsd#~MSFtm>N^+}wiam3PXe@QqW?HoNW8oGL9nMqt0 zI6Vn!lUq#Dq6CjU4y^ujCuHRgx7K&{h-x;JsUCWK&APq0@4kE$n|k7Y2R=SMjd1$i zJ^P;Je+h}kUlB%%RgSF3p2Lb<#3~Y*A7vBMdVC+$f?gY?Y8Q<5T=rD!+)C)%B@zVB>$j?0xK{-X5<#UXOnVH$kYDlk& z4bj!!;r1BD;>P2Kw0AKy3iY@cJ#=dp+0vsF? z$u0G3<)vtNwX%%fwzE&vLFnyWuN%iF1SGKr>a%S?SXz=ps%q?Gxr(w}P_Me(<$z zi-|Au>Z+VK9OzsH)=-mvZdnnc=hG(I)9MI5;gVOu`Q6hCw3p-s)?ET+?jIub`H)!O zIe-N%K2`}E0()I1b{EE*(i+ykB~;5mV5#|!SO|sMhkI|XzpEINlgW5_DegW-XUp*A z&qFP6O2+@4U4mM*R4;yF{=;f^HW)hEHyV+mB7RIuz61p!Jl3!?gSW@T+rBSN0^mVL zLrH1ck^^^wM4)0EI3gkD^NKd$l*3a{ZET(wlJ#*~HBOfUO7(j_gM%YWgvUiI-Wkl5 zG~o>$J8JG$`_s%zv-@{7E(hmsIF6~)`sXjr)*|k{L}}<9ZWEwurcphlurGe@;xr{G zt?0t_kP^q%U3ZKTx=i|(IQ8PrZs=};kq9V&o-_w!0?tQ-07 zq(%G(;-N$VqsU2uZmUK?P^i>N$cNA4UF&^rs1|%>=IV(x#1T!kNg*p9m13UNZg=0P?7=)5%HqnQNhfIKM|_wrhNM7 zRrlBZoJiwZ43D)5ovA3_5wzSf#BC@~Jp%)+Lk4zDs=g@oDw^tog(tUhpXi;x+U|UF z5)AeaHWY`@$Z^9po8JCY?vvb?#GN11NW34T&q~-{s;6=%kJ4qo96%d1p4hFiEs9E# zl$_TM>tJsY5J3txsGKS(RUUvV0K zjt&_c9eH4TRE@;X)_2QSgfvx(PpB8+7rV6jfXXhFB7vPnU|p8W03d5o_Tp1NvpJC4 zj#B%MPMXd~M5rk!$C~yQU)ymJM?zsy)pM3rt5@hkV+S{`G&wY5s@8=8ylJ6xaekq4 zA8H9*#d>X)^$vBqmRH41P;v!|z+=-BR^=)KCFS_xF5@2HS-&uAS80rBc8dJnMiAX? z^3U-|78-1(pi^$60Wls+UL9|3b#phs`4M7Of=|_#gnBw91j)LCfzwZ}1u1F2jHXY& zm;nyAl3C#8YuXsP(r0K3a^RGt#MgnO&h$Y0G%WmYa&?L9?3aIYm}O=D%Rcqo)-DaO z`CnyKt%|1j#~ri&SM+Oo+{~8!>=sam0nlPGck>om>&HN8;5C8jZ@_;O3ov(PJEhE9 zQf~1TCV|xD0^56Qz3BI%vk?)~E9>tCN+UXf&{1Zu4xjNnrraz-CxPBKY*880_jP|m zpD9v5d$*K7_f!~vZ~hM{H&JuMHNxVaTA{A_cx_TxPxqsKRmu|lcX-yaASnFJEh8H% zkoHUqrBa*pXufuK%Ss$NI|ls>_PkOtf(=O(sNfLC3B32~v`u3pxc;{kzf}b z?dF-4u=w{kD`*Amxet5>@c(IOWD1VHf`PeOX-0N(P!U+Wdyj$sG^{Q2JB%iyjAdpx0t<#O%@j_wE%pg-JVzwG9(TG38i+{r1cv`(5Elp?#UqCvDULfBQp?P)@m`wz&{%?p=}W zK}Hd0bz&kVs>FZB-Gh*h2edGJ+`UJ=pV1dTFN0VU&4g`#G>s%fbAICPnFUVp4c2J* zFcFK>-DY8Y!ZN$m&XVVkdl>bD52aE^bMHqt#p3g4v~}SIzrq+(1?>-H>DP8X=J3Zy zh+or|(l)0;u!bmPMR^x(lyebqm9_s6u(g>e#EITgRbtC~$tK=ecZLrEb6=BFA4$@; z=2AxH55JtN2`x=!3pC5>u9FG`TIZCfg0Mg73G}-j5lEoqzRjE5j!t#fM_e ziWU$2dx7Qmh6jo9aKR-(Wlspo1{&{yQ=FCH1^3)MZKT(l1r)Ka-6FQ;nJ)^DIL5eCy=w zm&~7J2an*;oOw**dpf5~kBx`tPo8*=K98(^vJlvJ-(J6Q6Rx z(RRq-^I8}weMd|$&B*Oy6eEd|&X4Z7JLyXha*8f0th`$%XT8w`|M4e%RY7bgmbpeK zK*J#PYdgIi#Sfnvbsm1-Z;u9U%SJL zLlEaX0Ui6G+zL!>K^W=6!4=Zm@^C1~n~!d;VITTlkRB_<2H(S0D)=?^`iqTY-yVd` zWrZnnbUYmBZTp=C_dj|^gvh0+`4-U-7b;`Mty(E*%WVW=Rriv*LDC^hY#RDYu5H!N zkukTWvE|R{;)KB|{urTxPvq{qK#ZnE`6h8k&NZpWyAhu!V2AJd9cp{8Aa;SQ19kE) zCc}~S>aL6!F`{Rek?RIO@jqly$iMnV_l(M5FrBOS&zdy$S>2v7lA{kB0An5C_P!#g z(Q%UX$49+j3hx;m{fAKZHTZ(h!>KZ4&ABO(2omzx->ldb!_*NrTP4*3h1hx}D*x)Y z>G3C4c3(_7)JIVWH|$@m*{0vNe4@-s3lP>tMn+*}q1AgTQ*<$yRJ^QRNXh#>PsV$J zmpdB47Bd&xZY&&r*ocjaH+bQ#`bT%$FGd_LOh}A*97pRWO15?rApxwEo2H%N@li5W z!}q`+bd)E$3*;KVV*I;0XTh&;sL^Ww8}$X|;iQtJWErjJ&$5c1r0q-$3~f+Yl(lpC zon`da4BVt8%b0%|3?4L2BGlQ%;ecYNttuAvWDjgrleBJ7)Ge=dVVmOOnGTwNgE~`I zJzOY@%JJpjiZ`!A6dMW15fLHrf!;9=Ne5V2_HzBc98Lr{AITNU2p_G+$OCPHmJJGt zRibB)$I01euhaUtw^s-cqvNvh1qSEoz<}N>e3z##SX;GLDF(miQ8gok@%VRsR@j7q z{_|6}^K`EB_#l#CBP?gK@s*`SL}ZnaIqfXpf$ri2-UAF&@TGC{Gih6+xyg2IIcBa)#!{Jf}Kxgf8kAYvd*#Lqo@ zUw=NKw26gQ=8-nhGkK9L;*Z2|s%bg+s^YLH?+gCG^jQ}j@VSg#MO+U0W=E~Rzu6+=`?{E8#JcOSv-tM&V8ti+?6-DnA(uDKZe9B+v4pw` z&mRsu3^#ko!NqS9gUDg-Bt=Xvhr&;Rem9DLcbQy~B#LnwAvsteB7R?&TTdPtCSMIi z{ElSYp73C}g2WA6Tol%)L+cq$gwh+iHBORcn(j4z{Iuxsw#FO$)@jtnC&O@NqH~f~ zRlN-UHG~k&AcI2bK0ZQA#6(#E>q*5|s_FC=&<=(SzKxIFgzB~61!O6XBAz%Scc}7- zPxe=|v3y+V`t6nD=fjcu6?Gs5%3+CCvz-uqO&KZ+g`TdX$k(d9`xU}pl`Ysl4YsR? zJT6)GEO)Z&EHv-gXU4MT-#U4`16q+#X)QG>nf6T3vbP6nQ^WQ48Ri2Hl4?ymrV-zO z$HK{u${*@9#Xh2M{`xN*`t8O+<)p=9i)fQAk%oEyVJWSdyg7HC_wcPtO)vBLWGPsn ze+2n$v;+;an7hFSC`CNe6OY_WCA8TDKf@+6iEb1UdD^E<32}jyr`K~n%97@ArKqr= z3zmu5hQgT;^%ZFU6pxHDRC^~AtEsGNX1bU$hQ3%ac~K8=vt9uVou{$NZ{O-FP+U+C ziz$je%k%DtDV{Z&sIBb*XelJ@h@#mRtNf+3+c*QSH(MPilIvgb-@I|q98Bdhe@l%> zv(*!5PU0~2$HMq-b=~DZM|sxAZPEW%mH&3MyCJ=~#>AU~`&_X8mA4b@aew$6H`D7q zWz6Oks;(LU!XVpMRnUk-re-aecJEBNXA*CICsJKIUt(}oT@XHyq_g_A#({g|w3AY< zk2`Jq#>bZncSSp7?A+qWIRuRU^0Ja#?omKs6V}Y*vB2Xspok`OZIe_A_AzzgR^>shfFlJbQh5C)3OV~ zg{h#_%G#M>A5mtAxy6DQ`8Cf`qd6_jxhhd@ci4KBR6sq=ND>0i!Nz4}`RQ-;qA9hD z2$tR;7=bKnE1%-c+np?_n5j1Z)pjGEEjAmB=Eke2Vr3%P&>Y;L_qnm-KQd;# z2@y4Ajj1SGuC-oGTfSp~Vh{F=%1)!~+C6n$WKZ#MbTfjYdawM$t9m?7@u;UPA2;LL zpM(f~2j<~n?|hs$v7}R2r}nD$M=mOulr_ed(+69ig!9E-i|z<%RNJvt>IMbDUu;qA z(9grROBZe8p9lSV$t#G8l4o>R08UG%t8K&j^<+SU;txO^K|6-E;DNiHb#yod{%4a_ z6JC2+}#b62Yx?<`&}Q(>a?4;Zm6rk#}Fem61>=6`wlo$d{u@eNzT=gvPwk48V;kmf^U}c|ASPxCEJz{W76ZJyxty||frh#&X z{7CRC`}VEqs)DQJ=Y@uuyVH?6h|Z$KI7{9?f%P-6?PthTUSs-9HkoTPgVD!Hn-Nxe zQ*&-rO|O+}(DHgV@X5&A#Fm_G023H6q#Y`_qm7X)XnH3H_uKOIqcTDTK&{QNR$^ic%Ux}^GF=|G3oVmKk99&8k z-Y<1=?qHMSiw%gR$Ww_eN60B~gZ4tDdc*N*nGvMu_Hu;Fa0u-jbrw_Z^tZ;y@>}#W zqK1Mht6F@(6iF2n*a%=X512j&jsStTlFrgf`k0w57XhA==!Eev2lt=Dl_mAw2{(MOdrZqsqo=;RKpCl?cJamWkoY$z6ZuZyW_g z7(2J{%WFbMK0gFR4iDpPzFAV4^(?w*gFna5Us-X4BnrYxr_(2aHEj3FE5(gs(4(?7RJd4mbacEv>oMO_=?wM@3;XB0 zC;+x!0epsQT9cU)=INy_;Dec1ai3LkMIS0#k6ZwU#`{y1_-*z2`7VGXqO`y3;J}jV zSp5qWt6+7t^xj8a5nYZW0D`l+&dcI>{-+ml*$pHCVLNN(Yi`Q?2z*@l-sviTt+m1Jg;Iz&^!Eo-e<+tt5lGN6KOv9Kdi# z&Lx&S(tOH-;D}kyL$xz#ff+c|JsvUEQqb6o${oz6@()5Sfu=F&iU8-XXYIwV51w*59!EmM9Y$!x$F^|$1fP?jJ1tt%p)L7RRGP{+78_Qs- z`pD3o5hx)kR-z-{2Pn&=U?7g25NnqAhLb+Xa_)K?*lad|2|)O&?K!;&YJKqAUK>gExbAHf_w@ zD688GDygcsfD3Vnp%{#sen>t)3IJbD@en&zew$iYt^=uJ@#eF7ibg&Z4PMuyXn*4V z-GPA{$=Fq$5G6~_&P#o60ISgWu+)qEkrVx=A%;Hg z{n-C#|J=u)ENgCdJ;hrs%UaC2FLtH_^9A+>AxUOeXfk@gh+nrp84|$Pjakfo{!&@} z_%ao|IHbSvnRm^%K^JlWW_(&P;@s2udDm1XdjD_7^xC^nrnwos1-py~VblnzsvUye z!{2;t(CV8CjtDqRmDYG6CWjOVQHe|LitZsLqRI z9P@cs#=0b%*-Y4l{*0E`VB$dR6&2xrM-gI~Me|h-?FbMiQurEN=st6zF25XB2XdLj zjaR^#5_=|_A>uIFtcNu)a@~DhftBRealjeTL zKk5Yj%8p&@%Rpp2FPpJt{LdZvBI5phH6J}pNzEQGmLyzxhBgFv%>9aT*mszuj3;0r zllv|2b*@l~Sy>x=fEz02zP(Q(iI(I%h*rxi2xkRocj#78tl#M0*AYXX0(<} z-uQm0-JI2bpIz8<$5deEsnU)?ey8W6JJ)L^`;%o8BdO#p7XaivX*J#}$ilc1Is@L4 zOB0?D?!(fXULoRb0PMCNGkD_nhAP7qJ@d=L7bwOCnkIPM@~ap`3+A`AW0V68_#c^; zSEzxrWJd2arMu%|gQDYLca^jAz*u*h!bk~joShZ%E!pfzN(viO5%Wj}1$^B%TB2KU zzE^hy^m{WGeyv{WPB#1@dRp%Yh-zPVu9q)LeY4PoV*(60@(+JflGyf#J?-ko258%P&=;z4LPQ3B`6W9X!@=9Z ztLDG4i)$hnz-v=;)p5(09=BP(Hh^@_AeNpBJN}b6ZwGHpj`l%|8nw4A2@pUNc+cH> zR@<|WKBr#+$>yf~&x6{hA$~>0)=mp~0WfxSYfR&&-@` z!+-2g)_F>s8wN4`^2Kkmd)jyHPfs|&qgrB}sX)*h2iSE-LUfKC|27hNClyqKRr*hR z&WVAi@Q7y*zc?Q)99`NQ_HVeAWIu;l!K#hmQ;uR{@=$=y?Bat3P}1ACR&Vbq&?f0F zz;*U6MVQ?v)-}Z0&cj;f6z|nWW<`5@i@Up999oq&e$knRKESmA1XW=FP>|64V#&Lu zw{(5j$NiQv$SYv?$veC0Is29GGM+UBEtyd%xk0mTn<|owmZoF5?FhEAp8$jMxF99X zgRyMq0JxG`ln41CDkE51mDj^C(6yIXem^MmWfNrX6~$1*n>8v;q$R9(zHBBr(-`d0 zPg{=JK)FCB>sEdx?bn=0PkBd#d`8Dj^P~*1BbT=kYD9bW%p>B1V!YhK;4mex)vOOBg!GPYH>tt5SZjR{=z83?4QVq3jNI%o2=D|XU zl@8yQIGYz^gvFFm{%I)t@9Zc(#O0ZLFdPyPEO^f75-=@Dd7PHi$IXU6JVE(2lsbUm zNRbsJ1X)ADBZ*#0~3KDUEzLv zCjLVtxCuOIvh41gGAj<%`>X^P&+X_oOu<%+DmK61yxgF8N-R7Vqs4kJwbC0 zBN+W1HpopgAZYxKjJSwY(K>Wmo2c}B){fomUg>kRyBp4ztZY8ISlG96lh$bRh4ep* zB^-uzbTazZLjf+^;k9bE=QZp}WW(_I!ThT=-@_>oA3Br;;>SLYtrxI%u&RjjVKu9B z-Lw4^-}@kHgM#!v9sS$^V^?+0UyJWDHlIe%BzEed=6Bf|wC^ym(@TFiyCaRjMfc@} z-|*hlkdPrIFnK@nBvuO_rxz!uJO}BUw;~$PU?bRatqIJ(WZ?|&wn!6_F$L|c2V;Ja zSDf9)?5WA?Q&Ra2(kbD*!xkBV?0t}e z67=YnTGJod`8lq{z4Zr5WNxi8$Y`@s(hZsjikp}5Y~^d%o2D*nimFcW7mHxE{3&$G7<^j(W>8lcWZy&rESALMJ_UIfqShr%HU}J$4+t63Do>1LL{>qwA0l8EE|3oe5qI{COsa(cic5WEskR{xo6wv&8&3 z-mm6bH2BX3{dVrJ{73}Bki|1^2FsZDQo3-yZZKLA$U{=;=7c!woerR*JVd4NEVVPq zcU>^BR7&m%FzxBW?rD#J5dKR6z7XyCH=d-DyEPRErb(2D?37GIh)WAkxOoN_o4d2d zpa*n+j|J|B$%Pfj5cTb5dTEXYp6SHcC^7mz8=;*x#R6xb*}R#?hGP=_&+0gum2GtN zyTV!|vH>P3G0}jGz=zxb{uz>y{}LpWUI^sNkDPv!!Qn79z-y!X2NBRY7d0vKfAtpITq4kDvR(Qfvqn(?jK0AU{kNki0Dft- zxFDW`F}8Kr&^9Pt-3)pD7bXpiXi|n3`_it!wGNj@ zHy&4;0-fPaLv13!8+SaUd6r@1x=;*yl#!}uc-xs+>J)F%T>{xP{+UTQo7tSo8v=mA z$FcQwUb{T>h1m>(N#aFFRj*4}9oZa-mbnL$#xu*D=?Em`cZ+;Af4k=Hm=h}XKy?J2 z?BlE+68f)t`weQY$qU%U_z3+$ml{5K7J5(+fnuKzY79q3f98#jTy~?QaB`};Wk`JL zt+R|wqlkZkRdw0coS0k72bSz$w6uCBz2=MNDPkdz+|+#c9c4F9cjP7lHwKW}bd7Fa z(!cxdy%TD#WZ~jmHxeushsRcLdQ~@xvikmD@MFP3a?{`FZpOg{(u7lOvG~$2ENc3* zZFHOKdH zys7RhB4@IjC+nlGUE%}g7n301VtRfc9%M~x`#pr$XBb?vwA|I$j>Zd@IL#+RI_3GO z=7R|rh5#=4(>1I~c-#F2ofRaoRb(>_EZ%z8&q@V1iXkRt*@8c_E1;#K->x)hb4#|! zLc)LaZV#8A7Q7aDHpyPL_1EkKf$pRQr--w&WqxVHSi}`8lV)-SAC+>~d?)yDEbKO` zMCDzv_N4AQh$QyrY%Gx88q>B)d!_&nc_xOG$ttOwFGesK;1u7diq50Tj~kij0)d$W zBG8$J(t{2rH4Xa)!F2z%A!-*meo-^*4{W6@)OcoC!Jm|HM9B(deYcC3^M8`An{61D z0&z<=O5N7+$gKu!L|ye~E1Cwd>Ir?_s&S6m0zm7!p*_u_R_{q``D8YU?q&*{N<_J^ zD|^NZAXXt&83W?Kb(Yg7sllA_kCJXIo2J04!1%(p#Ct#9Dqb&?++~9ZQu^h2)5Q5QW7*DG-iI9KLS8}rkx=h59#BT#>s0mq~kAq;2Pdio>$JBMOwgE?1>TG$| zIFgH9Dh(Jr3xtST8-d--Uy)ZaTFJp5HY?*cD~3z9N+8!AP}Nt|PdYkkov2h9nSw(`WA`=X`_js#4sAIeAk0Be!c&q(q;8S<@vKRSaU|9DgY zq53&HxeIOD+Z>5wY!4o*SX>}p0u5w!oVDtHL|m6u3wELmRr&8vwu)DEmYcW}HYhkH z?kvitu`@Xcu+aK=%}@8``TUdCgGz@pby^1H<3h?+35-YH0LGnYBZ~COi~PPp`6lGM z%#SzmR{;yC1Hd|^20b5J^}W9(9sNt#u;IH5<#%5$!s#NBJz7`rxr4a~NomJj zlT$YsBkB)NC*ikj<=YAn3gPDnUA>qa{L=raJB=hha-Te*9Mi)TxCP^B$p)F{AC(L? zfk_Gv;2mPPH}EZU8iEG1H<^hFx62=CKlKN?>0p$*6Wwu1OaG|&jty>X^xGrh$rhHdNcg1z#~Ki;{`5ILTXcRE3*w+tiF`TK0;1>p&6V z=0=(MiBV2gr6XJ1LL|h=bVI**5;4orY0Mt!L$bI2%gTF-J|rV;m$wTZd)&`XZ7;;j z0mAL1m-{*&d$tHiDd-`#mQ4A09`1na9>He-B5Uk*-Yb%m-PB+Qa9b-ewaGILr}_uR zzv=@hWFhOq4ur@uHKr~)-I`Dvbgl&;e0LYXJqr*LpNn43CrRkmYeT>ZMTagPNs->& z2L=F^mst_XU%~_@UPgM7#CM9ZJt8tVbFTpS*|kLGx7fjHUP)-w+xZrFkT<7q*_~tt9X0Vu!j0X=}MJ*Fb9N9`Qyfyt*Xn^Vs~r;e+2F8!z`t`el*r6c7iG!O&Ydq zv8EpQ7NggZh9^N6ZgZtNv`f*z32B)&ZWtk=1#V3qvwmxJJt0KYMSyX`Q)3w4k;w6M z=<-UQ;OT^`&sz3YT-aa!JS5UQ!Mi5nmxkTQvkx>wTL1_!DO+d3%_BKtdmt2bX4CM9 zZyiDdyY;$Y;A#S*Qu*7dnJztG)64nvG3-};_f&$V&SEz!JhOfFIOp6~AOnSt^&85z z^}J4?&Aesq*d;$K9{NOVxs~_20$7Y zZgx3&3bZlS8PXAB;N^&{0$QDU>iF&aSLNX6ox(DZaBe|S8J&#Mn_p0sfok)eLG(2l z{>l6^m?@~;OrM9Cj4rTryA}7kz8K*=H?=y8CL)lDgrg<^K?NJugYx!Dj*yG#BoJp3 zZ%y0eOuhcqZ=Q}(c8&Z)a`FQb-!c97@RjLd*l+T2T?LoWDej+N&B?bpS29;$dx^>eW?9U!PD)>V5y9j@;#q^+`(epJ| zXZ~?a>E)SHk6D*j zF+vKdbP}br)*d%&y3k3kia*Cbt(8hY!8EJ1N+8pVJ<=HG`vISV%8|Sq`M!(BH#a z6Ic4^5J#tcTeLiKC?6RpcqH48d3>Hi079BcDKdY>bcri%Z`D`OU~!+cOK1D6Rzx{k zu;jlKZCU(=eS*6=N;fF5dnXW--h;iY?M+9bugp^Mt9yd?_fCgP08DQrQz0sSTeec5 z_<}X2AlkBRJf)DNt8v`}pi*;&-4#ftAj#?tHdR_JvK%R(Fw|eynPP~9%b|(((&$iDG4>iwW?Gc-VPeo+Y&0Si{*D72(Dfl$wIY z%q|jt+vnZ(fp3{Jdv0U_Rf~hX{*jjs!90vln{th|oxdsJ-o2d?Qxf{zQo*^^?g6$o zfdsKMb-U-yDX2)+7}d?}Vgkw3xiwpmkje34vWm*CTx}sWt!eFNOk!FZS@?S8rw0NdXKgJ*llEmV(qZYiK`ZY>N>6ik=7 zRuYE1xy{k!SOB+2O$&ciS?^7aav;!$5yA`K6F7Dcl#ZD1EbOa5rPPRX@Ux_oOws-p zLb&6mvCak}dKQ+M)z*vAfCfL`@)4J>S z0F*@*|hDdC1Z#pwbU#jZ3q`*ynAU%@M45UuYPm|2oWe zqtD<79GM?%cPAMtU$%hI-=7;zZ=E|h2TaT@!+#}*<0igwXfk+1qen@lEMI?aHCzfQW^ft-= z#ML17FOocBH`wBXQA^bpu$Qy#^6EaF+JCFgz1@HA2cUp}_2 z>;cpun9Uz^^QND@B)<<+VcqA1WVNNdpA^ykr?U@D{^sq)QEbl+UALRX?F+7Iqu#JS8+gpz9 zuTfy*6Ig62L-&HF(cBkU_d_0C;%@a*o&o9$^)mia0j)Edqs2z|U@l4TU4nw8!xKcK zJ9bpoSth%nf7Q=xVbG=P4B0InQMRGtWhda$As9y!{15tT5TfALH#qT`UoGV>^|DqG zWR0iZGIi)fNcmTEK2DNTqya~Oq++&IAA)Hvw_UNAcTz7RHlYu;MR_{>AkXE)10vva z=|*x>QF_=&pij$A?sSv88??`f*o&~jG2(ECF(uw!on1$ODMs zMqQ-&m$%lH0R&T7Pb^B0M%F+J&F99`KW5>zWy}KI1CNCwx9xF)ADywE0T_BmYiGsn zz)DtK(-wDZc~{ah#!Rklt_>ig=uN^9QmUcnN@R8bu-{yS&d!kaOSK1TcpA8s0!AVv z1oPc{x68>^CwRAw3_mjeo!F}N?gLge7vuF$L}EA%hv;(AL8g9a>d7sPaxq?X|s`7bpHCas$>J*lGp!Y+|8m~iFX-QZU|v@IGwaKIpQ zD|QnocN@0Afu>YB#xwfIp*WQ;c=dGT*!WLwiMw%f|62%j|ecU>JA>pRtQm0hVArwQu zU-M?jet9imsX5MU4(#8`oLH|S4+FvwxoDD$7a@VWl(~nS|2BI4SQ=kpmW5-hENQlb zJ{8k`^8^6|D21waqbr^uxY4d&1Ea70+Jp3ErrG9)t#hA+ih4OeAEj=r%Mt)y;#)Wd zs~Nqt*Kn0pFj)Mi(w$2kFqAvGZe(lvI*>Eqat;N(0A4kMYls9+-!#1TR|%+1MS2}4 z*2K~>SX!Cg=Ug8-HKX=yuMls>MoE^80QG+0N%E@*v-PKZT^@G3YUd3h4`Uj8 zKuj^7wmeI4Nh4JQ{1VITceW^ocrp0@#Hq8dAF%8vth>xz1i4?d;9O*g*Zvwm3vjv3 z*r~N{<=Zl-(!ohl?*S3Y)sYlVo#^`E50pnsB=p43QZJ?G)Id||_B{y9tL@hEonyPf z$^)!?k^KRA#4{}O&!I>>G~XvAXar1!>pY{11rZE^2ye4&t z_f~(kRg`q#9jSnyTdp9t_viUYhJG)tth0pK-jYE}c|RjtWAgg(oy5X2sF(2&w&2Ex z(0-0bCpHM|a<=0o4`$b}hRY!LEPKr!$yLDOalwBW_~$<{ z;Xqrpyqo}2dYb$n6d+ga_}Xt*oQ2-~2Gm+P!==9dvBFs88C?Js_l%_Y9t@!_X`9B< zB*Shz{ks3+L+dAj(}q=jSr^-|FVu8{0^;pG41eNW_{5aoJnCwLLfze(#()~D(CWHu zlgkTOfBAWTL-&lTqf?L+8CznA6H|{UY&kps3#6;pUHE5oo0|Uj%=nyeA9q3c)F~gA zr$=Z#ly|leqO%%0Fhn6@V1V!XD#pQYI?*m(__@;2+G0QBb5r_u_GyBlabkhmQ;S`U z?$Wfc+uS32cAH}v_tIHBs{*xdA0h#(NjF~gJngmOvcU~dm+upzPFVyYMkyK`OZ|+C z_k}b?=KCT|xtPI|#-_*g&n-ClXHdG(^PMrzv7(l_4j5oWi<@2}l0GO*v>!SH&nNc4kx1$C*4DV{Em;}cllp0g509IWdRo+ zZ1z|``mN==lRvrf5KL1aJ?apg3YA)zG+;0sR5FaYSkL0xnKA-&B6Ta{>^u6t-RtQA zFGk)TRB+)R2uecT8vD4FSB%Rik`o0ioLX*fKL#*{T^iT==J}GH6(=2>a2;)oduo~P zfV^upYL!&eX*_~m8&Gc$iH^pG&$nRm3yf^nfCz?K`TPFcA{Jg@pdWb#=awS(m&cD$ zfHRAzKZQDI)W=1HFauDyNeehw4LWI@mE1(bh}>TK9@11RrdXt1oq;y&gcybplJmU- zLpQg}?|UNt^O`!hToPX?KMnQ5xN}X-_EB=%1FUM>xK@%kZOsZI2{(YXfu-E%Oj;%$ zY3SM7pH1o?M7}OMzupA=hAtH$){(_-gCo5Yrja&`gY3$Dk!Z?t4emv7ijlOs$&0dV zky=S^0SPi!y5YQmHIB0#Jl3s%9?tilzT4d^3!u?pRBY+-GSb8j!*H-ik)z&A&r-djc-F(U30EyNu^@G6=PRC2$^& zMC~BzRxmz2pT^4rOg)MSwRtd+ba7hnk@oeL`xG7F;^3Y}Yx8NS+3&ufE44^4X`q%x zB@mdis_uAFy_Ks1?Xr%Qked0yM2OA~1r6c+6L8PY<#hwe)??<>vLz}qwdp?~y{dkx zvw2=Gso1U~EWJrYX$|vpeO!mH+u-1)PMfLH_I@)chj!XvE4rVNy_V@|4mjgNfR0EB z=brM>a0EJ)%maT^JaK2$dR59~Y*(f(`&9M?DwXjqhNo)@n)A`NDd>hpW=BL;jd|-M z*UaP0=RrLx1L$M38C-(K?caXPN@*co_@TiCpF_Spe4&j;(L5sfYAXZ!U1TljVHF&B z(AzuF+k~bI^^jm7eBZ4~GvNcm1T#x7y`%ac;a7et*0x8hf}rd=$hbAUZP;yb%{_t@ zZmyL!Tx4)f+F~F-TQ?VgD>2&uly zoJ(cdJ^irQD|FThZYkP*+fI}Tj7L;Z|@ggz4fDNw6LUlWTgSEk9z6lWX zv1)I88VRsC*2+fH2im3Oqr#X|cXjswryOUyBuuUHj{^H<32^1GGxj+jzFpsVU2XuJ z)m;VigNWZ;EcM@yfX6eRrE}hOGJbq*Y=44RkY(9hX|Xp(x@@8d7|n{lss>XL84F3G zJ?&R%J{5=Cf5l#{0MR#-+iU4SEZTrL+=B}nbe))cH3OoOcoV|SL$TF{+)zVqsmL8D z2orL1@^iW|nfDL58bFu(bL|Pv#|Xwv(e!!ZhI$iEG)~8V?m#60Ctyc~9+msB)sEb8 ziwB@_$_NjhJ+iz!x2jm9;Cv^YmV~X@JYEcHY;S{ex(^H%s8qXvl{?QoJGnP;@of;m z3saa$hJG?y^J|H7gM(x0i1cS7CRsyw z0|RNM9_l)3G(hzOc<+ZiJEu^VS+4}2#n*5Qjk*wKDkO%Rwunisd$6C7s0_omOj|9= z2Vg7;w1|1fl;OD0++r|La-QaO=D@FATqqjQ3M_eA!RG=j;qp6bWxfI{?3 z+|ILbb8;sq_6qeqPdyhw-q}}h;|xmfZDLd6#!?7q6pwntT|Qt_%DM?{>r4Jy)*-C+ z|D4VYNUZK%Mt|V2HPMK67jXkTVeKw_{d4B4Q8tz?Eg;2OH)eu`9kvmW2x!5unu#?Y z9=sW8IC2LqA}CcxlQMSUVl&`l|AjaSDbV4^k%2`Mm&bn)C(8bo$t)SYX{gg%@z)_o z=F#+BJR_xkLeqDAWbdaz#S?SL=GWj!=v!0?1h{dgp@Rx+X@^39c>*x9g{e2b9+Xy( z@FM+p1su(LGQ~u;k64c&K4l*vm}I^DkrH(Hw$p;0Kqz$x$B;>Z$0+AoG8nBt5i`d>UQp-=_Pxoq&v^6jT%ACbTM&Ypo==cB~8^mU|nhMM@A5Xm2@b`%7& zOa_(z(-A$}m04HFU@Y3M1S&oIKa!p@EUM>g8kClj6a+~D>FyLs=@4m_66xkgBP}T) z-JMHHcQ;ETA>G~j&i>zTyU(3FbLP~XntN7C8DB*nmG^?&u+DO(YtJ`9|7CYzpv#ZT zJuJx9V*R8)0has~L{l046sznA061KQoCBE^B<%GvajhAvr^Hc8Z2aee>6j|^}W7F;{n#2SuaTphKhQCvDAsIe_g6p{! za2NdKg+*)$e*AGO{}l!(#aUwiNu*>LKiXvuuxJNuoSvZs8D-!9WZUcJu6h$qNo=KA z3~*@GtYG86GF;x@OZLR$r-&~n+lIQbUoAv{L%JJ{x6|d;{fQEEa=Vt9NAQ6J`Rl`z z1M`GzGdXR&W+q!8DSoTKF73o-9hlibOfr629ouB37q##W?)Y2=ghiKGn`sG_0_C>8 zxt+`v5;|&H+gPV9+>K5t#okI4f6ck-0V)GN#~A924lH`Ln);4`mSe8Y>UgeQuRDf7 zO&hl-P6?ssM@)g&Hr~YJ;{8Fg6Ic!*J9kd4z?qWGyf=(^mMLWoXa5kDv=tQ9hn71K z?^ih>^RZoaUt2u_380(}*=Y>CV)8d~oct)JkPgSPpT z`*g9~q0a2v<;jZ_NMVo%|NieJ7n+JkmE-=Ogq%s?9&t$DiTv*pURR25I`BT^)VxwD z6`op7PB&xBAo8B3T^G93Lq2)6-a|nYPfVf5(EPE@177-Itlj-3Op2cD3eIotnSt|6 za!WgBg8;G4t!la1ziwTe!0slrpaYaRJriy>zT-o*g817ABpu1)QyadZ9bedHg~k0t zSJDI-Xj;nMoOe0Kbtbn9ukD*adySgLX1#>?%L3FGbe-=_J^Ra}Xix@E6D+?Qn~X+d z|823F4ceqP1_V`0sv~Z%z>D@8pIPp$Q}0^g0YvKI=A(-+?t8>o0u6&uydkE5wU2fJ z^uzu4yVm6{HoZSB0B(aLE9I`*P!?(00$GzSwZIElGtLc#GrZeyQTYUo3Ht-TT@{c3 znxgP!ss4IzfmGaZQjg14e%b%@c$U`F1|QODO)99+rtPZLzg`>c7HN%~td4Oi)L!qT zp{^sxa@3&|Yawv|OifRF;0N(iCzt!t>jtj$vg8I6W`UpKRS}}|&c~{d#DV0{6Y$lg za|pY=56E@4k%+x+MmLbhwp^iL1*JaHi=jXHUFEys*A7LGgiJ?hH5WbVfC9!4!#Fb4 zU!LHWlOu`R3@7CleDc0=j!;3v9Y}_(Tggm0N}p8R0nT+NwKgQ*qc~6wzpe{kh_%<3 zi>Cq^`IbvfDy6r)j8c6tto_~T9J@~(BrC2eAwahJ-6_GaNhG=^(+ht{*?nb zmOiVNXcYBxlRJ+!<(|S`z3vv8St^f zp9jgk4U<%>sC+|@83zmX*>{(}Pq^B>fW>%-JQ{)P;qQFC>?AYj!xfi%O0dVAv)-%6j^>D^YF}2d5;wjkSfx7axsQvE} zgpcmqU{R$@&>?g$Mtww=^yJ`xOu*qba1W`_zACD=AEf~(VVJH8l(^YQ zhWDo)Iq2(rDRaKJ+`c&%$3a4=4XtwIwnz_T0>(Cy;k^XMyqfvW5jIfE_mUx=wd$g< zR;*vck))p_Nj@3sd?Y$D!o!e7lb|sb9^%07;wTehl{Q z6B(HC1os$E@?(^ZAX zXSdw0(qbNjllO(z#wav-cm94mSdV<)>emo%C|tP3fsLTWfu92#Q;lC$4F|be;LJm< ze)^5ly!gG^2I|>HiaS`_x*J~&3*1lvo)5C4`pfC%1ofsYS5Dhwq5tqW(ILG$Fy*2R zRGX4B1EKrr`-zM54DcyZn_Susvz)!b2T)%2wIYX)M(v?d_!1TkO3$qDtfwTZx~u!c zK>>IYcAC6HwT#tdxBxSKGXz48*sXhWa!aN)5+cF8qp0|L^=_(IcxpqIuLu^?-x-nk z6Wr`_c}?E!;(D8*+vEYdlc&3E-1oS|@Qr5<(I#S@;?pG5)w>mVGRM3;lgD6J^l!NF zS{f4qZ&0^BN07hsg9h#kbS8U%Ufz(P1(JvFO+{VsXk%Kv_*{vOBy5-*^e^YH z!l8H}IXmnv3*-aB$o09AuhMds26*^y20H9aZCjr_c#cYib4o-)OHq!CTe$qSK})^l z(5lH~NW43|O*rKOE{B>q)BpQ5PZ%BHBhwrbs*gP%_xfmAe>*j=8sFF!kZemTE`j||Rt8sv{H7rJS(Y>IS z&xwsPUh)InU4lmZB1@0CIKefxwj2T17#O!-uS>D*taIA+QV`WT<+g(USE;YyXV>AM z&X)gh}3W%86OP+oOW%paVr`7)S}q2tePSTGAZ!K=TVDo54l3`3}J3k~l< zJp(DfdeS6#r>l zt#Ih>eU|^I&mq^FmYQ*8EK$Db1brX`y{2(U?@4q51N2=$#wL|lgR$tgpmkGMba)e( z+M(E5b5IJFf?N)MVDS^%kdp@SW{4K>fa1$4oYZYqid#=#nC?BW=Fr0xs72XCsG`4M zu~`zS+bl`Pi4ZnXzM^2f3@;9s{+Hi+D6%s3PXk!}qkk%}tFI~a*2}p3fmczdoi@%D zSYp5pPLIjHu1#U#YE3>5(_;6KV3a=^<=nJ6E*4i{y@@xnKA4nb((mEOY}CamTd04{ zcF+dEG_Gb; z)@%1cYj}+}c1mt(*uu`cw^3fgdE!p!-c-iec+-+k5 z0Y@mL&zV?s5+(O`|2;?2czL%ts1$_sqQmsohh-TsNfT z^|dk!cxQBV%rb^LpRFU}N`T)ibCLVix<*>Dtyv@B6Xggc$5?y(y6gdUZf96r$LqW$ zi5TbZ51?^%Lfo>8FQZ=YsXsssWR?H#GWRFtVJ|{~gYYl|{#9-4_nM{cw_szEpA2Sd z1wIQ#%OVyI5Y;N>MHd!qH)O(ND}Kk`-wNN&wvL-L0EoAB(#jlMdqet!(7|i3oseFH z6o{E8!_{{$qq94`$d7KgXa7~#T@T8U<51fI3NwS`4?LB9wQzl>U1}Oc;@q=E!@7GV8lmRlalI@lPCHbDYqA7?NBz{EE}m#Bw8!f$z+A zx)<<#)AXR$gaV5VdKssvG36+T$j03kUQ^c1I>E zAhmZfxejmg&v~J5aG$9)qQ-$QfOfl`)%j~01c9)|9)${xj4-;JG%>hdJy&0qMo* z7i`!APBu3MRrN`*MjJdG{L(;Vn%hB$u<1^KB}PlVH}A;5P3ZZFAwAu7`NDaqXVDo{RK}knr`yOSrbwEVLCG-O?B+4i4tl)-Jo2hIrE9SSk2N1w| zxt>&f|G|7?j#ULuZ+*>aeC)~o2{VRKhKQ<*HkM&;)G|+q5J!F@Y(%3qzv%XjQ zYJ;?q`(Go6T)C5;;2*#MJy$}M0{kdOfo5QO>k$f@5Qnv8V*BX`ON9Q+qAHa<#LC%1 zu-Lcl=-z85HyG~Dasj|$)vVcz1Tn9?Z@g8ZJto8Y+%NnniYT$=&tWS6j`B(d8 zmM7PmNFoNr*)+7WW}5dJ5XHaF3ndxrZdry0JJiZDMz@~OHOW{&ViGErOZUk0lt#=j z0pe{U!=0$nNBogqaA6bNj*oh+Q9IQ}OgkB2xy!v!k0VpTdU$b-0iqqXODI(RUPPM2 z;Gmn#)|19O6JcX41AZ-<{333WY2Ar%<*Ry<@s13<9oj8nV+s`&icRKi*}OFxHX$BX zaM$k)%Lmt;N6Brz)$ZW1fn*%v(+$<_i~6?FDaKt8@vI9+*~P(f77$g+1-lloHf@ip zEli?4a3UV}J7ermyc8%ZYv3jaR1;aY$2kwc{Y`A+!=8btTNM^?xe1%00747-@`dHl zn8EO1wM7G*xPp3n&hnk_li>y!Qn1l~XzKrOJWQ%~e*JkZ_wz%W#VClHPQ2>l`E5Su zQtuK2P_jn-6!Nwoh4yCb00b$#HS_}=Rs~9=TQ8b{-|v!^zlAMeK{j0O&7L8ViO*`T zEXsG43|Hd2^YSiv4ZEwKR+~V|`(B^ubHv|y?6WT-xd5@&PIBhhYcNv7Cs0Qyrxh#L zvw$Fk3dba$CKrXy06W<;_#yYI=-)y*7x}F%+;}rJ?Z^)*5Jd9>s;Q{cBapcLsmk#y zu!Q_nMMdWWr9v$3Qh;lQpL(I+O_<(mb;r#Fu09AMZxL3`)Gn?%v1hF3l1Sz5{yGh( ziU)ipewoDkL9dQy-2(UfSrJBVkcxiqk}_BmJn^<797m8=W_jmYV8dp^N(D6fL0c$uert+CIfq!d zr*zo?}d3-wJq(<~cpl z%@3Yp>{4wA;kbAh_^>|CTUVGTxK(4c(KBz9ZF41Z0nUfcoXx2|p1BL1Mm6vqq)HW{ zK*Qi8>jtPg058k@GSxBtcSs?8PpSz#{B@H?Q)#wWv;a-~C=o12V}*yrv>YG?AOHEC zKl?=Tk)EKetl{OMp$ja{fniA#e%%)jICMVzHF= z_fx@}FAea|1gMtm$_KSJ523@wb>b_AU+rCoqS(O~6A<7j!tWH|Uh)_Ip9(md^1TXP zeD7QaueQDHo&JHdez^HRh&0AUC*~Xylo&hUDdT9)nyABwCt=nFHDM{_nkIv6Ycb23 zN?_Bui?E&?;)B}9i)OIpns22dc|~%Ux^U%Q#bzNa;W9G5k4*u9EiaAB@Y2D)wY!Q0 z8(x%k-LJv2fjzu!`$9PG_qLP7;tGQ)*#;O$*tAU2qTCBN>sE1KAk^dGtK%n1^VM;a zb$~>>b(uy1EM2{gGT#dLdUAC;TlL-(ZC0~1z>N0^?D%jh(LcQb_zQt0K(!s8Aq&>J z4zA9O^4D0$=kh4s!ayk$G6sj_pL;~^H)8)I=LEvE&?4a?!&{aA0GDRDS-qp#!)@Ey zO9mJxBUgex6lg_1=$1WzX(l@(_<~$9YIzPKJ;Bg8C&T3|u~Db~X@9UqC>1vz-OEuW zLtr&b@GF0Dma<`g5I%YWaP9|xFF>$0_P7E5#g(iL&zmv+Umoxxn_FJTa*{{|sk7=M< z=4;5(n+8YZiO6wyFLwP;C-FnY+~Z~XYU|FUmd#Uk~@tU5fhJ>^RMlzI2GZbF3l9Ys%lkdb{cIm4)31lex#EWZ5(zl3v2r&J= zL}b|EhL5aUlamK_^P-@XIi&oNl-CWcge(=ben-WQK_P(>bVT^X zN=E#|U@^16wG@$u><8A6Kr`G;Fma#Q6Hm#`7c0CHrsS;=74D5_|04aYQ>2lT#AK%v z9j9C@Z3x7yC*Z8LGztw%Fo;>DEVWfK^KWHpCWtcZy(t4->U+9Oi#23ka!sBQucZ9< z+AS#pcVocN>Rr6{ns>xK2DTQ;%NCWCg=v93wbmi*)vJQ26llPZui^l|WQT0G;M&h; zEW%%qG^rrO>#L(B|14{sr}|xDPGx>GQ)@UqO(_cK%iHe^d}K_`hLz7NeneDC-(~(- z(Z0!(FzSu(xz)%7{UEmjEm%uQ>&mq<4}9&Lmag+mfI!{-vlJxt+?=*`ZRQ6TQ!t*+ zcFi+V*{71#!jZ#H;oFcjrhn5L@)n%WF{FyLey8#ZMXdlF=lJy=Aw0K#X(W1}VNaiZ z3hnNfyO3%z_1ZY$LPXt)cD2y<@Rht7Ci^kSVB~t*C;ImLZKJ^Of}${D+L_QJ34QCz zqSRO(fzI%@0dD8NDsD$f`n0d??HgJF|0O5r?QC!@1Vx-)NnU(D&{a=24?ZM)LEyy3 zvKN1?k!2b8OfiXJ`3FO9`;Gex#YU2l*dtmYr+QN5fdQ&WAMIr?UU?GCy7aI&yL;67 zzf4MB88Rpjj8NJ2F=$io(p78`y?geiYN|$|9(6>ZJhw1K5sABuO0>S1f><}XTanJ6 zi@|(TN+PpO;^2%f*6;Gdn)WaR;$B0gg7obE<`g-Ccu&gCD0t*BdvbG`fc|)KSASM^ z1H)}I^rBaz*wQR^pUWz!bpKy*h?Tp|=sHp0Gg>2oNGb9cZze=m3C0gQtx@pu>xLa- zZZgD*^`E6i$Lj5FT`$X~Ynu&>$ z(oB`wl3{tqE#i<4K{uIak40@lt>qLt^wVe*Nn|)lxC>lNOk}etGKmdZzLtGY5oyhJ zE`=nMQCu{P9PS+|X;Ar{G}@@dp*uXt*V5E_wi^znt|7{QCuQ6@2v4p*~XF6|bDdliSU4xUv1@HDZRt zCZ5gv=SeoSZPt~WKwzTE1J4D$$CeMiFNd#z-?BT%w2Q4sw?|QMAo_H+1F25@*1?g7 z*$b3!^rF8(pU$)fda!ZYO@2BkkF!Sc%*6BZ8+=@Ld&rjQ3w&nTg*K8<&5&-zcPgRpU%q(MVMbXRokC=qzRne;786sdq4H1C}J`^#FNT8vnlkxS$G?vCsiyf z!qyDr38cHM&@U${Q_B2@>K8o3F|U{_Bh2Y-zT*7KXj9wjshcB{9wh5H$1R`{M#?PI z=ZWW;7$%YFY@mkx3XU5!!{#6A3J$Y5*O`YSR&LzFrnF{Kj64bQPZ1`r$EXAW zebozI8#_E6WF10HY@m)vDMOT;j=aL_pDctr3hM?UZ`}kZv$w`1(cCG*5Xd$*q@!&l zF2qYx$|Q`U+#%DXr@}oB^fnzHh$riZt|2ggxCXN$zdg>GC~Ar9ZACbC7H;!{6XRXZ@!sliHXWHqMk;dO)W`MP{Rsg0q+K=Ncp}u1BbR>|ciPjb0i8-TeOZWUC%kXKwj+A%H!}u7o4^ zBu2=N-OOU#Z(e-;!t~WzdJVmeAHuciL1521uN&bY8HFCUWD0gmrM@|ANNgNq33BYx z&*P=0U4zDe63%aoeL-V$qzi_n;xo+rmD?$E(P)q3>Dc4?bY*xjEi|e(NLKZ0?xd)R zb3!1(9}N7y%B=7DtughSk3|=Gl{l3$v*0y`1z_%RH>S@}1Bw=uGP}JY5=vI#qH34u zER%sP0VaaCANAx_YM*uU~^!n2k)%DzIL%12jvg>0Yp1 zJTFN<^FOe!!z?HaXS+UXri|#^Lf(fna&phkY`;pf4XbH#c&eeWVY=HC3|oXbZJzNo zpI$oYwVT(VJxDyAzDf)pt?dL`woP_Cm8w@a^3WO>$QW&vem;mmm)gEH^8Kei*OPui z>+P=cKq3vzUvfTmj!xYR`0KA<6q%!slj3l}H&OHoTHaz%WwxrAP}V6*@#!_4f80pb z7&};2uZbvOYopK67(2%dyIxC9dJ2k~Wxw#SF-Qg}R+?U)ybpCKBI2x-%fxAz`1{En zQtIaR#wN=$>cj2=`%`JTu83}+(!BxNq^!?)X+S*Be0VWsaDBluS)c^V{Ohlx#^`rN z=^EjOV<6$+hK&u5Z-(=w0?df(6J$1fdqVxj0xV2z!n0pZm7hKQ6F$Ye;Yq?~q=8yA zwHh=pnmhrc|Gs{`hGm#xUsnF2=hMW{8!epEY>nfYpbx(${xhuc`PGMS=5K5e%G!)* zPBo?iwx5zxL@s;P8X;8&Bz1aRT3{Ngk@GKQt<&mh0G)?TUH;!KoyV)9r2z)W38q!@ z5d%1;r#*BA$;7LC7Xvh+)0#h` zk{#q0jyJ~f%ui(RWD7eQJ;AX<;9~$0De=5(k)22^>y)*6G$~ZlKHEvRqqE7{h%?1z z%m4Cfd-FZa=!TciqFfwnI=&8on?iMSc{A&iaj64H`eBWlqzsnxJ_#1@P`|`b1MBzwzwQw$IQA)sbAM z=6Ce%x8o(t0K`RhmW_T^6900C6aWayX?4B%fo*r`@?OC!c7#o`J=cJ4dujW4@OVdd|l ztWxO--g;lYu3pTko$tb5|HF_VNRVJGnwCs!Dnx$gYHAi;&aXjL9 zW+BPS{Y;A{*nq=QM=TkmMwCywFEiX*<@Fhr$a8TuH3Hx&K1E@6<;expCw^UovbT7?A(F)q7;J z5FnF!vTgZ+k0a$Z9;O@Jw;mX5w&;!ge(#_M6h{S1{SJR-^_NBHFCmy1QH!_ckjo(M zW*l~6Z`$_a84bk~Lh7cNpp=_ao41D$mR_ia%i9EluJbbi36{ibGqfCMIm)d^!@x&Y z$kx>HtvIXRUf4q!hu$>~ne;nn?ogAY$^RO(|HIT-FYU_9{GuT%QJFAg zS*!Ro;5+Gbsw_*U*4q?Hj8AVEO+=L^6d3)SxPK-*xb?AhJ_GIIf8-@K=l1jDAWx^B z*%+1EXEVo8gvL4l1Lg=TX#L}DKMJdISkxF9zEc|BMkISE+9;r*Fxb|Mx4ILaXb&jA z>OLiQ98J9&+V!XJYJ1iE9OL#i=_X2fWNK>RMD4Tlc$gZ4((pc_0Jhz-1QX#&EE>H3 z-rzcJy$wdB!}xxlRf0fx)b}8ixS?QtSiGS-bL?^Hl zPW*WV`sg*L>OsG&^b{_g_{{yjZcbFg6KDK%HQ6!xgC{cyy$2%SQb*~AC@;%?5D)q< z?VV4V_|+E+q|ZVPsAX-)GBJLQ7B26EEImsl4Ym{f*s!=?UU0dnb^HStMKArSMr_t{ zORppKeh4F=_cBm4`HVJeVhe?p;fzm+Z`wx1Ad!Nuq_eg9ktZ9g zW>wqjy0Ljefu&DRXI%{yOds~lK1EV>$0kV<1MzC4;3*R{2Yzn8uPg}a=>J<9{pUKS zOX{K{V}#N1@R@w+Il%6V;|Nrlb|$>0+SBwQ#)>1oU5f(LxVl*WL^U~?6aKdk=)3E+ z%zNsj`5z0{D;-zm5Z{&4xG?8J!=3BBht%YUeag@^N%OY|-Wv&*)-9>~Si?J)5Xj8o zJ||Y0`@H{P51I$@-q@upc?_&Qr7rx1OO)0Z=2O}ChWX=FV=AD(xJk9nhfjiiO;mLi zILx_?^mLkPQK^gP`b0zJk$Q}cRw6zp5c>d@Y_J(?Rv%$390V5ewlo?SmR!FXCh9qJ zj?Mr$r}TPI=$%LPrS;J>)G#pP#n4f%n6 z2d4bXo@TyG(04pc_TMjxH|ysM<=qw9r1_S$-+J=W3BE{LX$ zELD2$ys~#^$%oQva$E^^m2>y&4gRYeV#4zITA#7bd6S`;?OPtF@W%5XyYbaWw{F3- zTRn5428*&MAr!fGQ(u?@i$!Zp!$@#RUvhn0WW(`xE%QHhewozIA94}PfhD95UA<)j9{DPY4&P-?{pdPmD6Ge8b%tyQHs-np3caU(7v-<(3%-yjVh2)5nJS=@Zqt?()dz$!A)@Nfn%YE#mZv6 zrM1LgC+kGr)gaH0m-P3erPX$l5ZGkHoCUwl2o|I5<4yy;@KgCAR z?X-DI6c($*M!6S(Q+rTS^i~`9JatUjz_z^TR}K!*!97p=Jj8qT{eZS`@69|OWox8C z*0X_!Pxdmi&kGV=CNEKc?pB-0Xg~^9CnxzeEz}3xHT!N;a3I$k3fX7f^)BTcs){o6 zSfSy1jDNg`H~&5_xjhbW-DEo@XEv?mvmwzuv-(SmEL!z?nW>Veomfl4Yc%gefD1KS zO4(b+iU6D7(I-zSC6r$X?ABXJRI%Is7ZdchXE*>w#PwSWDYcJ0k*)vd zGt5eH>b0LU_LpzIhMAV@_iB4HBkk-u*p+`?H>_l4>j+>QJ-z!Ve2i8f%<3(pZk;&U#TSTq71Rp99yY2|Rrn>&2(q9v22bp)e zEDf&x!)>FD#QnnELjhOnj(nr+dbnt){abz`;d)KM(T6zq4?M~jtYaH26C3)*DB|i` zbG3`z0orY^S=+>|NsCIhURAdO;(N|EdwK89@5-!5!&n;lDfGDg8wBh_BWrTBzLdA6 zRA`LnP>w!D5Y;)__;mJApp&u`>WSijF)mpG>TgmhbCXrcEc_mOTR~CUFWJ;t&yKbyIfonIh|$i{Cg)D zrLOZhQU<``C@s4)W*b=edzPWK1+&F)KCqm1CAd)ls5_J#epMidOrR;-b;?=ah^Yr# z&DvB?Tr!=y?i1gFiM!kUcB-lqKjW8hH zFYC3W4T91UR5v@=eKfpi4Kqu9U!vJ?!NsL?VT0v9K|<0}NQU|vTf_lS`D6pK#;%STfOR6LI)#Q0e1B%G4}4%l ztUcrcZc~4-Cp}u?pG-ieH$OD^*Lg#=pmWY@=H^k~0@w50nAo+eAYU2dub0xFvr1Tx zFvWt{;8+m$zC_mOvBu5)QwJ;GcsJjpir>3ns+R*tyhekStqajfBqF#n3Wn<8)a}5+ zF+w@}>%LuAs$%^O_hs|u;pem`4!9$V=v2Xj@=p(^DWKZG*j{c*_5IuU8*9G3xH?d> z9FQRSA_*nS^}^Tx#%a_Ap@1=(WHG_X!|q3-epDZFBu$O_gh3PLz=YZCgq!rPOmIP$ zp028NUty#I4Zmk_->#vEsyvz{e>@eT_m6Rh@wJG-i425iACJR#@zbXt%Tn~QjqtEX zWd=#_{(YmRw69Z1)ZomI0k`%!+C{S7lTKvNN0HehF6(BboHx7mU% zTpD%Jmj;m^X)E2u02-dYliFBcGlQ*KW+#BDLcUq%P8}M!+I0VUsz}tgl&&l0S)bUO zDHj?8Q%R3?6+U(eJ5bv*sS*C@e2>gEO#MuQoUhcs;^tKzR{Hl45l)ggJ6)kWLxT{` z59)6nwcL}-w(H)&u_N1O75u!Exv8z3Jl@bicIg9oBSML0*R=8`e5F(kNRD&>FaMG8 zmX-r4nIiT@7n)Lq$APDftplmuf9`Xp0y#~Z1pW%n#5akIufi(R-+#Q?;7rbI zT+mDFTs1#Oh9e`oVbS5TR?1=gbumDOvn3_bDyGBLQ!#&4pST05CxOXki9wxIp2(a& zcEs?+cyW|4nhrtBb&JhY1)xKjCwuv4gCB~|J%A>L1>CLYePi|Mryl@9m2Y)lJixEo zs|thNiL*C_FhCsCuuCJ25 z&S;o-tYH*SpxyM-2OU^nSYug!NXgaG7uTLby?g1O0zVgdun9gwF)II8UxmD zXSBm~p35FokShZKV{XwNkM45aQm>c4Wn{~xaT>qNB9@L+%a}!5i5P~Y6YCtUT_L%i zFC{RRE=!6epyiHM-9-3B>@_#z{Z!g-iNPbKTZZO1)aNg%F z#m67^nnPm(4MwUOw=GWWqUJU@cgvq|E|NxhE4;I5A^tHbdQ=x{jXWY@VysK5Zy<}g zyC<~VL4o@2plpK^`ijg`5*P6Z``GAzk~zUu*eTgwnPeyJP7%9u%5l|dR<5{cA zM-PMzBNS3^M$=ITaTl6 z5t3T-fI((eanS!o5WM(<3P-=Vy@N*dhWwv$I6l))C#gjl8#4Rb!6a2(vIa3@Q5R6D zRG2#}TOba`a%lF9hQ-)%dOPtCM8(KTj(V}8c>{YYdA3avGt{~joDa+-2*+XO6Q~^? zcio=9LI-jPxQinT<)VXMU@Rb4Tt;8K7!pS&(Zp)i&3`~Eh6Y??g|G1LyiBzfV7#bD z2{UVf=&u~2*&ER4b6csr-puCitYd3aV07~&(SK%E?lHyi7dGx96(7WT59 z@~jj!bQ?G2#V%S`Pd6#rJ4|fF5`yEO+{V@Y&V76Gj~o6Nwog)xb(DH6@UhFdw11=e zrXoK;SRfXJ8>}?pQWLq}bu+2)t?vGxtFOpASnoAk(GyAQJmN*^=+|ujD{FS6^T@7q zrk;&<#HD1Wa_4x_;`hDtQS#v*KZ&8%idft)@rOjq9vbT7`Ehk3l|v-eWRDXs2d(V( zD}az#d47#uw_EO(f>~+q7tQX;heuyIV4G9*Q;Y8-&DfyRORfHx<5EIhS0Ng&N$hE6 zmZ}i=)8;4+j=#`3oDmZnHw1@IkPYVZ~KT$mDNO0v|PPz)E zj|mgfw~j8z&q#JV^{Q=T#odaIzlYFSoms>Y8w4=_+(1_&1 z>Uf&$h?QYb8&W-IIa?4;r43aP-ihd8$r`~i3rn(U4sO=Idh;oJ@NSx|Cc8K%iVWc= zyrp>jm5k9fbwEMbM2$Dupw?F^MAU#6dQvts6on~j7|u3qsD2aS*4MYubS$S6>sUmV zUW~PtG&{7N&sE<#gsFkxQ;C4&(i$rnkEqrEy*?)+6D>zAMLF0Qy^bKf~ZM)RM~A(Mz#&PY-8DgHSr zg<{6>o_I7JbEBwr_QTkoKp-qN|3#5%TSEmIQExn^h2;u$)E_3QZ&cB4T+|qm`SmWxh2h+~z z$FPsWL3IpEjC8#Y2q_4@P}S>=MIn;;_;b&JoMoA)e~ti3IoFfTBt>nKTr}W8{IIVqk056a^6IIKJWlv&36_2-AF{z? zcXv7Rqof13Y|5IKJWSQ-5-B0zJ?59^D@*5e64_xfPC?P^vu=~Zmv9i|L_Wj{dAFh^ zHEe$H-I%l#to?;Rj!>EXADw9Si`UIYUtV7M5qnA4qqQ!ITgwT$xnEM2GJT~J_KzR4 zm~3CnbgpPB%NkT(uDT^3c?xyIu_sU2?dj#wsgx-8_>}jlj3)69N)k7&$`C#U_McoL zXEW8LUqXAISD2oxP}4~@W@|-Xg%hAxN}Z(AR)=AVxpmg!=jjhGpVRU9&mhgk-V#m_ z-@bK##ZL6dh^O%WO}^(pJnyrzAQZxQ*FcnGyV4RXZy{+Dzl!j4=-d9Bx&XzX;_|Ot z;mK{R&Z?!d(%aY(6cZ1A-$|`}0lf!NnGzoL!RR15rV|6gzvZtg zw^b}QrZApwJWJ&lrTY)Amb6uhy0>gDLy3OzD95KX629$){MEtckz~pEdQ4z8Q5{iaDC$4|iNj zqudt3eV-fC&9%b{UN{uukqEz_9-T!nu6UF642w6AwF z63m|R% zDkYARTvjbddalj^1M~rrHNz*YF*2U`lr_DW5bZpll#k6WPP_p4Fa!Of+&7p8$DZ!p ziWZWZGiI;wuMM!Y$D{dEo{iq`QYp@qLdI5z7r?uX?^xI0cN+h2Ez+XxQnU!TXeIZ< z{F3Kc*!ybSjGnDw8<$27`nY)Et!&9j@De}6D6cNI<-0n}KDDyxlI~UHbm)sDS3R3k z1}=xmcGAF%IoA!xAy3soMFblP0!_U#h}!UE-IRq z()yd5>-lUO{+A!A{~9;yl6KAL@q%!Ee~m7L%I8LIp^QduEC&jpQmZpO^$=l^`2nH3MAlJkXmgTa)q=0ajf z@e1X}qiyK9vR{)hy{Xz}|MQMDb2??Dr+3xY!6h>0Pv^-L&^@yfmAj2o<0Vnj4c}70 z(^6M4r2Tx-_a)(#gQpr)R#(XhCKpZ(T{fxdS3~`hdlme95dCGF}#-*xOz-$iU>ACzh z1jg#0gk9sslL5d*A(?M-==s2{gqtfM7x%{)eb{6klP+f^ZD8N=#l(BRu5InUcEY*Y z`dhj$cYkq?N&PiY*BYCSZW7eSytShCKwC))y^uC@D~xsNc<&`an+w_OVl!aLv>=2y zlpemf2#DaioT;Y$=WYOt>H(~6)Wp9Fio0>H|5KhYqsI|(dwKfWt3uq6Wg0vb=8q(w zqrgP9jSx>8RHS(EjAyDh_EmZrED$J6pQ=(yx})m1^qIjCqh|CPnL*hnSeO?3OegUb zSc`Iu2CIelW)lw9s4JwG-S{VKjf06zmeOJ><_X)Pg2;Ofrfy&G0fC?S90ixkre#we z)0ex5r97+1F*eVYa1V+vB;W=Q`9}*44%i_QO64#HC5sK|_^o5T%ml>1^mah1{qweB zPiqeK^5aiVbX@))-_D)dzbcz*m~-HdPvK8Cu^yhA&-1Rug)4DM3G$HDv>TD@xK+x} zoCG^2gcVBdvNPRYumDt`TF_Htm-xT`?bod2$y^;`+*hYwAb3bPCV~tqrXdH(QbtKt zExi;5vxep>kNl^RYZcu}%*hGGJzf( zD`y@-9OlZ30p!a82M3*h(14W*nXn%SR6b9N>(^sGQLOEjkDMuCfxl;AMfdk@PCtoX z1HSKuSxxpMEq{|Qx_Qr)i(@FcD8aj|Rj2A9b5a^<&A;=#Fh1;F2lMxk7*6E!Gl`w- zCTDl&eg%sS+ffx;FF}}!rPmlf3(&VT{ISlXci5zICy&l=xUx}G<2TQYP}R8W*-W)L z2_W@f>8>>L>+D-?I$6Q+XhG%QQD5lo#tK3s%K%k{LTIqj8Pk%iO%j)~ZX~>nPtPC>OgC%qgp&EW_4Rr3m z$0k&3aGfIlulbyZ=3~E$8NVVsSrhz3{b0<8lp9#u7^PK00Q6+;28W(|pEJdW)=Che z)=Hz@^*9rQ{yJ~-wMaM@J$5f*YI|o_ACU2U5h{S~LCUMX+BpIkl4SBU$`h-wxU~t6 zF|C*-?8~I9_%D@VF5vW?T0eSiB34z_aQ#rKE!*3F;EZ;KH`Fl2of5{?ef!{g^tifp zKa+YNmx5tw$Sn4hH@|{?5f5x?h>>ZI-T<-ZTptS}j5B@x6 zx@YOE8z=@ITA^>Rq+X}!y~VP8!_}D`R&TC%DfB5|hbz<9Vus>^oN?a%QuC@k&F+ta z#cO5vxz>Rn-tHw{nE-}v>=+Atowr95DgVl{64lI}$P8pbZX^A&!i;``K>JH`Xh|;2O@My#lG<(S-SSH*mORyQDg4O#Vq+V1!vA$P2uJ{4o*Bc{`BG z2EF-dA?uRo$M!;7o9BnuL+G?P+*-n zSYGaZ@&?@sD&aPms#VFHJ=-78ew*Qsgobx9MoMYf)NDnCW+jnjnxwNCh7#&@%2NI6 z37<5cqe>9U;m6z8nYpd#aL z==($4wA_gaYa)U{MOBIQurQ?a$OvDX{SOwB?5I1(ih1|HSJZ6`1{J>&R`PfvuUfN& zWe0)u@pICZ&;mxw^UNL0rtp-kX~j%J4vhQc7R(gQ5$W7N{~1sgMS61g2}wkxi0fg? z__^?$>}g-QGW060{ddK!?GS{uW`>wtnjD5k7K378;1Knt?pv7&JsI!b;*Gc7?YycU z$_BGVKz3L>1_P7ovsK;C90aUYeJax|2sx{JB1I#N3RHfbdjC@27}SQRKzs@wOPOcv z&j|EE+U_!lj4J*qsYtX{I`u@@P_PohK-c6pnyz$ zKn0e9FH!`fpRZx9sFH@&~qId}T^I>=mYC^n~|=D%cgW}hF^l<#J`LR4S*mb`c_I*rIpb#qh~TU35j zSh|AN$9$8qWeiI-DIqEV^&{dt_&T}lfCG2qCdJqEhoG{_kn`BMAIp{uHgn<-msY- z<*_=+ph7rO~pO3zbPmieq?QjUFOXbv=Ew|sB7=Vy9&-p237fmdFD zQ*<^>Ep)EjfaK4Cmy!L7l$GxNPt{U@;!3N29L$ohSQ+mwEJ2~TB~j5omw4|)GPYP~ zicmf`lnUcT=88=z1|qB!hz8j##SoXjfot_P!zT(Saut7km878$_NpD&BozrQRBy)= z!LD&Fp(S?1$Jy+`N3ehg*sb&3O5(vDM4Ab|n9}i(xBMktF<5>*J`ZyC_{6j1lcTSy z#cUhjA27sVTgZLRB|g@8P%V%=@v(Hl5<7+y&(0l+MnC<;V}WCMSNf*%52!b+`0Was zcBX$@S=Ji%f(ev4&E93ZRJ|Fg^(X^PeHi$)d3E<0>Q*LQ+qay!*1ne6vyNed1@rdp z7H0UOg>DuAaCiD|MV?P;=Z>qQwlG?fm-<^Kbe_ZsC5Ef-*Qk zqncqPeVeBj%%8-4^MOTh3}l#RNP-kdAz(zcb| zj$+;@fq3eP3w|}nN@_1Y)Cs(qKrflxgI2vmDDctOUtzBnl4W#P*$ zQBKgC@3qyKipnH~v|{_*>g=I^(VmJ*fYVqX?YPl(qV4;(JvBnnb@eziyL(B-1Md?% z9{Cw9cMnI=riG^IB8xd(k%WrX)>*`x0`B{$v?+#B?n+9*+v4KT32=?%pwxSR*T+s< z1N0i<{>>QMIxp{TU~j)0lWMYY0&!0?W4+TBd$Bl1X0jGmm_MMJMOVG?vNJviaq$X z<2HDzL#=m_+gFyzr_Gg**Z7=yp4)Jv0jutv(?QybV6hb9$8vqmJ;^0% zWv-e*`gle5;pmX}W~%t}m=xL1LmC8Uk!VfkjtzXyX#wQSyRBy!65FH3J<#Nm>>SEw zh4Ud~aRV!>lL2WhMmkK6+EtD*T~MMPO7e+EgW7H88M4ggab-8s_dxXao80ednu=H1 zJjJY}sZDEFNZ_m1G%#OPX+oZTD5rhFMQoGr@e3Up1NGXc3LJP*o!OCpA-JLw3+n7n z-_v0D9eCUOZrlnHt8xW{4}A?5yPto~ovxf`o6_MoZoL1YOgeMe-HG}J;7D2}k?gO{ zQ_JU-N398C173|J&J^C3Tj|eLxFFYs;?LNmf9ATtE)0V%o}{;o0-kK9pQ{d;rb{$r zd_SyilHRHQW7pFH6Vm9uiy;2Fk;e+++$PY;QSMvLNcXzix~UluZwX@vyzTdyd3rP7 zGU_HCfyj)n4V;`s=_5@_j1Ywh8mC7tuLTqYV?(2|OzD(x7yoT2{D))ZZ7%vYErQ_h zQ8*Th6jGhAQxF}e_|~d)RTL8LOZDR6x(EpHPu=7<<$M&&QkYRkf+KR&GbI%7s(6`~ zr78uxfJQ0=H*9W$@4V=9DzVZM0C>NRpYadzBfp2r>{Ux^>xDcKk_^7nm!y0RFSc?adHt*&Prk083tCi%3S8DbYG{6?rY?iH0>%ZMf6LrKD5sQ50 z{Dn_(h8k7RfEY(pRF{q1v&NDA4R6pK&CL?6vz@GB)!sbF*GTSGj?%w0tqOBJ?*$&U z6nCkSAWl@8&L~OaSZa?AXeBA9{@2TS_}e|Uqf*OSO{UCGNY^gR=+HKqadyOCS6j3j ziPN>#{IlEtOB}~BagxOg&}%cCHpwtfvseuys*7HyVN2h)n) zyy4V7DZZy>wDD406EyR)C@U@bU^N{X9rzv_|GI#_H@-Z)Bb3M+3u)#H|7)j7ll=D- zn*1EcBt-^u=2ZJf!IG;qJ>M2rSC?ENsS^j#F$0!jV(t`uo|-L~i%nK|86!l~aC7kY zW6$=5!CGsAi$n5qlCr$$o3Dj_^CJlRzAvNnq^+7CYCj+B6F^Z{<9tq?XIE?Z^p_(9 zd2@*ZeMPFX##r$fK|r*MKm1gFJ^nuL*G`gfiGIQ3dqqpb;bPabD+M|bOv2M+E&P|{ zE?+z=tT^z{01>7!>R$ck=3^(w?M{>(Szvc)2&T?}8#J`SL51&cqe_b9@5NT2#A-~-R$3v-3uTcE^d4$i>x720o zs6NMv+;80qWvx0Nai>dN&wH{uPVQx8uZ_}gf3oDVcUiUUc7U2}T{sGndj;3BiRwz+ zOvcG;E?!Kk69x8_4L@LfR<@!GR%3-@zCgT{(v+`5hoo~BeB?`W+(!U@eMHlivpDbj zDzC*t3tSfQ1REVSBv%!M^(g2GP%5T_+h5-EOA8Lyv1I~H(#f5D^H@+LcCDzvG+J_J zZ+Heh?-WUn0Xl;lIo9+^&)D$F0<%nmggf2b$AytE_lx2rIN_ILned7`?-!aP0a)zk z++os0mkWMLr{{pvvNt5Z|KQS6j(2Q(B`JUCy!v?8Vr9)1u4+E#kpjtzai1yr&PA*l#A%;!%5(x3)hUH;cF0;W z^J;mKJqMnR<9|`TZx^SWJq5cm#s(x1=vIQ`5k50`82D>U;l7_HcVW8SQ;=CPrMv!5 zYOr~?WW+9WamWME+Hdxx<9Z6ewd=@hw1A_B9}9fZt2vK7GjoAP8XhrpLol1d>Kf0? z=5XVR1oVwmw$X8wWDzWmG1g?~L^fuwnK!%?0ZF>SdMgf~k_ zTBTq?e@C5)z2soRqji%iNj36_$;Z7Xld`WsCeRD=9iym{qUAFf^fr%I4n(i`UxLx(DT` zzt+J^9f^;5Vgo&u#08hrnFzW`KkKtAk0Hp?GyZEURrB{K6%_YIv~%JLCRG13qTOx9 zjft<>wKdu@Ig@>?f4DIsOl|+NVGs@f@r3nxQt{k|yXFmWux|^LqWo z)SMCkAEcG@A^&uQJd_vmQs(-~DJtz=H6QjvOS}4#g;%gBXXMc$eQw~J4W%k>U@#UQ zC86EYl#EwLkyijv9L&-Lxd-i1QF4Okrq(jw_b1UE1t$gEWPpo#WxuXXFfy-p>-V>G z#t?$q_HyU=xX~$Vk`BOCUl+EDTzwp@BM(^@c02?0Gx~Hp*sx?ov^8V8bhL;@9BVt1 zS)*|k05bmk(eRP0VqE;(+de2)I`)KNL?Bt!{sxcj(V5~^CY(vlGX+E$l3bnGb ziy1wf$!aizd44Z)1z+##qjlS@^Uvp_E4#h1bQ^4>!Om`Pa(|#jbTD3>SbBxoTH=Pu+dDmY&i; z&Z>><8;M%$I?1}^1VBywXOT{sSXK)0Y*OXG+r~Q$bG~(O^xUVQ2JH5uvLIOj6%&o2NDgA`Whekm#>Xsi{v2m#42pY zYURwFM>j1HUpV&B+NWaqM8|hm&;gU6()P$*XVG&odMVU8BG;pDKmK}2H|$rME5upR z4F873G)`u2r)St3oW0jRKpq&tQ1*1D0;j75XxY%qLc`uBxc_kiXRWuX+N3OPiGLTw zos~t;2+io%en@}3mej->EsM5>9T9i!=Vm=qy;PUUE9KA6*%8Oq8V}@6i->61(LL)= zUElb41mz8Q9~Jh|-y2kU-YEkFLJzaN%tq<;@4ERL*g`DL@8Sf(wzZ(60Q80F%%a>S z_+m#H2=tq(`AB6_u(K=2g56Q$&tSQ=tXq(se&)&B0frdC+?!q1azNA5L+}4FA}4oJ zq9m1wtnmq$)r6SfFg*Fv3-TueoR)TKjqjX(72s6fl-V&_dSR4Bs3CmCHOYAk zu5}U1YpWV&6u;GX0vXGpH6v_zWuR~{z0kl6-`qv`wKlh`dyVvV*C;)f!mfB$W|zk# z^D}4_JQ{jD&;IKTN5C`v(?6n@+kP}zxCJu#hwh~9IK3mizBpqoF(r8`f*hC_kK9-s4DvpQQ7t_@DFA`Ca=+l-mouPDRr#C znwVv1tJUEl?%R#Ag8CeI39A9k?+OU;izv<#PSGOe&yho~Y09ic04rsev)V_W*0t5p ztaja{PQpD6e~%Q7l90^^=r5VT3%H2eu>bxvEnVHI$@H##VTdrq;~-Qx?XKY{x7je+ z{YNc*AYKbzn^4{>fM06f?cTe|=mH1Re{;ycnjZGE+H+$#08etS_wzKyBKaO-mVpPk zW2ZDT*>C;S8*XIHjb}y>nL;Ae$=_&MrH>($Orf(kl2LzJTd)S4v67OnS<)1hod$&s zE!db!%vU6RYr0I*QiYz}!m`vo}}{t0yS#iCL;i4KpNT$t5H;v?9Y5PjXe z+P~;SU-bXOJwXbQegpo$Z~8q{#JQIlel{1G z>E@KjR&P*^hRn3pR|6iIG0CH{yKBY*fyLSn1aLHdkf2)qn&`uqfQZlehJ#jmWZeMJ z=J`}$PVGi+F>!)-SF&+Q$zb}mpTg#;uVu{e-QO4D_j%=^SO83=!c!LsN8`M{6$5tO z=)Jn85T}-FJ{~qK_kj%*E(*gBvzw*wA&KB%2PgYC0^21gBZ}d?ZZ&8#Wno5P-hGoa zPpfnvFrMP?9aWZd_n>fWtTc&eYWct_>5%INN;)e|O^lC(Ki^1o_AmgHmAZU1?btI( zx@=6yb`AE$e|;%GC;3CYk!eQ(=`^L=O}EV!tS_fo-<&T2#PgFK%MZfD#^x?{nekD2 z7M{;A8lwBY$v(XqNy;yI-fLu~b9&jhC!qBK-@UkdVw{Fl9Qt+wwpCLgL4^#FybAQ? z83#*69=`a@j^AwpUKJdX{j(&&2jxf}gh6-eW~z}v{gNPePvV0Fz}K}GNH4MAqpX}RJ(42qWzkr3{=}E&dCFTivpF%<{|bnnw%y$^1fi{e&@-%T(Xw^ zlTin{rlseyGH+;qAh_}3Agf756~3%;=>8ECkla{0p)odKOT{>{jj$0mSO&J>tmzHZ zt_ytsTqrhO7lJ{8n@`G?G4|%DAE6sNTls5j2fRBUn!U*)jOm6jbwlh>$Kmve_884+ zx2evS8ZTxjZ2e}&=)WSd#O@X>>f2P_eN;po+(JAn3}Xqp6^7RBs+F44wk0nUSN5+X z)4sM#Fq!ZlOLee5;R?w zxBNI4$ra2FcDb@rGxpt&VY_WIem{6BT{X68XPC^{@{4Gk(beAuR^p*@^e z6;?!1EVaB)=)&o#Ed3z4?>fq?`otWvZWF3hdjA==(!awc(swCPOIbr2@rz_7sj!cx z1FQA!!mtsV?{I1c%e|h}Xev1x0SsXL!|3R?Dl>ht6lEOQVRrY@oBSZnTzvVc5 zr(CPC=Es*?n>gW7gvd{~7r!>vt(avm2C4YGN_N~<6YI>-sDoHPEcDa_{Y;APNeFn< z4t%F-nCm-Ad0RkYz9U($-GwOlpP@hB9`&;OrMMRJn#3XLy$~v0589*&y5hy)0-Lh8 zV#p{n@xdaRQeL^5Xr+6z!W8%zW_x9_L+@{l5?sGr4RevjAXTpa}7B#O$>HrtMeNL%_T(o69%#2L7nkod~}zzivYzC9gN#^ABn9 zIspa|?#XOBX-Znc1Hx#FnB^+A)K8H@B>h`;81&Ak-g5uo$(DB=>61-9QQ~;7$7JtJ z*up*4AuS@80rDXfc{+9?ftSoLSu74=Rg(WiTkq5Znd19G+=ZIwB>jdPw7Uk~{wn*3 z+kXkJX{n?egOg8n;_>z>9V=)$(s~(Xa6XEc60KBEeXxOJ-u_I)H8;Blb79*MD_@96 zFQ(po)9d{WwXUt&pu*p857E?1u|w z5T+e@N=eO=Q95x_5Wo7I~)GHnt8cc4`|K=PmwW->Tm zU0_)w9pzA3nT@lf{=SPxzjpXE<24-@OI^yKL&W63uTjy!V2%?DIrZR8%IA4P7niba4|8#Nm#=Z!{5JYunLrjHn-(foaMyHR!r2cxS`!4j z5%^>$>OfyY@`wW^t)dcaQKZXhm;@mZM92y-@dcLCl9ASai+bwqen&+8ow3SiVINh8 z#L`Tuiwo)R%eK>Z@mkH)-UY_a^O7>|kB>#-rLf0-38+hKZHW61HPGY+B5I~KNO&kJ zrD=9S7!u;sS=o&BOKuId;JnS$V3Q+kV*cPfMOxm0WvyXaMO`%2D7NdIXmU}Y#(;4x z9c49pwmX;Wa!#weCdomb;7sh9OkjEV7}vMT2mPHJUAArmgGJ?_`8F1WfvEsneqQ&6 zEyw18(0{N8U&0ioMxFd#`$7G^jN?oQVxB22eOHYSI;o^b9lW?ujkL{$uffAY_+Nqw zco&YJaY7Y4I(aHuOwD^~n&D@RYt|Xzp6m7GYfH&!#2T(zh^MZGT!pYL9qsF~xr~e!378Bu@GoyzB-NkH?4KnZU@;0UxP8%g;06qmY-=S>aPJEO)YJ>;SIhCPrzc-= zB+p+nznr2I^t{x4;_KMadZ|!XPrC?y`t_+bsn_!@qZ3K}r&nbaG58-VFwWzXex}pdc8C5D?`TU;tB}%7 zbbhYydF6uWt9=+uW+~P->ah&(Ls+ou3i;d!*|TGGdE_h9%r#NP z=8>AqMekJRW&nZJ@OT-y< z3Dz^rpQNVw({8lC&OxJ$QUxu}oU|TwIK$chW-@9`VAtt!D;0shQaovX0zOpU>ifVC zutdn}6U@seLb&04|rHBeZFJTrL)IsRMYCj`?$ zLRZ$8xL-8ybi+|&}Bo@8N`cFO0#qops21oHk^51lM;$nJ8B|2IPY?CR@0?%d4;#hR4BZ+G53FhnA zBwP77GE+a~ZxwyL5n?a|g3gc>!OA}ca`lhtp{0Cl5bmsm&w-qp{{;`G zx}FX7hIQ3jBTTjcS%{c$nDsbOW+@vKHCv+tcPScQwe{_w4rg8hCH@#5%P1=i5#V>8 zX}3n_Df!Z%h@anKILkhR8GF?wLwfGfmg@{ns*(?g9Q3T)j*eWDhx8T4nV)I>(DQz%3{kJUG=>; zIK&>#e`)W3NxHPXF}RnUzLy{)gR^a*$Jg)q2=<{un8jlxjAd*Xc8 zXNEy=u`kXoeFBr{x!2@KlJ#Fylk>}2l${YYsA;m0jxKwtuRSTg15sn2YAD<0b6q?C zim~d-bYH5eohR7!I|+53))28s-nj*jX1-X4gpcj3v_@m)(4ehj%6#AZs5x@TQt_3U ztG-gn)iFoxQ9V2oJOU-B?Bu*NKaJ!>auQi&o z@W^%usb#O$w8ON?#|BZnZ3dj9z6^oO&HYq?=ccoymtQm6`B9Zs7iqxFcIcKmPt*Q2 zldeo0S3)zoAPDlT404s9AbM4$KF=(0#5`=6TWafXr24Yfsd@U^CmQc)rX$Uo zO%@P49Z8t<6Z#K%h;4JdQ%m$)F~B?4G;iuCPRGo;s`|_VUY-fZVWnA(Q>>bN(3vDq z9KYdx!-wLYYnZOwZ#JXRwd-JdRdhG}_vZFot2?S{K_7&SNc=Piht)ELAS?QEak$0* z6;EUDuvY*wGld@`RtO>da5yEq?KAs%hYfzQMIw)7?}pZBgF}RkDP68JA@!SSiOe7f#k7eBY z^sXD&&vOshx3xtLN1nDD9I3)}LrVLv5W0ko{G67|_{j2&Us!d+MCyX~ z%0+ir%CS09)mo#Gn74e`z8GCyu}uNtsktmZ+y;Eke}Z~5Ew2PRyim+U>TuB#FtZ|i zj~+o7{U$lK>;jN)`LGdrYq5KF9BMlJzql9@pXU>a;l2;%jBNxyoj-}rFvo3`yk!Y? z6?=I@>v3$@0t&1Fv!CVm)jS6nTmrl1ar90k9{9P7NVy;47BjQ&2FzSkWmU~EcnW!! zU9*W^vlJYeW$ky>o6Pj2c&l4znf=Duo(*d7^1v__23Txa4dUD+G@p?(g1m;80^9gk zrSA)xp~&^pgV!d9K&uAUVZmRq-kvM)*#Sfe&v)J_4PBFi-M1QDMO4o&@M6TUtO?<{ z#8o~v;}HQX5+*P7ZaNM&jh9sf43dx2FD!6Y3(geaY8dUdIo`TS$#fgK48Xi~qx`wT z#;_|52t)oXmTYPnr#rY%Q-hl{?Ra+m9XSnlZ5|DO7l z%cCN(W!D&|-Zj4wr!%%K={+E+8mrz_)Gad*Kjv7^evnmJ<0JdAxy!_22ob$D{0$jE zz$Hi4`Nm#q_J;@QYoK(+%Zmjln}p2zMI& z9XkFeG&LKFB8j)v2vvYQ^CheZBcR`BHtLL$tIe9>Rv?vzT!mH(+*7H2o!`Byhe{ zKiu^CvP#}E3x|-GtEv;j@A{H?pI5c_IuYG(HQE_mYZ5_nsc)30 zaxRzg%`wqo{I@IKZR-1@DKG9j;vF4MVpmDOkD)2Q{3!~2(08&*xX6SE=rvifq5;lk zeE9v>(BWT)$OgC*{!16`^UTr>ow7-y!}RZ*tcAbfa7p3i(C7BgXb8M6wM-|V%O}c` z99Jv-rUIvPigMR{**Ytlt^9)y@71Dm*G(Of49{F1yNYqE}0!> zME^^f{^lw(L_b9m$!xBk*@~g1CR}1IInpzvaFs6$?AvF;`Q2k(AgeCL>?T=lhnUKj z!|!FI?75FfRo$B@WsL0Vs-@#?d@F`ou$TOm7@Y^w7ef8la=J*rdzl94x#ym&r<%Rg zy|vW73lnE|8ouSMh#4@uZZN214W#wL9=Cxep6V>;MpUis%MQaNhZBXoGOAFaaQj9J zu;_}*qJ$kIsZ3Y$X*>|hRVhvV?3LJ__0ieUspyyHP-E zkGffB=N5VHYyu3)t7hOk|$wvn|cp zZ{gmN(WYD7A=^JJtd=)n%No_Tiee*+g63%+5{+}&pHr3X^ z+MwX4+GBS)l8^~ymD|sgF}~g!JJaqW_!Tv?_&4uI{R(KzBX98uV7nB_W34Nz4t+#3{frWfh79woK+?m;yA&Ci={3UC1Sz+=FV? zR`uAx+6XD{4a8EY;f$^Iu_0s$rWk^#`~g3isvA12)rt{+W+##=nL1=!^TY=ht{C|Q z7#2d|&7++VAdoDp-#e7PgWUM3&PZoaIP6H^<7qt>&I;9pLNwo^K+Ko&PyY?`pc8g&?OD)3;Wa;c@{9+LJIJkdoqAv^cFN`^@%bgPw}&;yjKo znr+QDI2P>bgM4(7kgWOj--v>nTbH+C_ui52vysHK4h){ z6*8B8I0w_NbZwCLBq&_)eyy`6SCjOow(o&49RDHZlP<4fx<&)K#|CK$HeLU+*Pa6z z==YO$RwA|%t&FJ6IAFiTz8c}SSC%`ee{#TRob(h*a;m8Cu916)p^)`IajEYUdK7sX zK_w_fgp#dqri~5J*HdbZCXrCT^C>q-98z?^;sn)unsp3rbd|f%9S<;dVLi2e_35%@ zf0|;hk4+G>C}yc7Q=_+*(*}omd^X&3NS}woB48qH1WUJsh#wcYyg{v=dY<7@==_%0 zu;smKE5;JRZ}Qi0B~)FBGvde#ylr=LXvnC9h|Moz81|hvpMVHPT9M;<&g;W`c?VsK#)hk^ zY{;}&|Ia*#)Adkp4F_%W{J;kehU7w0$zD)UL2Tm%N-$Nk!-f2sUrH_{=4Ps(`0v7^ zNNIm>NZr{EBvp0PSft*{l1$ncUhFdygCGf0TyIu!id}$O=zzBHhc6L+Z6gg7*gbkh zqUs{V@EN)}qouHG!UT$-VhVp!oCV%NW_}6l_RD1Z1j^Q=k`#Q0=jStxp}gB;(j79~ zl>aHw6Esf`IRu5spP9-QMJ&P@&ILTpP)NWVBli9I8rrp@={#& zH9(kj+pDoEUG-9O3cUoW0g$(qa0W3!+cF8yCnPMGx)B8sV&{8~_=Eyrg2XHgG1-67zX66=5Yo>R^s3IS%oIUaF4 z-M6DZaQ9AofQ3*Kvv7q&dHtZY3kqL^^YR1*Wy!_|gg_#y#X!JGM?8CP0%_Fla=l-s zum(Ro8A^O+O2AT3;P}`?eUKQqBYzJ};BoT_8DHz*fFu3}n9)vp>8UPcl5g_|NJ~q5 z;ru8H`7dcr8$itWzdnD|5oM|u{+!?ndZ|IJ&Q{<`XJ3*03&vynZg2a+$w74fy}k<| z^n`25&cP5NN~z$AR(k93(epimw+6$$cosyRqAscl(*4 z35HBGIoyW?Wy2NAgINrAThJns4moq>gwTWW-H}l7I=MbX2+b`5aXvLl$|F0CvAd50 zCGb&Jku6$$3Q6Mqno`03*@Sy_is)1pYqY!Z+yl%@eMjUa5##pI5eDgSXgCt_skd^a58-eSz@w?a@P>{+`bfVHklD}; z*}v-Dmuo1gX@LRM<0B4H1Il&!XWPK!8hSLoLs{CB+MR(i1TZt6@!)f2kH4A#eM5`E zbIW_halZQp=voxJ1=%H zh`~T#%|)ojhf-~I^pp#lvli9>-i0$!>;k}z=8;MgjiFLn3OHm>96(J0K~Bh`sJ7ui z26EFwRAC#8MQ?rf_nTZ zUMTV?#0-<@Orc?-JLVzI$KfNROW6co=)M0rCw3|1k0Dt9 zfuLwGQt21U*7TIAFTmK1=mH7(QPGyEOT&PQ7(%T5)@u6d(78Phm^aOL_);H5-$C*J z5&6#befoX%RJ@t zY(TSMkgma9^QS62S*BHYkjQ7m+O>+3)_CYqU6$FfRn9SrdKd)p{l7uRI zx2g^(x{?-6oqkKo?j7pVZU#!BH=SHTEa)A@?nr1Ufn#W1ucBv7{xj*S0$gyaz&2f} z9+J;2*moa%oY|bN?+!%C00v}SCO-bzU_a=V3K}*N)#QfAmzxJpf9G@RBVk$i|p((!;!Thz-M*kiLaKxcH7L zEG4qD2f1xE5*|4EPnhUD_CkTPCpGVP_OSArI1@d=jXh9?7lM)=|? zFgKN4r=TUK3ya(v&@0AD#b{GrQ4p_Hy1)#$nSm#g8seTFW zg*hKEJy0CY2-6{ZFD?iW$D*~&+ z@uSO)bxj~%7O$yn=lUvp`1Z2uihJP~EPmr{njCC?NcH_^W<2A`2%6qH-6IZ-JjaxJRQa_nGLZXPk15d zD8wFVfE9(qZ~MthW|ukTM#Bfnknbs06PHzaXQ;o^uu`f|PH(vGnAU=F zTk^hNxtu5@XnBBpJKf6peg-Vwi*Wj9?dV+V;*@rMfET)3P2bVU(jw^$=iX4hI*0d8 zcKFtP&=nb8{?s$}RsCVX-BSE+{j`|mD63MfOS;G0qf$yeCFr*+pa!B}X$gkm`RIRj zVg@nBL;$lmIEZgFF6frz5U3oskG0$WV@&v=bF}5!#9)@Wm{l(4XGZF=TGzbHYlLa) zRy&W`EO1d9=Z&t}FO)Co<4*xvjGyGHy-D3PI znuLn^f#AT_L%q9x6e3V*zpgp%0|)%#sNjpqG;N3lS)c?2?C{vu@FZg@mhyRAa66HZ zzwM-_Rz0qN7YKeZYLmlMEsu?Uj<5mm^SH%ZmaSmD=fk^y)HAbzY=T}9kboo1AQ@kb zPxJP)^tUW)0^|!$Q}Bu4=^2!MuQVS6*U7PB$rE<_mXJK6%5Bk+M09}7$VBlLieef(4{>0tHr#eKu= zyESd1A^qp%-xV1Y`=7f8oj&m}2uuyb;tw17v0r)42`MonAg%95*$QF?NPo#@d)|fk5yIS0$=r@ zKti)&HJpJ0Lv-=C{&I^Cx;!U=!PGjXi)tsvU$CEwB;%O9!ueD1|A!)`=0(p{&o}x< zpxH-d9`4{bS|mC_gnqUwq{TYRpQ4ld9BWn25z-RW-kJ4_&v50B`D%?%6hU*$_gl}l z{^SE4(8^*?rQg=|&OO?J(q&^}8Vl*`YYS`*o8Yv>&^#(~G_S}VM#m*Hv1Ncr-$|ku@cM=N5%f&U!3z84f&t+EX{-jDNzdb+A5@H!_@CVmQ&XZ%4k`HJD;5yxW&Gy9&_=rTRRl@GAPMz841 zUUjEZLhsYu%P!<_hU}jc4^3)xHEBhg(1eY=tEGkFjN?QG10JE)1cqr)*ERL>*TGK{ zE3lr$3Ip(8ukS=AoRHayBo6ILWYc%;G2b{AN&-58=I7`~Ud|s#X=X(9GX0oqu*fFW>fvltzbS_O1kqz@2aB zoO%fRGuc}fUHL*GO5d8$GM!DxZJ_(-W$o*Z^Ac}|IclJ5$7g* zvq)Ox#bI{4h1fOJ_=2^X^oTSL4p-`#tpKcgSjU+#pRXHA(>*^od~S{Ij}7%BerFX% zz6b&8WeI>Kc%+|iAY}+n86>@bR0_Ft`F7Cop>9&vEUR!=m;{0zO^%Bjq zgz9hLBd##2VPaUGv0#Gm}T}!rU58mYKgDxo5P=zr+wu}p#EQEzZrn++~ zd3d$8A9K^7=MyBm#4x?rciP6sz5?P&qus#+;N8<-m81b3X1_|02`L73M8HY`c5_<%k#b98fR)zIHzC`520`?1cWQjsV zcQsCcx*hx}*TcI`6A1|0Q4lcZ&m}hnUhEH=SEz{&nEUWloRX;m^6I}nw_@b-^YUqT zqeu2!F9F7tX8;cSkiN}9UL+qlUCdwgxU%Q=IvVI!Cfs#V&c3ZnjG4kKFy%b{=*jpj zwDa|p4d+n*{~nlQ8So+dM?dhuZ-q#sNW3{6bSgJMtb(1X{dNv6fnu2_==7|?zLt8m z=TcO!54OOE!nVUv7tsl#EEWJGwZycNPaJVNQ8MYh2C8fwMQ3H-I#)QH83p-ZrRU8T9*f%gsakUc`~0?u3f z<3=TCju}^pb{_`(q5$1jj_esY#JASdh(JD0W`QKVSA=XD+-yy{1Q|Dgfl@cCt&nq(s}~D~-z#Ybvo-mN$#x z-vcdN6V-^FMYvM;?b0w>q4zblVhL}^_(BE}SNqHiWAKx0*@o$$bJ^eZDW$)wn(5$& zMXnFJ`Y&Ia4MWTNPQ3u#>!MB)@=bVmR+Ga7f82bXPr@b%#`}_!PM6sV9h6DZIf<Wv8 zr#6CvUN7h&ind0iCZi*oZ8o9GQ)b<;uGfcUKNENh-*ti)!B`|k3mrQ})E`SuwQE+} z0Ml}TEP*Ldei5ov28s;y5t?-V@Zw!I??IsBamJM;t9w<|t}~b?Qg;+MHo?!NXY!K{ z8ytj>*Y24}4mVG7e*=MD%@fE7_}3eG|6Naj%1|?2^xWz+p65`%$q5|3afb10!y!|S z*;dPh8g8Hq&0csV)zh1^06a;IA6(i*Zim3dyEfW}>`MUDDx|DyogRY_%)L16+MYahx z=0_b~4Xxl-sqZ+vuYPqjlkPC<$~2D#!)kr}BeH7;j_I@_5+905&FT(zfZNQNeg-2f zCJ1eT^Cmj2(bBwB^%%{AH0B)O7^*s#%USVRuD`qRP&~LcID^6@Z7_-(6qFen7hRCk(QpB znj+r+NV>|fD84REcPU*9hyo(gCEXy>C7^U8B`i|XCEc+gxim-%g0zIvAs~%NERC>q z_}=}$ANQG=drzO=Irq%0*69HM$+?8kAh<}|W_@k-M3uD3@1hEj1I=K2Z%ti(;BsuY z1j_q6FlqSKM^fdHBp9`tWOT%v47qUF{I9LMXW&}kMfkQwWV;jee`g#MYrE>_Lzoxp z=Z82MlM@-V^Os6Edw|(Iq}Mx$=%9av4r#pulq$JpXG(6%lGHQ=0tIJngsw%VEL692 z0@LJ^KQ)~=!U|dy@BpI-97S#YBmAkv;rajXNhBY<@joLYNK88S5ul#3-n8h3C)i8cxh zrnvB{fRH!&cDc>WBCzF~_I|>;r0c975Wqsgak!t~1HpWWN8hB!uLga;IT zLiwe9RAn+z*dHh@x9b)X#Q%8maw-l^vEtmqDL|a_`41;|1{GCx>r)^L1|%*uXXJPp zZ0{DBvxm&>m+|_1DJNSV(54nMOtMdQmzQ6)P;vRqFZbs(XXJ>i-%(Tc94z_{2j?o8 zS=j1!88A7`{;Xg}<3n4w4vKKY2>L6drTSVtAH#aU6@D}6wUZmmPzj8Zn%fFToY7fC zmGz|$0;rc4(rRfMIv@6+8!Xa?SN*r!ek&7k1s*DIt!;P{)luA&zFh~<5cm@)Vg_Lh zP@=N$l zj{fu>z;_0rVag6eclqM<%Xc$?b-n!byyXi`-5cRCnXB|R)t4gq#tN(1QGxBCkJN|9 z9ObUQHgsCDRdc7u*2w#*p{x1_m~DLdh3FA&RrY6%NapWg9Im~JlY-N!^X;FtV~v?7 zulqkhl8({8oXy&eih>D4oL9nJ^y)ufS4S230xrti(R4T=Tn1R}MvLIyFOB@NT0x!n0c67rPy6FKo*`>W6`jiNk4&!K``<62g{INAIslpyf%Frq{^Bg?*oSq zmu=2cpbK8MJd>jTFg2lFOA`s~a$D`Go;5n!@lIPafC%V(#noIzRC2`&s1YMvTL+u| zlR5^wowpr5HnE>L?>*Ryc#mEyhXTJ`3|KGyo5(#;b&fe734eQg1H+Ez4^t2;MX`}k zJ+6@8Gaw6aToADwIU~nGCdtDJcnadkFMpTRTv!^=&OHaP3^Iy5jl=irIlXDd9vHVm zW)fhk{H7*I-ed$ytqb{*XG5`fpQGCp2fKCe8~@<1p0$RkE$$|)*fc=cM7N@ zFBv9W1(MJ|k^G;i!2po#0=`&iU!CO_3os1(sax`wZ*BT+pv28Cko|aHIMpAty~*EW zsKbNb^@8+4-|{Tn4LW;ly8cxjxjWpK=%pC}2em?S^G>x)ty(E(E}6jX0`?1NaQUP5 zhGM{RC(W%AdnvpyCc5gfmF;W=lQ08Anm}FT!#w4G|KA6<=4j(TJr(q`+MQPs663OxPMvk)C$Qgj9L@W;f z=||US_TDB-z}7o*?uu1gsCAqm2m8QCAF++BmGH)}?H}8I1h#M+47Vg*>p>G6js}NA zs_3ig!OEqqUQTDAQkV#3!}N@WxQE%E8GIqqka^>7Tw9|azlk~~MV|XpthRQGID0qb zftGHLzDad+IMJ-=7!q783z7UfChTUge*2#dllaq7gy4QY+g3t_c}6+Nu6&Gpm%)xA zU<6t=^($Um#X4bZ!9EZ;*z$~q9TECx_pj9e<%BZSMPp*`y|A@Ly|luC^$l~CY<_H^ z-UsyWyPsCaJeobo9e95U@`G1#e#NXNK4y9076>*7q6-}sdu$#UYTm9+0q;kM=6xF; zl%(_&%+uypbS5|IhJl@w-4_+Paw&Vp(uYb91QtZhJRfR3b)Nz`!CjZI&6sDJhcm?{ zftVQMnM}CN)-h`Q5JfcigteR=tmoxWT>NJ5veW5!Ez) z?UHbCuT3WZqHLO1>jESm0oMIC>=Rrz^6j66tpc$29-Oz9;rqCA33E)~$a3%(->fYk z(XfGlpDU1s|Jzl~(8=F#2#l9UKtr;Zkb4KawlbtaWX0f;Dm$1{w6mW})nygc{}oH7 z!MmsNWj*)D=s8eaFnc$Zz$)B#EISvliNwdfG8aM0JlbNJ0U@odKEr8OIw3tb5*5ky4KeEG`5>2$4j}VRY~I;z4f_Xq?8-Up;!Wvr@I`9Eykl zBQ7ce&8zt4#nXeIwKaGut&O-|F{XLg8Mg@2|Fxm4du5*=sxq`o2|7bmUX+82mEZas zo?_Jk7Z1KRpFm)06b8C$P*o~|TN!iaMpO^aGI>y-G9V>>8Le`1*YJy_R*OxV&|AsS zOa@QaKRO_tLF=k;u|unW;k%=1zHrCT2kAY(Eyo7e3N7-J4AO`n*D_#z_DZb(ufqZ> zIe0sG89AB*Ds@8k@uxAPfA;f4gi=rqqx+lVEf`Yy7dEKb{L6njo@y_Yy<(fk7-Pa6 zD8=YXY{?%)k7fNBGQ6bv)>A^j(3Qgf;Kh0h&Gb=!q@ zHCzo=1Vm52L7WY2?JG<$@>C8FD`6cDy-P*G9&{P2`j#H|^=#9UAJK9~(+T{N&f_?b z)3aB=3m(7tWo2DCjj__-ap)rI>BuC{oD%&t@<&bP&&#aJ?|Jx(VHm7n+pN@A5Wxrf z{|2xZRinXa?XY7+O zt#;^}{-`b$BSykY63Ji<8~aTo;0}-7+};M?h>d(hpl5MZI)_8cRR54XsiFq)5Zxl8 zqab>;-)Z2_odPzz3i3tMf|Pz@1(_=Ya;%heg`jV3gC}Yx0_RE3t324?lDRJ*s)QaN z+)%?VA))2uv8CkxlU)VTb>#G7N%k?-`a(35f%9sZvE?PA5_RfL_Rb4?E`psaLA<=V z#|BGDAD?RFL^j;n7^KZ($%lCk5=$;;{NPM5u0Yizg=ptB%X2#;014gkk~%p}rRA#5 zu~241i0oaCEsRGEJFueh3=&vHkEYjdfr({2H1su5|9Y>cVKFdfO=RuqnRJik5b+=9 zU45+6mvXChuxT)ku}UFz$ntxlyTj-@0_YQax2GumP4^Q=KC}7~Z3Ft$rTkPHwRbTB z*lUN4bdIKoOD)#z$KI&Yn}YA8tLP$pvXmCQ&G2`^(ISU>u(MsF+F0T3;Eq}u`PvGX zQ6?Dwh>3Mm|Kebu?j5VOECsf=l_9-!v4IqUo9ocvE;~N?p znxb4sl(S{pXB(KHhQG_jG~X+L8f_6EX8?i*PFz+ z5CarDevihtzDqjk*0WR`ob_na3{iHq^=`sYMfdzu^Md%?4CKnVS9=+gB-$AuVGgo1&q(TMG1BdQTY zfeujq211&=z6+2r8-n*xGtfgn77D#lBINyIL(vTbI;)~?fy7EvdcQjld&ycU!JdP4FO z^zGkC2Tk==a+Ul()RasQP0_rOd2#Py3d-QY$Cqwp@#CwLsKMNFL7TbSm!Tw8vAoE; zJ=9w>4`YlCggHdGMpN$q&KPx3C@Q`4+jBL(Ud-Pb+!+Ykt(u{*uz334G{N^vj(haR zMgwvaNRZo9qn;V5euxUaHgZe_W)a@cwgRIQ-`o zG*hO=Fc|BAaQ^{G$dXlseCqA?(T$ajKMB9)}Q?Yz!&?9-}(&a)V>xARund6*#@{Q<}w2q!UOWbm?KXr?Rb~y zcncbSp}d){$mKho=TB4V7gMSrc;VnhmeY$Xl?|Z!o@i$rW|BO-Obywn01-seYI6GM zFl@O8yvqf)VAbfxRP67MgLP7I!{1Ve{EE1At8)0gib{fF>itR4#l6czw|Q;Zeg9JP&o6`w7Az@~X3a6#dOTRAb{-$f+LBy{>cpbQK6g@G9}@ zdollo8iRx&mBVSwAWi#}*D1wt)fYF~{g|{k+-^2!D^g`705ghf6~|=GH3Q+O%KOS7 z$X{k#F6jW}!J>3?ogbqc_>xiYrq#I)OtNHR8J%ja0@)i67akOAle9-hvH<8NdmR5E zw4oFyWazb>X>`dUYb=MYSRhUhfQ>4_?6|qx<$kU7aX@B$C^zIi`YHch)W*j+=ehWq~wy0`c ze20c+pT4u?4=T!c!YAZSHcgKe^(A&lozR7!wm!pI=DK>J(C?@BmmUp0y2?d6GbomN za1VC`yH{L_RQ~J{M7}il{u@>qDpA~%?07-n)Vh?H$z&5Vr?DlFZRxQ10i8TY!w0D2;VRy{#ybV}?K zT^NymNhS}cr0OZ=9EJR_h??;WaBXd*@W&BS~@#^i?>=e zi)bEi>r)Tuzb|_GvXSD#!}lZOLufV0-MUpv0s{S?7czyd0Kzz|>pat{u`3QuN$HLs zEw0_mI#l&1d6aDHn`=*KK4S9nhmG%abIaB3qhD(y0c_X3T)v2_W~7w0pky@5he7_I zRX@hL_}`)RI5c+Kq{EN2{z7BVZpdXLNX&#Q9mqSZ$LxLe)ToSZFe!ej>8WXc?4!@$ z&;MihM>ns@R8KRrPxtg^VPIJFwZG|BGy5P0N$^|{{)AW|9N$8JeS;PA9RV>4g0v#6G9JHyFyZyNmzHo0n>PNB-HmmDRXl79C{}SD$@V%`(5oHy z#l((aYwP%YsWt`IN}Y+EiPvpy&xLAAU>g%&85Oso&7tq2%rJF}b+{tinB6mLQ9*Ko zqK3GLC^2m-&6Ct{mR#*`CIc65zQ+!kceFM#Zjyt08h00mxZ9m)|2ao^JR6?M%$1iG zjb8yz62(!;`)mx8YC58z+=(Qmg7ZwM9aL$c;U|kVv}9t0MwPO{r30=y|6cn^Ts1_s zMqMh%X9}SK7tq3DrFk}bs(O;kOptc30&*EI5i~0iYphAsSNs8|2wzQKDB9=mqp-Dl zTt&EU+H=*0djVbwK#!Oqj${` z<{Fw8ScX{4HF4OX4bmqc*?iYb%Zxm$lc(1NJnpbG!6Y7stXdO6yr1%;cWXVPm1|YX zW;l%0zz%<6zg(GEmTOrRI4o1pc)e4vnS1qDCO`BX1S*zKZcwBV&(!s~ExY7fa%#nI z#r$-?7sJ=$?LefGW1S{L*sXA|p~3tL7I~jNcrsMS!v;UVeBX#G+)DgZ84JW& zZP8Mj{wrlb0D4BgT2;G^)oOfg%izP49(M$?G@1dy6O!XmBWvB09d6Al+Lc7kEqkr(NXU zp`fF*i*NqEV8pwd9otvjeB>|LQ#b14?~mW9dCO}K_bRGbbsbh%3FSS1Jx(rg zuXj!i-hEIuIO{aXW-I3v#h;qrtluZTxW)yZJlqtIsmQPqN=a&Aw_U3i)kGL|jzFp$ zKy<)K=wLyVC|mW%=ztBM)zkauhU?D^O9Dbqn0X>gl3%a3mRno09W3TNs*k(UeYHUE ze%h-h(^;uThup>uS^iVI69q#H3|33vp8c$+;hi50q^8n9s%w?1+60Nn$bV$J7V=eh zkX|A}49na`x9rS+Ck_7rV@nZhVZQqiRI28rd;PdrC}V12@;gBF9s1qTJV`3tIoBg# zTLo-5cg7;oPSA$n%uM{9zkF*Z`w9NeD9YHGXmM^i#9eIJax(ENa(t-Y#Q(|NH)RLb zvZ18^BfYthXoYK=0z`YufSdW;;PeT4fdoBYipB@E!}}uZXlDZI33BHICJ)Zq^v`^P z3X%frsnJEa65l;an;I%owq!$Nsw{LutZ?qTF30z(3}f-!l`qbcvUs2Q+)gEBMqNK% z?v`n%DE2Jyg$UgjKv#2@nM8Ye&mDaxi4F+=!E%0miTKxqxqaFw*q5XG&Z6V1rP{Ri zIpA6R%||i9rHjyJ=O%F_OEx{ZdKwnozU+hJ{Bu@5a*>NOdam5P-|96};`asKz9LdH zj^UxN>Q4u`5TUx=V%k&_KZk%^veepPDw?FO-i!GguV=}Y`cY`+BWW^WB-awqKpzSJ zn$oLBSCUIHH(*Wb%>lo4z3h8i$2vd2{y{gm(XDmzYEujAfL76Qfu7}fk_G2)U)2HM za@UL|8dNs(E~YGeV1K#f^tpHRkrU;1f_&tyN-dwPtnd2n>gJ@rE4th%lHx5{uB15-p0F&QfvYwjU%3to|Hy)z{UHgN@rCD9|9QEIm` zMhjD-`+%n{Zhn<%$rIkS^XLYk4|3>pBuQG)gEq^6DI`a$l2@UpdQG4Y;t#my!H0cD zqi7&$!6W~CxL1}Go9?|iUe4Mg%2b2ID9@mZySVyr?=8S#*?x`XdsEO5=W|!5(dB z_8Um6)k9l3)99|23R8tAQkp9aj~4_hBr+ybi>D6Ou7HVkeg8Nfe!aRhw~Vj{w^Aap z0v5EM;ECCQ*iPXgx-=SRG3-BrX%Tm`g&91gs2fTQb5P9y|60V6Ak!C1DRp+a*PI8z zw!;s{df|m6YyX~?iNB=fyCq9j%6=`G!1U&!?SqF{Y`%(BgtZLYD1;TArB>MAuG^P) zzRPYK^7NZBycFH-**6Ig(N+g%NwKUR3a%`_4+P9tl|TtT(LMOdx6KaBBj~8%8^tRx z)w>aKPCoL;UaYq``Wv-{nUJ(hdPU8F1dB0sRxlKVfx z4e@oB7k-k`0P*8-J{1et?{eRFrcdZq3%W=A;o*Q;kpf%Z};M9}}|@8EUPwk%Kya4IZcQ>6g$)Nz1thI?sO z!&vYSc>S`~JbaK30 zfW_6S_a2qJKCT>9zOrI`n(Mgt&W`30|T=Il=g)Bk_z4 z(tema4N{zHc3zgd+q`};^ar(zXm6Kr+7)Nd_zU5{TO>!VH$A`ZG}(*u+;equfdY54 zfo*x@u}sI_gi~y<8bfMtJ`NLw-m%WuIk+18k?$tLia0g}b^;V@PFkT`-KeK)(lC{s z83k7>tQ(r>G80L$v;$@qPF@+z3RO=%l{kTwD0kQ2P+5|w36oM6w#q7bg(!Ndf=~0}t)dDd)OE#5} zq`YlHz3eJOWk--`y5`WzwnNfghQ8&GfGh+fU3;RYfy8S2E)6m_+}SpXFX=;WtBs&! zMZsqHyucDnNMipSmWi)83H~{vrU>_Fo)NXh;~iZl)!ASB zpkm1f945*{-l4+8^`#PUuL_;%=261E==)HVAkjOqINsh;JkcG{eG7|%{raG;{gHI% z*lw@}kn^y_0iEWWOyS5_1w2R}pHuydy7pPdX9r-Qpe=dlfjRj}d6lueS1oamWS&C| z=UaL$Y7rhwCgZU~tusHAyJT*h;j;HzMtS*?M*+f1eC=4p&e<3~m`;iVJsP%>VNQ^` zQgY=DfGq@qt9z)S>I63lB^e;p?o=~#>9l<9BK7`W4IZ6&ptm)mH9(j6#t--ebbI%M z`;AQpBk3YPF!c|Zo(f_nF^U?8ijQm`V0oVoc?mwDQ};o^#;2a5_GFZ=+lsu{Y(q?9 zh#eT!t>-(I1=NSJD(p#%SvFG8B-+%$7E%5rNyy0^?3o4T{N{ds?*Xl0>@uu^SL_m*xyp}hEq7)3qFb&`DQ}x*rE;TSc)9cFoine@ zjYq0@_{jO^%@LA8Y^mjbizsa!#dPU>8houk=JBM%2+~J=o4@O;`;W=zy+62^&8qoz z7pF7*xvm7cKpkt`vYX&k0e?9Xn2zvdH~}j%URsP``8V)Z=4UwdAbuUF7$1trOW%25c zN@2E~pQXsTg@G}AEbb0hNUr|ACe#Y{L;QV;W#OQYMlXaatjK=nb1pl1Fpvg413OU5 zFuYrPTlJ%q3&6n_ZwZdGNYipH_?w4&)xPh~k+s-O-Z`>e9;yw8-F@y!wS2D7A>0n4 zTqb@m!RcjGa!B&jE(;mhgF@33Hvl(7nByVJ8tb-mIsV9h%wMs1U;{3Okg-1Jl@*en zI`{I?lYhy>V0Ou$csL4Y& z>TUk^<v#6GF*l&V-CzGt_ zux@@HyU?|F3Ix&6>2Vs$ugulIWiY#tlb9!Wp!Pq(ziu?yx zI5kuPx1tSTDiH9BgwUa?m6=(swjk^iDEVwNfxyI!x!T&b{!~Od|Z8f}KTCkxlbMEUN z>0)pQ~>mKumrkg z>7TR6BqlC6kVWwuQs>Y$TOo0~cu=qbe(DK7ftl7mu06NfA4M3#(S;2N#xmk00vc#o~CMSE~AxdpI(t(y-lC!RjV3`e|Bj)bzK^`2jEw0Yg%dj^PoxSdo}oF0bwxunrs~30KX)F zRJB3{3@XH@e7Ut12#_TDwcRLn;LQH3!8M>nrn>ca^XC$fkR(E7Y!lIYO$WMr{Aqtd zDHfdJT}S7K^PU40buq5S2-WwqaGx2IhJC&-P-hRpQ4IXh@w#U1Rf9zIMQou^%zfY5 zO7cQ}n6y_ut%gqMMY(PA8@eIs zXiuz@RF*%{k;%i8bPlE3L^b%Pye#*-1eCMK)W7!nIWEAAEeL4*IbeFxp7=Z1;e2iT zaf0RkCI)$=(ZWw*Qy>ynf>7PaIw@{Spvlzxcw$S@6UTbkFHcn3`R7RpV+qwc?*{m@ z!1s=|)=rC`#{|VU4>jBuV2RAatmpP#l~ep7ki`^o7bT$s6Wo=x#pL57kGZ#KG40Nx zwK{VRLhA20-cMVt%fHx)d{FB6(Y4&if=dP(v{j%;$A z094fh;i2INPADlTuD7fRN^*Mt^voWYV6=jHfpXpbQKl>1TKO!4N82%n%|vvW;Cnr} z>=^3P(%Aa$;U?G}{iN8KJxIQZMy6uM!(t<@Kp@3UtQjpv4!q*+;-I;e6epukI>KZl zovys!tH!0XRPfp{U%sCxMP$SS<0tb2;AXs(qYVD)TKpQRg7!khW67S=fgKY#YaMg0Wq~hA-?(V9dYy)Z%Qg- z>-+gFAAH{0dW0gIH*LpDDb$lc{fJ`j9f&x_%EXp<>rwXCsB)_#&iXhLnoLIsoD*55 zX%vnTK$<4*4jr<$V8aNB>|K~6J;vuzrL@J6Br#ARp)W0a@27Mk@0-q<^IPf;s-e!G zZ;~06KpD(3_7eZpQ}?$!wJk<;(x=L_o{G-R>Fck&JzjqdFSsIU%Hyi!tY>=jU7HpPLyD{ye zD*bIfKe>moVi^2zOZnFtO&LEE3QXinW6fPD)=U9;^{C~4Q}#brifou9FIotPXJ(SYNw{ z(bKXCbT$Q^CV)>ZGZ5b+hE&@CD0U)?g;B%dh~ekWISk}_B0E@<IeL)m|Uder>eaat{)HfS8L{ghtPO^4$up2&-6bapyG zGbY}Jn&P!V1!2rr*y&PZ+`%X+@e$ScJP?x2`S4^rOX5SDr-;0 zu{S;`4FJK>>c`7nU`j;Wf3a(nAn^B_5sj`J7r*%`L2z$=94v1&|xyrFn7hdbjkrF3Mw#_*~n|D?91#0Uk$fgF3TGwe@PeuQ)k&AZn82bw^ zb-Pe&WX60HEOCPL?U*8wUZG>j7C`P55J)-J&bImdh?%tHj@v7f4_9ap0&JVNQkDIC z{g`u@j)@MEI1_ZPhEHQ(ABZpP<>noi3D0ffFDhIwqDqYxtLn-xg&e0c7rwy9&iwK2 zd){3yIj3&|Gr4smO&^V5SS2ET-wq02K~eIY%9QmU6tv3Xch{cwe{z+wDPUqlam(d> zZ(Uy{5ap(Fq5LKM-P6t_r1u4asR;cfj~sTJ^K#i{0oSMUaK9^cw`x}(^hbQ;z#~|u`AoZIQmPnE8&pawylNXyqHILO~KH)x_^h9Iw8wv(i&wrJa9q5jcNMp+hN*o17 zX%}__!*ZVy)-=dx`~LiQzClD;dzRqF6chTT!dP#zyA$JM_J7t@&7}tu)V09}K0lui zhjC5{tz4UjY9RzlCD_5e9ayj3D#l0uIQb|k(>_voB8lrabpI>LU<@X3hHN>I-m+JX z$7Q6qAJI+r;YxKFgAN(>I8|nxP4T6kf$$@^RJC~XQ^~7DOU!sj$;n}_^R4Zv`apu5gxui6es5- zalX1C?vi-R+Z)>ofG}2`E;(?&)|3fX7l$6rA#lMIYMn2J?{(f;jUJOPLlVn z*lm`=B~)7!8Z|SK%Lf)?V;l)Oxr9|dJ8Ne45-8?Y%k<)v&~Mc}b`i1199nX`75q8N zdClGaNH^d+<6bTjd<{NA3(2A``P`~g4w%?rm0dh!B7BITcj!_e4n z`)8|{CE?sB+3Cvu=cgW&C333WlBFBkW-mR(GGW{D2b3ULEXze{Sunu{z>&TM8+4;! zSb_3wYNOA$Utq<{8{Md5r0C3b$-sC3fk1HWZ!aUs!!qTksME14n(kfg9=LR>rY}T4 zGm10|9ovplyJBWU}|T{l7ou5xG6!fJbqh0iSO=;2^2h1}UE|*LfG| z+#tv^+^bAoteb(bqy28IL@1CX?Jr$Kn?H?w-^ojkhrOE-Nl=RUkspOx_v()vCCgT& zNW-Ck67zX%?ypUd6J(-EDKGiTd4b9-c>nF+#?yw6J!67E!`xKeeqb8V1YGaA% z4k@WU2YHgQMcq^Cmo@)1jiv<~-I7TQ=k@!tRzIE!Uh-Ngxe&NH=>8vbcl4+=!Wgbd zhZ(R)P#c%n`H;%IwfBqPz7B^49YNCP6|GdIo#~z?Si)U{yR9m%zOFP%L~p3ymc^56 zDU$~Hfhx0OLa8w!gkXZmKOAOXJIyC5ub^U7?9eDt5l)dYDd~TgMbilU^Bx+}Lsog& zZC@ldaQe{nOTS)fy}(6O@7$2hfN*+M{>g~^P!8w}VZioGIkw*gz z)>|4SKP63CeRNr9M6+6Fy8~)~;=a{mO`Gldubp=<;Q67ri>jm#>tVT|dS|9weu5m* z9@%-wM~=C_G+P)nGvsK?c{vEbinF$q@68hIjRKSt*@&&uMwjn&^71+`G1=rj9vW*rtnP-{b3nG*EW^!RwG$W%{!-n>`3Yk}>bBiL^SN zNk$*1Wa3lNg3swO+iDLr#V+4?@N#t=KhEc{r!pKvve{xhRia#Pef3iBdO4;w3PvMi zEYd4kai*8+U9TX*M~;TAWj9e5=?eGBSq8Nq?S4^O)mxU^&2Cp$&?z{QzB*PT`$bbL zw*u-DM%^aw17EaS^{#Q}4ed~L9{YmG`MiwhD+Z80YjOz;&dw>l`(Hju5a@&{vz6n0 zN?E179fVtk-kvFww!kV1&YvU{I+tZg8wq;p%O8ULA~m=F=*mh}e5F^d%;o2nZ(|W+ z$Z!7-po5Yv;dotM9Qxg2g!=O!0*#Zd;2*cgpCeFo%DOV;=oEkT{WrkivDm9d^yQ(a zT1MPRRvykxpH5;=Ycq!G1jU1O2_q*tOHJvd`#>jm9~E zd3n!gSo@f3V&1{SuHX!cFc6JGw?|q^;hQ9UL%SXuhA2_NTv(?CR@ftgArKKA`v&A5 zBrQ$Ch~7NI+htS8X)t9;ytgmG&=VgQo&?8XHT+3ggFTje;0M8ceCQ7T!Op1`Amd2w zzOHu-dsH6k^iHNFO-;f?gHvl<<19A7h9dZ7k@xAg&42VV_x##*D;DHUS$1DlAMQ1u zY8^PjV{6;+d~{eA$d{L4)>HMGyU&V$9DBfDL69X79*yxV4LGYK z`;E?HX)QtB)Eyu%CEE1<)Vw?|RA6worcSrK`a6qBxNHQOcCGD_!19$kR~b1jBkB2D zg&nc@0G5Mtz+%a_4&0DUhc$-^pyDQ8{;~cowzlWeV7mT z4so1G>TdtHQ7}OO0*S>QPhAYMgh+ZjB;_G^Wru8{Up0Kq*yWyObs>=vwd2o8rJ8N=F^Ls{-*j+#8nxmTWka z&t7Z0y)n%?&Yif~3J(k74uGy4yiHlwhZN_LK=L0w83R_K59#BL_`$tf@Hzz5UEpvc zD~TF|$u$BHjJ|2`wnT=wV!EpErO!_hYR<5#NX64Pv`YXvD3QME9<1m;GB`NkPH^OP z8D@lvwwAP;ftC*uT+1@--}~{C;|Ji-J%2(eFz5HZ^UmgeZ_t6n6XJQ?c_-Od{$Gcc zIS)hrx@Co}K)7?88;<6N8?7AMD#=|0tU4mQwsU)_vov~kHy7Cacf_o`N|8Uu`&KX- zMNsVlosz0fZjvOL2fpSGEwi3dkwr|Qj>`DtHWl#&Lv?pd4;6EcIp+UTi}{54sMR6; z2I2jsuk=XMXv3E>pMk;}2g$l0BgvwcfHcCSfB~YfNZapixq0Pi~%=qQvjg_~2 zHFzorl5>k)z(t^rl22putZWv=GslF>D^NOjJ;lUu?)ohEy*?KF3L=+ViPxZx1r-sp zY#idkFzX{PHUm!Kgw_HGZXc=y4^L=YuxYUn28QSdw9Y=aV8eOQU#V$)OL4OdQYL=1 zE@QFbeLX&l(F+0rKD3p(AJd&fY@L_tAf$GD8(6;$)#pQH@dC4RM(=w+1x8+jOz|<` z7blO3!nE#kjTz`i%_e=ZqoIYnfI^%CZQz^)s)E?^O#)djbR>~fiuYC&2?oKA%kpLC z_KbxoHz94mscbQ%+N-f_5JLVz8z^Uv3m_`qo_XeZtrHGIkPiFk-M)HMZ1fRSu%Cx% z7zaNI*Sw~gJ>+^3QV)K&c*Z$CCYYhJu77db@%(HR1 zSdRwJ&5crUDe)brNDEVj*}sTObudKN-g1kc@vBw_)aD@j_6vMLH@OLRY$W;jWH_w4 z?JNY1wkm&a-v#CkoR%!pPpEvOIoIs#}Q_}{JJYvyS;^}Z$a!ynT9-)_v zokRlXFxL5$HiKUH72CsnkexZ`wg!E1F$FDyP*X8R48sF%zY+PqaR)EKA6#qtn=7z* znq#BJM1qraqC;r@$_Y<-OaWvOQy)9!%T^hQDq`GjZg+|7%GuH{cQcen?mpwJd|hgB=(rLCNZBoyuVhJ-YIK=6n_;BWUxAhbZLcKU_wqrVk>Q>k+jaNDZ}RSCk;|3Rn4oTUw#&y3Gk zDyd1aKFQ*4&@x`Ki;1j`j;$tTK6kzIP!2l?y?|+0qUCbID`Vvz5$*UY(Aoq{;&s2TGo+t+#5E5kk7mrR^VOHz}EFCf3PaF3yMSJ_;0#knJj-I(tzu; zysi*;H8*6+Kfe`dVCNfbp?5`+;`%jP+F$?iQ|55;>c;Vv&cS|dXlv*2f3W=fs~!H% zM=xO-Grh$Hljrb1{#^{JNkoS2l*&yYr;3B$s&H%Da9w=90^nrby2tqAGV+J-zjrqynntikW(;M>&pKB+o6OWa4?w&k0@r2}m_R1ls{Zpi?sM!N^-Dwb>wvNVT47{@k)u|Y4SOM`cFJp2LXxxgV1BicYqwD*f2 zK1>Vi>3(3=?O8f!&5woO`&OL@F1FH;e%i6C4<+kkz0YF6&pdQ zzMYg%cwuAf-z>qIYAL_`dSZTk-=|qZH3~AmYtD;59BBjKMf&nBMpR^1CBU%+VqN`} zF9#dcRF}Ml;9{>^Z*|u~mshq-*qbGGrUvXd`g!J#Hm?ss$-?PH?W-!gyz#WI;}v9D z_@lFvO1}H{CxNzCkmAAAX)@tIX7rGX6VSnT4_eApE_~<_+1zypDoRtamVqF~H|2pvz{7{OAg! z5C>41EaWjy4%6hrt9?o5KN*?$qMN2KMYnmajyh1?YP;ZM2>fv_C%U`Y&!_PH{0?HB zMAJeqMbL-{zj1vm?#jgZh^jU&UV&CI!nnXu7W>MmN_`K#M7!)Nb+_J`xS)x=XbzhP z?i_4IO2O?DjcNd>Oet(7IV9uKC4#{JO9Fx3>BKB40Rlaf$(#q3+dZ0e)DN>SQ$>DC z`-h9y{KgPP@$_nMV`q9~fusi|H79&rn87B%N(hFqst5$L&utiCR1?5`T@Is1;_f~yCo zGU-XSS8t;_uGlmdc!qrJ$C%q>1m_JhrOl#fNtz$W=msYExZ&gI zwTu|(9U>w&tPjET7Aw@7`cIj~Mk)OCxNMGuUv8g8aTkB;JBSdgFRY!dTsBX&GGVC` zCE`W0lgnf!$0UI~2|M3Qg+#B-w_Q+e%e&93X$skj7W3ja?Ggm#i_`Q~&L=*+A18c$ z^co9)qnEBb%q*rx8PPf0;}&Wk`HSh!EJwwrD+`HD{3*!i&nkWU0p}Hs30b#{39=tw z$6?4G)#%$c7`BosnOp`38VY8-dCM*KgKE#xQBq)mw;p-E;At{S!0oWb%T^+FnpfpY zE6v?blYj$zgR#CN$vT+YCSZ?G2V)!W!*h4T)sJe2Y!j7*^HHy+K?7Xz zUy(Yc{S!h#5Ji8|upEAjJ8>+7%sI}(1z;xA5UeFUsfk0r8iWyq^5g^dilmJ86lzk$CLzEM zZJ8^_7w)Zp`6-s0H-_{9dx zbaHsi%Z_g?N|M82nYbgXik;;3a)d#TL4b8v*`DCMT6hVyU}IZzsd$j8HNZY%_AhlJ z;qA6xP~W`pi<2HTg_jyI1ztjK*9{VLd)n!JtWAkB+Y%um+=rP;Hp-gGfdNNvV8E!^ zOf*dbGxlcX2jv1%lv*x2qO6aK(~t4c@w%sj+T96FJ^l_j-34za!aqlU7 zuWu^T9NwM6k`Az-Z=Ic_)_IVU#0uy)+>}hZ7q@GY3Tc(2u2BtRJ{g;t7L*5BuEa9G zBTufe8u8otI~o}`d|&7G?BfgL#CxEN6mp#*%%0==taC1d6Lj?k6a!|AzTPf2g6@MD zQcD%i+BGDA0%9~0s^cFId4-x2@xZvR;-8z-!}W_VMjuVgM~67Ddn0UAfgHD&zZYj9s*Z>|{bet+|EWL`L?{5t!m+JZqH)r2X)XRR$7zK7 z3qO+Da8N#60Y-v-W2vQDJ1I263^1v`?a;|%7IP22Qi*UF-sabc#PMc~MO3cAU~3th zHsdh#$wx<-Ox3U5?!hLx^toM-dGAc0H$wK3zCoIWpAPrp`4-2$=8kzybz=Y|pTG68 z>Ew&1H0gM}y8@k!E!nCtma#-3Hdur}RR|$H*L9?K;5N)l;2Xpf4SAzrfI=aa-F2Yz z0maW6gQja|hRsf!RK#8hp!6fd9~q)#)f&f8X9vyDhH@ikRXGa6_)tS)SYzWY!5?!Z zl@6o9sYm_qvYf7fE7?WVuph?Eu{rH{=^SZuYybe4eq9q)I$lAJXVnORLwJ{^?w`Cx zwr0Il0vGe2;-5YZnd9kt2J~7M#fj_iP%vm==n6nv=nRRlHj{s@gKV~W@MC!6IMrZ5 z7n|hDKLC|B&lJWNg6!IikO2CPUiqS3&tN=xQ!EMWfjL<*{&Pd0P%buw?MPE>a+SONfz-FmuKni#4a;-YH`_x)z8zPm^r)dJbd}F&cZ&=+Euv7aEKXy&x995&A7udj z`aI`xpl8v@K=6Wv#xJ0-5%_!+>koYh#x+u~J z7vDu!**nm`35Q+SKO9n|M*5EQs=Wu=c>n%&8H>lJwyd^fy0&pU(Hf1%>lpo1QwzFV z4Gvrv9HwvIo+kh$wxT5k{vz_B8A5qG^Okf>pJ8UUgvtpZk@WdF<7pWG538igIB;jG z@6la7?R)(LKNp|?{Q%$Xlo4+uQ!1pXk{PW{WU(xPASsDC?bBhb3qzZBU&Z4RuMz7) zUh>~BPB3{mV-_TUd*;vRc6G!kC{m|W7X#&&)uq7AJw*hz!9&KIoEHw7`F@DFHBfTY zK0p(ae9`Xb4dvx;9YRDl?qW_YNKaS2atg5C9 z!*q9d=queV-5}i((%pz4-Q6H9-7PIjhlF&4G)Npky8D~+{oL0%d-lwlHNED!2V15_ zwWH9&lDt$#inO+SxQY{p*+AqAv6UpdFMiH@^i*?ayJs-@R=!LAqOGk-E%e?fTm!5l zMIi241ujxqonS>+$atv{(YbQ2b=)merrv6(pk^?1@ge33+7wd`DB(~Q%ACDL_JJKR zF9}`z_5DjNzJU>L-vJRDgXHVc$C3IU8nW*cQWf1?bn`MVz>X&WrOfX^ih@i;XVBYB zJE@_IT(g(#=UuZEN=Q2a zW&km~pH{~VtV)r)o}mAvCM7c%U4dO=n@G=yI;nTVv-^$BYPA4iEYjM?5n$0R9d!ab z_g@s#cOqeP|Crl_85GBk;~yNdG>WJOiRc7PGc-TL^YoqvA=^HycmW+0@BFyftBc64 zL66}glb|V9rsedYJYqE!ui4Oh{e@4-{Fp6sUc*HxXZv)1C+31DxB*h7UuFNSO8sf* zW1kycx=fLaBaAtl+*m)fCFc%dEfTDc8yUR%21#`rv_z)a9s<}Uy{Km6vMHdY{T^9 zsf_rGbMBOp?6P!RTB|b_KL{*G5R8qKIULkFp}v70xgzVDb>b>w&RQ95gX8hZX2#Un zJW7@S*w3NoV7y=3bJe$;j`>*=UI`}Dt5Kh$! zpfBSUO_x}RfxS3y;FZGWJGZUuu^$3Ah2{qm;mhH?!nb{`{SrB5KV zT!Q_ia<-(7E>!1*tTpMvy$DhH5)#?{^}{bzMzm94o9a`jKz|JGjj7Zcx8Mkay><16 z1FZ^KXz=9h+u64a^;~=FL@n81FHw+oN_6-}POe}CoCDZ|JSNXQMD%u~7JyIqe(?BI zPq_3x#*$(L!d&#!U`Nj6@z;~tqa$4ow&(S`nU01wdYQ@qL#pO(g$j-tQlE*xkX_^S zsoI`D4SJ-lbV}_v=L~qZ+Trmqt{GxWj@GDuWTO#b9Eu;B2TK{o;6P>$BIC zE~hS4G*Zl6lD(uv2hPR(+;;p=sIJXAs%t?S($6N}#O1cLjB24zKTcB8y9TvBXRzC) z$R%AE@;PZga7v$pN=Ztt638r2+)Aq8W@0FMLdVn?B3r-Y!1qg$>tSpaH6l#*#O9ol z^CFNE4ZNW?@#2>v-BGXZV+yjm*`R&137yW!`Y|K73Tia`#SH@Ydwjd4J8Sw7^r#WK zp+^_WUFH%mo@OGm=pgp;*5u&7lwV2;Ez!_8BiPF?gasqlQPBa|>~r|sXB7i-$&r3+ z^KYS%MhN&98#=Q`{8OqIYhYvZa*Nvcz@ABcnFZBH)|zy6l5tvco>q5_rvaSRGm%V2 zqLotP&@X^xKnbda&Eq4NUe-NJf2ey=?*2XPDhIJyGCpqeUQQx=&cMlAERyuD{iF#DK! zr8wN=-`-&eF2?u@*LP^2EXU4Ms?+s!I z2zJ~5Xjg^?~~R=HXeza+A+I3vu?aorCy zpi|l-pcs@C-&~_}(*}HPeK+Y#0;ADx3^tV#XZP!=1V*&8!A_A8IC-#=c)o?4i}9zj zfWuMUm0}4NPf=&NLNGw(37#@n2m}!gK~J1?tIonpD=AmK7nWm>z@gQx*U^P;LMZMK zbnmOqsp1sDW6V5tr0oWsyFLXneQt|G0z-8MswqyUdHIJb*|}EQRIgIvmrle!H1r|5 zZ?(i$X0Sh<@>DXYdOsm^fe+Ntc6i}f3vS3km$La2w13(i)_G>2>51kPeJ01(^M1Fs z3})%~HUm^|AjNyR04$j%*DOv0+fD_-sXZe=;VMT(DZMvP%F#2VzJVQ0UfHdKFPiYl z&ZC`;WgTe4-+x-j7ZPqp0M22+BvioGR4u9&RuXP}43XZq~yR z+TEz*z;C!nLI~^(BZS@yE#a~rBNveV5jctgY8O9a5q_4J`;r)pyr9A8OGnf+mWnsG2aPg}{)(05+3vbQsAz?5BQ#LneG$zT znuhLz-*uoRt9{-n_nv41nHm<<)=;EJ|L%IS08DJL6H1}n0?t*}V!-a`f9NPfb-l{u zJv>Kylxz{(8L`QG8{zsBahcS4P3X|mdX!S1RUqsb>7(zn1s@C0X> z^;$wBQnYd4RQVqas~p?g{9tHcLY%T;__^@I^=>#Y34&xXjphA*AaeVqFM*ZhOK^B> zY;b znYFptK;Jb;q0Zw$m#a$)Z_wP93N%Eby)zs-DX(NiX9BC>WvqnhL+l+FXck||Zxdd* zKmNhyy8!j}-ziVwK}=~aSO}n9X+52q<{UEZ@UK5J79I946&%=*u#P%=kJwo6h`+@S zd6(Q`4N`+A+{i@_N~S-m9$FZN2ol@5Teer9_{f3H=$vh(Jn_{`Wlm^(f@DjFtBnq8 z>b-+%hyjRQnF}Kpa;;>X-zyN51AzlV`>$J-gt9#Hyw1Qxx3B)AC`V1V!59+=SwMR< z3B&RHaISp;g3hF|T}z0wMq9Ez&8TNYb*4xw`9Jl$Z;`Pk=+jF43`R=DTZMYaXGeO$ zZI5%?+Bs;YlcTtV^4-WLOg@(GsJr1LRN8DP&x%6NM^LMrLONb88Rdc_{8vLV-v20h z7?8$5{GE(p=PpIHd-g;unDs-FZccLvlyL#OIZf{4a^rMs(cdH12H*<$cuz2Sl@RN& zRbh=Cb%s+X8?`;bvF&7FbeO~m!4n5qJ|95jRrHk=)0?ZhEP=klPQXCz9Z~im@x-#C0Xp607{^AU^AR!!ngh+DE!xYakj4D+W)65d(zL)*GEPVRxLQb7k`j&B#Vi*!`b z7y#~Jp>xAxqa;?qw5%P6b?RlHp4N*HNz>tBu7!GlV+M&5a+A$Q(qRx-lsXl}f}f%D zheKQ1%9L&?<|Dy$!BDCvNfUT!AggYHK>~i`pduXPqX}I>czebPIbxTT(z1!xiz5UN z!cEK$eM=(NCZyXoYx5^KjZxpIR#ge4sQNj`4=fY&Y5Ly>{2(~zQBHJjTp9^kqYZK~ z%}{TUiGq)a4?A}m3SNZkETf^*tKtf4MY9Z7cJM3LA5w|D1o{Ty|9ZikK`|(fw{!e*WhDWaI8aGn ztcgz*HjNY-JFD-qO3i2a2b-%l8N|&HW#|e9VaN>5LWdEar7!jLMWMtDU%|L^&~|^; zNxMH+vgQ5;e2y$bX8>=KS1cLb7UXP)WH#dWlXfwPz%-?U3|04S!AixO8_|Q0DO1?= z`IH;-IXcJeOOh_*sHbTz z#Lm)FW?ShOLhK;+d368u97jw!f>Pa&=67_uakD|^X0}^zs0yB^MUnW;qwB{)6@}$1 z=qsd!THF(Un9%8OQI@Y2aGGSAduF9^_uye03Xzpf!j2#P#vA>m0JAIZ!!?H$z95i= znyPjUvr95h<#(M?@HPog;aBUlwdGwj{U`5GO70-M$Sfs!H6o^-L4(@7sN@X`6&+Ld z<-?Jz?y=B!!UB4KBK6d+c(G~tF7ob5Qp4GX2M5zitht}$J+jY}ury%4*f*RaC7-OEmktzs)~*C#>D#7e^l+h{NfRHwwZ<*2=eVF$&>GvaJ1MO5z}uY z<%X4{HF{uTDsS58g||NbL6pecnijEYqnW~WMi{coKdWo`*~o)WA#v=!D@Ci4x_W*( z$z6{N)3N~xiqOj}|KqP<^9!E5o9Lg3wQ1%X-B+&@U4!#OzM{(Fp7^Vp{6g=gm;a$1 zDH-thkCGT%ZA|1Q{LdRNmIQ87+yuU)gW?fg{7sn=tHM`CQe95mJ^@OX?I)S}k!8W1 zaS(oHcn|W>s3^5Kgswlq|4`)f_vBEceyFay@ug|5H!ib9MbebO6N-mOe_X@o0sCCy z#=QTzn&dk>earAfAq;0qN?n}phqK-NgCQ0?;>q0|dE9s5($77Gb6T%P*mpKE|3Y_^u$n`%46tNxxYv`jZCHbGw@Ntz9p$7e64(vKT71sLLu`Ys(Q}9G8I96@# z!0k^56TF9VwuFxRV=su&dW+f#GFKN)?K5u0ntU1|)Zz4FGK95W~Oisaw=_;VWL(|ZhNT5RI4?{+XT zi;ejo_?U6AO}-d0Q1j9?Q_VWQ*$ASF{DMSPDe7>|rn5zf<%a%L7qU8e$CtOKn8lOU zee+rHd#>|0X`$>z!Vo9LYk5lHEaRnF%@rO!g8lSAMMihDCFryht^yCME9ttZ?liD} zs!b#imzC6d6^h|u?B}9TR4CtiAl30+O3%<`E;6HoR?@Yt*|@Ld^QztMd0|6d3~r<8Go(#hY>Q z?KDJ_c;Cc%~9A=kM@i# z=L8IkH%+jAVsk`8iE8-8^rIYL2v$#S>Yd>4mtquA9rB}|)vl)lmN?&I>1>fq;Eg(EUlA4HgU!fpWR;@wd?S7^p4bP=V9dr8MrFgM$*1jAEQ7BO#hC>Lk6yAi3 z=zTzW>*0&8m6*kw+#pI4nCy_w19x-zEmDX_71u3J+)p-Pn>V?zW-onYi^u=!JTJyl zJ(2VSD|d8*$zusdJ({NJTIoK`U>&UUPWWz-ZvQL(#J#9+&2tj}i@Uoo?z7M+dlsS1 zgw<`5|3wwSe{`a=N=DhL!tl0n{ysMQleC=n-+pmB6i+R3eMn@?6mLuQ4EHN~s3?oU z8RPt`u|@j5GbjFju-JO4>B1qtsiLn-%8_uRfxfZhJdZJYv&fG8e01+D9Hdcqso?RB z2po;wbd*uk3W~?M6g>T)`tJKC*R*Q1P-OYx`G@G(y2z7_2T3nBxi~qT_Jq~_l-U{M z;<-DsM`PMM;l6NA`@_=Y1*y@U{_S7f5M$;e{|?%S8p)}Vbi9dtE|v9+F(n<;h?cAl zyb1BXHe{EhP{~+mCo!h0Ap1S(EYm9uZ=w!&UUp)?*U7=PtQwf$WKxc0noIWLl#lrg z!>8^hnC(laGyl7r_gkbire>`^;?Ku>j#f|`EQ;W=%QibYVQaQRQ%Ua)N@M!UH`>m2 zhLN?#66-az70S|N$^C>9FI_%}!}{9&)S;BRV5wLCTnp=*HNn{*RAA~K;uqfisVpzU zEmAo9*YFnk>%4DQb+$Lo0Be*pJ_Wmw_#@w?LEWJ{ ze18N zFuZ1_hiN8~=h^5A-5G|rcEZ|f$YgBd-XC9EZXGo&J60B4&vouI3|Al750tw5-yhtg ztYOGu9S>u4Vxl+=qO1cQH#MW%f9{l31O=Vd@ zIAnMnnUY}^Cs3j0L@(g^1iM7JLog$a_BTcPALZxU7klpK!tr0tj`M7SH;h$B@*vc5wE`$(Nh#3nkB8 z`JRj`);*u+OV=GQ@y8@Zdf(?B#HX1|b~&@aQ@GRWS43W=yChHhJ6t?)4v4QVr4)ua z^}qcZmbYnVy|vRza*GA85}%tw>Zc0C#?DK&LKacb>NYt8sUZ+!`a}Y11p6$?w9bqh zj@a);v#RXxGg));*WR7!cg@Ez1;7o}J1bt%2(*1OZ=$F~ju8}oeIIH^cXFC8H&uL6 zz8Xkj%Ix?Sw=37-Maz$aKj#B|D|3#szAui=6s`2qpAM}to>CAoa$s(SmhL3P9-6}n z7Adrj?c!gjnvDk082XXxuf*N`m}>Ltg=Z2zsl;Fh4|9_&%bvOohkp_yr2ZA&wuST$ zEzw_VA{^|0XrIaVvwOOGR7VJbg~OzWWCw&l|+_< zM1W!zq}Fp$j5Np6O*yP9ruWQj|F?o#G`1wI#gvspwP3*`XSGAitO((4%DzPSmHV#5 zLX<{7a>1+1TqJ3SgEC2l0Z#pc+$O0_gg~T}s1ic(E?iRzm&jWf{bJ5XBwXXEPez93Ik_IhHy?NYIbhroJfr&(3-J~ZimhAc%-qemS=$oL z<9XxZ2hk0Ju!GXiG>Y)KAFuZ)pU)X~O=xXuU(>LA>MUu3_Xs6#GYNGPaqiIhUg(Pg zcvdYjG&r7T>1&9=N73!Bhcr{J>k%xxv}|n%U}uXUGcQqFIp$y!E2k`G5;OKo>-%cD ziF@1DlRinN-glT(vxgQ8B?Uba)9{lXIVh8=6E})dQhBzm=R3J^1W#FG-e65yKK|O; zU+r%g$1m@P9d#O}OEu&J1obuEwboSS@_A^njB1=igu<+hP3_*vq7m z;vswpE>1A|Ajr;Xm8_6Dbi@OP+6_{;7aWCs(tKkJR+PaoaWLfz>-n%b&?9G9sguxr zk3%cJLxF{Nwl*wS5XoYzS8ovd%R$K%7#U~qEVVp(S!{UQ6r|v<+q{t4o>dc4L|}oj z39v6he#v?8_z@h&Ght#vh-m_Z3M8K;4E9J^6Sj~^1w(wz&F2o})5F8pMne?5aS^C5 z^Tzw%laU?5UzO%j*PGosGK;Zd#w`k)oXfJ_u?e$)+?FT2<1C;3QaM-57)1}zqPd=Qm2{|Cph?u^<(WR zcW#dzGw#g z1fy)WnPuG+{bfOolKtmJuD_>pjo5^525oFxnWY`2Z)pM@#Bu)bqQn3FN!PUy-V2Ex zhyr!TX)MQ$`BPE5ZFs8Y*vr;N2?4&yuae9Vb#ei;Lo%`prWH)fvc#gJPtRybFUeDS zve+ND19^%LpZruv^~lJQmy|4urzgb^C9&~`3WHJqtse-fnJxfP+EjMQeO9?oBUM>;hShlQQy4kg-_~NztLt>NXR%HWZBgBD{ z`?JX29^ED%&CUCvZ_@O`q2#+zE^e;E8%r>h(?P~2LL^sjsGpkxfF5~sB2y6a+3H(J z2$bn|JIB62qef5GqeHNN34D(%EQs!3Wq&>}f?5!?? z#&l^yCB}EIp4vcyWY?U1WG-d2KQjmZ0b#{~Z6usMr74K{>o?UtRrv(oX*uNKX!y~I z=41F>y&6iEQf|bFtX`UO^L8P=h^6vq*8XH8)ws}HBs!_Tbqf~5n?_*9BFKh2ca^F7 z-UoYqpfj_@EZiUJV-DA4tyH2gV;3BNST6pA6?{i@&gJ3VomY!PD5!{k?eCP%=W~NY zXe_}L97}K{C#bXW0>EHOh%}V4R}yVad)GKk#z9Op@XNw0@X}{EEv!HxI55{_4fgR| zKCvAr&tG+5f85voU}0wq)N-e5RXE$F5D9u^nOh0!%zCf$`Z(uPKB0O2iZfJgUI!+aQu^&{;0@rm|C%&p&}ElcJtGagZ^ zq?Zsku57Si^MfIvI<%YhCX}8C>J0>`E{+(Y85ex1i$L27S1794T&+i}GilA|p4@Q4 zP*FDu*J?spUW@vwSM46e)p>uzdkf>mCS{z+CJx@~s7r+kN9~^O z_NC1k6&$j9RoZ!$oVtb{-s#Y?hF8Xh(4hXyuY@=^7Aii!=FS$SWwEX534d*=z?+vj7}h2K1v4g@#cB|z4$gmK9YW+* zpChIBKH2!u(I3|PA(kSM#b)_IGYm4fg4jw@us(jOeshCFxi12U9m=m<*z$s?9=uY0 zd)UH{i9GArl;z)ir1E}YUO_~yH`3lH&~~Otu?6bEl7pWQ5I|ZHwoyO^Sh4@wl=0;< zaHUezr8?Jmcy-q+GVU+3YJZM<1Da2dC@*URe#zqoS|d;eE=alV5M8s>LNfdT-i$e_ z{QOqC4jB>$1=$dJjg%JM(3n#h$<>|`yV!j$AKgERKcm(O|9{IPX+Lz7=%#d4&`DEA z!MNSS<+E64Loy`#w#kfwR7oS3_s5|wPP_<;wWeE9OYG@c`1#Uk_^7ILQhQ1FJ2WK6 z5=mBmtyAxA-U1Exu+6zp_TLUXVxKs-H}-8h&8&=Xub^I7pPF=%G2+z zDi>&97dm^wEpqCK8p&$e#Nv)DW8B-?I^@U4&)wMf&~|sW6*8-3yR!=SzW2nqn>7*9 z=Tux^7K)pvGzg5HTF`!+PN-v(;k53kk+k5LT!Po3)8#62z%UY5sveiv|*aCdL-zU&ta0u8bQwSGD;n(uO%9J<$pdEFTD7 z_FA4k-xaQ-eh>ApflLF2lxsOBdlMBopZQ4}AYqvt5B;55ux~PFt=cybyvni{k_D4A z-8tD8|32=!S)_vg4@83G&~~OhkU*0+=_Axvh3T`&b#FjB(qY28>8ZzA1>nPCBt?7D ziU!Z6x{xWH(T3r;)j|QJ{Hsy=x?wv5G>+(?y7zHb$1TKcuXTMt|0gwym2sl0qpD{) zRlv=>F)v7U01}ak$L2w%?H$m0uHz~+`VmW&#XX6m6k6=mGEeXG64xfQZOO6H6c(HT zWkbD=KAKjP9x3#42K4A0_06my@8N&#M$J=zG_3bd5}^b8X+hn48`@4vP;|@?*i1FA z7v+KK_2y6iWTb*=%qKDXxi;|89@QYkC4-+wq@k=(f`&lZG9gI*5`C+xi0PUJh+6+o+AGXU(5 zy@H10N}3@{j_Nxd_76zJwEv9Kqi$%@+%C8?DVdu0y6@Hx-ucB_nfy&zJY=}-KsQN2Ht~>4T(t?LW572s@Nc7t;0Lfb6 zwhs%VpYE#*(QyqiTgZ3wwz7R6$kVKk9a~s|kRF5}>1NO%?YV^HxTsWZy6vD!^=iVp_;@-Z z5OI7z9jLN+6S|l7Kixjmu9}j5P|5M5Fke2ZRryjA`~vx{;S@{jS^Sf-d~ghvL1&a7 z`!gx4g@|0+NC?;nQ<|Imoa+>If3w`|b_~?e&M4;PWERBO=>d)&`3sEtf=B^}Z#9-1 zuy!(QG~}~o2yqZ?>N}{Md*EA-%1KE| zli>Om@YO5Tm!BA>8E)g7s{kwsv5__saK>i!*B*lYRn~=En6XR&b_xn3fG8F5n;qf9 zpbE%9eJ2VL&ys#JVELagfCLy7T#bicWqcdC9zugM4Ng&pT?Uf8$%y#MfU&zCU~t6Y zn@sGRplH6<13m%)ZT#s!A-W5o)0n$t4T2O}Na}2Bp96FpvU@&FP_j@rf^80fUtN|a z@X|~sKVLY`D|G_@eS1AvoHdN{-|w>nz(cs4m-5dLsKTDu95V$5BUkPyD=u&R_&Yqs zlGiWh9y9x(2zS}@#~SGm%hcwMNtoGwnfD9PKtf;56LY|Wi#TOOG{(Lh^e^u48%ulT zzIy*Y>Oosrc_P9E%mrq7M0>8)CRa-9e$T;a<9IuHJD`QA>*)Q^G_3w1=|0&3C8UQZ z-p3C&q!O|7pjAVkqm{qJ;2$u?zG`o29%1UY2M2M!CR8zf)x?0FjP+joCYT)0(Fpxv z5g_W-oe!pwbTI(M#` z_17)Hdpcnf7=GK?yvESxrdoEpn-90Rshf%IMl<)<%JYACjo;?gIy;%%`L1hL{`D7Nu44_nhXZB3% zJ6i4bLRc1k0Lbj{^X!w!P2l?QYko2SJV@!mI1ZUPAJ9*Mj^OIBTD0iJbY|mefYkfi z4DG{w%HVx|dRr{8ief-NhKdJ9GG+pk8v$EL4PhuQ$u;*rC6$!#CYK4Ol$Y*=UjMvV zP!GrhuVr50 zJ%+`%!At?PblfH+6unCobTZu%=#Y#GFC9W?v=_p)a{mn=VoWMcYP#OjVt@SRfdmjI zJ$P^gRBLJ^S{)gHgca7sC&dz|y{k(KZG5!rzrOzdsfB_gX8=08+ngkS-+Jn@iybgI zV)aAn&%~M|^obzS@c>0BuzE1lpEQV8gu-kH+OG{R>hR^h?Ld8D(pwh!91j(#@@x%& z95nE{sqFuav^bs9#{*`gr$k+1OYI9~Ub8s>!{EXM!^52SW1XKvTcR0F74idvRrfa^ zsMm9DY@F3OuARcuK|6K=tw(7MH5%1Uc9xuj7AP{9*m)Hkv}jd$624@gFLxZ z8U@P@pu2o=2iVk*F3D*O7+5Z`yg5G#(Ef3=lmB{Ze;(_@2K5<8y-C*YtGlj?{r{dq zO+RcVU!rUY0PFz{%j~dJz?)%a$hqwfVYuZ7;*Ft$6wlIRuB#eoEy!x5MbhR}{iN4B zP|sAJI}_wF!d5Pv=!N=89cHmzKDBNXPTU3HJ4-DPN*Xt{cpyLjg~GDP7I|hF-5#eb z_lauzz<1DIH38<7{y-T3hjrBoxk+k@AUq?bF2go5I znoavThHn=iJ>$Kfmcx|B}>DYezN=NqBn|x?rp;=JDO3QoSp9nl!NqxsObX- zip1EiC6ooWC5k(-{j<mPtrdI7#wZd`=YpxI*OJEq zm0o5lBAUs55jqON!~RA~aubQ$rF{Y5wiplrnj!r6u~ ztqH2Ik$euiponaZyX&J|aZ6z1YCgeQ`J6`U2A0x5^&hdNuh5Uni?V2T-cTnY28OSi*%o8?St*-CeR|;DOkr@@!6aDKxC>dJ-w4r(FIvg6VDkWPyOVI z-(Lk}VVgstkSEGTdAELw+IWCk3UxdAMG{@`LTS_tDk4K9<)_$nf*#)~UnoeIjEo3{ z|3HlulH88<4%oV|wJCC$pARbw%HhGyQY>ffzC@ghgQA#dzPnu)wA!(@J`z=a20ZUn zIycgxqtN5w(_Icw;T4!IJb`o9{dOrg!1-29kiv9W>uarw)>0iqL1$JDQbg76`Nkg~_tQb|U_-Z3lk$H`BU?$tt>c zj7&3cIB!HI$l#Ko6qQR7fkV>`)LU8J!H4V>Xi(|@XwmzDAx|lLs^dWy8C^FINW!v* zkvCW-UtL<5_fNsRO($ZGPDjOD%3HT1R_{kF(e`T>s!kJspuiXafTCKmQ&HxKPglz< z4&z`VROG8of`C{Bd9zoi)dOaai65n?iHJnBe=kGvjrjvM8Ef!&$H(r}Aiw%b!*1y1 ziMIcc_1$}*?{en5*^G= zk$(}+)-C(G!N3u=C{Tm(bYhme(AEMP*Ajec2?bT*Q;X;f@D9zmhbcz^rcE`E2GGGb zV`>km%ev8iGuG*U3jnt!CkzV*qw#!pSo2UOX15W)3IQ(#hWig!PniNOLSqY+*^J@tu2()6hkcOpfp{>o%jP|C*8X+Cyek1a*B)oAg?KD-N^@n z+r=eup0P?Z>q0K7_h|O@Ig28z1g|AXhhJLJFj2zH<@`O@zGlRN{g2_Q$zjPBtl(4& zy8l81Y0`oS^ij2QOV5;~2h3&6)qbAf82uz-v8AK=DrG`ntt33)6Sr3W zjZynNRIDmf2tU4uD%<~(=3pMCT;FCZ(*^^ksoaZ|XRpGPHSt=B1F4tkmhsgT!@beT zcNG>{rSF@!2Wrf)2w`9(Qte$e zI6+aN+luS-WqLZky(Z5`2giM1G!_=zU!H z_7d0KMz1ymw5|R05R$kHh*a4FocjT~Ws;!LZ6_a*NI8}uQ5hflDAD&bJc7m-}INQ`npmi~z<#^DoP`xfI=5qXginlI#> z1RI0^uBquYlzXJA9n@ht|4Z74<`2#eIfD1w6N%@4QFiDzCpCEU>mfMIcoWLE;3a$W z4n?CP--2`s+#f7)Mzvqnu^eFN2hzNCaD`d^@1}ZmKGd z_M$Ewtrg1?OmhLGAWT!9s++B0^RUKs6hWKgaPrT4(TY(1of6C<<`>Vp$O2UXonHhA zWIk_-x31Xdm#)OrDBV>3%-$4kRnWEN=V5&rP6rp=Kn?`=Xy2d*z|BxqG5^FD!AiwS2dHsyKxP@y-EExbJTPqTmhW~4P3es>5KozEwvZJ(x*zK3JXCp4<# zu28V8r0c-EOcjV8B!BfM?18PgFPotmPZl>CW*uWyK5at8a6Xj*&AU`25>I}%Q>Z4| z`AsC;vwe3S8nb44=Rbop9!`Qpl<7=cns>5+gsi}3n4xRSEFT+woU-ITrJ_VHBu1Eh zZlIZa6s@A~z!QhvXMfVQIT5VksS?zS$}@R$J7YYCT@d!WX#{$iQxhxR4?=}JW_ywq zF1I=uumQ+6k!yih*Z9H(Us9%M&4TJbwzE>^r7lK2&?Fhcb7m2@)z2@hLVo$hh@sE@a595|f>e9OMGM z*U6P#R#x3ST<`Ct!a$XF+tM2b`!|{AvoOUxxTzFooX?z2!ntk9WeaYdNdB~|KmWAh z=RC5+VhHpD{N}{@E4B`MUfV-;aB!Sw>?0tANzIKlei1JJja?^^hU4f7ChSI z{fjaip~BhL6*>eTI@PDL$;Ja=dxt=l>U6qMekS$8iO8zN`OD(3B~ToSOfy!QrqsaIk%CbrVuTNoA`qv&iQFh8LRvHS_A%VGX2(v5H zv-tUeMJAV%JY)}T{LwUL6ny}ep`bYc9uwT^_49;M8PfHMDTg{t^=HPm{6HHQm7b`C9&XuqU<(LQ;_lH{DB#ILSO0DKc>+L*k}7@O=#UlRFQ=gP!nX`TZ{2L;-2M(l z!5mwp%i*H2%m-nQo`buB;+n6E&UF?WP9XDqyWswhN5k}!2w|Q#c4!3X+7;e2Z25AE z|Eb@y29<9{fL*^RUQ2L~4peDoi_NiKh+UoUzZpScNBl!pU1i-jDQDM5hEqT~a&vR1 zm4GY9(X$h1Orlda*%{h&6J_W)Q-Di{RqQBoV=-f;*f$N(sQ-TVb9rxEf~)yQ;Kw%7 z5|jTKs~r)SzZ(J4H({i|C?0z7RgIxN8E@+sO+pbN)a%?Z2?Kz`=#ALep;ZcUyj&Ut zt{CiQZu5OJ)o!f!@*7YIoEsDD>`m5~dk~hs0seHv|J`vnzEbr~!VOTvQLB88XM(X> zX%c3IN(VFvYNf7p#~2PgLi^!(HtC{JEbYz-U*JNNtF(5S9px6@^&qSJd=jq}j$iuo z`xGaEO2wY;{v8s#uxti&Xp*3qLAF2`vhDht3ZyeP+_`rw<_ow?yo3+01~FqHEwETL zV7KPaZq4}67uB*i;PK5r=A1Q(V8W;{VDV_j7`{O6>)*KRe$u#+>Vl8M19wf)K)p2z{(~K?6S>+|7RMQQ>I=t#5Dl%HC(J#R3dL2% z|H#*W_vDUI{r}t-;8uuV$^HxL3vwpyux%PN8{No0;9fnr1Uj}9b%iX;AreWoDpiba z;DVxBS>Zol>rtv;4xKjh1fWY5eW{tlz(ExhSBPjs7YK^yabH zzpdqFk&OjUu8bmjA<7 z=9E|_Dt8Cf(v2pFW9?lqQPZk;36jsFlF%E(+h;1fPW>or0%;(SsmHF|Y6WJ$DKlldF0(C4NTVEDq ztXOuQ027mEVrf>afO&to4YXUD9?l??MhC%Q#~DswOn6KB`T%3Wj@2R-&>mpO$FC@c zKivPEcm)U4m<7-@&{aCPeG;$r`WAuLXctHOcOz^%6(*Ujs+S2Xq%ty(y8Shf{|sIB zNIn>-!eH!%xGkImO`~o&4D{o}9wE^P-oMb4OvVS>D(Q%n%x9_!fi_45hE};aj+VTG zvr{YZoJDjm$U9?Ud+%axP}0cckb_A?acfu45Axs+hH?LD+K;o~A2#jT}N%Nc1~Ln@=8 zMy*)l*30c-+rj+tild=$O-{HB7?BV6?F&H4`Ikhh{sJY&41YfZYf(|G|EU(7L{Q7l zh4(ZVZwyTfe$I*hJQU8YTL*PqD6q#`&PNAVxjgL(M3dE47Er^!Q&`42JF!c7?Wo(l z9cu~tZg^c&10;}9k?3H>1I-WpnQm&J`kSdZs*DrIG&s10jl1e)&w+Q(@L|8`yN-7r zfvcZ;e%FI}T{qC#A#?!vOra*%51qt5W~<~Bxu7k!HCZ&XmLDFUyG(=sR?6+4IL;CV zwiwy3UxGh#EX)a|EBYm~RT371EB1@u(y1Y**bw*8o=tE;{2}pvd2UD1=7)nlk)OWT zY(!dj8$`Fcr5!VKX1Itfd8~(Sy$I4WFtAr8M%4(ZF*bZxDcOWbCICubk;0Ft@|m4s zw;C!7D8NWYd0#kNGSZg^GL`L&%zyJBi_ChXr#1zYNAXE8bS3zh_C|roO4ai%PWOH} zZH<~-Q|$45Z_zrsh|hv*r#)di$UlYp&^;b2fCJwsJ5$2FDMOo%p1_>vn}Bkl{wgFO zi;~=x=Z2(S`#qWs#a7oJ|iP0^h6;^?TJ{72-h)W~NNPmx` zm24Ko2iLS%?~@W|6UfPK<~P|#r8PgI$#rqr?uhpFT|*Q4)nD4dmI{m2*LE|=UQ+lm z|3pX+-4RfB!R5Zpw?&>JiaX^#qU^cgQqAOQC%mAxjfCh0=l~=y5a#maeDrOb!7_lm zfQ53C{;h~x*on8>60iZ<4dkHXPS>`L0%-(LIISyKEaep5uS?x@b9qlgL{Zp6I(2ya zQR`yYc!vF9)DQ~0hQ-*$fK*~uc%baJS7=}Aa<}?3yUfUvl9O8xmy3ghfH&&1txIdg zvbjdot#B>P*jR5KCze#m-?gVb0zc^d#f`bNE=9cRIs?=&yIl`wjSI!ebF+Xx5K@c_ z3)x^p#>*R zRVX4v>&ZK6rSkDpSN27+cIULnl&`pSzzjOdZ_^h3Kc%@cjJ~fjd0+(L?27$4*%fLw z`ek1jd?rw8^UzLEX}COBM(^`EHGkCyrhI*q()eMQP4V`TKZjZ1b=iU?rFa1%H=rW+N#xJ^Bjr2zifn*L_w$ zUNJpt*7p`<(&XFz8P@04gCUeIW9$MHu~LNpgSX)>D7j>LcLXej zlto{r?l?$Sd7@DjgO7-5`mf&W79p>be#~jq;5EYsE zr&M*fFQw3aIkd8VO6--foWS^Yx*%8q5r04>EcN5-v^1p~XHI|vay}h?^^0aTTsc5o z*CeI154-s6;`(NSIU!i9sV@>wdXCCs8^9jljGS%!i{fQtf$m1pwPOY*2wDW*7kdCI z9^J#o$4Jre_7-AQfTQ|L=7ob{iyQo289!Nh+unF(N|B_l?<-}wMR+)Ip# zLL(lAz$k;eeV`>wO(R`t&Bwkd-U!CE=0Q|LAKE^0^>P4Ea-=QwcsoRU#A3lSUEl&eC-VEl=8sv)g@=ewsH`crl2r=`ykhqc`b z+vNA-s1cqB%eD8*`&8OUBuHmquQPb>%={B@!88qJ2T2amC{}4}s6JEW9yzS$gP{&& z6Fsin6|VQ65Ak@zi)#7_UQ(0mgdQT~!C60ip+Psaite2gj0G_Amhf|tkW|X-4l`*) zKx6^Cf&l{t2TC-O=}JiuMP_qaHUPRFVu3hPvizVs*k7=1X?LF zKm=TIWvH+WC)<-yvU=^)1rXd*r7`yLvg=<|MhpO#yB0Hvv>HTDZj@NTq0cZKlu7e? zf!GfZXUPe8=nhSJtd?~%*oDV{fCt-qZcP8KUdrU=BIMj#-PM=djtwap{p=n%yeF!! zHgbPu3Iup@O0t7!=l=LT@h<=R%i!3+2bk{rJ3j4Z%o8Wtx zJ}n&P$u+Oz zGO~Dw1Eh~y<$VS^aT^F#%5F_562GPr%l2&-jMguJpF^J-ZQ8z=XA$_u#!mcfrx(eL zV=(~Jr0DF6E40=mZQ$Q%0)Ef~I^^R6_quK3B(MdbEpe%RS*lr&MJe8S&z5_W!Zes> z%UpzRq0z_FJjm2KDw5T#+P;U#FQH3!M0UX7C1bN$9|z9(Yi2JNIbKdJ&Q>e$iK55c zZ57guUfq#qM0}&~RgVk`ji@{}$Xt#fBiuamW!Iw2=9M_X%TLdF10mNQPcNp+Mdr-0 z{&3I1t;@PA<2*1I-?tTM(u6*iIPBoqJRg)>0!dQL(TJ(kaim9Vs-qS(X{G{PYM*2G zA%6pYuwGg?*-k~N*^=$Xh@`>Sgsvy32N46_tv&!z<#};+r2|td)L3hBKd6=ik(^MO zAvFP$F8~ZCp6{}s)_XSaEgfNG`t<2F(cM(5$8N1W}%FrWr1y zE%~+#Y8BE!vCL8J$@vyevkZ_0fOcXSb>paTi>vXHO@A0@r|QXNn3kc;BY{0SeJEAuM4Xq2m)0`r3lfgz6|H*T ze_#dX_eVPEWw@bJ8b_?Lx7J6WF#BlalG$nMu0Xh?teH5R3*!2`3jn3NUYy#y&+agu zkNVz$J#li;JV?`9j6`REh>6>*D9tZx%n*6zT|NS0P2q1r*`pqmF1^ac+h_K8S%y}> zPZ1#Zd@$w#`G=8c{E#+u+fbKGff`zciBFv8Ot@WmOrW9UJNWgdg}LZAGun5Rh}_9C zQ|rdrX(e(l;_+&1xs?)UMR3k76Tygnwrd5i$xgvh@%!6_$!T8d4Glk3;=qd1@r-kq zV&GDM%0NH^t$Do_+O9z(zfI}7iH~XOLq1qE%4cfN>lntWIugbBhN$`vmHXmOLJx0Ww$k9iM@c3RnKhphP3yK@>m3=ta zcB>Kw8{H6z6a}A$EXPe{D^Iu0gY6Z0DdBs|Q?cLSM}iA@0Ht{==BgAUIq%=J?>|u7 zNgpmk-S>+#(?jGq)mltB+Wz#^e9>6#7VrzQw?lHAi1{y48xgSGiS&OViOMddv?8r$AI<2c4~%S$r!Z6LQ-z&`CCiB#aVoqG)fk>I^f>%K3?tpI=Rl*X&9q(8WqEyaumbfwD zBWDN^TQ0bqtA2HCpNkPKv+-o*) zp;y!SD)Bj28;FSanW8kPHR45fn!}fS(8+n07Fw`OZ*X@xKr#`JzciY{@j2DS_SQhK zaC5`YJlm4u-iu4ZAnKcU+E&SRVY-U}S)ESumq+9c=niCx`JK!(HHdgh!{!v>MH_^| z18sW%q0DtpoHh0?Wto@WjDmPe29dOrg-G61IO8D3BM~C=CoX6FPa!5Ln6G-3;?|bzGBHwtjuHsklAznXnVDldzc?QaVl7^u zpAyWk+|y={Ah-TE~AzU_LII)*EWY{)6b{lw6)#6D8f5kN(V+Q5oMp8gzxmLsm5v z#s9xetTv7wWESEsN@DqFhn}r2 zMwZT2q6KA#qj>+ay@s4yNk3(Op)DeLsJ93uUKz7flYhxyN^bI89$Cj{a^f?!W?hTp zUV*k2Q3CQ3rMLa!t{;4twRDh$yIGAO$M2}=RUFEr2SZ?bLr*(*`P2L|e`x(Mr+&Ry z*halwT%2<0c1Ft*%pnf^Ggf{_)NhIuPsE9I z)W?&eKAmgEzfgl*2~_pi)c4?5yrH`-Gk*H7UavjB2=7F!^P9r_pOz-8o9QRs$9Q#? z{d1L8z(M2BASUF@7d3$lM@!Y~D+G(hdL-{%2u$e~@t@Meh(F`A>rD+Nx}r1)bL_9y zlrKzFspHT@$DMn|uwtcaZX$k0vm7?U`SBmao1+k>I^OFLy#bcV81D>HLOxw7z>y>_ zy@KA?5wmzKE}%3m>=74)Cyqi~)-#3VrWwT|xiPVf| zxfio9wvF2dqiUhf&rrrl92qcEI(9uAbQ$tb!>K8uDsOcUR_rg%2KnMVU`iP6?tm(` zz83U{B35T16Yqgjkp z>!WaIk*G6mfA**A7adkgR*Kqg!@PL;j5-cGZ5dd0YYS}871d?%a75az*nQ~@kZbAX zu&NRmbXEnsA!EWgqN{q4P+iEn{5r^4l=Ta+37ZX$YlYl|Wb^jw}_Il+$Q! zFFAw*YcRh~Bh$Tt2uhm3`K$U?X#rp)`xd( zQV|UUb>&8S*XZ!wF={yElVkOog~ZHx`7g3fmS;%+g_7{LHzx&p6AfF zUL8N7Oge#X3fdY+Lkw-`{YP&M!^(0X?t~qiG9L;Y!K|K0?8(I8p{ZneQ2LwPBs$oW z`yKyvojM^&U}SdE3++gJa{bw&e{a`66rN1IOTBSo&mHDT0!3jAsu9tG@`w2b2aVSQ zuf1`1g~x}lmi#!1_)FDg?r8ko?=v{`WorCOCf$44HL*ln{DyT%&PX0#A?EU&=A{(p7Zt{#({*jLIG=w|B3j8C+{7 z7X!RA=}3w)ib7*y%QEdy}}7&BXM2ro~D7@4!oXmFN75 za4fgFxO>HiW@&@fFpOsv-Pos(4}-21aca_^gr*a$zNxU32s|x!r?~fG3$7~KR`mcg zuDWWNZ+9-sQK$kI%ek5ks!GotW9R4VZWFY%hFn$LI$Wn}Xm_u^1}MbbZUrem>(d@X8Y$K1i+1KYxBE z-EN=pxx-FEez>QnFVNt+vRVRWu46s~e@nahF9LmlJCdv-t;83c)g9xlloPO25wiqc z+G92Y;mQ0S!1Owrpv2BLcd7M32KXZdX>Q0F)d|cCWB{XE*>SgQD3#=`wErIXw7XAQ zje4xh|KSkf@TFJTnD2qnu(VDj!;)V%Lg3~aUxv);tyx>?L@uJGtQYXRUV_rl2mu&~ zI2t<~6}Rj?{kp-&%x5G5BqX!QeFjVOEQVnyyV)$hRV@6QQ~=SGAIStJbPm3qyl^-K z-T{REIX&*Vl^#QEz>ats>#I}9`gQ_7?T91=z$w`DxavNHogBG7?e`pjgMpRuFr!_tJ8@4K%7b% zd`lJ|AsBvxu(U5BOJtZg{r>O&w#l@+Y>^5h`F#79iNKol(7?!&Tt5sqIsZu-7;wu@4az)4&orKtnI|sa|z^ zdwVUyp+Qe}w_|{3>@~f5ZXtp+%=R38acA;lbJj@=-8Qub;i03siCy_Kw;GEa7Y7wb%gN_S^rcL6 zG;slMnpV$6#z1A+t7Xf7G(UbLFZA43l$N=8?4e1SA4N=l0^hi!LZm_=VTY)(qkKPq z{$|N0dEn_pJ@OJ%Q8UDNQLSv$h{-tS`RwCg7O1udTyW=LuY!EjU;5u5Osabi+mk&R zMTN>2lnU0SlDlm1iu3cQ9xp0=?WoqYanebiyHele@0>aTKp`SR$pY@`NT057`e?br7V(>`lFed5ynm~vLR zyMOH~&VNGbS_A7u(!TPZuL_Upt2?HQ@;j5CPEpHC6F7=zBn!fn^&T+V=q2^}yIJizdM zGj5lfT$s@q_0)tf7@e7>4+W7f!J! z)eO}b;mhn~ExqrEH4<$<;rAi`0!J#UY~?q-i_FNF@!0tSkDD4Zn+V53eefRGZ6Ac0 zKBR4{_j`vn{hHIcNt76VHSm&WjP=;J^zlXoe=4W)Mk*lnYMN!4g7U%sVe{#G*d6dH zAS#S~HdNtCsEWlqs=r}SB=RYHNIKMWD9Sp)kr|zCd@irQNHI+<%Xd#xJg+FULG*Nq z=CtzYv|LSx^&#=gs{^Z%raO*h+YbgFhbc7kzQ&VF8wORyHRMnJy?i1BVC@aWF5CU` zl}{mjn9t+k%)qqt4{GuUeb2MK~N&Vw+Mk`Xa!=00LNNr3A7Wp-dX!^gX-! zHHR#O96rH(n5Es&?>-H_p4)}9Ia9_x{jp>A3-EbRk}7NnSt|F5L(R##=D#7A<2-@= zs^S!;RR2MkWmZBG4Rd4jff?-`^YXXK8lELL!WEK>V~&S-M`yUhs~TGD#r~+dLf^=G z>tvqu@6UHkDmjEU41T2eN|h30opT?>v~3trL!2*1iEXl5PBQk`mv3pXPpOrdUzis- zE}oWirvA+2aWLXjr$R^(EBId7bLZ7BY)5bok1~lX+q*DGYoh1pP4{5FZ+>y@NzvN1 zy5b=quanRADb0&zx?f`NmDLRUbjKO|*nZN7yA9F!uJMFn=iCMUckQr3Jd7*hOpau| zY3f-~^F1)6pJC>CQkOZ~*H97vS_1R89?y~j71-Y!Cavxm&ODcf($|!Qkr+SeW+F3= z4bFMZfojJ5GP#8B#_W)t6Ru>4(a{+5!K$(Fn~zsbXbo6}Y3=rI_4Y2dn^yL|$3a-! zY*aYX{CFD;%;nRD!S_1dDN5Jg&VaU}hd~%&(&7+4!L8~mK_lS4`m8iSoMNgk8l%L_ zjflhI>vQfT%qtYuNE?8=rHF&ZD8$|s{a>z{t(+~RL@wUl(bNPQ4J>Qv3K1Wo#Cp2r*M?!Y_Z;#?EOQ3mnNjLzBK@F1k9j+5BZP>|5KUAyN)+ z6a8N64N%Dy|Gc!iyss;L8;b+*D11S6EAxQ@ivaKk0FQhhvu8%~l`xlN?}6i(3es_z zUcl&HkDL!E4W#~NJEm!V|5x9V3b-PDV})n=27a5C4Fo-^kfGe7)E9}SOXOL(C204u z%_dyHugI~AfNLtN7V^`Kd3EAlpR`ec(P!U7zGc^_dsPeW&;p=Pd>IFThg)mkGGJ>qrMWyhpPDuC{uOyNzRhblL`Rioy|9c^fV9L`Gt;Mg-21F8wj< zq?mMb6S(LgZJa!Y*VU?igZ6*jOT1R?dlY;niFDd7@Lff6P%(99$6q_SYyb%>4)bsh zy-{jYtVi^9dN1@?A-e;)sa@ONhw7esy*l(PNff1$MJ~uJ>&dX@a1p zN(ipGts*7LY)KM&FSJV8W@@@$JAr)UiSeYd#>KzzITUt`Ro8v-z%`zNcE&3G%Wm=| zQRB%$b}H1n@VEJMxN?w@zriPwj)R?zcJ~C1`ciW92E9|@lRT{P2Ao4r4qZ>1aMVxL)*tvRr zUHWEqY;7ZMfuJIR@O>OEmnayE!sj3L$t_b1^J0a8dWIM@m5~$WaPA#mZ2~K2@~B_D zzsoTc?AUI98&3|EcSWCgG@VE`%BR>toXJr|Z>il1+#Soqm(sKiF*sQWkq14#|1*=A zG13s+^!a3r^)zZPv?%&0s>*pkL@vLu60Df1Ey0_?42T|_QPa|y zT)*;^5@#hed_{hE;T^t8W7L$Iq#6CLyg0F&^{?r*635+@iT$?9UT9Wkr~+rdNMX~U zG+3aA11rXtpCetaQ9Ly|BIXY9qbUvTGj`yjk??J#(1*oY=<0v1BBhPEPKD)Aw^(YC z=*@O*LxttchR)cx|KVX)fID`O-BV^sXR_Am`DIDe8jll3*Y?MpP>D*PkNO}?gLaPGhNSfk~&0uINn)c^d4zZ_Nn>9Am_%Ns{#!Peyftw)tdH#w>I?m)j8m^13wihzk&m1g>lez|`-a(^KtQ&K6k2+a6g7uY_#TYo} zh>Q|$0DG)``uu}>V0UR)#EuvQq)61Rp^}m}d@&5V#A`~2t(Tp1Ot9O7RsyI|F~oGG zURH`4_d-pvd2UJ?In+tle{@x@@yzv$xXok#YO&dkuK*=Xt>T2eLq*9w=B4sA9<3_d zBgjE}<5&Lz^D`EX+#SI>j5y=)9lJ_FL$FS)T5o>7msG5=p7z$@g^j7V=wp(%^e_yR z8UbEb>63iOdnC2zZ8b5TG}2hT@S)mdcFXi&Y>R+f3%$Qkw}WV0r!3KxHb%LVwJKT~ z{W&iFCxAEuAO?+bo?S2GE?RHUSYUNot$*UOvADW?Ya`%N)g79ok_$26JGY>j6=j@W zmlsDASH4Ryuvj>fv`XDoUwB5BK=-~9a$gz)yI6ir+#B?Q=X{2&C&~-I)M2n@qNH(| zS%pZFO@F&Iu~jfDyw;efBEMa-^p`~4bbFyp46UPDq@v>wXK&rWirD_`5DMEY)6M*2 zaSqy5WiMQ+xU^SMA>6CWAxRqVKBYE_40g-#Uy9Ukk~bKT?=UXyA#xYh zZ}|+-fP@^**)S|8NT`Tg>YKb>e6n!|%|p{SxcVdwPn&mZ zxcgE|wBax4%56-m&=LEL;bNK5H#pg6PIbF?{Wa9yg&V9*xbpVaPebOEUm2ctNKIhGHyYyBc?FdX1s zOv ziTs69?~M1fnk(`&2jW0q91*%7eES9U)9c;h1({SQv;C0hNC?@{yhwtva&;6S9M z52l+iHx`ze(`C%myh-oPKL#%3^o(AF9$g*CsHlM&TW}hXi8sVn?jG+%{|q$_$$7h3Bk(9~$vdh>O@P4we&d-832<5z1WH z9vC!*I_^pZ{pBV$Kdsz@IIIe0Gq}^{c}_BVmBPyxQuQC}YRx+ZVrgn%j%|bN_b7TO zUpLKnhwd7THz4U>s{$Dp{f3*OFIZrG(kme?;;$$r&p;BH@~-0bpIQ1p7PdEUZ&_d> z5jD1$@lE>1En9YoA0-!*e-Ju{DCD@?LF&<$X5(KBWBO80ABw z;C2-_(>~k9#0bAL?rR5_fb7oZh?`WPS@FK?FOSnk8&iAni2mw}1E{nMc|>#n=H!ka zZ3AltMz1nQ@R!F@{x{l`kA&XOHm!yin&agfn-|ZVQS1-V5qkr>R7gAhv$Dg-Wn~W3 zr@iQ6C)tjcbf7PivWs@x$>*`u`qq~YUnF&Xz>?3{AoNLlGb64AS<2N?*&+)}h|-2|Uiah3=-ywC(uH>*zSbw1woQwm ze7(IrhXeznZu<1zibhrFDyVzTThxk&)HX7lLR#;F!{19DzRujPCNvky8Nu2*1@QYf9A`=?et=UiKK9D2*A z=aTviaFS;z4~?6Maeq)8MayjPQ!BbxF31Syu8O%QI~dWRw)t#8CT_3z!;T8un?}>H z=i6M~5NLA}hm$QR_$}S+9fE7+4X>eL-N1rhD~;vrq(F&-)kIS^qLvf0+MHK_nz!r+ zW%TvSWFi+iml1=#db2T1*fbFU=CCWzWDguvbSZC31nOyM_%A%+b=!{@m#}-C#CksX&RMML5NG0xLq$lfi(Rm_^IMlc9+I)27JH%UIF-D@baMB<&&?Nlp>#h( z7HBPC-WWZih4DN7T1d2MmD z0JJgkq^Upr4`s#$WO9vXvKdC>{q%1ACD+v~fDQ8+7lX;j1p==W>i~+i(@~4!#Ar`> zC0P{-7s@RT*Tn3{g3YO^-MEk@Wy5yUqDe1J?;le1)PXn`v1Q<~VtcOJf&k9A)xqi? zM&Rezo(M!(cU;E0y>c0DlMMK#bU3yvx)8T9USX-k$S~~jK{|^_LUsy%X-V(yOn%ox zw_lElE>6~X3f8GvJ+ZxLXkGd}_x1|wy@RuM9#25c!Rp7YMf?5x zD^%k5uEDc}GU}5);3}S_N?Hz=uf=}froV)*Y1jvmQ)eJyYpX86=;h#$oyXA?{yE<% zI3hZ;)C>JUlp@3+B^en5_pRlDB{%HjvO2wvR>_As5+_w_Hy1C<6_KuHL8s?H)kpu% z-SDDKXSH7u@J_E1aWsyxoIuDqILw{l9Wv4tdsmBRGiy9`G;GZ}3RP2M#RrIQRL(S0dM|n-F`d*Loqi3R+tXb9_*uD)}%>Y!W`2>)|x*;TT2j25ngl}Zasi2)eoBN zmCOsY;v)R|+!07A!NTrir@iMk)=56#eoI}NKvydfymZt2;RxDjiKRTq5^wLx+w*WH zCtEI6d?KGJSRH?r6B98w?C_-6`OB@>hZ_zKRGXdS)eodU`fMry@v^=i%X6bCpQ?~H zgV_XMktQKi1yw{a z)K*H1ZX+n#ElmmLh}%Z>4f~o8YETX_j20z7z69c+nNo!6vz1%w&5&czH_ur$uJNW& zJ+1d>ePfhcW5x^tk9S!^&jJi!v+p$)c1hH@w`onjz89Tk}V#s&6vZP_*y-HRCIsJVW0kP zg`NRg9s;`OXNO8%oCL&rWVnGJvkQH>`DH7w&Zsig05T>XGEJ=J*Pm$lF4+qmiv4cr zvNDm=sQUJdD2aO=1AnKZPy)m5HK*++iGMMpzNb)0!=mG!wpL%%@o?T%U&yb>TWzhF zC#mlaiqg!O`Xaz8<+b0_&{UokUmRPifGL^|&S8$3?_yrz9bthsd$zANP3rWlVm(m& zBH&{p`LTbA2Z)S~6I2@rf=RI$ZF zoP>1;)j48IjQs~514Ai4p3)pRN5GTkYMkQQf(JMF9lu==B?WSy;0i-TsY z!-Xy6h-e8M9oC7Go~{i>$*^5eZBPH7F*~hJ-fxNlfSI=J^mI2H`7BSf#wczDa51nubx{H60(!`& zq~~fm1)o~>ZFbNyezRT7Pq%lb-H=}-CdQ}g_%hu1>y&R25Oj#_^x$JZoB(l0^fl_z z_8*OV5ggHMf|aLpX@GErH5cxQxkkY0&dC2t418U1cpC~vA7;K2z2*lAP8UV^Ux|7w3IYOb=>fV&?l+yY_`+rO<|{pwsGXa(9KqfBcFebOU$*=L~) zRyX*|onJka-w?UEFdGofbm&msnw#WYY6T(tM#{>l50{@Ni}E2vgOK5L6}?`+4lfCC zo4w)?+`R2Ud-Qz%0d$y}99`HS<~?8VZVxcm(R24T&Jn|Nvo?cTP4M){V4p*CV=A{j ztp_wsgnVOBSJt=%9*lsSpL9!ioD?W0W%t|q8zdMIY7uzA)H0tpOoHP!UqUuNARdcJ2e>+kax6^!O#Q!7*c zi?Djk$^9CjaITqFEZxk#KIZT-xNEqv+;y>K9Q>1~44layT9zg7HmESv3b&qvt*&(7 z?-b&;iL7;&a~LsbLo<+Z*&hl029~;1k}h^pVMvzmJF5h^Xxs9MsZwYU>XlK>YTF~4 zMSLzHAE{z|H`6Y(wc?-2#9BIQ?T@>Hp`;sTeZ&92ecy}_DYvbsKh36^UPkm)k-BM3 z{)atAkB+i!GnKK%!-S?=Wu;WDqPe^QBwWoz)vkFTtER0GaKQe~%|I(BPkm|$u0~#w zX(3%c#@U^xM4<6b(gKbIBbG%jPlU8&pC}dfS7Y6ClW%~TBUqZFe%=Q;e_Q+5A*piZ?_O-TLO6KwYJ8JCTmG%^S`Q z8$g`VSG|z3ic)KH7Ie!3ACnj8XCcsw_4NPExT7IMDg#G~ zl7O_IHGq+rW1>k3->5@?=mJ|a>tNsV+m}c4K-&Kp-=&he*CV@N+M4e9_@wR$pg=*O?lze7kpfbxK#TCsqQ!4?)p`AcB?8 z+#2>7n92)KGR%K7KFKW*u!5Rzv1`YGxQ+w>IUqq=+WqtK*+{tub~ysj-H@ndzF)*QJ7Ou95Ju$PyNu<)GoQlZygr2WN8oRXgGN zewq=b&j@X4A_Q$x>y9*4Y^e<~)2_E{lk=F0ahosp*g0e?KjHX^u>YBB9iBfkFvNIE zsp4#_qh)`d;z0>(j2c@Zj};aLd5!Q}-2mN5h&Iq)Ey9}ooxwK@anu~;hi8SMh$SbK zTIzcDJHST_^uXBu&}08cvBzr&tQGD1Bml&3WXc*f91)80ir%d8aGBe8)@9ieRV_I*vcl$i zWE&Zsv!(jexSPOI#S2&0*a-EyPH-Oqs?Ao3Mu^>ZQgwK@m;nV*Y(Pb_&tq=lT}mdu z%Ntc`ik{Z6liyTJ!mFwrxxv2Nrh%yAt+*$;8sFhrfo2p)nIb{P z1oiwrBiMxdGd86eTM9>cyd}2n7t}SiTfJ+EYx>saXdZ~b@HVb-Jn0Ir#S8u>+!$WL zqv8_WNdwjCRUj8JB{w%lYbW|sRQ-HD1jvMgM9`Fi^KKTEuugugmRC?!yYhSJ{v{J^lT` zGj-|;;;^H=(JI|hymgmy7KSKJ*aY|98~zvYt`KavH>%@uS;wJTLI3$J0yPAj-PBJu zXua{ZH_XLuJsWQCOB>C~$cf{yG2Lxv>V&_XJvBtL2^uN9P}jx`S?(54J2&1)oC|^n zO7zF=@t@saXgkPWRyvB9l&Rl3kU6iJ!kflsqRlj+tm$R0zx zV%o4n_RNHJ;{@b?5EaZ6j21ok+AqqXKrgDgE>BsmR zj@)(Ab9(hZ$8RP`91g-bf$}e(cI8`I?=eR#`zOFr_t|5gHh5kk;hoi5IF1Y+NGyMHw1}JhiMR6C4{Zi%}hdr`fB7^B;e$=@K#(AEhSI%_M zaKt*wT9BLJ#FqKDx$GIb5^#G#o$C>jm&VJY2X!Cz!rySwVXroqNOwZ471WEaxsW)7PmOEapl7A)ROy8oUd*Oy{d9zZgms{;@?fC5NaFu-zaR5I~Jm~&kAv`?<*){nT>KcDuxU;)p@y8m*F7?^Bbmw(2T*gA+qmdLOj zN;`wSj!JGa)V*M+*_Su!XuC|?yCWmS-=jaPJv6CDzAu_)!pJG;muUC$zaWXf{5fR( z5d#YDCv&?$1GKpw0*QC_+79*PlaC)%K9>ZYK)Ussh$oR=qsbbOjjTZ?FDe!9*Vs_I zK9fW?5!dTz`5!qP>)f)}&MR>Qe+{{?k=_j^KjDn_ZZ8WvhX3X~jU+gc1vFs&xf*J^;z zMqpP`9B4&6_upEJQVbd)rriY|$TN+jEs;KV%ftUgK}=m>V>0LM287kfM@2nu|>OHi_4yABJ64TvM38aVk=Y9^~aJm6VBf^043RaTiDS!trOz4 zfZV!$N51z=S33xXzdu<1u$nU@##_^!DQIyD-U|payE^-vGk%+{|xhV0Io8PXc2d0w}{ia-Q&LR39dg;;;!GKnUdwhG*X1 zkEh4_^+Fdg%!fWlW2t=Hi%!IkuWAJ9PTDhO_d ziX}zZUkx`CyZNf8mhd9|O7y}oF~mR@TZsU!qsU$MNl)Xak0TgN6fs{(cD*9m-()qqk2n^RdE_0Q&q3a31nF(R zT!+G?>=w>bGr?&S+KK!|xF}ufna%ra4V2usR(> z1uiX7w%4`+Ql3=c^V zjg4Nn=>m+^1oG$iyOP8SUAvd}fcpYu@37km*`Mf^GTZ{eJ9ko_x^_Qy=giW7da(TO z;Op7P$P`(GV5A_kWlkEl6;V8gnDH}fPPQKu>ySO~_%$6v3?=1YN_Ct!K1V+Y0BdX) z*5kQvx|1CU>V*ShiOuy7I_H8ol20W~oXHo`a$YqzZrbl!{vSN?%2s%oBdM8<{a;1l z#^I5bSHSSd?XU#}i={#=6%ZRNewCWR@dxyR`ew_ipg8E7u5=3k!&DWQoQBc7Yj2v` ze}G&1a6%>0<5ypcz3i%Nj0!&U+J3?1+&l0E0k1U`>*l+d&gsOAY7pO+g)N-Rma*3s zKYr?k2DHoU(Kw9G^KMym0OgA76SfV8UsF}>bwJu!{TDsbt0g#*;U#lQoD@hng?nLf z{&;zLkJG-!^HyoWy4)=OAZBZk9Pk;HI%mD1H`ZnEi-G$xpoiux?f=`hJ3Bq}qopzG zIf~BivpQCpN*}6m9m77Gamqd=w8y_o;zOV$%_x0f&@&!^J{^_XBu^i*$Qk^AyO`qh z0X)cg#ViJQ0jL#HZ@f^YQM->NEh2`viCx=mylU8xDUw z2Nm}fj|3$144&#-93mzqK(gEU9>*m2#>+zitUx-Ho~ADf@7d;3>niA80UmI-UVL~h zKCy8I_$9KVJvUO*r;*AVMhGh3rbihaMKgRLJVER;e51PBnvV0pp-2Q-4@`Eu;2Fm% z7e!!|sk+HRn)GX`(vO+I4v8w4e313e9Uj{_d7A-VXbh+E}MtioCgAqxgH^v9-oqI5Dv2w41iK7;dE^WHpP~V&>>SwA~}c< zA2If}z6P2Eg;HPV#9sWjuMzKkAgx{=r%B8BFNBz+Wl=rMWm6w-O_K?jKG1l`f(iH$ zx*H-Cb{3qz8Wif(g)bfYfH+5Zw@>F|DCaw$W)AklY+XeO+9-SkzR(m2SJy<6pVs>L z(Q-*Fa{|mM+K6iF$Xlnr&k(2_Ks&O21^QviIS*d72uhi$_;Gv|S1eeZ&;^I=7-eUa z&^lLmsqvB46)OkoB0T03OVBZ%$}mE2tK;VklV#6Kt9Sx_^jYY_+7Iv!^gnk2`re0= z;CysDT-&#?D4>~@m==0kzOOi4GGmDU4tTva+c}ny)YQOIS$r(Xj+#ncB(+jsLRbPU z^mdEYKOZhj0UFB>;;8!=x>dxtytP0=apIOizP!`=weEwka#(ZO`pI>d`&(cS*u4mR zI@k3b3Oi`}(+d>c&Tg9QeASzw0bI*v;57_s#;0r%&kfZv7RW@zmvRm5ctm)0qshzQ zL6yfS3a+%beuNE zD#Hpa;Q=9}!#ZQ9mnR_@@ROEl(NIw7k!vQnU_cjFXxw(8QRefT2#yXTiHDc;j_}LK z!x1#At4zA2yiRVs)dRhIKaflICtTqO^!TDA7$nu|P)$*ug>A+UgWrB9JQZNqt*RWJ zq`d|jQRS)OpX@TWR)VoAH=r2E$b|a2LT7s43@mQNq6OJ5!-{`q3XtX5LOx**U^-!| zd_x#LI9t(N&-?Cz*ey&{8BdRacH3U510E$(Q0L2oa9@h9xXNLl!v%uAt`jH&ddD-fGHc1|{-<*+1f> zL_@nV-2aspVKQHb>F7IJv>X8#Iw7V0pMH({_qw+qiRxYKxNRDN;SkDFb2L?XV^qEe z?TGZ$ro~Oe+h1cv!$nyeow457Wt=3%C7^pOUIgra zu1;M~RF3NXb2|MiI22XGbkwo7vtL_1B`Tt|(u(R=7=V4uniyVS;XvKK!u6eW#5uD= zSRkRIIy>;4GnxPF7J+y6m*sHG+OtCp-QCroH1M{FlHU=Oj1P4J$#&Xu>GfJ#WU75} zPrW0Y@mawfzlS5a&Ea`VE={rzt`T1xn~?s_rSD&91Ex20gvZQi7Byq!6YwDLutDe_ zh3>1eXr8<8s=(Bsqg9`#M5!XK>1PJXF9u^@Nb|3X<&An@u4&Y2&AR);uf%t~Ojk5I zpC;7bQeL|#c3muHMPhU(fp;cC#6wCgq$}_j;^{~iuaxYkkp%8WW-l=-&~n>P-)u@J zqlMFb+yb1LW5Y$x>lOJ9zKe6LTU2DIh`)(E=3wjqj7Ug>Vh!OODjVe^Pm;bwh(MkY z?H(yEcdo6VO8+W4Sm~H*64msuzF6=riz+$2C`$WO^mO%lzZ{(5L*c;jSMxCmXA`8D;@2BH*>K|6|Gz-82gn+I zc+c(sI5d~vh?`(v7DV-IIoX68@r*G!ccNte6&f=R2k(!anCE`=^GG&A{D^lm`?uWj z4Q5W3nE(@VS7ZG}l9)Lf-3K5uS>@PB<>v+N$~0n-QBCJa>VCesea^Or;E?-WAwHkY zlaaicf>42)4MbepSm<7RKmU*mT9M}UqPgwMiWq*|4(uA4Xsy|m*(&{}?f+T>&D_wh z`ln*#huPEidH&fhks4$V_&QY8RH=wV@9`z>lRfFZG=s!G5m+;EePCh1CMEe^Ip9mv zuX&5ulGB&Yy=Fl2p%B!+5uBrpqV(Rg`yWSF9T!#8g#`qpMH&P|l$2b$k?sxwL24=K zZUm%dQQ(E8JEa69WI?)?PU%omx=X&<@6Y?2*?VWsob#OL#NF#km8dflF*$)R)QhV0 zgem@hCGxaH6UFkdD7G6*VWP%$4Z!hn4Cf5rP~zfNc{xXdipuMInR4XerNL8E z_MNeB0FP*qX;3*`lb-NrD>Hg-n>}|nz4@70d@wzOMl1TQGd{|_;WM;vy*()D0n|r1SGt%!VUFm zr&jXpuN+tUO|*KOSoGo}aqVkUiyyp&u$2jvmrGI`Onv`FMV3Nb5Xpwm1e>yd>OhB8 zwHp&Na)dUL%La;4sJ3Hi$2X7JZUVPjeYO1MtRF|1tx3Vsow#;NN;H+QiO8X0@p45u zFHN-c9Ln9~Byszh9X3cQ&f>0Dj$7Z=6X`5&kj#=L|HxA_=;DBF{?02Y&eKLfx&t)v zx2H)N_fN_6?mwHB4_fzMG&P8JTR&)i7LLdvORgcWh~v?hbL3&Kno4GKFkVt&Lj&EF zzS9E!5|!Q&f@8xq@8rZOZplRH9kJ-%i2H$8IP}Pw|ImsvO*#J;PbcRhmzm2bh!r@~ zxFl@O#c63$M8+-fyZIh2sD)_6FHmyL(t7gj(LO77d!WH&ug_F`*mE-D|Gph=fmXAh zCaOmSB_h>CBw%NZrAUAG>R{bXv=^XSi~2p3%M=xl=@r=T+bWAl1- z*xBW=&6PV0jK1r$vYkk?IitfeLMc#uRNZ@sUBoP`m-q}+|C()1(-3%iN>$4{08gT| zpKyj-P6lc?xLJc+VLcyJBfzKEtN$)H1F6#15)N=UdW^1fBmwjlqsIO;F+t)jkAyQ& zrx=;H5>CcOI1Nou!2CsARw;_k9+cKXhg{h}To9+VQ6wKfvA8+r0Yp^z;Ny!BAOb#U-iNz4zMqv9mNLG_t^q_ ze^)9-kI~Ptw2aq;2R#MgtU7vRCCOH<4VM;Oxj4hdAF;~4e2`DGxB&WjpB2y6F^#Kc zx4uHvEZcGZo2$sr9#21$sC^DPAK6(qRt+(ANz%GBxHvHqo(as-Jt3OcZq73-FCkG7 zi3r!=%L3&Mj`x?1v2Rpq%Z|@?fT=WwXnTi`60OfH{uKg^q<<@OJKR`5H6ESi1(ET0 zNgDU2`2~)cyakb(5Ju#LmrN01Ef>3eRY_D!E~^sh#HTO6E@NW)AAalTd+8GPTP~BV z-c>RfgWh)(B%qNT5zi!`hBdbmenkHduaIbUt+o^YEa>{gyn%mKNtw6U`J&Uezt|1b zg?r6H=*zle!IKvMPE!*s=bsYvjCWnE)(#Eo8a{MWjrE zg6=u^Ax;OqJ%5p`9RkG`yv+hnNPFlcFm{c;4U`;kZm|I0B>!ZlRq6{a?xY{$xKw+^aP$@BHimtPz`;gT>< zpa3pp(dg$6b9nV#_|8K>>Wj+OM;o)&WAy$9KyfoRI1$F~4GAxg_t$`N_tDW+Hc|c1 zdjVtq3z&m?r1}BYhrl99!8Lud zk0S=YY|u@42+v{E)HtZyReJ<vIte9UTM?3>j9vi4=P$4g&DraAz@b_r&fwj z$^5c^rL^Tu6gV=eRm5c)$E)H0yeF1H8@VsFYM*zd_x`zJ85pG2&{&qh2w3*n2zLRB z9n2?LX>R)HICT^FuAh>@f6@GE4zD1)U)Okj^cdc*wUvHGpctLyW4)(w^Ze@k9I$5q z3#cs@iOflQ;`}aH4#7>ajo3_W`L}mK*?=NttSLnEAn#8h|LJzi6!7py`LPc<*F1cP zji^Lp2Det1pXA?_M5eys1{a@8Q#YL+JsAyI3=|MxD z@62eK$itfIxuK4?M1!Ao!Axq289Xe%W3#0lUT_l*llQvK=6y}s3c>Mfyb035QAd1O z!VSVyLLfoJA3~F>{E5iYRO5~h$;P`AqP3B6vwLlZsBO?P!7|dtyNSWdYiGzn)jb1! zo`i{PZEiPyO?CmLWFx{XQ5dd#MT?*(Son&^1HA4}s}A5-M`+?S51G}Km4Jx$qVW32>?oeuQ(M&brN-R35B zZwYh&&!ByD-Y=*w*tRhhp5GXE&Vo+&HLLX-q{Z$B*z`KN{f@!-#t{g?QG+?>^$Zpo zLzJ()HFxLF{O0%phK;HNhsyNAqxG1)h zTwo2^@$3c9kC>{JV6t#-_)%;HuDq7IW*jVs2&&%$nj^wc8bIVu6X)iKVw*BvWgL&K z>_tOtQ3<0D6M8g1Y{|Y0dy9dR`}7m1>}}d=uE8T;(3oE55J(xHLo$Z$i>g+an%?LV zxXLTajiRDJ{M#a?X~@Z{fJ@5~I1S>DhZaG^E7*_b02W~GH*#^Iv*8bk`qKcB(kA-P z4rr*mi{-h2iPvpmxq~=bg2;H#O@GTVlg~*_M2vMH0LF6pt)JaQjiKjpq1cnW@o$N@ z_`IKnCOyG{=4h3VG|Mb84PT&4pDp~%wEh*f?%(|SroWPost-$Q1NxzMQF22`^5Jhf`h=tWaC7;iTHIw z{7tl*zK?13Dw*@$l?^Yd8D7;YM{SlQ&ZI zY)UDU@@}N-HGMpNq(5F3FBo~Wq-+R6>fX3YkJ$p3)-xbJbIFK}0Lp4Lokc5fu(JJT zmCJ9LqkK@ea4MLyCUi!_DA^z>Sy)4Q@b{<*Vqnp!2!I5#hy$i_LsE4wib}62-t!L5 zKS*V;LxCZ!)CpdV;Mrofud+T+Mw6Qd)9h7HcLNYYP($u~4!EsrQjCeoQqZ#cz6rqv z@v$+$v-G)tR$NR^4sNM&1NZx%y}-9KzPRW!(Irv9EAuzYqQ@?xjO}2kbK~nOG$^_K ziub@?;=@3N*EF#GSk~c@{3oEN#Kb`yS$RN!rU+wx3fw)Rd|24sNA|1zp-!MSMo91_-E<^Xt+p)%31e?^wtE=9O@qSG&SI^U-tl$4FtUiGx{0g~}13-+Q}9sUx*C;bVGXwf6-%8$3?KpG@`j=mEE%snN?XN2Oz?3*GjsSRa;6 z^)FqUwk^^DLJE@NySc^ld)6ip!EBX4JYLqKM1R@6EBVG?9>HbyrLs9rEf}{#1f$` zxhGO*=6YnYWX}{$ywjpV74ks24pBs zMDbv?RBDrss2ewps_?skwXBe@Tio8Z@o)hbcSmPEZZP=la7$PY{P3g1V%qEs;@Q6; z$Ta*f=5rbD=Z@laq7mMoMM)5s=aCdi%3g;;|pdMtbvldSp?_L#r@Nx{~D6qm5Tey@YFV&zOE| zBXZzcvF;+xPkeH1=4z4Q94o>%c01QO=&c)zXh_hp|8p5CoEJYgOM*>rT$ z6q*TRfIcS|^6GrZrYD%yx72i@+JUZvx8ZjfBDTy;G+~2I{wP!+?zce1r)vT(wZtk- zrR&p|9K%{2MpL&1aIFQjp#<$5>6@05VYmloyp@r90)_?eg`WW*WHpA!xnGTS=rI-@ zv;L?RJHROHT2I|Fac@dbCZi*skHX7$lT!Jlj^rdt`T=t)bs}n$@{$kFkw9LwBzf)M zt_7&aprc5Z(|FyCNaw!IImA#Gn?#*eb^RDr*?(gwd#He|HN@h^!tfEX7!g(mj$Xi? zjOLE1Da+LDlG1@{5jqWCc-^G(bBp;Fj4rt1NBb{5f!;FW>vTGa7ED@4t`Q-eB0H)? z?i5_M(4>DZTXBTsgcz%iWBob-91rFjsJ#_?Wyx*UNJ(w7=a|Kj?$rS&t#`Khn`cu?(h|et+8W9+$zV~dQSH$Bg@4Vv z6`#+kk5F;9mYCoQzY}i`Vyj?H_vtz5n+R`4 z;lw;CHtVjmCyW*wf3{Vl);?--(M{P7wK;7B@sdilJ__A!5^3YQb&8w z}x?vaATtap70d#p+5gvS`BK=$gx5!5(ex7$3$V%{@}9&vt2!SkizL=;E< z8CyU1)j6x5lt4W{Wh3RiUp3OR2}hs{UZh*wA?}Lvrsk?5GijxtGnz-9{^>r8lKXo!t)) zQZDwGA!rFhZ(qrAjnia-e|v?lnZF73Z2m8K80H`A!AmkH>64)lEpiyj4g2#2?xU!& zsy{(|-j3S^;PoShpx58M@T%}P=Pw}bdREJZWH~ny3p7hsOs22Y7F4*uA)jvn6T(%O z&HkKDQh9;kVuRxht8U`zKH5WfS9D;4wEtqz8C=McAl+|?Q--ePHhQ_vjaMDah~e6S zL6*0tMYAue7Kb`3t(Z=q=dMraUbymIv}l#VkzK~FPls~y1WH904cH(tu&Y4V2(jdw z4ZQ$flF83TS)!W@M&)XV(d7GUZq{qg+B#ckTDHea$PqV*Na}aj@Q#u&3O)#~LHv*y zmY>YXQ(xnLRn0ATM>mbby&2OrxPaRX>N)SSw-CTtBUE9{|))PX8M%b2f+5Th+hZB z*T4L+A?)yIg{or7mS9Lxv^c}RWTdR^E4S$?Yq@&mlgBOKrtxW9l|u|PzCT(3l4vRO z2N;iOanoXeEh!hnqW>GtfJ`KsrZGXJ&9aW|G+Mrdd{Dvq&PUWnvEVlZ*2I?QbyIY0YW-Khw=@}i$-s9S z5xc&;&eDH#o(TUb!XfnV($XgYpe_6nUVE=1k-shtlmm4H5%ylX1G6Q?-i|F_;F{KD zF2&yRnDD0pZ6ztvmPZqj-6DIb79W9tV!fX`2`8~!TOUMtzl69-TMNVA{k)?onmh;U z=r($>-*HEY=TZAv5gLl&?o#us`tk;en)%^E^>)d(?_cWeHkjBUxYAU8cp;V9kJk*b zb=V3K^lNo7I}DYVf2(?#(9?_SuQtK`9^Ah)OG|8*qI+(GmZ8FmX%sddpU01RLN)1+ z@_k(R|5^#c{z**0++IT3!*tO+4Ke!k-Nk{{@OFLL&!*#WqFMTU3!WFj`Y2Ir<@Nki z!nggZ3ob}mG_v#z$1=ne(PFt8k*Qk*UZx#&(--*9rCK#z@s7~#G!6}3k_%IL4&p#L zvF)_GAaKOKV$sQU8S3gf`JP}uL_Y;jhifyc@6v$|O^Q01?3uornV6UX+P*?8-0>oz zblxFLsmh9p6KjX<=JQ*h(X}P7FUW^!B-og2wmef3OD|`@%Iy8+4b5=mJpGV)f^Zx; zBh@$VM6l(kU&k*XXd?mAe<%ukAP8=tVNYH6<;;rmw7F8QvX~&QXQa#&(4C>2ZgCSJ zr*;?BAHLk4j-sh3EnPL~Tz+<#UMf@rc0{JmwWFalo%Hrizv>p3!daj*!qDSpAJ`8r zU&iVo6=xS6>yIz+MjCDOMXD}>i^J4tpgH9Jm<8*d&)w2p29=?qX{4H$GG1Fb(i=d9 z=h*t09nH=+m!kTVp|3roO?5cU$0F^D9LB&*wkOu4`Ydv-8%oXq3oqi++BB*-GOM=v z8o(odV9pgf(Gyyy?b`8@G{Q%`j8zXOj?+-`lYvcedR2a!_c?%r%_UbVcI7a(at### z$g$7lb`4K&j$4KY2SG`3NlgBmC<@;!LV-~^ry~ogy=@_!#*CMwM38|QhJ0y2Pv_qX z9#&G!>^F?%g3u}gB6&Bn@->l~On(+H4f!znm(}QhBFg@4f&+D5;Ex-9qlqB&biGv9cCd}DLfp}K< z76RJ_S`|Kw{*4yf%FrCeG9bUd(HlHr44^@&Y3@nL$bO1PYh5bu$O?#eP-Wp~+f+VJJ7ku`mv58BA(v2BH{Es|$?nD1p8r^tHB777I3N4fmp5v{nx$avr zIVD)IjAe5n|5n7!^{blvO|BsoPt2CgvYk?guB}P^q+<5Y%1?jMp$-j&gljskGx;@! zQHa8ErhNaZr~cULPV01cUItqv-Fi{7^tDmq2!L1V3uo1g_ur)OLQ{(RRUbsLQirto zDFs>`0P}lY81yVuvK#H>j|Ozj!awQ>rGBPDRm#<#jXckwD^I7J&KVK24tyTM3QneQ^37;spA!N45Qu)6#iOPwWV^RK%RkZ@`Z2^I0>^Rm#!vTdEuCuirLPBA`3&Rb0(xdw#MNPbt_UPB(ROh=~HnZ+?`;lsz{z9$5`o zgOfx8EiA$McE7rKJf`CZkRs`};z&}RF2~=|ikIa2Ws&9SJpDhS-v6(=x}n57qxId0 zBur|dUp1%HPpig!3pd&c0@f{{gHThY2A{J$_~HO4+{YTp0vD{Dk{_7>XFFgQ`x)+L zFP^r@iNK@7Rs^-1@sA%aqNv#TQcFTdp1O|osw)dvN)hK;OXNCE-Qyu=Nk|OUQi8pV z3C_v$|FeA0k8RYo8SB2J;i+fKXi&k zOD?sx&qK~Lk+QRNYF}N2Q)cH)3V`K0RXqvK{Sm#r!F_RO1wNPoVUZ<$CaL?O#Q<=@ zea7lAy^YSnfDJp~pIYRy%d)slB7UB_Ny4K$KRl(zvcDdiJHp~Z9b&1(?To9kL7>yV z77)fiK8oiEmBK5CS{%^J>(%e0Q&Thio7{3}V6Nd{gg5gGT#4^X5H%=zw%w$4k>_%4Ls2q+b_2Mh zjzXeK4~rMyn>Uf)fE=nqmKcMEy{>&bUx~t_A!8j+xi5@Vrj+0#0QgC|yMNv>ouwDQ zYy(r9k-Sq=XRKGQcZUF&2)?>c!Y!Y>H~23AQ(D3iY8&4>Y)gv-7@a|El+5iO7#Qe( zt$kMIw=KwP`^k=T7*=7h^8(_!#jGd8>4BH-aOwI5E+n;;k)4Nj)$F@~y6RgkO%C~+ zlY2gJl8Ed=gXzCqJp07aC*UB6vaABxn?JIZk`cd9*e~5zTD@#tA5bZZ=tk2t zlTroGNM>fpT4_ntk~WQ z0C#Fpv`03^NIkLldx4s2eoV7NW~7(Uam-t0AkTnC#TDXLCm>caoCv;>Af+O)B;A6^ zriZ8(r%SThVuJxHoARGQ)A= z46D9HzlrG83T;p(gP?f~(Y}H@Uk5KWO|nAK(XnBhwL~_V8+5>HHzWvl5%E}0r2PrJ z0sdCpBK{8>MS={^ULsI_nuf{miZ8p;zyDucD(~PfN>?y9A{WDP3G;ZU|glfr4RmC7wuPlFW-0x|JBYV z3CaHgpFMr4W)?7?s+~SZ3s&8g+!w?@PCr1o!4CLxkgcwLx@m67ofzvK5Tigb5f6U2 zy=6u}z6hJ`X|stOTW-XgY!H$0zrah{ye_XfN@n=+YQxYYW zCi3N5=`KgV(Vt3C3x$)ATMp)0o%s-O?mK0x*xQ{UCX21b^X}@_q;w)?q6}}QzyDHr z2|*kGHM;4%xvILch?2FgWbmpYh0$7I8UnCC+_}-OI!EPmr?j%+1ykd2BHp; ze$~I?tk$Ye_Tm;CQf9cLdIpt1Eib zk2??#Itt*vUY>I6Jvr;~l<^gi-rsyiLt*aa)enxF<#87DdcuYHG9cr%q7Fr0?G!yD zf)nm`CVc>g6aI+MF=(?SA1-46PJ}$jBgR*y-jEArMl0d4vRkwZHVwq;Pk@~AIOrM@ zl}wtG-zBz(f)_~$o<%sFGA{G=b~uAAP(j~N)*UG8k=V6@Ed*Ilq*R5iL^T{QTQRv1 z*}0N57-O^x+~3~jM*sKsuZhPlg&OPKJX^lVeC-a4I_%*5g_l!#oIPHf6={Uv^Tz2= zw?w$+RA6b3ffp;M}%lzL?$%3UIYGWC1Nmtqx4BsZdgLqz(XAUX-pKw9B#h zsotSpiEt^+{oDeiUB}`^^%<~8$n(KZqypafbpB?*cprS>p@kg0wt@PfOnK|k0i8as z>WLamwh=7c#Ewo|iuN<&5AE{>5VSrKquJKcm)sa>$w}6_j@0?sFJu0Ma01VW3Nv6# zNP^+~us|8L`!AEiy{txuZ|OL}(`VzVHBYJ&*WT|}0J8W#LtHmMy&t=-dHy17ACQ-wvJsibvWwf}gZ9bdAiCUB zF*<@LbWKxTSpvpFs!ZuJWbQLDpgSGXR{@zq` zU$9{ETPIf<(ZS>&*IODiL(0n3M5fS_T)XUiLj?j6(<%QbMLR#w+<~ZM?r`+0&`QH~ zB~rXB73XfPC-GyyO5I9Ja$716^&-z>r*XGqRmgxb_;lW4q-9a-^Qq6-Z?efDV@XG% zvfr5^!Z&&hkg}9jlJ%OdsV)de5}4V!UFymQp~Aax@^Jw8^B`9GWkK@8X;G!>`!JmDh$PkxkoYK=Q78$>j<wY)L!uNJN7h;5+OIxb!Mr5wR!sUlkA|b>dQzqX{SgZ};Nr z*;8aci9g61U`h2y7{mt2?IjuHW(<_jIjt%R@Oo{brC#XD^NY30m~L+|rlUgb-Jqwm z9Hhy5dIBf9yzuCc8PBZwtE+=1eyp{C`yak+#$PB&mdBjHn`5`VEeq1Qr_m<)f6g~v z!tstv_vd%tNx%hP{1_PjIJAjpHH{*g$Hksyiyyyy-lp*d;n%RyO)1^xLpe`>5Ezno zYNbq_VlHjA&j^C2*^Wqkk08?*ucxZxjrp+BdAWTDURf|)Mpc!?vs4DVHgC%_rJ zMk6(Pz_k+*3Ztkq7#*wj*J9wWDw8~z*MM4Nau*r8Hqj_}pn|W?+!x5756EwjdcdK6 zd>q1-h_!aQ2rWYSsWOFYEk$0s1+nv6CPcpS+L&73Esg%sD3~`eTLL(s_b0 zO|j{!VB|dQUt3fNQ}c1HzmU0E__QW8C|T>)Qg1VgZAU3JrN+!!*X+bxq28$Pr=Q)m zo;-gLS1xCT$&f!l+)@1tiQB-bBL|+$5&UL07N)MJ-jTgky*Z0kOlw6xBb}JVri*(` zivU%secHLtb|KKY|E`p+<#|X-qF_7kgFx;Cb^6~Uzj!?3oUe+E2FQ6jw3S_x@rrcp zyEo_AvDsPeQ+Kt9{A*8}A#jq{aXZ=!BU7mtHb$EWFju%{BTCC%-1tO(t$u(7YrPuz zP|B&OVBaBGE5`t1*`#6|;U72&u0ADJYGk`zB+#I80jpleTIcTcckrM9E;vra^R`(< zBAu@jqB6cf9g@Fvm?f-D)Q$LoSox_3kD$C+WyPkjX$t^#l|9nYz@XVgUq}o1A!fH2 zp>XvQGd3>jRJ!9YT%P}1JMUs+GUX+4;DLUUn~J%TI#9iS2_aS%T1bD_DJmk8(bpBZ2Dkx7XBdjbvgbG$@V?ln}CZ-E{FdUx>5{ zvgeO}uzh!zP?BBDbq2N8ohSJAAuT)QCp*&@xFN>Jr9#1E9Q7VfL{53AfRQN{7Z>i< zQfoHyp?-!(z)eL-!T4)|^o>MSi`%mB~$f0_l%L+Bo{bv}Pftr{c)p;^Ym{ABnbruDjVN5i)&h47e zWZM5K7EWpTS~6vU`EY-oO>1h`Q&wNhG7VhHmoC4f zza{iSnx>IMh|3N_iZjmDPJ-^qAh}){C+nBrRx(>qZi8dg%)~jUw0Z+k+^y;{JS+ii zGE!9bXAkBf$u!@}|8{`3n%W>%l^Xp{@swxeSpgS2z}mE8|MeipCbg#cfHcBbVa|L`B=nemVyA8G{Znh-T=aZB1+>q?%E;9)z>&s~v0q&0$4x4Tl5tH1g*e>wXKl_Y}4`f3p~o?bq1AhO>|ldFkDTnuE&!Kguf86 z>QIX{_paX(VZ0YjHi7-Bs>%p*1zRJdzeXU#a`)PprOa|Fk7I5k1profi*iYy!xYBu z#WZk4r%-#z_T_r-J%1o{ODMi5SaAOqzSl{j9)1{i}LP7LVe8zV{OvH~VPMn5YK>(06_{ivv)S&+S4jg0h z2o269m4#5C|LM^Td%jRkC*&!4q1u7w`EIh-ESzzqswMNr=ZUCURa|IiW2#d4diqRe z0D?EBW~FfGDs1;2I7TpYL5{ayHglL5wTYd8xLk?2Y ztZ3f>DNCKR{IWejP&>Gcoy(?~9dB;@HnqW2AH|aBMw49bj=u!P!TJC0w%_L`JsG{F z8@mJG+O#fpNhhaAE_E_L5-0Lhu7`y_D11mpuFZ<+nLPDCHqjeTtY%bU4q0is^Olgf z9wW6%*P7IEZl1QZz4-Oi;N2UlLe4g{xNYLUcP;aM&ieA6*WykIA~)G@&XEHp5i~DJ zcx??$oD=raleGw(MPwN!H9TvDZ$SQ6WPqo4^}n04EUz%A{MgQSw5fuEHOfUiKenwX zDE+4(WhG~6fQ!7)ifkRr!29ZKZ-x_KXj)HZyr=YZ_FP(OLYW{u ztQvG#9z?9J?tG^ptb9Hd;}+do9m$$`p$d2aH9!4)s{q>yL+zFt^!ZmOKfH{T&~X}q zJALcP_qL>hE-xs>oW-(?bz0RSNkz@ zMl@tl##`pd{lZrI1sXCLcr6_7krD<^F7ilJy3gYIa!BLj&P|vO5paFFCh(FEvMsSZ z(5ekUlQ;`h9?Pu#zJ)}?fv%_`+D_`ew$fejEK%s)2W8gQJl0ywo=Zdd;HCtJS1)hy zq-{Zxm{hb6L;T_He(!`V$VsHy_J1kzf#9p_Aabfg%c346%hua_jU2AeCu@;>!#;SS z&ID^x&IIi9*L3Q4ShW9vxOlP_pD4`7Tz2=ZOK>qSF8|xRxrcVa8;EUhw*0xs;&8Sg zjF64AtbIS!lL|M5yrHbK?FyY<{pr7*+DVCqITz(9Xmr#X|Y+ z?6mpU0R@C-%o(>A`)uHVe{I^JxLM z?|QcqvE*^!(kR$gGQgr39c#dC$y@~MLb>LSxg7aP)r$(bDZFd8t#_-jiRvG@tJY%S zTU96+8IGcdpl#%t|w3*fzJVN5pY4V@Mw<%lw@4%SuC0#m)O})zfaYYc%U(cy$64 zvmy?i=CX0eLiN}PI||o`s<1fbW5m)crZ*Mr>P7D$TPvHIM!$?q;`u3t_!P0^wB1+E zX!6geod#?JG5;8*#r7SZ*V{Jzih*yH@H6<2_?(_>XJE-`aJ^Kz@YhjK+G25YKN;msATkSv+)!l;N|HX!6Y z&GC8m3t4F$%d7aA(sub6Qt|7g2Yl`T$c(y2`a9ROZ#D-K`(1T(If|GTOUbqnuFtqG!- zVdmNz$hnB{`nWWeDt>HQYQ-d^AeUw`#c4ZGbBN_85tdi|%}bFZAlrKu)5S=dChS8P zyokhRM}IU$S|{hi^#zsNn&~K4m^}t2^j_26CjDDzWw30hw$h!Qjo-3PtN!RfN%SU~ z$&VQD>NJW>Oa?COi|;2yy!;x~V1}jVRaQJlZ#}YkRQ$-zMpHF$_QZno_Dyw8sJ=W; zE0sPQ?dp?Sm=OqZqPM6>ydy20xooXTUlM(>w)}diasi}kewIs!Io8ps8Af$loNXwY! z&N#l`zJFc9;q9Q#dznAYq4qD|#vN4*8m3&SLh@7jEDm?tFEXv)_U2yeNzZ?34-}73 z8QBGjW3lM0irp6Rq1n@uZ@w~ajl7x5Q3E+(QY*F$@boho%OurvnI#B-&`0m{7Wl{X z&f;NYb-c>uI<~m(VMVyJ4lh8WcEGPBIhRj3&9lD{2ncbJQ29ovPF1B!Lz+E5$%u`) zEx~|#DUfv0{b8ZsM(X-%p{uw%?r&-~Tf@Zg*@1@tJbML=c}dKj234Qszf$2rI^XMQ z{ug+@QN3MVmp}?QfT<5_Ws^|GsRz@cS82}AFOSfA9#=I1s`vpuFe*~LA*T4u*qs?y zgstQWIa*(jY^)%%<4y$&a|gjze5%JYZu9R7u0>JKCDPh^|CfA$cfKM{Su>SNSe*Ex zZc|@=^^BW}NK*-s{_AjQprmE3^>Q?5(fzSDNC_=io*j=QU|zpMwEl9kPNHMN1uxrB z)e9K>GoT`C@^FQ&){U5Gm|2r95nG1vf0}A;%4!{8*;?oNT_(}>0AT|<1kyO3<_LH_ zIn=3wDMQg?-X4_b+d?m3f}VU})7LI#H+8pR?*vHM5jn-7-|FnoCPcaOY-)gJHPLOrO?uMU?+vKBlaFLEIIud0#COW$(lUB-7fyL*&CNfV zIO&a2*^Di8)o8#+>UeRdhPgMSI@ePpAA!BidH?l|%`1ebV1tX}Vp8!j6_S?56ko@M zL^$P+LjFQ}pPe~|tYS;+8EH#`zL=NCjxh*AubolNvO|`$0Kp#7?F8o%yPk%Imo;dFI&)n?o z2?(=rlU3{9?=z)c$?3_jrg_(kjU951#A#<5LP;Bl{c;Jv6ZIy2&>1zA7XCa=LTKG& z=QP%>x+Qoz?XY#(<6#mKa^Z|NJ6(ZBpc{Xh zn#{8gx+xCj?eC+6rIoB^<&r{jv4t;>&%OV#eQ5s&m>1aOO9ajOY8KRC&0 z52Zg3iC_MHWKyF9bN*lO&3ou;Gs=!oTCbHAj}6SXWV)g(*y21LKM+5*fHcM}xJgd5 z<$tIE$~vca#%gTvkc}Ur1{h$R@zUT62PC%Nyb=$I>S53<&2#;RtY61KPC@n;rna;^{aqeH|`r(#wsrV0yeWxmFyk5;)#+=0OS>vq;?x z+|h6mK4B@)Lds4^uHKUJM@4?GLuDh#q_AV>yC**zWWg+$G%t>*_={ zS)h52ECHNrAO3ugjET2kqU3iR4Yc9z-_KM9Yt!7(E|*Mom|YCVRNx_T+}YKl^=X{8XHW+O}WWBV75 z$K1L!Zvyh%TdQ6DSKIRov_94&p!h0tpl0a7#xs7n1ad5m%YN7=eXi5^XE^LzWjTIC zwgH5WKM!@~<#|c&@DMi5Z{>e4pn730V+ENwpX|8`?N!|NtGe_}2_DiK&cU}3Y8B)B+1GOfl<$Bl?L_qfb zAQ`aBmP0x~8;vRYB8P=@5io>kx_ zp-ovKi#MSym{0sb^MltO5+33-MHjW=S2bA~*CGFK9!8ArK*!7LWu26k`Jj;biTixw zG-{&7<7MuEm7gGI+zXg#Dg|1sH;sQtgW>^@pVq0WnE{2c+CPuK11#!mBk`S(B5Nd9 z887ZX`^8IstD+V@z**TYrv3|Lc+=B)2w=$b=Q;&%wMntB|72;L#dt|(p^^5-9ne@0 z2yE3>eO`$+MK1bc=cHh=dXSR|0~T=<9|V?{C8}g<_A^3 zs>k;~ldtWZUnQo3i&L6)V?}yFU3uijD|mEJskhMK-@ZvR_iYs*D~8{Cs-{T-3w)q( zxU98>ZxVldn13z+(g7=}6!%5a&uyL0T0t?L65sLBf951Gubbrqxwv3*Wlbp(V#aNDTaF=1)AKX}iU|5H>QS>qb z8C9wV!}<02=nzmcT%m@m%7mW7ckQ2%vPAwGl~>iz1opli#h2zRIJ*6aUJBXt_(&s% zIW@hInmBe7z7zvc33s{9OQr&_s?Xm7kKFcKMs}d4OM=#p-wOE1g_W_o&vcXkW z(*LfqhV`imUrN4!QXgv%8DH4|3F7I!i5prE@3}1itjKe!WL55$PqY4%AmF+-V#-A3 z>~EH@zorC(n^^O_Wi|{pFm+-j38Ltpb9ASL&n9B zB!!hdceCv!F-QlmHXLyM^C$|f@AjE_3igiaC?2=9|EQGUWMm|6oSI+^hyQ~`wm~iu zm3Xzde)nQ>jP(3Jph$i~*U#U4d(18XUjnsIi!z@K!AbkEin@514KK;Uj$PoIKpN5B zFDTnqdCr1tSNp4_&)HW>ZMe$y#UnJ93o}2~)AM;_U;}Zt5sG&IIYJ-V*o+d#tfdp< zgug=ty}MfbWh)=v*WKR^lq6TSigNxy3b>I&YQ{!;`A>6-{yKq#)0W*n>BFY7axs}^ z;aGSM-yi(FoB$YG0&ofT3m+61?3VW5e)!H@7FMXM}kl8A+CX3qE>d@7ybYLWX2EZanux4m1OOCWp0 z|C)^@OmOZWO_>l=m_RK20mApG>kd8g##gYVHWdf=q``%MzXG; z;1n0LipGLgot*c#6Y(*mB(~Yzs$b<06Rxps*}L@ovGAW8(l9cNWAE0HA1hhis?V?r zcIrM|oc+iuj^Y77%(xmqB6=TX$5{c+q0wE6j+Y5C6y5m-8SeG@#}ye=#Pp(Q9m+#u zLVjLdEL9rdC-c?aKz@6sUOD$yEtUQ8cwH*ap635JI_tQonl6s3bP0-dgGxvv-K~O@ zba!`mH%mP<(nxm+B8!qM-HX!Q-OW4u{&_!l_uiQ~XXc!lx%2(q$P$<2|2WG);{tC- zg?AlVO(0nzvNLX5y&tiB>F+%)#v9V35?E$z@7sz8=B}bM(yuJ48t^iv;KqpM#8H`U zQ#{m937&}E)Q?zRlF$$>bFRynjC9>jTK3MSIj-z#P>Pw8t|VI*CO46RAU*Jb?OQqx z2HPfLJf5!&U>VbIVPf_dtL(iwoifN;4Xkf!CyW>VO3Hx$q1!m&pz#OM)LrMaWA@kV zc!`)kY4&2$8tgVrM>;N;H5NTaRD_R%)ig+#?-VTb7{6Wd%q`?PFI~*pU{V_leK?9Z z`Wd}dn|F!WHap|ezF54q^f@j%0hQb)n_w-^bjY8u|GojLa&5gx#-fHdv;dxHkH?=T zxZL%#&1XVu5ZSXt#b5|Cgm)fCo219|Y)qy+7?U2OW(TZy>Kdcc3@1Zw!3LN(hWJ>3 zXEU7L=E3;w-)sRWX6o29d242mydfPt9q;e`t;ocT7a>;=MT5Urh`Y0Z6(ZJVIh1eg z=I)%P$6W>=UC`Y5h zvY7uz7Q{Y9XUa}jl8uB>ONuK8K2r6rQg+3@%8N%&s7*z8$=#JKV`P0e(nMH z1zVH2TPT(gr)_*6xK~9w#$W`;8#21c|G##7V2muR;6lc>Ut3Xw1AogV|9a5t{g-`p znJSP;K0cEkuehPnw%RYQNRJZd4%4L5RpFIZSLeVFF_v9?@$M{$6cMt@Br?W!6swEd z3Wvd~b52Vo*!^EpzEK|qJrW*fPKAwOk8i63CV15+Rsj;`g&YH0C=>*$VXx9q9lGlp z!4#@|ccN+z5Ve0JCL2rmA~{=Ks)jAVOMfdKK_ok+h8iPcK#6{@or`6ph}riFN@;?X z-C4Cmr6MHv%$C1Q<^@#tyocG=*Tlx$ixV}$Dk)*9S11x^B4F~Tm%?h%HaR|{^8aOY zvC4JVIMe5tHVbzTU=FdBe*JSHSb~SEJUyx!^-~`StA(bEmh>UGbwI8x3{hay7zZC9 zmG+R^#NXOAJ|d=;NjB(hTv`fg;HzX%T5PLb=(AUASCg}dnM{jw-oJb>f3s%;c{94? zDlT-KvTJPN9q0y3?_FbfDkxPX^<@aSUWgFkzge#eFYt=c14b#Dgi$m{=VAyhK~=z3 z`0Mp2V`qfe`)V6pRS@f^=PYN}PJEY$p4GCf41l!_2H#Ps?Bp0NR-R0#GotgtpMP_H5=YDn*{zDe{VZ+=9 zzR)Q3)8bfAyjY$4l782Uort|N;6hbhzJH7qvM{@JZb0#3#?Nes>6xl|U)O@GmU~F? zxu`AfV1b8*%<^S0d*_o+Ta+x#;8rQE)pghHlOH&2V7OBf_vbM0_>i}Kni=44Hi;hZ z$@0d@#S{)2^nFUP-&pS0VY>G?s4!f)TD(5KB4ahN{{L61CNq>Ksq0VE!Bww+zv>+H zkDPaiEilWS%bD`X#vhoPjc$)TGELc{OhuZHTeHVps-&c|!<~X^%V4maphW%JL5aV0Xq2}xvk6xb~@}O@b z(=#!rcRAf}_}IHfbBtvjl0C4X|D}p4$j?%gyqHOErVp=unR;cvxogD5uMrk?DU_6Q zr7BTIXpvB{N@?OhoWen*=u*)${g`f^miw){#El=q8PvMB!6=TgKFrIv)b;wt1=lz! zNi0DlJ3ZCZzwWD?rSVJc1m)DxPZ4KOoy|9I@}O18u)Uhhap4# zD_$z5U-pwsgZ?L0iKg+()?lZ!U#VVuS(?Z$XS)23H=M_EN5%(`K5A|hmYa`5rxe;4 z(rvIUq^ux!I!8gajiGchVVAeR&ioZl%lQ){OB5($0EJ@1kCJRVZNwLjJKEMh=4j65% z4bvYSq?nEyh< z83s6qc-$rM;zO2WWKJYjx++NAIq`@;RR}**?`R$oG@FeQQ|b@86*bjUra3b5!JT}>)%@vvC=0y{Ik8#|1 ze$xXn$${ZlVT%}|LeZ~3X&(fhJG5

~(dan_KS_=jxA{SIb3-gt9VgzWuVAyRH5` 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¬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 W>#a6%= zero_at_or_above", - "fee_formula": "final + floor((initial - final) * (decay(t) - decay(D)) / (2**128 - decay(D))) for 0 < t < D; initial at t == 0; final at t >= D", - "decay_samples": [ - { - "t": 0, - "decay_q128": "340282366920938463463374607431768211456" - }, - { - "t": 1, - "decay_q128": "339889484297159900493648602831395569057" - }, - { - "t": 2, - "decay_q128": "339497055287118257930533248299650400736" - }, - { - "t": 3, - "decay_q128": "339105079367080975794817953502835654533" - }, - { - "t": 600, - "decay_q128": "170141183460469231731687303715884105727" - }, - { - "t": 1200, - "decay_q128": "85070591730234615865843651857942052863" - }, - { - "t": 3599, - "decay_q128": "5323057870043623375265121302168923694" - }, - { - "t": 3600, - "decay_q128": "5316911983139663491615228241121378303" - }, - { - "t": 4095, - "decay_q128": "3001302186225928519447628915004417640" - }, - { - "t": 4096, - "decay_q128": "2997836947964109484105344435429137776" - }, - { - "t": 65535, - "decay_q128": "448578" - }, - { - "t": 65536, - "decay_q128": "448060" - }, - { - "t": 131071, - "decay_q128": "0" - }, - { - "t": 131072, - "decay_q128": "0" - } - ], - "fee_rows": [ - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 0, - "fee": "9223372036854775808" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1, - "fee": "9202201616401419411" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 2, - "fee": "9181055638855315221" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3, - "fee": "9159934075995213866" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 7, - "fee": "9075691408129487047" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 8, - "fee": "9054691496519770908" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 15, - "fee": "8908369439011360162" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 16, - "fee": "8887562713709937803" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 60, - "fee": "7995460709951335352" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 255, - "fee": "4544718788142383059" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 256, - "fee": "4528950239025284596" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 599, - "fee": "65937678028368294" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 600, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 601, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1800, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3599, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 0, - "fee": "13835058055282163712" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1, - "fee": "13803238529972994238" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 2, - "fee": "13771455742796052931" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3, - "fee": "13739709651334290934" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 7, - "fee": "13613091396010955051" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 8, - "fee": "13581528148581462183" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 15, - "fee": "13361603446451114442" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 16, - "fee": "13330330561541330735" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 60, - "fee": "11989485095127495662" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 255, - "fee": "6802957498364342015" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 256, - "fee": "6779257083494175915" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 599, - "fee": "71268385174867108" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 600, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 601, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1800, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3599, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 0, - "fee": "16602069666338596454" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1, - "fee": "16563860678115939134" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 2, - "fee": "16525695805160495556" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3, - "fee": "16487574996537737174" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 7, - "fee": "16335531388739835853" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 8, - "fee": "16297630139818476947" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 15, - "fee": "16033543850914967009" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 16, - "fee": "15995991270240166494" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 60, - "fee": "14385899726233191848" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 255, - "fee": "8157900724497517389" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 256, - "fee": "8129441190175510706" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 599, - "fee": "74466809462766396" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 600, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 601, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1800, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3599, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 0, - "fee": "18262276632972456099" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1, - "fee": "18220233967001706071" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 2, - "fee": "18178239842579161131" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3, - "fee": "18136294203659804918" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 7, - "fee": "17968995384377164334" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 8, - "fee": "17927291334560685805" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 15, - "fee": "17636708093593278550" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 16, - "fee": "17595387695459467949" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 60, - "fee": "15823748504896609559" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 255, - "fee": "8970866660177422613" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 256, - "fee": "8939551654184311581" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 599, - "fee": "76385864035505968" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 600, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 601, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1800, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3599, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 0, - "fee": "18446744073709551615" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1, - "fee": "18404275443544569064" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 2, - "fee": "18361855846736790639" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3, - "fee": "18319485226673368001" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 7, - "fee": "18150491383892423054" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 8, - "fee": "18108364800643153456" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 15, - "fee": "17814837453890868721" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 16, - "fee": "17773098409372723666" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 60, - "fee": "15983509480303655972" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 255, - "fee": "9061196208586300971" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 256, - "fee": "9029563927963067234" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 599, - "fee": "76599092321365921" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 600, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 601, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 1800, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3599, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 0, - "fee": "9223372036854775808" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1, - "fee": "9212618807418150336" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 2, - "fee": "9201877993426478367" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3, - "fee": "9191149580545157043" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 7, - "fee": "9148359654010184691" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 8, - "fee": "9137693032240170144" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 15, - "fee": "9063370717315263099" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 16, - "fee": "9052802221924064440" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 60, - "fee": "8599671045411758433" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 255, - "fee": "6846913243858004888" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 256, - "fee": "6838903822084240589" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 599, - "fee": "4571976806974695801" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 600, - "fee": "4566593977358320110" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 601, - "fee": "4561217362640007375" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1800, - "fee": "1074010432735978337" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3599, - "fee": "55508445646640394" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "9223372036854775808", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 0, - "fee": "13835058055282163712" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1, - "fee": "13818895756712426836" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 2, - "fee": "13802752118781282045" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3, - "fee": "13786627119943561666" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 7, - "fee": "13722313085493613281" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 8, - "fee": "13706280959815140713" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 15, - "fee": "13594573174606075194" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 16, - "fee": "13578688534651899342" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 60, - "fee": "12897624170759157718" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 255, - "fee": "10263197454942952691" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 256, - "fee": "10251159148977154037" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 599, - "fee": "6843926794275282897" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 600, - "fee": "6835836303886082412" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 601, - "fee": "6827755154601213974" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1800, - "fee": "1586419990339021438" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3599, - "fee": "55593060045791169" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "13835058055282163712", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 0, - "fee": "16602069666338596454" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1, - "fee": "16582661926288992736" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 2, - "fee": "16563276593994164252" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3, - "fee": "16543913643582604439" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 7, - "fee": "16466685144383670434" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 8, - "fee": "16447433716360123053" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 15, - "fee": "16313294648980562450" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 16, - "fee": "16294220322288600283" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 60, - "fee": "15476396045967597289" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 255, - "fee": "12312967981593921373" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 256, - "fee": "12298512345112902106" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 599, - "fee": "8207096786655635154" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 600, - "fee": "8197381699802739793" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 601, - "fee": "8187677829777937934" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1800, - "fee": "1893865724900847298" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3599, - "fee": "55643828685281634" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "16602069666338596454", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 0, - "fee": "18262276632972456099" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1, - "fee": "18240921628034932275" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 2, - "fee": "18219591279121893575" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3, - "fee": "18198285557766030102" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 7, - "fee": "18113308379717704726" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 8, - "fee": "18092125370287112458" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 15, - "fee": "17944527533605254804" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 16, - "fee": "17923539394870620848" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 60, - "fee": "17023659171092661031" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 255, - "fee": "13542830297584502582" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 256, - "fee": "13526924262794350947" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 599, - "fee": "9024998782083846509" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 600, - "fee": "9014308937352734222" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 601, - "fee": "9003631434883972310" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1800, - "fee": "2078333165637942814" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3599, - "fee": "55674289868975912" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "18262276632972456099", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4096, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 0, - "fee": "18446744073709551615" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1, - "fee": "18425172706006703335" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 2, - "fee": "18403626244136085722" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3, - "fee": "18382104659341966287" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 7, - "fee": "18296266516977041870" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 8, - "fee": "18274868887390111280" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 15, - "fee": "18125775631896887287" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 16, - "fee": "18104574847379734244" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 60, - "fee": "17195577296106557002" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 255, - "fee": "13679481666027900494" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 256, - "fee": "13663414475870067485" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 599, - "fee": "9115876781575869992" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 600, - "fee": "9105078630413844714" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 601, - "fee": "9094292946562420574" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 1800, - "fee": "2098829547942064538" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3599, - "fee": "55677674444941943" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3600, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 3601, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4095, - "fee": "55340232221128654" - }, - { - "initial": "18446744073709551615", - "final": "55340232221128654", - "duration": 3600, - "elapsed": 4096, - "fee": "55340232221128654" - } - ], - "all_second_digests_sha256": { - "600": { - "50": "a5d0666688ef4a2dd7153351cf2871a5b84a4fbf7023bbb876bc2de8c740cf23", - "75": "8acacd858306553f71f73f15197ccf8915d3473a6a1d3ced8876ff355fd2a865", - "90": "d29e09a6613fbd52bccbcc0e49ba74ea7469bcea65904887c67b3115603b7109", - "99": "47a588db69ac7fe12529c7cb9b15fe91b41494195360caa9bccb939ae8133cc4", - "max": "7c849a9c92021aa28ed2e4a32f7c33b15c2ff8513157f822229f53e85c0f9612" - }, - "3600": { - "50": "9d60494802e0ebc9136779b23c27cd4a18f13b9c68102aa343908cf6d422b3f6", - "75": "700df4fd9f07ce4eedc4ad337d17836c478b0a2b1e752ae3cbfdf72940aae60f", - "90": "4cfbc14801fe33bef5a1c0201d2cc03a992d446efd4bacdd3883469e8dc7d5d1", - "99": "7988ddeb4a28991a8b29a05eb9ce25763fc27d41a25a370d19f5b1364f285bfc", - "max": "56a3f80709937290c4323f106110af97b27ec87e019266c65bd91eb3aa316e02" - } - }, - "old_factor_q64": "18425445863997925462", - "canonical_max_abs_diff_vs_old_q64": { - "600": 32, - "1800": 82, - "3600": 120, - "7200": 133, - "86400": 133 - } -} diff --git a/src/routes/launches/vectors/zero-seed-launch-events.json b/src/routes/launches/vectors/zero-seed-launch-events.json new file mode 100644 index 0000000..7e677c4 --- /dev/null +++ b/src/routes/launches/vectors/zero-seed-launch-events.json @@ -0,0 +1,130 @@ +{ + "scope": "Local Forge event fixtures, never deployed", + "LaunchCreated": [ + { + "logo": "", + "topics": [ + "0x0a54c7540832cb23832d30bbf27c5597cfb779d070a1c253a7aee90571c2da4c", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a53", + "0x000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a535100000000000000000000000000000000000000000000000000000080004646000000000000000000000000000000000000000000000000008c8c0000d28bba00000000000000000000000000000000000000000000ed8d74db520e3e09750f095a65726f205365656400000000000000000000000000000000000000000000045a45524f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce80000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2d3134ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73764400000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000100b8a78850000000000000000000000000000000000000000000000000ccccccccccccccc00000000000000000000000000000000000000000000000000c49ba5e353f7ceabfb2c1d8ee75c05313d7d3a63c0bb9e04715890c7020bc8528b61929317e98200000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000" + }, + { + "logo": "https://example.invalid/logo.svg", + "topics": [ + "0x0a54c7540832cb23832d30bbf27c5597cfb779d070a1c253a7aee90571c2da4c", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a53", + "0x000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a535100000000000000000000000000000000000000000000000000000080004646000000000000000000000000000000000000000000000000008c8c0000d28bba00000000000000000000000000000000000000000000ed8d74db520e3e09750f095a65726f205365656400000000000000000000000000000000000000000000045a45524f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce80000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2d3134ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73764400000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000100b8a78850000000000000000000000000000000000000000000000000ccccccccccccccc00000000000000000000000000000000000000000000000000c49ba5e353f7ceabfb2c1d8ee75c05313d7d3a63c0bb9e04715890c7020bc8528b61929317e9820000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000002068747470733a2f2f6578616d706c652e696e76616c69642f6c6f676f2e737667" + } + ], + "receipt": [ + { + "emitter": "0x838047Be0779C59E69610451cCf3Fd71F5fB4A53", + "data": "0x0000000000000000000000000000000000000000033b2e3c9fd0803ce7fffe47", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000014aa86c5d3c41765bb24e11bd701" + ] + }, + { + "emitter": "0x838047Be0779C59E69610451cCf3Fd71F5fB4A53", + "data": "0x00000000000000000000000000000000000000000000000000000000000001b9", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + { + "emitter": "0x5100000000000000000000000000000000000000", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a535100000000000000000000000000000000000000000000000000000080004646000000000000000000000000000000000000000000000000008c8c0000d28bba00000000000000000000000000000000000000000000ed8d74db520e3e09750f095a65726f205365656400000000000000000000000000000000000000000000045a45524f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000033b2e3c9fd0803ce80000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2d3134ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff73764400000000000000000000000000000000000000000000000000000000000003e8000000000000000000000000000000000000000000000000000000100b8a78850000000000000000000000000000000000000000000000000ccccccccccccccc00000000000000000000000000000000000000000000000000c49ba5e353f7ceabfb2c1d8ee75c05313d7d3a63c0bb9e04715890c7020bc8528b61929317e98200000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000000", + "topics": [ + "0x0a54c7540832cb23832d30bbf27c5597cfb779d070a1c253a7aee90571c2da4c", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x000000000000000000000000838047be0779c59e69610451ccf3fd71f5fb4a53", + "0x000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a" + ] + }, + { + "emitter": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a", + "data": "0x", + "topics": [ + "0x478004cf03e8c3b648608ce0259ce479c813aa671a383230ee7763fe5da22391", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000002190d584e30f4a2396c1487aa784428f2068cbe8" + ] + }, + { + "emitter": "0x838047Be0779C59E69610451cCf3Fd71F5fB4A53", + "data": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "topics": [ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496", + "0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f" + ] + }, + { + "emitter": "0x5100000000000000000000000000000000000000", + "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000ffffffffffffffffffffffffffffffffffffffffffff3a6eae2c58e969b1150a0000000000000000000000000000000000000000000000000ccccccccccccccc000000000000000000000000000000000000000000000a65f6d53eb058c019df0000000000000000000000000000000000000000000000000000000000000001", + "topics": [ + "0x3e58795f52a8e88efdec2da89ea3798175f8764145ccd7bf5e40633a7d571f6d", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x0000000000000000000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f" + ] + }, + { + "emitter": "0x838047Be0779C59E69610451cCf3Fd71F5fB4A53", + "data": "0x00000000000000000000000000000000000000000000c59151d3a716964eeaf6", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000000000000000014aa86c5d3c41765bb24e11bd701", + "0x0000000000000000000000007fa9385be102ac3eac297483dd6233d62b3e1496" + ] + }, + { + "emitter": "0x5100000000000000000000000000000000000000", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a65f6d53eb058c019df", + "topics": [ + "0xf0ffdfa84530baef2e9f8bbb160545a59bfef8da319fa27aa69e4f3c8f05a41d", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a" + ] + }, + { + "emitter": "0x838047Be0779C59E69610451cCf3Fd71F5fB4A53", + "data": "0x000000000000000000000000000000000000000000000a65f6d53eb058c019df", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000000000000000014aa86c5d3c41765bb24e11bd701", + "0x0000000000000000000000002190d584e30f4a2396c1487aa784428f2068cbe8" + ] + }, + { + "emitter": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a", + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a65f6d53eb058c019df", + "topics": [ + "0x908c6b2c19765fe47aef218b4f71ea1a10a9e156d9d5c3500c3a298e01428805", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x0000000000000000000000002190d584e30f4a2396c1487aa784428f2068cbe8", + "0x0000000000000000000000002190d584e30f4a2396c1487aa784428f2068cbe8" + ] + }, + { + "emitter": "0xF62849F9A0B5Bf2913b396098F7c7019b51A820a", + "data": "0x", + "topics": [ + "0x478004cf03e8c3b648608ce0259ce479c813aa671a383230ee7763fe5da22391", + "0xfb5475aaf870f1b1fe76ffcb64a34cd6b1c41d22f723a49abf6bab09e5760c91", + "0x0000000000000000000000002190d584e30f4a2396c1487aa784428f2068cbe8", + "0x000000000000000000000000000000000000000000000000000000000000beef" + ] + } + ] +} diff --git a/src/routes/launches/vectors/zero-seed-launch-installation.json b/src/routes/launches/vectors/zero-seed-launch-installation.json new file mode 100644 index 0000000..e28d1e6 --- /dev/null +++ b/src/routes/launches/vectors/zero-seed-launch-installation.json @@ -0,0 +1,1720 @@ +{ + "version": 2, + "scope": "LOCAL integer vectors for ZeroSeedLaunch.previewInstallation; economic ticks as requested; no deployment", + "supply": "1 <= supply <= 1000000000000000000000000000000 base units (InvalidSupply 0x15ae6727 outside); decimals are always 18", + "ticks": "MIN_TICK <= askTick < upperTick <= MAX_TICK (InvalidTicks 0x1434ed7f outside)", + "spacing_rule": "tickSpacing = ceil((upperTick - askTick) / 256); alignedAskTick = ceil(askTick / tickSpacing) * tickSpacing; alignedUpperTick = floor(upperTick / tickSpacing) * tickSpacing (src/math/launchRange.sol). The aligned range spans at most 256 steps, so its two initialized ticks occupy at most two adjacent tick-bitmap words (bitmapWords) in either token order; the spacing never exceeds 693148 < MAX_TICK_SPACING", + "pool_ticks": "token0 launch: [alignedAskTick, alignedUpperTick], initial tick = lower; token1 launch: [-alignedUpperTick, -alignedAskTick], initial tick = upper", + "token0_liquidity": "floor(supply * lowerFixed * upperFixed / (2**128 * (upperFixed - lowerFixed))), the exact inverse of Core's token0 debit ceil(ceil(liquidity * 2**128 * (upperFixed - lowerFixed) / upperFixed) / lowerFixed); computed on chain as floor(supply * mantissaLower * mantissaUpper / (((upperFixed - lowerFixed) >> shiftLower) << (128 - shiftUpper))) from the compact sqrt ratios (mantissa = low 94 bits, shift = 2 + 32 * top two bits); saturates at 2**128 - 1", + "token1_liquidity": "floor(supply * 2**128 / (upperFixed - lowerFixed)), the shared helper; the exact inverse of ceil(liquidity * (upperFixed - lowerFixed) / 2**128)", + "installed_supply": "the debit above at the installed liquidity; sink = supply - installedSupply is minted to address(0); totalSupply = supply", + "refusals": { + "ZeroLiquidity": "0x10074548", + "LiquidityAboveTickCap(uint128,uint128)": "0x20ea795f", + "tick_cap": "floor((2**128 - 1) / (1 + 2 * floor(88722835 / tickSpacing))); liquidity above it, including the uint128 saturation, is refused rather than capped" + }, + "pins": { + "runtime_templates": { + "ZeroSeedLaunch": { + "bytes": 18366, + "keccak256": "0x707be0d4e2c25c21d6d9aafec5bb58e634118c1edade48d0dc1fd5be5de2624c" + }, + "ZeroSeedLaunchPeriphery": { + "bytes": 6123, + "keccak256": "0xe6c0d086bff1c96e4f0a2e8068022795a0c74f96d15d15cd041cbea2d2e9b529" + }, + "LaunchToken": { + "bytes": 3012, + "keccak256": "0xec01758e39962fca797fc966e3e541ac87c2c72814b9e1ab86d6193597b182b6" + } + }, + "creationCode": { + "ZeroSeedLaunch": { + "bytes": 18884, + "keccak256": "0x6ec3a947c32774eab196724fd1a98c4eb927af430dec7ed1b111be414d85dc20" + }, + "ZeroSeedLaunchPeriphery": { + "bytes": 6343, + "keccak256": "0x4698c93153732a8f4371252ca5af048a7fe5a181dbd6e26ee4e76f2c942abaf2" + }, + "LaunchToken": { + "bytes": 3172, + "keccak256": "0xb92240f5b935a76e949aab04e7e9e4a0a28adc3ff1a89a349c67dcaab13ea432" + } + } + }, + "vectors": [ + { + "label": "fixture 18/18", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": -13798330, + "tickUpper": -9210880, + "initialTick": -13798330, + "lowerSqrtRatio": "19827018619021557321624769538", + "upperSqrtRatio": "20005058055867721043010416951", + "lowerFixed": "343219262584225398096347565219905536", + "upperFixed": "3401913497194347663748393335707926528", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1121810267768648385524834", + "installedSupply": "999999999999999999999999508", + "sink": "492", + "debitAtLiquidityPlusOne": "1000000000000000000000000399" + }, + { + "label": "fixture 18/18", + "order": "t1", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": 9210880, + "tickUpper": 13798330, + "initialTick": 13798330, + "lowerSqrtRatio": "39614081718424154951297988133", + "upperSqrtRatio": "39614085829356414111595421918", + "lowerFixed": "34037340847382845064040913858633410805760", + "upperFixed": "337370602003729386149418028072584609792000", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1121810267768648385525007", + "installedSupply": "999999999999999999999999559", + "sink": "441", + "debitAtLiquidityPlusOne": "1000000000000000000000000450" + }, + { + "label": "fixture 18/6", + "order": "t0", + "askTick": -41446500, + "upperTick": -36841300, + "supply": "1000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -41430970, + "alignedUpperTick": -36843520, + "steps": 255, + "bitmapWords": 2, + "tickLower": -41430970, + "tickUpper": -36843520, + "initialTick": -41430970, + "lowerSqrtRatio": "19807040648528048271455314437", + "upperSqrtRatio": "19807040826424659756276285499", + "lowerFixed": "342943927995065015897478397952", + "upperFixed": "3399184441581161300402487951360", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892132017", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1120910338181752946", + "installedSupply": "999999999999999999488107389", + "sink": "511892611", + "debitAtLiquidityPlusOne": "1000000000000000000380239405" + }, + { + "label": "fixture 18/6", + "order": "t1", + "askTick": -41446500, + "upperTick": -36841300, + "supply": "1000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -41430970, + "alignedUpperTick": -36843520, + "steps": 255, + "bitmapWords": 2, + "tickLower": 36843520, + "tickUpper": 41430970, + "initialTick": 41430970, + "lowerSqrtRatio": "40075743594324059322585979863", + "upperSqrtRatio": "44189976340101102891421684705", + "lowerFixed": "34064667930597629112500749730287357927075348480", + "upperFixed": "337641462014695448017496846709930090227635847168", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892132017", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1120910338181752946", + "installedSupply": "999999999999999999449009087", + "sink": "550990913", + "debitAtLiquidityPlusOne": "1000000000000000000341141103" + }, + { + "label": "fixture 18/18, supply cap", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": -13798330, + "tickUpper": -9210880, + "initialTick": -13798330, + "lowerSqrtRatio": "19827018619021557321624769538", + "upperSqrtRatio": "20005058055867721043010416951", + "lowerFixed": "343219262584225398096347565219905536", + "upperFixed": "3401913497194347663748393335707926528", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1121810267768648385524834552", + "installedSupply": "999999999999999999999999999560", + "sink": "440", + "debitAtLiquidityPlusOne": "1000000000000000000000000000451" + }, + { + "label": "fixture 18/18, supply cap", + "order": "t1", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": 9210880, + "tickUpper": 13798330, + "initialTick": 13798330, + "lowerSqrtRatio": "39614081718424154951297988133", + "upperSqrtRatio": "39614085829356414111595421918", + "lowerFixed": "34037340847382845064040913858633410805760", + "upperFixed": "337370602003729386149418028072584609792000", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1121810267768648385525007495", + "installedSupply": "999999999999999999999999999283", + "sink": "717", + "debitAtLiquidityPlusOne": "1000000000000000000000000000174" + }, + { + "label": "fixture 18/18, supply cap + 1", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000001", + "error": "InvalidSupply", + "selector": "0x15ae6727" + }, + { + "label": "fixture 18/18, supply cap + 1", + "order": "t1", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1000000000000000000000000000001", + "error": "InvalidSupply", + "selector": "0x15ae6727" + }, + { + "label": "fixture 18/18, supply 0", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "0", + "error": "InvalidSupply", + "selector": "0x15ae6727" + }, + { + "label": "fixture 18/18, uint128 max", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "340282366920938463463374607431768211455", + "error": "InvalidSupply", + "selector": "0x15ae6727" + }, + { + "label": "fixture 18/18, inverted ticks", + "order": "t0", + "askTick": -9210300, + "upperTick": -13815500, + "supply": "1000000000000000000000000000", + "error": "InvalidTicks", + "selector": "0x1434ed7f" + }, + { + "label": "fixture 18/18, equal ticks", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815500, + "supply": "1000000000000000000000000000", + "error": "InvalidTicks", + "selector": "0x1434ed7f" + }, + { + "label": "below MIN_TICK", + "order": "t0", + "askTick": -88722836, + "upperTick": 0, + "supply": "1000000000000000000000000000", + "error": "InvalidTicks", + "selector": "0x1434ed7f" + }, + { + "label": "above MAX_TICK", + "order": "t0", + "askTick": 0, + "upperTick": 88722836, + "supply": "1000000000000000000000000000", + "error": "InvalidTicks", + "selector": "0x1434ed7f" + }, + { + "label": "low price, 1e30", + "order": "t0", + "askTick": -88080384, + "upperTick": -85983232, + "supply": "1000000000000000000000000000000", + "tickSpacing": 8192, + "alignedAskTick": -88080384, + "alignedUpperTick": -85983232, + "steps": 256, + "bitmapWords": 2, + "tickLower": -88080384, + "tickUpper": -85983232, + "initialTick": -88080384, + "lowerSqrtRatio": "6358821724476877202", + "upperSqrtRatio": "18145426989333890332", + "lowerFixed": "25435286897907508808", + "upperFixed": "72581707957335561328", + "tickCap": "15709448636763698050107317641464762", + "supplyEnvelope": { + "minimum": "8690091540532083688", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "115073586432", + "installedSupply": "999999999991410763432286846442", + "sink": "8589236567713153558", + "debitAtLiquidityPlusOne": "1000000000000100854972818930129" + }, + { + "label": "low price, 1e30", + "order": "t1", + "askTick": -88080384, + "upperTick": -85983232, + "supply": "1000000000000000000000000000000", + "tickSpacing": 8192, + "alignedAskTick": -88080384, + "alignedUpperTick": -85983232, + "steps": 256, + "bitmapWords": 2, + "tickLower": 85983232, + "tickUpper": 88080384, + "initialTick": 88080384, + "lowerSqrtRatio": "64455109368432855965439496557", + "upperSqrtRatio": "73786024109890745434315100291", + "lowerFixed": "1595334313507478198208865611889941678896332009971300433920", + "upperFixed": "4552419231679360084912250521926274578455984483792549052416", + "tickCap": "15709448636763698050107317641464762", + "supplyEnvelope": { + "minimum": "8690091540532083688", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "115073586432", + "installedSupply": "999999999991410763454585671556", + "sink": "8589236545414328444", + "debitAtLiquidityPlusOne": "1000000000000100854995117755244" + }, + { + "label": "low price, two liquidity units", + "order": "t0", + "askTick": -88080384, + "upperTick": -85983232, + "supply": "17380183081064167375", + "tickSpacing": 8192, + "alignedAskTick": -88080384, + "alignedUpperTick": -85983232, + "steps": 256, + "bitmapWords": 2, + "tickLower": -88080384, + "tickUpper": -85983232, + "initialTick": -88080384, + "lowerSqrtRatio": "6358821724476877202", + "upperSqrtRatio": "18145426989333890332", + "lowerFixed": "25435286897907508808", + "upperFixed": "72581707957335561328", + "tickCap": "15709448636763698050107317641464762", + "supplyEnvelope": { + "minimum": "8690091540532083688", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "2", + "installedSupply": "17380183081064167375", + "sink": "0", + "debitAtLiquidityPlusOne": "26070274621596251062" + }, + { + "label": "low price, one unit short", + "order": "t0", + "askTick": -88080384, + "upperTick": -85983232, + "supply": "17380183081064167374", + "tickSpacing": 8192, + "alignedAskTick": -88080384, + "alignedUpperTick": -85983232, + "steps": 256, + "bitmapWords": 2, + "tickLower": -88080384, + "tickUpper": -85983232, + "initialTick": -88080384, + "lowerSqrtRatio": "6358821724476877202", + "upperSqrtRatio": "18145426989333890332", + "lowerFixed": "25435286897907508808", + "upperFixed": "72581707957335561328", + "tickCap": "15709448636763698050107317641464762", + "supplyEnvelope": { + "minimum": "8690091540532083688", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "1", + "installedSupply": "8690091540532083688", + "sink": "8690091540532083686", + "debitAtLiquidityPlusOne": "17380183081064167375" + }, + { + "label": "low price, below one unit", + "order": "t0", + "askTick": -88080384, + "upperTick": -85983232, + "supply": "8690091540532083687", + "tickSpacing": 8192, + "alignedAskTick": -88080384, + "alignedUpperTick": -85983232, + "steps": 256, + "bitmapWords": 2, + "tickLower": -88080384, + "tickUpper": -85983232, + "initialTick": -88080384, + "lowerSqrtRatio": "6358821724476877202", + "upperSqrtRatio": "18145426989333890332", + "lowerFixed": "25435286897907508808", + "upperFixed": "72581707957335561328", + "tickCap": "15709448636763698050107317641464762", + "supplyEnvelope": { + "minimum": "8690091540532083688", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "error": "ZeroLiquidity", + "selector": "0x10074548" + }, + { + "label": "full width, supply cap", + "order": "t0", + "askTick": -88722835, + "upperTick": 88722835, + "supply": "1000000000000000000000000000000", + "tickSpacing": 693148, + "alignedAskTick": -88029796, + "alignedUpperTick": 88029796, + "steps": 254, + "bitmapWords": 1, + "tickLower": -88029796, + "tickUpper": 88029796, + "initialTick": -88029796, + "lowerSqrtRatio": "6521713082187947545", + "upperSqrtRatio": "73427235184772856332486820067", + "lowerFixed": "26086852328751790180", + "upperFixed": "4438714482609126741834905067882300589470816915495961231360", + "tickCap": "1334440654591915542993625911497130241", + "supplyEnvelope": { + "minimum": "13044209498050253036", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "76662368857", + "installedSupply": "999999999987511320520150058769", + "sink": "12488679479849941231", + "debitAtLiquidityPlusOne": "1000000000000555530018200311804" + }, + { + "label": "full width, supply cap", + "order": "t1", + "askTick": -88722835, + "upperTick": 88722835, + "supply": "1000000000000000000000000000000", + "tickSpacing": 693148, + "alignedAskTick": -88029796, + "alignedUpperTick": 88029796, + "steps": 254, + "bitmapWords": 1, + "tickLower": -88029796, + "tickUpper": 88029796, + "initialTick": 88029796, + "lowerSqrtRatio": "6521713082187947545", + "upperSqrtRatio": "73427235184772856332486820067", + "lowerFixed": "26086852328751790180", + "upperFixed": "4438714482609126741834905067882300589470816915495961231360", + "tickCap": "1334440654591915542993625911497130241", + "supplyEnvelope": { + "minimum": "13044209498050253035", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "76662368857", + "installedSupply": "999999999987511320481816572317", + "sink": "12488679518183427683", + "debitAtLiquidityPlusOne": "1000000000000555529979866825352" + }, + { + "label": "full width, 1e27", + "order": "t0", + "askTick": -88722835, + "upperTick": 88722835, + "supply": "1000000000000000000000000000", + "tickSpacing": 693148, + "alignedAskTick": -88029796, + "alignedUpperTick": 88029796, + "steps": 254, + "bitmapWords": 1, + "tickLower": -88029796, + "tickUpper": 88029796, + "initialTick": -88029796, + "lowerSqrtRatio": "6521713082187947545", + "upperSqrtRatio": "73427235184772856332486820067", + "lowerFixed": "26086852328751790180", + "upperFixed": "4438714482609126741834905067882300589470816915495961231360", + "tickCap": "1334440654591915542993625911497130241", + "supplyEnvelope": { + "minimum": "13044209498050253036", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "76662368", + "installedSupply": "999999988808623780691083208", + "sink": "11191376219308916792", + "debitAtLiquidityPlusOne": "1000000001852833278741336243" + }, + { + "label": "full width, 1e27", + "order": "t1", + "askTick": -88722835, + "upperTick": 88722835, + "supply": "1000000000000000000000000000", + "tickSpacing": 693148, + "alignedAskTick": -88029796, + "alignedUpperTick": 88029796, + "steps": 254, + "bitmapWords": 1, + "tickLower": -88029796, + "tickUpper": 88029796, + "initialTick": 88029796, + "lowerSqrtRatio": "6521713082187947545", + "upperSqrtRatio": "73427235184772856332486820067", + "lowerFixed": "26086852328751790180", + "upperFixed": "4438714482609126741834905067882300589470816915495961231360", + "tickCap": "1334440654591915542993625911497130241", + "supplyEnvelope": { + "minimum": "13044209498050253035", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "76662368", + "installedSupply": "999999988808623780652749722", + "sink": "11191376219347250278", + "debitAtLiquidityPlusOne": "1000000001852833278703002757" + }, + { + "label": "full width, supply 1", + "order": "t0", + "askTick": -88722835, + "upperTick": 88722835, + "supply": "1", + "tickSpacing": 693148, + "alignedAskTick": -88029796, + "alignedUpperTick": 88029796, + "steps": 254, + "bitmapWords": 1, + "tickLower": -88029796, + "tickUpper": 88029796, + "initialTick": -88029796, + "lowerSqrtRatio": "6521713082187947545", + "upperSqrtRatio": "73427235184772856332486820067", + "lowerFixed": "26086852328751790180", + "upperFixed": "4438714482609126741834905067882300589470816915495961231360", + "tickCap": "1334440654591915542993625911497130241", + "supplyEnvelope": { + "minimum": "13044209498050253036", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "error": "ZeroLiquidity", + "selector": "0x10074548" + }, + { + "label": "adjacent ticks at the bottom", + "order": "t0", + "askTick": -88722835, + "upperTick": -88722834, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -88722835, + "alignedUpperTick": -88722834, + "steps": 1, + "bitmapWords": 1, + "tickLower": -88722835, + "tickUpper": -88722834, + "initialTick": -88722835, + "lowerSqrtRatio": "4611797791050542631", + "upperSqrtRatio": "4611800096948861682", + "lowerFixed": "18447191164202170524", + "upperFixed": "18447200387795446728", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "9223141579672", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "108422926327411597", + "installedSupply": "999999999999999996344855661603", + "sink": "3655144338397", + "debitAtLiquidityPlusOne": "1000000000000000005567997241275" + }, + { + "label": "adjacent ticks at the bottom", + "order": "t1", + "askTick": -88722835, + "upperTick": -88722834, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -88722835, + "alignedUpperTick": -88722834, + "steps": 1, + "bitmapWords": 1, + "tickLower": 88722834, + "tickUpper": 88722835, + "initialTick": 88722835, + "lowerSqrtRatio": "79227672562865279167584692032", + "upperSqrtRatio": "79227682466138141934206691491", + "lowerFixed": "6276946463590405995231569687814752028850240276668948152320", + "upperFixed": "6276949602062853172742588666607187473671941430179807625216", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "9223141579672", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "108422926327411597", + "installedSupply": "999999999999999996128023363811", + "sink": "3871976636189", + "debitAtLiquidityPlusOne": "1000000000000000005351164943482" + }, + { + "label": "adjacent ticks at the top", + "order": "t0", + "askTick": 88722834, + "upperTick": 88722835, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": 88722834, + "alignedUpperTick": 88722835, + "steps": 1, + "bitmapWords": 1, + "tickLower": 88722834, + "tickUpper": 88722835, + "initialTick": 88722834, + "lowerSqrtRatio": "79227672562865279167584692032", + "upperSqrtRatio": "79227682466138141934206691491", + "lowerFixed": "6276946463590405995231569687814752028850240276668948152320", + "upperFixed": "6276949602062853172742588666607187473671941430179807625216", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "51979", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "adjacent ticks at the top", + "order": "t1", + "askTick": 88722834, + "upperTick": 88722835, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": 88722834, + "alignedUpperTick": 88722835, + "steps": 1, + "bitmapWords": 1, + "tickLower": -88722835, + "tickUpper": -88722834, + "initialTick": -88722834, + "lowerSqrtRatio": "4611797791050542631", + "upperSqrtRatio": "4611800096948861682", + "lowerFixed": "18447191164202170524", + "upperFixed": "18447200387795446728", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "51979", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "adjacent ticks at the top, supply 1", + "order": "t0", + "askTick": 88722834, + "upperTick": 88722835, + "supply": "1", + "tickSpacing": 1, + "alignedAskTick": 88722834, + "alignedUpperTick": 88722835, + "steps": 1, + "bitmapWords": 1, + "tickLower": 88722834, + "tickUpper": 88722835, + "initialTick": 88722834, + "lowerSqrtRatio": "79227672562865279167584692032", + "upperSqrtRatio": "79227682466138141934206691491", + "lowerFixed": "6276946463590405995231569687814752028850240276668948152320", + "upperFixed": "6276949602062853172742588666607187473671941430179807625216", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "51979", + "nonempty": true + }, + "liquidity": "36892603211249009589164678", + "installedSupply": "1", + "sink": "0", + "debitAtLiquidityPlusOne": "2" + }, + { + "label": "adjacent ticks at the top, cap", + "order": "t0", + "askTick": 88722834, + "upperTick": 88722835, + "supply": "1000000000000", + "tickSpacing": 1, + "alignedAskTick": 88722834, + "alignedUpperTick": 88722835, + "steps": 1, + "bitmapWords": 1, + "tickLower": 88722834, + "tickUpper": 88722835, + "initialTick": 88722834, + "lowerSqrtRatio": "79227672562865279167584692032", + "upperSqrtRatio": "79227682466138141934206691491", + "lowerFixed": "6276946463590405995231569687814752028850240276668948152320", + "upperFixed": "6276949602062853172742588666607187473671941430179807625216", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "51979", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "36892603211249009589164678108647352867" + }, + { + "label": "one word wide at spacing 1", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815244, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 256, + "bitmapWords": 2, + "tickLower": -13815500, + "tickUpper": -13815244, + "initialTick": -13815500, + "lowerSqrtRatio": "19826847842167416264437938874", + "upperSqrtRatio": "19826850377651757028987077894", + "lowerFixed": "340285338570426986943133710140047360", + "upperFixed": "340328897859719402375886813570007040", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "245443876583013952532725738056", + "nonempty": true + }, + "liquidity": "7813072147042959328981570090", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "one word wide at spacing 1", + "order": "t1", + "askTick": -13815500, + "upperTick": -13815244, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 256, + "bitmapWords": 2, + "tickLower": 13815244, + "tickUpper": 13815500, + "initialTick": 13815500, + "lowerSqrtRatio": "39614085868187661632152093760", + "upperSqrtRatio": "39614085868777914215468221053", + "lowerFixed": "340235842344027696174984133961348900978688", + "upperFixed": "340279395297400810272991339629943340400640", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "245443876583013952519617503627", + "nonempty": true + }, + "liquidity": "7813072147042959329398836891", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "one word wide at spacing 1, supply cap", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815244, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 256, + "bitmapWords": 2, + "tickLower": -13815500, + "tickUpper": -13815244, + "initialTick": -13815500, + "lowerSqrtRatio": "19826847842167416264437938874", + "upperSqrtRatio": "19826850377651757028987077894", + "lowerFixed": "340285338570426986943133710140047360", + "upperFixed": "340328897859719402375886813570007040", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "245443876583013952532725738056", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "7813072147042959328981570090179" + }, + { + "label": "one word wide at spacing 1, supply cap", + "order": "t1", + "askTick": -13815500, + "upperTick": -13815244, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 256, + "bitmapWords": 2, + "tickLower": 13815244, + "tickUpper": 13815500, + "initialTick": 13815500, + "lowerSqrtRatio": "39614085868187661632152093760", + "upperSqrtRatio": "39614085868777914215468221053", + "lowerFixed": "340235842344027696174984133961348900978688", + "upperFixed": "340279395297400810272991339629943340400640", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "245443876583013952519617503627", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "7813072147042959329398836891643" + }, + { + "label": "first rounded width", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815243, + "supply": "1000000000000000000000000000", + "tickSpacing": 2, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 128, + "bitmapWords": 1, + "tickLower": -13815500, + "tickUpper": -13815244, + "initialTick": -13815500, + "lowerSqrtRatio": "19826847842167416264437938874", + "upperSqrtRatio": "19826850377651757028987077894", + "lowerFixed": "340285338570426986943133710140047360", + "upperFixed": "340328897859719402375886813570007040", + "tickCap": "3835341453200165024746725095425", + "supplyEnvelope": { + "minimum": "1", + "maximum": "490887755932439467353941835249", + "nonempty": true + }, + "liquidity": "7813072147042959328981570090", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "first rounded width", + "order": "t1", + "askTick": -13815500, + "upperTick": -13815243, + "supply": "1000000000000000000000000000", + "tickSpacing": 2, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815244, + "steps": 128, + "bitmapWords": 1, + "tickLower": 13815244, + "tickUpper": 13815500, + "initialTick": 13815500, + "lowerSqrtRatio": "39614085868187661632152093760", + "upperSqrtRatio": "39614085868777914215468221053", + "lowerFixed": "340235842344027696174984133961348900978688", + "upperFixed": "340279395297400810272991339629943340400640", + "tickCap": "3835341453200165024746725095425", + "supplyEnvelope": { + "minimum": "1", + "maximum": "490887755932439467327725366242", + "nonempty": true + }, + "liquidity": "7813072147042959329398836891", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "region 2**96 crossing", + "order": "t0", + "askTick": -44361442, + "upperTick": -44361441, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -44361442, + "alignedUpperTick": -44361441, + "steps": 1, + "bitmapWords": 1, + "tickLower": -44361442, + "tickUpper": -44361441, + "initialTick": -44361442, + "lowerSqrtRatio": "19807038019411659194957474410", + "upperSqrtRatio": "19807040633177772115164400283", + "lowerFixed": "79228152077646636779829897640", + "upperFixed": "79228191691712772083984367616", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "2148", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "465661575212763904543525", + "installedSupply": "999999999999999999999999306", + "sink": "694", + "debitAtLiquidityPlusOne": "1000000000000000000000001453" + }, + { + "label": "region 2**96 crossing", + "order": "t0", + "askTick": -44361442, + "upperTick": -44361440, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -44361442, + "alignedUpperTick": -44361440, + "steps": 2, + "bitmapWords": 1, + "tickLower": -44361442, + "tickUpper": -44361440, + "initialTick": -44361442, + "lowerSqrtRatio": "19807038019411659194957474410", + "upperSqrtRatio": "19807040633177774421007682211", + "lowerFixed": "79228152077646636779829897640", + "upperFixed": "79228231305798714412255674368", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "4295", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "232830845814061502201641833", + "installedSupply": "999999999999999999999999996985", + "sink": "3015", + "debitAtLiquidityPlusOne": "1000000000000000000000000001280" + }, + { + "label": "region 2**96 crossing wide", + "order": "t0", + "askTick": -44361442, + "upperTick": 0, + "supply": "1000000000000000000000000000", + "tickSpacing": 173287, + "alignedAskTick": -44188185, + "alignedUpperTick": 0, + "steps": 255, + "bitmapWords": 2, + "tickLower": -44188185, + "tickUpper": 0, + "initialTick": -44188185, + "lowerSqrtRatio": "19807040633595087861311793454", + "upperSqrtRatio": "39614081261743854815199363072", + "lowerFixed": "86397621618948338744379310080", + "upperFixed": "340282366920938463463374607431768211456", + "tickCap": "332631834722324988722751326912774400", + "supplyEnvelope": { + "minimum": "3938561739", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "253899790408353378", + "installedSupply": "999999999999999996725183603", + "sink": "3274816397", + "debitAtLiquidityPlusOne": "1000000000000000000663745342" + }, + { + "label": "region 2**128 crossing", + "order": "t0", + "askTick": -1, + "upperTick": 1, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -1, + "alignedUpperTick": 1, + "steps": 2, + "bitmapWords": 1, + "tickLower": -1, + "tickUpper": 1, + "initialTick": -1, + "lowerSqrtRatio": "39614071353619282147775793476", + "upperSqrtRatio": "39614081261743857121041795825", + "lowerFixed": "340282196779882608775400081047712432128", + "upperFixed": "340282537062079388641046796912818126848", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "1917669756958261589446310", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "1000000499999924847075404150522817" + }, + { + "label": "region 2**128 crossing", + "order": "t1", + "askTick": -1, + "upperTick": 1, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -1, + "alignedUpperTick": 1, + "steps": 2, + "bitmapWords": 1, + "tickLower": -1, + "tickUpper": 1, + "initialTick": 1, + "lowerSqrtRatio": "39614071353619282147775793476", + "upperSqrtRatio": "39614081261743857121041795825", + "lowerFixed": "340282196779882608775400081047712432128", + "upperFixed": "340282537062079388641046796912818126848", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "1917669756958261589350720", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "1000000499999924847125251163448363" + }, + { + "label": "region 2**160 crossing", + "order": "t0", + "askTick": 44361441, + "upperTick": 44361442, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": 44361441, + "alignedUpperTick": 44361442, + "steps": 1, + "bitmapWords": 1, + "tickLower": 44361441, + "tickUpper": 44361442, + "initialTick": 44361441, + "lowerSqrtRatio": "59421114591338830873580461336", + "upperSqrtRatio": "59421121890309939821076534892", + "lowerFixed": "1461501099102177137331732072520887438988072189952", + "upperFixed": "1461501829852544000734507580892718290576772956160", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "223246168317635", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "region 2**160 crossing", + "order": "t1", + "askTick": 44361441, + "upperTick": 44361442, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": 44361441, + "alignedUpperTick": 44361442, + "steps": 1, + "bitmapWords": 1, + "tickLower": -44361442, + "tickUpper": -44361441, + "initialTick": -44361441, + "lowerSqrtRatio": "19807038019411659194957474410", + "upperSqrtRatio": "19807040633177772115164400283", + "lowerFixed": "79228152077646636779829897640", + "upperFixed": "79228191691712772083984367616", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "223246168317648", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "three regions", + "order": "t0", + "askTick": -44361442, + "upperTick": 44361442, + "supply": "1000000000000000000000000000", + "tickSpacing": 346574, + "alignedAskTick": -44014898, + "alignedUpperTick": 44014898, + "steps": 254, + "bitmapWords": 1, + "tickLower": -44014898, + "tickUpper": 44014898, + "initialTick": -44014898, + "lowerSqrtRatio": "19807040634050251886466666528", + "upperSqrtRatio": "56270000780324829244662726208", + "lowerFixed": "94217280028371943495823261696", + "upperFixed": "1228989939026549719071510155195115008447542722560", + "tickCap": "665914612369742589947895513565104132", + "supplyEnvelope": { + "minimum": "3611676827", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "276879701057981879", + "installedSupply": "999999999999999999201164169", + "sink": "798835831", + "debitAtLiquidityPlusOne": "1000000000000000002812840995" + }, + { + "label": "three regions", + "order": "t1", + "askTick": -44361442, + "upperTick": 44361442, + "supply": "1000000000000000000000000000", + "tickSpacing": 346574, + "alignedAskTick": -44014898, + "alignedUpperTick": 44014898, + "steps": 254, + "bitmapWords": 1, + "tickLower": -44014898, + "tickUpper": 44014898, + "initialTick": 44014898, + "lowerSqrtRatio": "19807040634050251886466666528", + "upperSqrtRatio": "56270000780324829244662726208", + "lowerFixed": "94217280028371943495823261696", + "upperFixed": "1228989939026549719071510155195115008447542722560", + "tickCap": "665914612369742589947895513565104132", + "supplyEnvelope": { + "minimum": "3611676827", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "276879701057981879", + "installedSupply": "999999999999999999193455279", + "sink": "806544721", + "debitAtLiquidityPlusOne": "1000000000000000002805132106" + }, + { + "label": "positive ticks, both bounds rounded", + "order": "t0", + "askTick": 60000000, + "upperTick": 70000000, + "supply": "1", + "tickSpacing": 39063, + "alignedAskTick": 60000768, + "alignedUpperTick": 69961833, + "steps": 255, + "bitmapWords": 2, + "tickLower": 60000768, + "tickUpper": 69961833, + "initialTick": 60000768, + "lowerSqrtRatio": "59421133364447837616263712467", + "upperSqrtRatio": "59422792634974961669204623761", + "lowerFixed": "3637760950140238375968157297076375377496176024616960", + "upperFixed": "529481580862594314452120214967405192950959490094596096", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "1", + "maximum": "6958376342586156033918", + "nonempty": true + }, + "liquidity": "10764374391619", + "installedSupply": "1", + "sink": "0", + "debitAtLiquidityPlusOne": "2" + }, + { + "label": "positive ticks, both bounds rounded", + "order": "t1", + "askTick": 60000000, + "upperTick": 70000000, + "supply": "1000000000000000000000000000", + "tickSpacing": 39063, + "alignedAskTick": 60000768, + "alignedUpperTick": 69961833, + "steps": 255, + "bitmapWords": 2, + "tickLower": -69961833, + "tickUpper": -60000768, + "initialTick": -60000768, + "lowerSqrtRatio": "54672387776301789389034", + "upperSqrtRatio": "7957648318868528504910952", + "lowerFixed": "218689551105207157556136", + "upperFixed": "31830593275474114019643808", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "1", + "maximum": "6958376342586156033917", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "negative ticks, both bounds rounded", + "order": "t0", + "askTick": -70000000, + "upperTick": -60000000, + "supply": "1000000000000000000000000000", + "tickSpacing": 39063, + "alignedAskTick": -69961833, + "alignedUpperTick": -60000768, + "steps": 255, + "bitmapWords": 2, + "tickLower": -69961833, + "tickUpper": -60000768, + "initialTick": -69961833, + "lowerSqrtRatio": "54672387776301789389034", + "upperSqrtRatio": "7957648318868528504910952", + "lowerFixed": "218689551105207157556136", + "upperFixed": "31830593275474114019643808", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "1545316099304756", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "647116794065", + "installedSupply": "999999999999124336682130621", + "sink": "875663317869379", + "debitAtLiquidityPlusOne": "1000000000000669652781435377" + }, + { + "label": "negative ticks, both bounds rounded", + "order": "t1", + "askTick": -70000000, + "upperTick": -60000000, + "supply": "1000000000000000000000000000", + "tickSpacing": 39063, + "alignedAskTick": -69961833, + "alignedUpperTick": -60000768, + "steps": 255, + "bitmapWords": 2, + "tickLower": 60000768, + "tickUpper": 69961833, + "initialTick": 69961833, + "lowerSqrtRatio": "59421133364447837616263712467", + "upperSqrtRatio": "59422792634974961669204623761", + "lowerFixed": "3637760950140238375968157297076375377496176024616960", + "upperFixed": "529481580862594314452120214967405192950959490094596096", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "1545316099304756", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "647116794065", + "installedSupply": "999999999999124336682121692", + "sink": "875663317878308", + "debitAtLiquidityPlusOne": "1000000000000669652781426447" + }, + { + "label": "range across zero, both bounds rounded", + "order": "t0", + "askTick": -5000001, + "upperTick": 4999999, + "supply": "1000000000000000000000000000", + "tickSpacing": 39063, + "alignedAskTick": -4961001, + "alignedUpperTick": 4961001, + "steps": 254, + "bitmapWords": 1, + "tickLower": -4961001, + "tickUpper": 4961001, + "initialTick": -4961001, + "lowerSqrtRatio": "21464918182048129249605627937", + "upperSqrtRatio": "39614081312229031735371803891", + "lowerFixed": "28482119491911494236774164116067581952", + "upperFixed": "4065430919570415286197717704106911465472", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "12", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "84291969943815295400760046", + "installedSupply": "999999999999999999999999992", + "sink": "8", + "debitAtLiquidityPlusOne": "1000000000000000000000000004" + }, + { + "label": "range across zero, both bounds rounded", + "order": "t1", + "askTick": -5000001, + "upperTick": 4999999, + "supply": "1000000000000000000000000000", + "tickSpacing": 39063, + "alignedAskTick": -4961001, + "alignedUpperTick": 4961001, + "steps": 254, + "bitmapWords": 1, + "tickLower": -4961001, + "tickUpper": 4961001, + "initialTick": 4961001, + "lowerSqrtRatio": "21464918182048129249605627937", + "upperSqrtRatio": "39614081312229031735371803891", + "lowerFixed": "28482119491911494236774164116067581952", + "upperFixed": "4065430919570415286197717704106911465472", + "tickCap": "74902568109385530148222453760019417", + "supplyEnvelope": { + "minimum": "12", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "84291969943815295401053197", + "installedSupply": "999999999999999999999999991", + "sink": "9", + "debitAtLiquidityPlusOne": "1000000000000000000000000002" + }, + { + "label": "supply 1", + "order": "t0", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": -13798330, + "tickUpper": -9210880, + "initialTick": -13798330, + "lowerSqrtRatio": "19827018619021557321624769538", + "upperSqrtRatio": "20005058055867721043010416951", + "lowerFixed": "343219262584225398096347565219905536", + "upperFixed": "3401913497194347663748393335707926528", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "error": "ZeroLiquidity", + "selector": "0x10074548" + }, + { + "label": "supply 1", + "order": "t1", + "askTick": -13815500, + "upperTick": -9210300, + "supply": "1", + "tickSpacing": 17990, + "alignedAskTick": -13798330, + "alignedUpperTick": -9210880, + "steps": 255, + "bitmapWords": 2, + "tickLower": 9210880, + "tickUpper": 13798330, + "initialTick": 13798330, + "lowerSqrtRatio": "39614081718424154951297988133", + "upperSqrtRatio": "39614085829356414111595421918", + "lowerFixed": "34037340847382845064040913858633410805760", + "upperFixed": "337370602003729386149418028072584609792000", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "892", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "error": "ZeroLiquidity", + "selector": "0x10074548" + }, + { + "label": "zero liquidity", + "order": "t0", + "askTick": -80000000, + "upperTick": -75394800, + "supply": "1", + "tickSpacing": 17990, + "alignedAskTick": -79983540, + "alignedUpperTick": -75396090, + "steps": 255, + "bitmapWords": 2, + "tickLower": -79983540, + "tickUpper": -75396090, + "initialTick": -79983540, + "lowerSqrtRatio": "364403974585733748688", + "upperSqrtRatio": "3611891681837818381911", + "lowerFixed": "1457615898342934994752", + "upperFixed": "14447566727351273527644", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "209898408956198260", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "error": "ZeroLiquidity", + "selector": "0x10074548" + }, + { + "label": "above tick cap", + "order": "t0", + "askTick": 80000000, + "upperTick": 84605200, + "supply": "1000000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": 80001530, + "alignedUpperTick": 84588980, + "steps": 255, + "bitmapWords": 2, + "tickLower": 80001530, + "tickUpper": 84588980, + "initialTick": 80001530, + "lowerSqrtRatio": "59674053255057240992775639467", + "upperSqrtRatio": "61928121734155291317938697781", + "lowerFixed": "80157150546117218022817454823280203296917128974654504960", + "upperFixed": "794499965667161131382886968388759023355152141081406078976", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "1", + "maximum": "131686236093764274", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "above tick cap", + "order": "t1", + "askTick": 80000000, + "upperTick": 84605200, + "supply": "1000000000000000000000000000000", + "tickSpacing": 17990, + "alignedAskTick": 80001530, + "alignedUpperTick": 84588980, + "steps": 255, + "bitmapWords": 2, + "tickLower": -84588980, + "tickUpper": -80001530, + "initialTick": -80001530, + "lowerSqrtRatio": "36435523675599763755", + "upperSqrtRatio": "361140860323799023561", + "lowerFixed": "145742094702399055020", + "upperFixed": "1444563441295196094244", + "tickCap": "34500899008510439365646822207418453", + "supplyEnvelope": { + "minimum": "1", + "maximum": "131686236093764274", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "340282366920938463463374607431768211455" + }, + { + "label": "narrow at the fixture", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815400, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815400, + "steps": 100, + "bitmapWords": 2, + "tickLower": -13815500, + "tickUpper": -13815400, + "initialTick": -13815500, + "lowerSqrtRatio": "19826847842167416264437938874", + "upperSqrtRatio": "19826848832552360555918821236", + "lowerFixed": "340285338570426986943133710140047360", + "upperFixed": "340302353254211717712258326192979968", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "95880253489875846689099223903", + "nonempty": true + }, + "liquidity": "20000684666478128866992076598", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "narrow at the fixture", + "order": "t1", + "askTick": -13815500, + "upperTick": -13815400, + "supply": "1000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815400, + "steps": 100, + "bitmapWords": 2, + "tickLower": 13815400, + "tickUpper": 13815500, + "initialTick": 13815500, + "lowerSqrtRatio": "39614085868547337807943695629", + "upperSqrtRatio": "39614085868777914215468221053", + "lowerFixed": "340262381761484649161671818375838411259904", + "upperFixed": "340279395297400810272991339629943340400640", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "95880253489875846672648633422", + "nonempty": true + }, + "liquidity": "20000684666478128870423680717", + "installedSupply": "1000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000001" + }, + { + "label": "narrow at the fixture, supply cap", + "order": "t0", + "askTick": -13815500, + "upperTick": -13815400, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815400, + "steps": 100, + "bitmapWords": 2, + "tickLower": -13815500, + "tickUpper": -13815400, + "initialTick": -13815500, + "lowerSqrtRatio": "19826847842167416264437938874", + "upperSqrtRatio": "19826848832552360555918821236", + "lowerFixed": "340285338570426986943133710140047360", + "upperFixed": "340302353254211717712258326192979968", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "95880253489875846689099223903", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "20000684666478128866992076598552" + }, + { + "label": "narrow at the fixture, supply cap", + "order": "t1", + "askTick": -13815500, + "upperTick": -13815400, + "supply": "1000000000000000000000000000000", + "tickSpacing": 1, + "alignedAskTick": -13815500, + "alignedUpperTick": -13815400, + "steps": 100, + "bitmapWords": 2, + "tickLower": 13815400, + "tickUpper": 13815500, + "initialTick": 13815500, + "lowerSqrtRatio": "39614085868547337807943695629", + "upperSqrtRatio": "39614085868777914215468221053", + "lowerFixed": "340262381761484649161671818375838411259904", + "upperFixed": "340279395297400810272991339629943340400640", + "tickCap": "1917670715792995950086461153688", + "supplyEnvelope": { + "minimum": "1", + "maximum": "95880253489875846672648633422", + "nonempty": true + }, + "error": "LiquidityAboveTickCap", + "selector": "0x20ea795f", + "liquidity": "20000684666478128870423680717318" + }, + { + "label": "ten percent wide at the fixture, supply cap", + "order": "t0", + "askTick": -13815500, + "upperTick": -13720200, + "supply": "1000000000000000000000000000000", + "tickSpacing": 373, + "alignedAskTick": -13815174, + "alignedUpperTick": -13720432, + "steps": 254, + "bitmapWords": 2, + "tickLower": -13815174, + "tickUpper": -13720432, + "initialTick": -13815174, + "lowerSqrtRatio": "19826851071004761957890969158", + "upperSqrtRatio": "19827812093588065585625405455", + "lowerFixed": "340340809573642414251959237650415616", + "upperFixed": "356851051837668481282209112091787264", + "tickCap": "715292168628805430581480072377462", + "supplyEnvelope": { + "minimum": "47", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "21617631916800636468600113422", + "installedSupply": "1000000000000000000000000000000", + "sink": "0", + "debitAtLiquidityPlusOne": "1000000000000000000000000000046" + }, + { + "label": "ten percent wide at the fixture, supply cap", + "order": "t1", + "askTick": -13815500, + "upperTick": -13720200, + "supply": "1000000000000000000000000000000", + "tickSpacing": 373, + "alignedAskTick": -13815174, + "alignedUpperTick": -13720432, + "steps": 254, + "bitmapWords": 2, + "tickLower": 13720432, + "tickUpper": 13815174, + "initialTick": 13815174, + "lowerSqrtRatio": "39614085654696434799085671354", + "upperSqrtRatio": "39614085868026277594831988382", + "lowerFixed": "324482970250540297643246454993782837346304", + "upperFixed": "340223934303891592275024797922651900215296", + "tickCap": "715292168628805430581480072377462", + "supplyEnvelope": { + "minimum": "47", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "21617631916800636468567531274", + "installedSupply": "999999999999999999999999999957", + "sink": "43", + "debitAtLiquidityPlusOne": "1000000000000000000000000000004" + }, + { + "label": "two times wide at the fixture, supply cap", + "order": "t0", + "askTick": -13815500, + "upperTick": -13122400, + "supply": "1000000000000000000000000000000", + "tickSpacing": 2708, + "alignedAskTick": -13813508, + "alignedUpperTick": -13122968, + "steps": 255, + "bitmapWords": 2, + "tickLower": -13813508, + "tickUpper": -13122968, + "initialTick": -13813508, + "lowerSqrtRatio": "19826867579970088338149538588", + "upperSqrtRatio": "19835043639045498244486435319", + "lowerFixed": "340624431438312818958422140031860736", + "upperFixed": "481088056794511001004000611129098240", + "tickCap": "5193010009933896919794506194877961", + "supplyEnvelope": { + "minimum": "292", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "3428443934989539259216457645", + "installedSupply": "999999999999999999999999999964", + "sink": "36", + "debitAtLiquidityPlusOne": "1000000000000000000000000000255" + }, + { + "label": "two times wide at the fixture, supply cap", + "order": "t1", + "askTick": -13815500, + "upperTick": -13122400, + "supply": "1000000000000000000000000000000", + "tickSpacing": 2708, + "alignedAskTick": -13813508, + "alignedUpperTick": -13122968, + "steps": 255, + "bitmapWords": 2, + "tickLower": 13122968, + "tickUpper": 13813508, + "initialTick": 13813508, + "lowerSqrtRatio": "39614084519061895971356284982", + "upperSqrtRatio": "39614085864187004001293752539", + "lowerFixed": "240687931454459110649981462574315073437696", + "upperFixed": "339940645914255788171351607558895141126144", + "tickCap": "5193010009933896919794506194877961", + "supplyEnvelope": { + "minimum": "292", + "maximum": "1000000000000000000000000000000", + "nonempty": true + }, + "liquidity": "3428443934989539259217557666", + "installedSupply": "999999999999999999999999999931", + "sink": "69", + "debitAtLiquidityPlusOne": "1000000000000000000000000000223" + } + ] +} diff --git a/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json b/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json deleted file mode 100644 index cb0fc0a..0000000 --- a/src/routes/launches/vectors/zero-seed-launch-v2-vectors.json +++ /dev/null @@ -1,1867 +0,0 @@ -{ - "version": 7, - "fixture": { - "core": "Locally compiled src/Core.sol deployed at the test's canonical Core address; no fork or live-chain validation", - "router": "Locally compiled test/LaunchTestRouter.sol; standard-ABI single-hop forwarding and settlement, with snapshot/revert quotes in the test harness; no deployed-router bytecode fixture", - "supply_requested": "1000000000000000000000000000", - "tick_spacing": 100, - "width_ticks": 4605200, - "ask_tick_18_18": -13815500, - "ask_tick_18_6": -41446500, - "trading_start": 1000, - "fee_initial_q64": "922337203685477580", - "fee_final_q64": "55340232221128654", - "max_creator_fee_q64": "18446744073709551615", - "creator_fee_range": "every uint64 0..18446744073709551615 inclusive for initialFee and finalFee (Q0.64; 2**64 - 1 is just under 100%, never exactly 100%); finalFee <= initialFee, else InvalidFeeSchedule; exactly 100% = 2**64 is not a uint64 and fails ABI decoding with empty revert data; see creator_fee_range", - "label": "LOCAL FIXTURE VECTORS - not live evidence; numeric start margin is a local-reference proposal, not a live parameter; the creator fee ceiling is the uint64 maximum", - "quote_18": "t0 order: plain 18-decimal test ERC-20 selected per launch at the top of the address space. t1 order: native ETH, address(0), the reference Base configuration; always token0, no code, no decimals(), no approvals.", - "quote_6": "plain 6-decimal solady ERC-20 (DecimalsToken), selected per launch for math vectors only", - "fee_decay_rates_x32": ["68913100933", "9923486534471"], - "fee_decay_rate_x32_default": "68913100933", - "fee_decay_rate_encoding": "decayRateX32: uint64, unsigned Q32.32 ticks per second (2**32 = 1 tick/s). Any value is accepted; 0 is a flat fee. Half-life in seconds of the excess over finalFee = 2 * ln(2) / ln(1.000001) * 2**32 / decayRateX32 = 1386295.05426695565 * 2**32 / decayRateX32. rate for a half-life H: floor(1386295.05426695565... * 2**32 / H), computed with exact high-precision arithmetic (not binary floating point).", - "fee_schedule": "initialFee for t <= 0 or decayRateX32 == 0; finalFee for t > MAX_DECAY_PRODUCT / decayRateX32 (integer division; MAX_DECAY_PRODUCT = (88722835 + 1) * 2**32 - 1 = 381061679028371455); otherwise m = floor(decayRateX32 * t / 2**32) and fee = finalFee + floor((initialFee - finalFee) * R / 2**128) with R = toFixed(tickToSqrtRatio(-m)), the compact (94-bit mantissa, rounded down) sqrt ratio of src/math/ticks.sol as a 64.128 integer. t = block timestamp - tradingStart in whole seconds. The operation order is normative; the saturation check must precede the helper call." - }, - "create": [ - { - "order": "t0", - "dec": "18/18", - "ask": "-13815500", - "upper": "-9210300", - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "liquidity": "1111119116089565312897934", - "tickLower": "-13815500", - "tickUpper": "-9210300", - "initialTick": "-13815500", - "withdrawable": "999999999999999999999999209", - "ideal_liquidity": "1111119116089565312897934", - "relative_error": "6.356e-27" - }, - { - "order": "t0", - "dec": "18/6", - "ask": "-41446500", - "upper": "-36841300", - "requested": "1000000000000000000000000000", - "installed": "999999999999999999302656786", - "liquidity": "1111138522742929536", - "tickLower": "-41446500", - "tickUpper": "-36841300", - "initialTick": "-41446500", - "withdrawable": "999999999999999999302656785", - "ideal_liquidity": "1111138522742929536", - "relative_error": "2.037e-20" - }, - { - "order": "t1", - "dec": "18/18", - "ask": "-13815500", - "upper": "-9210300", - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "liquidity": "1111119116089565312898036", - "tickLower": "9210300", - "tickUpper": "13815500", - "initialTick": "13815500", - "withdrawable": "999999999999999999999999421", - "ideal_liquidity": "1111119116089565312897934", - "relative_error": "9.158e-23" - }, - { - "order": "t1", - "dec": "18/6", - "ask": "-41446500", - "upper": "-36841300", - "requested": "1000000000000000000000000000", - "installed": "999999999999999999282285400", - "liquidity": "1111138522742929536", - "tickLower": "36841300", - "tickUpper": "41446500", - "initialTick": "41446500", - "withdrawable": "999999999999999999282285399", - "ideal_liquidity": "1111138522742929536", - "relative_error": "2.875e-29" - } - ], - "installation": { - "token0_liquidity": "floor(supply * lowerFixed * upperFixed / (2**128 * (upperFixed - lowerFixed))), the exact inverse of Core's token0 debit ceil(ceil(liquidity * 2**128 * (upperFixed - lowerFixed) / upperFixed) / lowerFixed); computed on chain as floor(supply * mantissaLower * mantissaUpper / (((upperFixed - lowerFixed) >> shiftLower) << (128 - shiftUpper))) from the compact sqrt ratios (mantissa = low 94 bits, shift = 2 + 32 * top two bits); saturates at 2**128 - 1", - "token1_liquidity": "floor(supply * 2**128 / (upperFixed - lowerFixed)), unchanged shared helper; the exact inverse of ceil(liquidity * (upperFixed - lowerFixed) / 2**128)", - "note": "Installed liquidity is the exact inverse of Core's rounded-up debit in both orders: debit(liquidity) <= supply < debit(liquidity + 1). The token0 order no longer uses math/liquidity.sol maxLiquidityForToken0, whose first floor under-installed at low prices; the fixture creation rows above are unchanged by this. Full pinned rows, refusals and pins: zero-seed-launch-installation.json (oracle: zero-seed-launch-installation.py).", - "vectors": [ - { - "label": "low price, 1e30", - "order": "t0", - "ask": "-88080384", - "upper": "-85983232", - "spacing": "524288", - "supply": "1000000000000000000000000000000", - "liquidity": "115073586432", - "installed": "999999999991410763432286846442", - "sink": "8589236567713153558", - "debit_at_liquidity_plus_one": "1000000000000100854972818930129", - "previous_liquidity": "106052588672", - "previous_installed": "921606703670075887283160121070" - }, - { - "label": "low price, two liquidity units", - "order": "t0", - "ask": "-88080384", - "upper": "-85983232", - "spacing": "524288", - "supply": "17380183081064167375", - "liquidity": "2", - "installed": "17380183081064167375", - "sink": "0", - "debit_at_liquidity_plus_one": "26070274621596251062", - "previous_liquidity": "1", - "previous_installed": "8690091540532083688" - }, - { - "label": "full width, int128 budget", - "order": "t0", - "ask": "-88722835", - "upper": "88722835", - "spacing": "1", - "supply": "170141183460469231694794709727778658247", - "liquidity": "9223595582101085260", - "installed": "170141183460469231694794709727778658247", - "sink": "0", - "debit_at_liquidity_plus_one": "170141183460469231713241006721831381988", - "previous_liquidity": "9223595582101085259", - "previous_installed": "170141183460469231676348412733725934507" - } - ] - }, - "trade": [ - { - "order": "t0", - "decimals": "18/18", - "step": "buyExactIn", - "quoteIn": "1000000000000000000", - "gross": "999083373736809197508878", - "fee": "49954168686840459832116", - "net": "949129205049968737676762" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "buyExactOut", - "tokenOut": "949129205049968737676762", - "calcQuote": "951668650101832044", - "fee": "50087823689570108", - "paid": "1001756473791402152" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "dustExhausted", - "token": "191", - "quote": "90" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "dust", - "token": "102", - "quote": "2" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "exhaust", - "requested": "999950046831313159540166992", - "got": "999950045831313159540166903", - "principalToken": "0", - "principalQuote": "10000312219037842827169" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "reverse", - "leftHeld": "0", - "principalQuote": "49957287200176383", - "principalToken": "999950045831313159540166992" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "sellExactIn", - "tokenIn": "949129205049968737676762", - "quoteOut": "904085217596740440" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "buyExactIn", - "quoteIn": "1000000", - "gross": "999048506325137632526244", - "fee": "49952425316256881582986", - "net": "949096081008880750943258" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "buyExactOut", - "tokenOut": "949096081008880750943258", - "calcQuote": "951669", - "fee": "50088", - "paid": "1001757" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "dustExhausted", - "token": "94478106", - "quote": "100" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "dust", - "token": "94478017", - "quote": "2" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "exhaust", - "requested": "999950048574683742326595783", - "got": "999950047574683742326595694", - "principalToken": "0", - "principalQuote": "10000661550" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "reverse", - "leftHeld": "0", - "principalQuote": "49957", - "principalToken": "999950047574683742326595783" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "sellExactIn", - "tokenIn": "949096081008880750943258", - "quoteOut": "904084" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "buyExactIn", - "quoteIn": "1000000000000000000", - "gross": "999083373736809197249895", - "fee": "49954168686840459819167", - "net": "949129205049968737430728" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "buyExactOut", - "tokenOut": "949129205049968737430728", - "calcQuote": "951668650101832044", - "fee": "50087823689570108", - "paid": "1001756473791402152" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "dustExhausted", - "token": "428919", - "quote": "86" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "dust", - "token": "428825", - "quote": "3" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "exhaust", - "requested": "999950046831313159539751430", - "got": "999950045831313159539751336", - "principalToken": "0", - "principalQuote": "10000312219037842827184" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "reverse", - "leftHeld": "0", - "principalQuote": "49957287200176384", - "principalToken": "999950045831313159539751430" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "sellExactIn", - "tokenIn": "949129205049968737430728", - "quoteOut": "904085217596740439" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "buyExactIn", - "quoteIn": "1000000", - "gross": "999048148471968146393535", - "fee": "49952407423598407276350", - "net": "949095741048369739117185" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "buyExactOut", - "tokenOut": "949095741048369739117185", - "calcQuote": "951669", - "fee": "50088", - "paid": "1001757" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "dustExhausted", - "token": "91", - "quote": "90" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "dust", - "token": "1", - "quote": "2" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "exhaust", - "requested": "999950048592576400875009049", - "got": "999950047592576400875008959", - "principalToken": "0", - "principalQuote": "10000661550" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "reverse", - "leftHeld": "0", - "principalQuote": "49957", - "principalToken": "999950047592576400875009049" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "sellExactIn", - "tokenIn": "949095741048369739117185", - "quoteOut": "904084" - } - ], - "fee": [ - { - "decayRateX32": "68913100933", - "t_minus_start": "-1", - "tick_magnitude": null, - "fee_q64": "922337203685477580" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "0", - "tick_magnitude": null, - "fee_q64": "922337203685477580" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "1", - "tick_magnitude": "16", - "fee_q64": "922330267740917652" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "10", - "tick_magnitude": "160", - "fee_q64": "922267846736753839" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "60", - "tick_magnitude": "962", - "fee_q64": "921920278629180106" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "600", - "tick_magnitude": "9627", - "fee_q64": "918173943809149156" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "1800", - "tick_magnitude": "28881", - "fee_q64": "909907303121253143" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "3600", - "tick_magnitude": "57762", - "fee_q64": "897655606672062181" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "43200", - "tick_magnitude": "693147", - "fee_q64": "668399831593786699" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "86399", - "tick_magnitude": "1386279", - "fee_q64": "488842197715736884" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "86400", - "tick_magnitude": "1386295", - "fee_q64": "488838729715618946" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "86401", - "tick_magnitude": "1386311", - "fee_q64": "488835261743244884" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "172800", - "tick_magnitude": "2772590", - "fee_q64": "272089486849531874" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "604800", - "tick_magnitude": "9704065", - "fee_q64": "62113647347197278" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "2592000", - "tick_magnitude": "41588851", - "fee_q64": "55340233028582759" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "5529596", - "tick_magnitude": "88722819", - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "5529597", - "tick_magnitude": "88722835", - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "5529598", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "18446744073709551615", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "68913100933", - "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "-1", - "tick_magnitude": null, - "fee_q64": "922337203685477580" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "0", - "tick_magnitude": null, - "fee_q64": "922337203685477580" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "1", - "tick_magnitude": "2310", - "fee_q64": "921336400758785753" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "10", - "tick_magnitude": "23104", - "fee_q64": "912379287313991670" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "60", - "tick_magnitude": "138629", - "fee_q64": "864277214588907100" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "600", - "tick_magnitude": "1386295", - "fee_q64": "488838729715618946" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "1800", - "tick_magnitude": "4158885", - "fee_q64": "163714862475909380" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "3600", - "tick_magnitude": "8317770", - "fee_q64": "68887062105693473" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "43200", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "86399", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "86400", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "86401", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "172800", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "604800", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "2592000", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "38398", - "tick_magnitude": "88718262", - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "38399", - "tick_magnitude": "88720572", - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "38400", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "18446744073709551615", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - }, - { - "decayRateX32": "9923486534471", - "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "tick_magnitude": null, - "fee_q64": "55340232221128654" - } - ], - "router": [ - { - "order": "t0", - "paid": "1000000000000000000", - "got": "949129205049968737676762" - }, - { - "order": "t1", - "paid": "1000000000000000000", - "got": "949129205049968737430728" - } - ], - "gas": [ - { - "order": "t0", - "create": 1368087, - "firstBuy": 102485, - "secondBuy": 34267, - "sell": 34271, - "claim": 70154 - }, - { - "order": "t1", - "create": 1326935, - "firstBuy": 84663, - "secondBuy": 35163, - "sell": 38609, - "claim": 79676 - } - ], - "other": [ - { - "kind": "dust", - "quoteIn": "1", - "net": "949971", - "fee": "49999", - "raw": "VEC|dust|t0|quoteIn=1|net=949971|fee=49999" - }, - { - "kind": "recapture", - "quoteIn": "10e18", - "netOfFee": "9415099531871718329641155", - "afterClaim": "9910631086180756136011948", - "raw": "VEC|recapture|t0|quoteIn=10e18|netOfFee=9415099531871718329641155|afterClaim=9910631086180756136011948" - } - ], - "gas_note": "In-test warm gas, t0 on the plain 18-decimal test ERC-20 quote and t1 on native ETH, through the periphery (create, claim) and the forwarding test locker (swaps), as logged by test_gas_vectors at this revision (forge 1.8.3, solc 0.8.33, via_ir, optimizer 9999999, evm osaka).", - "residue_metrics": { - "definitions": { - "core_unbooked_dust": "Per token: Core balance - position amount (rounded down) - creator fee ledger, accumulated over all swaps; raw units of that token. Unowned: not creator principal, not creator fees, unreachable by claims.", - "upper_bound_residue": "Raw launch-token units the position held before the exhausting buy that the buy did not deliver when it stopped at the upper bound. Becomes Core unbooked dust: dust_after.token = dust_before.token + residue (asserted)." - }, - "scenarios": [ - { - "order": "t0", - "decimals": "18/18", - "upper_residue_raw_token": "89", - "position_token_before_exhaust": "999950045831313159540166992", - "delivered_raw_token": "999950045831313159540166903", - "core_dust_before_exhaust": { - "raw_token": "102", - "raw_quote": "2" - }, - "core_dust_after_exhaust": { - "raw_token": "191", - "raw_quote": "90" - } - }, - { - "order": "t0", - "decimals": "18/6", - "upper_residue_raw_token": "89", - "position_token_before_exhaust": "999950047574683742326595783", - "delivered_raw_token": "999950047574683742326595694", - "core_dust_before_exhaust": { - "raw_token": "94478017", - "raw_quote": "2" - }, - "core_dust_after_exhaust": { - "raw_token": "94478106", - "raw_quote": "100" - } - }, - { - "order": "t1", - "decimals": "18/18", - "upper_residue_raw_token": "94", - "position_token_before_exhaust": "999950045831313159539751430", - "delivered_raw_token": "999950045831313159539751336", - "core_dust_before_exhaust": { - "raw_token": "428825", - "raw_quote": "3" - }, - "core_dust_after_exhaust": { - "raw_token": "428919", - "raw_quote": "86" - } - }, - { - "order": "t1", - "decimals": "18/6", - "upper_residue_raw_token": "90", - "position_token_before_exhaust": "999950047592576400875009049", - "delivered_raw_token": "999950047592576400875008959", - "core_dust_before_exhaust": { - "raw_token": "1", - "raw_quote": "2" - }, - "core_dust_after_exhaust": { - "raw_token": "91", - "raw_quote": "90" - } - } - ] - }, - "start": { - "contract": [ - { - "block_time": 5000, - "tradingStart": "4999", - "create": "InvalidTradingStart (create and preview)", - "trade_at_block_time": null, - "trade_at_start": null - }, - { - "block_time": 5000, - "tradingStart": "5000", - "create": "ok", - "trade_at_block_time": "ok", - "trade_at_start": "ok" - }, - { - "block_time": 5000, - "tradingStart": "5001", - "create": "ok", - "trade_at_block_time": "TradingNotStarted", - "trade_at_start": "ok" - }, - { - "block_time": 5000, - "tradingStart": "91400", - "create": "ok", - "trade_at_block_time": "TradingNotStarted", - "trade_at_start": "ok" - }, - { - "block_time": 5000, - "tradingStart": "18446744073709551615", - "create": "ok (contract has no cap; effectively indefinite self-lock)", - "trade_at_block_time": "TradingNotStarted", - "trade_at_start": "not exercised (astronomically distant timestamp)" - } - ], - "contract_mined_late": { - "previewed_at": 5000, - "tradingStart": 5000, - "mined_at": 5001, - "create": "InvalidTradingStart, no token and no pool left" - }, - "producer_proposal": { - "status": "local reference proposal for CTO freeze; not a live/hosted selection", - "rule": "buildTime + minMargin <= tradingStart <= buildTime + maxDelay and buildTime < expiry <= tradingStart; never rewrite a signed start", - "minMargin_s": 300, - "maxDelay_s": 86400, - "vectors": [ - { - "buildTime": 1000, - "tradingStart": 1000, - "expiry": 1000, - "verdict": "refuse: start-now (start <= buildTime)" - }, - { - "buildTime": 1000, - "tradingStart": 1299, - "expiry": 1299, - "verdict": "refuse: start below margin" - }, - { - "buildTime": 1000, - "tradingStart": 1300, - "expiry": 1300, - "verdict": "accept: minimum start, expiry at start" - }, - { - "buildTime": 1000, - "tradingStart": 1300, - "expiry": 1301, - "verdict": "refuse: expiry after start" - }, - { - "buildTime": 1000, - "tradingStart": 1300, - "expiry": 1000, - "verdict": "refuse: expiry not after buildTime" - }, - { - "buildTime": 1000, - "tradingStart": 1300, - "expiry": 1001, - "verdict": "accept" - }, - { - "buildTime": 1000, - "tradingStart": 87400, - "expiry": 1300, - "verdict": "accept: maximum start" - }, - { - "buildTime": 1000, - "tradingStart": 87400, - "expiry": 87400, - "verdict": "accept" - }, - { - "buildTime": 1000, - "tradingStart": 87401, - "expiry": 1300, - "verdict": "refuse: start above cap" - }, - { - "buildTime": 1000, - "tradingStart": 18446744073709551615, - "expiry": 1300, - "verdict": "refuse: start above cap (contract alone would accept and self-lock)" - } - ], - "why": "Bounds the requested start to maxDelay after buildTime. Submission before expiry does not guarantee mining before start: late mining reverts atomically. No inclusion-latency guarantee." - } - }, - "claim_recipient_refusals": { - "error": "InvalidRecipient() (periphery)", - "refused": [ - "address(0)", - "Core", - "extension", - "periphery", - "poolKey.token0", - "poolKey.token1" - ], - "accepted_examples": [ - "owner", - "EOA", - "other contract (ETH-receiving for a native quote)" - ], - "atomicity": "both assets or neither; a reverting native recipient or a refused ERC-20 transfer rolls the ledger, principal and Core balances back; retry to another recipient pays both in full", - "reentrancy": "the ledger is moved onto the lock before Core pays; an owner contract re-entering claimFees during the payment claims zero" - }, - "metadata": { - "max_bytes": 31, - "unit": "UTF-8 bytes", - "error": "MetadataTooLong()", - "checked_in": ["create", "previewInstallation"], - "vectors": [ - { - "field": "name", - "bytes": 31, - "verdict": "accept" - }, - { - "field": "name", - "bytes": 32, - "verdict": "MetadataTooLong" - }, - { - "field": "symbol", - "bytes": 32, - "verdict": "MetadataTooLong" - }, - { - "field": "name", - "bytes": 33, - "note": "11 x U+20AC", - "verdict": "MetadataTooLong" - } - ] - }, - "collision": { - "error": "LaunchTokenExists(address token)", - "checked_in": ["create", "previewInstallation"], - "rule": "token address = CREATE2(extension, keccak256(abi.encode(controller, salt)), MintableERC20 init code with name/symbol/decimals). The controller is the locker that forwards the creation; the periphery passes salt = keccak256(abi.encode(owner, userSalt)). Refused when the address already has code. Same salt with other metadata, another salt, another owner or another controller is independent." - }, - "fee_curve": { - "note": "Tick-rate schedule. Digests: SHA-256 chain h = sha256(h || fee as 8-byte big-endian) over the sampled seconds t = 0, step, 2*step, ..., last inclusive; all from the independent Python oracle (exact port of the tick table).", - "rates_q64": { - "5": "922337203685477580", - "50": "9223372036854775808", - "75": "13835058055282163712", - "90": "16602069666338596454", - "99": "18262276632972456099", - "99.9": "18428297329635842064", - "max": "18446744073709551615" - }, - "final_fee_q64": "55340232221128654", - "decay_rates_x32": { - "default_one_day": "68913100933", - "600s": "9923486534471", - "two_ticks_per_second": "8589934592", - "quarter_tick_per_second": "1073741824", - "tenth_tick_per_second_floor": "429496729", - "min": "1", - "max": "18446744073709551615" - }, - "half_life_s": { - "default_one_day": "86400.00000104363", - "600s": "600.00000000005238", - "two_ticks_per_second": "693151.52713347783 (8.0225408233 days)", - "quarter_tick_per_second": "5545180.2170678226 (64.18 days)", - "min": "5954091920683119 (about 1.89e8 years)", - "max": "0.0000000003229 (saturates within the first second)" - }, - "saturation_second": { - "default_one_day": "5529597", - "600s": "38399", - "two_ticks_per_second": "44361417", - "quarter_tick_per_second": "354891343", - "min": "381061679028371455", - "max": "0" - }, - "saturation_note": "saturation_second = MAX_DECAY_PRODUCT / decayRateX32 is the last elapsed second computed through the helper; from the next second the fee is exactly finalFee. On the 50% -> 0.3% fixtures the floored excess is already zero earlier (rounding), so the two mechanisms coincide at the floor; a full-span excess (2**64 - 1 -> 0) still leaves 1 raw unit when the magnitude is exactly 88,722,835 at saturation_second, which is why the guard rather than a clamp to the minimum tick yields finalFee.", - "digests_sha256": [ - { - "label": "default 50% -> 0.3%, every second 0..3600", - "initialFee": "9223372036854775808", - "finalFee": "55340232221128654", - "decayRateX32": "68913100933", - "last": 3600, - "step": 1, - "digest": "c3de4d91d476acc61f73455efe7fb8c750b89de3759a0c500bbde0bdf9bcd062" - }, - { - "label": "default 50% -> 0.3%, every minute 0..30 days", - "initialFee": "9223372036854775808", - "finalFee": "55340232221128654", - "decayRateX32": "68913100933", - "last": 2592000, - "step": 60, - "digest": "b2c022f5cbbc90cedca9834bcf64f636b97c45dee08c730ab4dbf17d8d5acc74" - }, - { - "label": "600 s half-life 5% -> 0.3%, every second 0..3600", - "initialFee": "922337203685477580", - "finalFee": "55340232221128654", - "decayRateX32": "9923486534471", - "last": 3600, - "step": 1, - "digest": "c4db35cd45f48222463cb9a940c506599715162db6c37b01428331ff5263bdc6" - }, - { - "label": "2 ticks/s 99.9% -> 0, every hour 0..30 days", - "initialFee": "18428297329635842064", - "finalFee": "0", - "decayRateX32": "8589934592", - "last": 2592000, - "step": 3600, - "digest": "8fdefff5fe4cfb98c43bbbf6ed6f0b9a0ae398f1f810ffea0522d5504e1321da" - }, - { - "label": "1/4 tick/s max -> 0, every second 0..3600", - "initialFee": "18446744073709551615", - "finalFee": "0", - "decayRateX32": "1073741824", - "last": 3600, - "step": 1, - "digest": "11ff0a905ab22707579e714aba31b895ed3234788a0dab10b0daa0881a9ccf36" - } - ], - "tables": { - "default_one_day_50_to_0.3": [ - { - "t": "0", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000" - }, - { - "t": "1", - "fee_q64": "9223298692930386783", - "pct": "49.9996024018", - "tick_magnitude": "16" - }, - { - "t": "60", - "fee_q64": "9218963276153076996", - "pct": "49.9761000603", - "tick_magnitude": "962" - }, - { - "t": "3600", - "fee_q64": "8962377276946957658", - "pct": "48.5851445715", - "tick_magnitude": "57762" - }, - { - "t": "43200", - "fee_q64": "6538119400055406284", - "pct": "35.4432162875", - "tick_magnitude": "693147" - }, - { - "t": "86400", - "fee_q64": "4639356258918185573", - "pct": "25.1500006743", - "tick_magnitude": "1386295" - }, - { - "t": "172800", - "fee_q64": "2347348307759775472", - "pct": "12.7250006743", - "tick_magnitude": "2772590" - }, - { - "t": "604800", - "fee_q64": "126965494298918151", - "pct": "0.6882813237", - "tick_magnitude": "9704065" - }, - { - "t": "2592000", - "fee_q64": "55340240759526319", - "pct": "0.3000000463", - "tick_magnitude": "41588851" - }, - { - "t": "5529597", - "fee_q64": "55340232221128654", - "pct": "0.3000000000", - "tick_magnitude": "88722835" - }, - { - "t": "5529598", - "fee_q64": "55340232221128654", - "pct": "0.3000000000" - } - ], - "two_ticks_per_second_50_to_0.3": [ - { - "t": "0", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000" - }, - { - "t": "1", - "fee_q64": "9223362868832139196", - "pct": "49.9999503000", - "tick_magnitude": "2" - }, - { - "t": "60", - "fee_q64": "9222821971723649262", - "pct": "49.9970180909", - "tick_magnitude": "120" - }, - { - "t": "3600", - "fee_q64": "9190426476420833039", - "pct": "49.8214017590", - "tick_magnitude": "7200" - }, - { - "t": "43200", - "fee_q64": "8835746255246636721", - "pct": "47.8986764273", - "tick_magnitude": "86400" - }, - { - "t": "86400", - "fee_q64": "8464509350746382498", - "pct": "45.8861971355", - "tick_magnitude": "172800" - }, - { - "t": "172800", - "fee_q64": "7768459773126180960", - "pct": "42.1129048144", - "tick_magnitude": "345600" - }, - { - "t": "604800", - "fee_q64": "5062770843274302024", - "pct": "27.4453357354", - "tick_magnitude": "1209600" - }, - { - "t": "2592000", - "fee_q64": "741753037726786584", - "pct": "4.0210512748", - "tick_magnitude": "5184000" - }, - { - "t": "44361417", - "fee_q64": "55340232221128654", - "pct": "0.3000000000", - "tick_magnitude": "88722834" - }, - { - "t": "44361418", - "fee_q64": "55340232221128654", - "pct": "0.3000000000" - } - ], - "two_ticks_per_second_99.9_to_0": [ - { - "t": "0", - "fee_q64": "18428297329635842064", - "pct": "99.9000000000" - }, - { - "t": "1", - "fee_q64": "18428278901356940707", - "pct": "99.8999001001", - "tick_magnitude": "2" - }, - { - "t": "60", - "fee_q64": "18427191665519151079", - "pct": "99.8940061828", - "tick_magnitude": "120" - }, - { - "t": "3600", - "fee_q64": "18362074764497997343", - "pct": "99.5410067550", - "tick_magnitude": "7200" - }, - { - "t": "43200", - "fee_q64": "17649146110668978989", - "pct": "95.6762127785", - "tick_magnitude": "86400" - }, - { - "t": "86400", - "fee_q64": "16902937523957200381", - "pct": "91.6310079243", - "tick_magnitude": "172800" - }, - { - "t": "172800", - "fee_q64": "15503835857875547793", - "pct": "84.0464625948", - "tick_magnitude": "345600" - }, - { - "t": "604800", - "fee_q64": "10065237787609899792", - "pct": "54.5637633795", - "tick_magnitude": "1209600" - }, - { - "t": "2592000", - "fee_q64": "1379731172434914028", - "pct": "7.4795376730", - "tick_magnitude": "5184000" - } - ], - "default_one_day_max_to_0": [ - { - "t": "0", - "fee_q64": "18446744073709551615", - "pct": "100.0000000000" - }, - { - "t": "1", - "fee_q64": "18446596500421042511", - "pct": "99.9992000036", - "tick_magnitude": "16" - }, - { - "t": "60", - "fee_q64": "18437873327830881974", - "pct": "99.9519115902", - "tick_magnitude": "962" - }, - { - "t": "3600", - "fee_q64": "17921603711721989946", - "pct": "97.1532083934", - "tick_magnitude": "57762" - }, - { - "t": "43200", - "fee_q64": "13043821263248043520", - "pct": "70.7106967556", - "tick_magnitude": "693147" - }, - { - "t": "86400", - "fee_q64": "9223372287116814725", - "pct": "50.0000013567", - "tick_magnitude": "1386295" - }, - { - "t": "172800", - "fee_q64": "4611686268689430216", - "pct": "25.0000013567", - "tick_magnitude": "2772590" - }, - { - "t": "604800", - "fee_q64": "144115215448268606", - "pct": "0.7812501484", - "tick_magnitude": "9704065" - }, - { - "t": "2592000", - "fee_q64": "17179874578", - "pct": "9.31E-8", - "tick_magnitude": "41588851" - }, - { - "t": "5529597", - "fee_q64": "1", - "pct": "0E-10", - "tick_magnitude": "88722835" - }, - { - "t": "5529598", - "fee_q64": "0", - "pct": "0E-10" - } - ], - "min_rate_max_to_0": [ - { - "t": "0", - "fee_q64": "18446744073709551615", - "pct": "100.0000000000" - }, - { - "t": "4294967295", - "fee_q64": "18446744073709551615", - "pct": "100.0000000000", - "tick_magnitude": "0" - }, - { - "t": "4294967296", - "fee_q64": "18446734850344432283", - "pct": "99.9999500000", - "tick_magnitude": "1" - }, - { - "t": "381061679028371454", - "fee_q64": "1", - "pct": "0E-10", - "tick_magnitude": "88722835" - }, - { - "t": "381061679028371455", - "fee_q64": "1", - "pct": "0E-10", - "tick_magnitude": "88722835" - }, - { - "t": "381061679028371456", - "fee_q64": "0", - "pct": "0E-10" - }, - { - "t": "18446744073709551615", - "fee_q64": "0", - "pct": "0E-10" - } - ] - }, - "tick_steps": [ - { - "decayRateX32": "1073741824", - "note": "1/4 tick/s: the fee steps every 4 s", - "rows": [ - { - "t": "1", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000", - "tick_magnitude": "0" - }, - { - "t": "3", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000", - "tick_magnitude": "0" - }, - { - "t": "4", - "fee_q64": "9223367452842311500", - "pct": "49.9999751500", - "tick_magnitude": "1" - }, - { - "t": "5", - "fee_q64": "9223367452842311500", - "pct": "49.9999751500", - "tick_magnitude": "1" - }, - { - "t": "7", - "fee_q64": "9223367452842311500", - "pct": "49.9999751500", - "tick_magnitude": "1" - }, - { - "t": "8", - "fee_q64": "9223362868832139196", - "pct": "49.9999503000", - "tick_magnitude": "2" - }, - { - "t": "9", - "fee_q64": "9223362868832139196", - "pct": "49.9999503000", - "tick_magnitude": "2" - } - ] - }, - { - "decayRateX32": "429496729", - "note": "floor(0.1 * 2**32), slightly below one tick per 10 s: first step at 11 s", - "rows": [ - { - "t": "9", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000", - "tick_magnitude": "0" - }, - { - "t": "10", - "fee_q64": "9223372036854775808", - "pct": "50.0000000000", - "tick_magnitude": "0" - }, - { - "t": "11", - "fee_q64": "9223367452842311500", - "pct": "49.9999751500", - "tick_magnitude": "1" - }, - { - "t": "20", - "fee_q64": "9223367452842311500", - "pct": "49.9999751500", - "tick_magnitude": "1" - }, - { - "t": "21", - "fee_q64": "9223362868832139196", - "pct": "49.9999503000", - "tick_magnitude": "2" - }, - { - "t": "30", - "fee_q64": "9223362868832139196", - "pct": "49.9999503000", - "tick_magnitude": "2" - }, - { - "t": "31", - "fee_q64": "9223358284824258897", - "pct": "49.9999254501", - "tick_magnitude": "3" - } - ] - }, - { - "decayRateX32": "68913100933", - "note": "default: 16 whole ticks per second, 1,386,295 after one day", - "rows": [ - { - "t": "1", - "fee_q64": "9223298692930386783", - "pct": "49.9996024018", - "tick_magnitude": "16" - }, - { - "t": "2", - "fee_q64": "9223225349592746513", - "pct": "49.9992048068", - "tick_magnitude": "32" - }, - { - "t": "86400", - "fee_q64": "4639356258918185573", - "pct": "25.1500006743", - "tick_magnitude": "1386295" - } - ] - }, - { - "decayRateX32": "68913100933", - "note": "a one-unit excess is gone after the first whole tick", - "rows": [ - { - "t": "0", - "fee_q64": "55340232221128655", - "pct": "0.3000000000" - }, - { - "t": "1", - "fee_q64": "55340232221128654", - "pct": "0.3000000000", - "tick_magnitude": "16" - } - ] - } - ], - "oracle_error_bounds": { - "note": "Integer result minus the real-number F + (I - F) * 1.000001**(-(decayRateX32 / 2**32) * t / 2) at 150 digits, default 50% -> 0.3%, every second of the first hour then every 10 minutes to 30 days. The error is dominated by the whole-tick floor of m (one tick = 5e-7 of the excess), not by the helper (which is within a few units of 2**-128 of the real sqrt ratio) or the final floor.", - "max_abs_q64": "4574692217333.041", - "at_t": "244", - "max_abs_fraction_of_excess": "4.98983E-7", - "one_tick_of_excess_fraction": "5.00000E-7" - } - }, - "creator_fee_range": { - "type": "uint64, Q0.64 (2**64 = 100%)", - "min_q64": "0", - "max_q64": "18446744073709551615", - "max_fraction": "1 - 2**-64: just under 100%, not 100%", - "constant": "MAX_CREATOR_FEE = type(uint64).max (src/extensions/ZeroSeedLaunch.sol); no ceiling comparison in _validate", - "schedule_rules": ["finalFee <= initialFee"], - "error": "InvalidFeeSchedule() 0xf75a271a", - "checked_in": ["create", "previewInstallation", "forwarded create payload"], - "unencodable": { - "value": "18446744073709551616", - "meaning": "exactly 100% = 2**64", - "verdict": "ABI decoding failure with empty revert data in create(LaunchParameters), previewInstallation(address,LaunchParameters) and the forwarded create payload (abi.encode(ZERO_SEED_LAUNCH_CREATE, LaunchParameters)), for either fee word; nothing is truncated or wrapped to zero", - "dirty_high_bits": { - "value": "36893488147419103231", - "note": "2**64 + (2**64 - 1)", - "verdict": "same ABI decoding failure" - } - }, - "removed": { - "error": "FeeTooHigh()", - "selector": "0xcd4e6167", - "note": "removed from IZeroSeedLaunch and the derived ABI; the previous ceiling floor(2**64 * 99 / 100) = 18262276632972456099 no longer exists" - }, - "vectors": [ - { - "initialFee": "18446744073709551615", - "finalFee": "55340232221128654", - "decayRateX32": "68913100933", - "verdict": "accept", - "note": "max -> 0.3% at the default one-day half-life" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "18446744073709551615", - "decayRateX32": "1", - "verdict": "accept", - "note": "flat at the maximum, slowest rate" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "18446744073709551615", - "decayRateX32": "18446744073709551615", - "verdict": "accept", - "note": "flat at the maximum, fastest rate" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "0", - "decayRateX32": "18446744073709551615", - "verdict": "accept", - "note": "max -> 0 at the fastest rate: saturated to 0 from t = 1" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "0", - "decayRateX32": "1", - "verdict": "accept", - "note": "max -> 0 at the slowest rate: one raw unit at t = MAX_DECAY_PRODUCT (minimum tick residual), 0 from t = MAX_DECAY_PRODUCT + 1" - }, - { - "initialFee": "0", - "finalFee": "0", - "decayRateX32": "68913100933", - "verdict": "accept", - "note": "zero schedule" - }, - { - "initialFee": "922337203685477580", - "finalFee": "55340232221128654", - "decayRateX32": "0", - "verdict": "accept", - "note": "zero rate: flat at initialFee forever" - }, - { - "initialFee": "18446744073709551614", - "finalFee": "18446744073709551615", - "decayRateX32": "68913100933", - "verdict": "InvalidFeeSchedule", - "note": "final above initial at the top of the range" - }, - { - "initialFee": "0", - "finalFee": "1", - "decayRateX32": "68913100933", - "verdict": "InvalidFeeSchedule" - }, - { - "initialFee": "922337203685477580", - "finalFee": "922337203685477581", - "decayRateX32": "0", - "verdict": "InvalidFeeSchedule", - "note": "shape is checked even at a zero rate" - }, - { - "initialFee": "18446744073709551616", - "finalFee": "18446744073709551615", - "decayRateX32": "68913100933", - "verdict": "abi_decoding_failure", - "note": "2**64 in initialFee" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "18446744073709551616", - "decayRateX32": "68913100933", - "verdict": "abi_decoding_failure", - "note": "2**64 in finalFee" - }, - { - "initialFee": "18446744073709551615", - "finalFee": "55340232221128654", - "decayRateX32": "18446744073709551616", - "verdict": "abi_decoding_failure", - "note": "2**64 in decayRateX32" - } - ], - "reference_schedules": { - "fixture": { - "initialFee": "922337203685477580", - "finalFee": "55340232221128654", - "decayRateX32": "68913100933", - "note": "5% -> 0.3% at the default one-day half-life: this fixture's local reference parameters" - }, - "consumer_default": { - "initialFee": "9223372036854775808", - "finalFee": "55340232221128654", - "decayRateX32": "68913100933", - "half_life_s": "86400.0000010436", - "fee_after_one_day_q64": "4639356258918185573", - "fee_after_one_day_pct": "25.1500006743", - "note": "50% -> 0.3% with a one-day half-life of the excess: the consumer-side default; a creation parameter choice, not a contract constant. About 25.15% after one day, 12.73% after two, 0.69% after seven; the floor is approached, not scheduled." - } - }, - "decay_rate_rules": [ - "any uint64 decayRateX32 is accepted, including 0 (flat at initialFee) and 2**64 - 1 (finalFee from the first second)" - ] - }, - "max_rate": { - "note": "Launches created through ZeroSeedLaunchPeriphery.create at initialFee = 2**64 - 1 (no storage injection), finalFee = 0.3%, default one-day half-life rate 68913100933, traded at tradingStart. Exact input: net = floor(gross / 2**64), fee = gross - net = ceil(gross * (2**64 - 1) / 2**64). Exact output: paid = calc * 2**64, fee = paid - calc. Settled by the local test lockers against locally compiled Core; not live evidence.", - "first_buy_note": "The first whole-quote-unit buy is identical for the max -> 0.3% / default rate, max -> max / rate 1 and max -> 0 / rate 2**64 - 1 schedules (the rate at tradingStart is the initial rate in every case).", - "trades": [ - { - "order": "t0", - "decimals": "18/18", - "step": "oneUnit", - "quoteIn": "1", - "gross": "999970", - "fee": "999970", - "net": "0" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "buyExactIn", - "quoteIn": "1000000000000000000", - "gross": "999083373736809197507080", - "fee": "999083373736809197452920", - "net": "54160" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "buyExactOut", - "tokenOut": "54160", - "calcQuote": "1", - "fee": "18446744073709551615", - "paid": "18446744073709551616" - }, - { - "order": "t0", - "decimals": "18/18", - "step": "sellAll", - "tokenIn": "108320", - "grossQuote": "0", - "fee": "0", - "quoteOut": "0" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "oneUnit", - "quoteIn": "1", - "gross": "999947603316768497", - "fee": "999947603316768497", - "net": "0" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "buyExactIn", - "quoteIn": "1000000", - "gross": "999048504527750217302727", - "fee": "999048504527750217248569", - "net": "54158" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "buyExactOut", - "tokenOut": "54158", - "calcQuote": "1", - "fee": "18446744073709551615", - "paid": "18446744073709551616" - }, - { - "order": "t0", - "decimals": "18/6", - "step": "sellAll", - "tokenIn": "108316", - "grossQuote": "0", - "fee": "0", - "quoteOut": "0" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "oneUnit", - "quoteIn": "1", - "gross": "722806", - "fee": "722806", - "net": "0" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "buyExactIn", - "quoteIn": "1000000000000000000", - "gross": "999083373736809196527088", - "fee": "999083373736809196472928", - "net": "54160" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "buyExactOut", - "tokenOut": "54160", - "calcQuote": "1", - "fee": "18446744073709551615", - "paid": "18446744073709551616" - }, - { - "order": "t1", - "decimals": "18/18", - "step": "sellAll", - "tokenIn": "108320", - "grossQuote": "0", - "fee": "0", - "quoteOut": "0" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "oneUnit", - "quoteIn": "1", - "gross": "641450041727074607", - "fee": "641450041727074607", - "net": "0" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "buyExactIn", - "quoteIn": "1000000", - "gross": "999047507021926419318928", - "fee": "999047507021926419264770", - "net": "54158" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "buyExactOut", - "tokenOut": "54158", - "calcQuote": "1", - "fee": "18446744073709551615", - "paid": "18446744073709551616" - }, - { - "order": "t1", - "decimals": "18/6", - "step": "sellAll", - "tokenIn": "108316", - "grossQuote": "0", - "fee": "0", - "quoteOut": "0" - } - ], - "trades_note": "t1 18/18 is the native ETH quote. A single raw quote unit nets nothing (the whole gross output is the fee); a whole quote unit nets 54,160 (18/18) or 54,158 (18/6) raw launch-token units; the immediate sell of everything held grosses under one raw quote unit, so it pays nothing and charges nothing.", - "exact_output_bounds": { - "note": "Bisection over the requested launch-token output on the pinned fixture: the last request that executes, the first that fails the signed int128 conversion of the fee-inclusive input (calc * 2**64 >= 2**127), and the first that fails the uint128 gross-up helper (calc >= 2**64). The last executable request has calc = 2**63 - 1 exactly. Every failed probe rolls back pool state, fee ledger, position liquidity and Core balances.", - "signed_bound_calc": "9223372036854775807", - "helper_bound_calc": "18446744073709551615", - "errors": { - "signed": "SafeCastLib.Overflow() 0x35278d12", - "helper": "AmountBeforeFeeOverflow() 0x0d88f526" - }, - "vectors": [ - { - "order": "t0", - "decimals": "18/18", - "lastValidTokenOut": "9147280263567190862134907", - "calc": "9223372036854775807", - "paid": "170141183460469231713240559642174554112", - "firstSignedOverflowTokenOut": "9147280263567190862134908", - "lastSignedOverflowTokenOut": "18145179163502972233172123", - "firstHelperOverflowTokenOut": "18145179163502972233172124" - }, - { - "order": "t1", - "decimals": "18/18", - "quote": "native", - "lastValidTokenOut": "9147280263567190861982364", - "calc": "9223372036854775807", - "paid": "170141183460469231713240559642174554112", - "firstSignedOverflowTokenOut": "9147280263567190861982365", - "lastSignedOverflowTokenOut": "18145179163502972233028470", - "firstHelperOverflowTokenOut": "18145179163502972233028471" - } - ] - } - }, - "forward_schema": { - "swap": { - "payload": "abi.encode(PoolKey, SwapParameters)", - "length": 128, - "returns": "abi.encode(PoolBalanceUpdate, PoolState)", - "first_word": "PoolKey.token0 < 2**160" - }, - "create": { - "tag": "0x224a5a98239f599168b798c4205db155bfff4332b4931a7fb5bc60c2e2eeee30", - "tag_source": "keccak256(\"IZeroSeedLaunch#CREATE\")", - "payload": "abi.encode(tag, LaunchParameters)", - "returns": "abi.encode(Installation)", - "controller": "Locker.addr() of the forwarding locker", - "lock_debt_after": "zero" - }, - "claim_fees": { - "tag": "0xe52d8a05fd0f2d32b280675736d8117fee83069a0eac7ab9279aa52ff05a614c", - "tag_source": "keccak256(\"IZeroSeedLaunch#CLAIM_FEES\")", - "payload": "abi.encode(tag, PoolKey)", - "returns": "abi.encode(uint128 amount0, uint128 amount1)", - "authorization": "launch.controller == Locker.addr()", - "lock_debt_after": "credit of (amount0, amount1) for the locker to withdraw" - }, - "unknown": "any first word >= 2**160 that is not a tag, or fewer than 32 bytes, reverts UnknownForwardAction(); a first word < 2**160 is decoded as a swap" - }, - "abi": { - "ZeroSeedLaunch": { - "selectors": { - "creatorFees((address,address,bytes32))": "0xb4ce9a2e", - "feeAt(bytes32,uint256)": "0xe81084ec", - "forwarded_2374103877(bytes32)": "0x00000001", - "getLaunch(bytes32)": "0x7d1d589c", - "previewInstallation(address,(string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xb665ec4d" - }, - "topics": { - "LaunchCreated": "0x8f81ce1beb182cad759aa1c797ef027d374f535fd0c08696441c32797c7a5888", - "LaunchSwapped": "0x3e58795f52a8e88efdec2da89ea3798175f8764145ccd7bf5e40633a7d571f6d", - "CreatorFeesClaimed": "0xf0ffdfa84530baef2e9f8bbb160545a59bfef8da319fa27aa69e4f3c8f05a41d" - }, - "errors": { - "ControllerOnly()": "0xac8ff76a", - "InitializeThroughCreateOnly()": "0x028eb443", - "InvalidFeeSchedule()": "0xf75a271a", - "InvalidParameters()": "0xe5239090", - "InvalidTradingStart()": "0xe0839259", - "LaunchTokenExists(address)": "0xf68475cd", - "LiquidityAboveTickCap(uint128,uint128)": "0x20ea795f", - "MetadataTooLong()": "0x85b8e2f4", - "PositionsThroughCreateOnly()": "0x74a99003", - "SwapsThroughForwardOnly()": "0xf17f54b3", - "TradingNotStarted()": "0xacf75be8", - "UnknownForwardAction()": "0xe5c78d9a", - "UnknownLaunch()": "0xf6806bf8", - "UnsupportedQuoteToken()": "0x658d8331", - "ZeroLiquidity()": "0x10074548" - } - }, - "ZeroSeedLaunchPeriphery": { - "selectors": { - "LAUNCH()": "0x096e3c3e", - "claimFees((address,address,bytes32),address)": "0x6906b4c0", - "create((string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xf91810d2", - "launchOwner(bytes32)": "0x60ac0b66", - "previewInstallation(address,(string,string,uint8,uint128,address,int32,int32,uint32,uint64,uint64,uint64,uint64,bytes32))": "0xb665ec4d", - "transferLaunch(bytes32,address)": "0x8b1cb36f" - }, - "topics": { - "LaunchOwnerChanged": "0x478004cf03e8c3b648608ce0259ce479c813aa671a383230ee7763fe5da22391", - "LaunchFeesClaimed": "0x908c6b2c19765fe47aef218b4f71ea1a10a9e156d9d5c3500c3a298e01428805" - } - }, - "LaunchParameters": "(string name,string symbol,uint8 decimals,uint128 supply,address quoteToken,int32 askTick,int32 upperTick,uint32 tickSpacing,uint64 tradingStart,uint64 decayRateX32,uint64 initialFee,uint64 finalFee,bytes32 salt)", - "Launch": "(address controller,uint64 tradingStart,uint64 decayRateX32,uint64 initialFee,uint64 finalFee,int32 tickLower,int32 tickUpper)", - "changed_from_previous_revision": { - "previewInstallation(address,LaunchParameters)": { - "previous": "0x917c1163", - "current": "0xb665ec4d" - }, - "create(LaunchParameters)": { - "previous": "0x662af306", - "current": "0xf91810d2" - }, - "LaunchCreated topic": { - "previous": "0x3a4e811187f4a8c0b72396e9d7ca13aeed62eb97a599a24af1ed99066041098f", - "current": "0x8f81ce1beb182cad759aa1c797ef027d374f535fd0c08696441c32797c7a5888" - }, - "LaunchParameters": "uint32 feeDuration (10th field) replaced by uint64 decayRateX32 at the same position", - "Launch": "uint32 feeDuration (3rd field) replaced by uint64 decayRateX32 at the same position; getLaunch(bytes32) selector unchanged, return tuple changed", - "unchanged": [ - "feeAt(bytes32,uint256) 0xe81084ec", - "getLaunch(bytes32) 0x7d1d589c", - "creatorFees 0xb4ce9a2e", - "claimFees 0x6906b4c0", - "transferLaunch 0x8b1cb36f", - "launchOwner 0x60ac0b66", - "LAUNCH 0x096e3c3e", - "LaunchSwapped topic", - "CreatorFeesClaimed topic", - "LaunchOwnerChanged topic", - "LaunchFeesClaimed topic", - "forward tags ZERO_SEED_LAUNCH_CREATE / ZERO_SEED_LAUNCH_CLAIM_FEES", - "swap payload", - "InvalidFeeSchedule selector 0xf75a271a (now only finalFee > initialFee)" - ] - }, - "storage_layout_Launch": { - "slots": 2, - "slot0": "controller address @0, tradingStart uint64 @20", - "slot1": "decayRateX32 uint64 @0, initialFee uint64 @8, finalFee uint64 @16, tickLower int32 @24, tickUpper int32 @28", - "source": "forge inspect ZeroSeedLaunch storage-layout" - } - }, - "pins": { - "runtime": { - "ZeroSeedLaunch": { - "keccak256": "0xe08ad382497fc9825956c2b4e9ccf463014c4ed65baf50c0a59db8d7b7aedfc3", - "bytes": 19694 - }, - "ZeroSeedLaunchPeriphery": { - "keccak256": "0xfa8ef7dfd8f5f7454366b471449f8a771f1cef34c764022c971e9394a05f0444", - "bytes": 5534 - } - }, - "initcode": { - "ZeroSeedLaunch": { - "keccak256": "0xa76b1de0bfc2d72b32ce963cbc7598aa67fa60ae6b8874ec176e3630ec965f2c", - "bytes": 20184 - }, - "ZeroSeedLaunchPeriphery": { - "keccak256": "0x7a4c7a556485b2005cb824b11641adc4d766a5c501e3a65adaa72646654ec0c4", - "bytes": 5754 - } - }, - "note": "forge 1.8.3 (cae51ad), solc 0.8.33, via_ir, optimizer 9999999, evm osaka; immutables resolved with Core at the canonical address and the extension at its callpoint address; LOCAL compilation from test_bytecodeSizes, not a deployment." - } -} diff --git a/src/routes/launches/vectors/zero-seed-launch.json b/src/routes/launches/vectors/zero-seed-launch.json new file mode 100644 index 0000000..1b4030f --- /dev/null +++ b/src/routes/launches/vectors/zero-seed-launch.json @@ -0,0 +1,1956 @@ +{ + "version": 8, + "fixture": { + "core": "Locally compiled src/Core.sol deployed at the test's canonical Core address; no fork or live-chain validation", + "router": "Locally compiled test/LaunchTestRouter.sol; standard-ABI single-hop forwarding and settlement, with snapshot/revert quotes in the test harness; no deployed-router bytecode fixture", + "supply_requested": "1000000000000000000000000000", + "tick_spacing": 17990, + "width_ticks": 4605200, + "ask_tick_18_18": -13815500, + "ask_tick_18_6": -41446500, + "trading_start": 1000, + "fee_initial_q64": "922337203685477580", + "fee_final_q64": "55340232221128654", + "max_creator_fee_q64": "18446744073709551615", + "creator_fee_range": "every uint64 0..18446744073709551615 inclusive for initialFee and finalFee (Q0.64; 2**64 - 1 is just under 100%, never exactly 100%); finalFee <= initialFee, else InvalidFeeSchedule; exactly 100% = 2**64 is not a uint64 and fails ABI decoding with empty revert data; see creator_fee_range", + "label": "LOCAL FIXTURE VECTORS - not live evidence; numeric start margin is a local-reference proposal, not a live parameter; the creator fee ceiling is the uint64 maximum", + "quote_18": "t0 order: plain 18-decimal test ERC-20 selected per launch at the top of the address space. t1 order: native ETH, address(0), the reference Base configuration; always token0, no code, no decimals(), no approvals.", + "quote_6": "plain 6-decimal solady ERC-20 (DecimalsToken), selected per launch for math vectors only", + "fee_decay_rates_x32": [ + "68913100933", + "9923486534471" + ], + "fee_decay_rate_x32_default": "68913100933", + "fee_decay_rate_encoding": "decayRateX32: uint64, unsigned Q32.32 ticks per second (2**32 = 1 tick/s). Any value is accepted; 0 is a flat fee. Half-life in seconds of the excess over finalFee = 2 * ln(2) / ln(1.000001) * 2**32 / decayRateX32 = 1386295.05426695565 * 2**32 / decayRateX32. rate for a half-life H: floor(1386295.05426695565... * 2**32 / H), computed with exact high-precision arithmetic (not binary floating point).", + "fee_schedule": "initialFee for t <= 0 or decayRateX32 == 0; finalFee for t > MAX_DECAY_PRODUCT / decayRateX32 (integer division; MAX_DECAY_PRODUCT = (88722835 + 1) * 2**32 - 1 = 381061679028371455); otherwise m = floor(decayRateX32 * t / 2**32) and fee = finalFee + floor((initialFee - finalFee) * R / 2**128) with R = toFixed(tickToSqrtRatio(-m)), the compact (94-bit mantissa, rounded down) sqrt ratio of src/math/ticks.sol as a 64.128 integer. t = block timestamp - tradingStart in whole seconds. The operation order is normative; the saturation check must precede the helper call." + }, + "create": [ + { + "order": "t0", + "dec": "18/18", + "ask": "-13815500", + "upper": "-9210300", + "spacing": "17990", + "alignedAsk": "-13798330", + "alignedUpper": "-9210880", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "liquidity": "1121810267768648385524834", + "tickLower": "-13798330", + "tickUpper": "-9210880", + "initialTick": "-13798330", + "withdrawable": "999999999999999999999999507" + }, + { + "order": "t0", + "dec": "18/6", + "ask": "-41446500", + "upper": "-36841300", + "spacing": "17990", + "alignedAsk": "-41430970", + "alignedUpper": "-36843520", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999488107389", + "liquidity": "1120910338181752946", + "tickLower": "-41430970", + "tickUpper": "-36843520", + "initialTick": "-41430970", + "withdrawable": "999999999999999999488107388" + }, + { + "order": "t1", + "dec": "18/18", + "ask": "-13815500", + "upper": "-9210300", + "spacing": "17990", + "alignedAsk": "-13798330", + "alignedUpper": "-9210880", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "liquidity": "1121810267768648385525007", + "tickLower": "9210880", + "tickUpper": "13798330", + "initialTick": "13798330", + "withdrawable": "999999999999999999999999558" + }, + { + "order": "t1", + "dec": "18/6", + "ask": "-41446500", + "upper": "-36841300", + "spacing": "17990", + "alignedAsk": "-41430970", + "alignedUpper": "-36843520", + "requested": "1000000000000000000000000000", + "installed": "999999999999999999449009087", + "liquidity": "1120910338181752946", + "tickLower": "36843520", + "tickUpper": "41430970", + "initialTick": "41430970", + "withdrawable": "999999999999999999449009086" + } + ], + "installation": { + "reference": "zero-seed-launch-installation.json", + "oracle": "zero-seed-launch-installation.py" + }, + "trade": [ + { + "order": "t0", + "dec": "18/18", + "quoteIn": "1000000000000000000", + "gross": "982091443427432213916824", + "fee": "49104572171371610653250", + "net": "932986871256060603263574", + "decimals": "18/18", + "step": "buyExactIn" + }, + { + "order": "t0", + "dec": "18/18", + "tokenOut": "932986871256060603263574", + "calcQuote": "951638594087469002", + "fee": "50086241794077316", + "paid": "1001724835881546318", + "decimals": "18/18", + "step": "buyExactOut" + }, + { + "order": "t0", + "dec": "18/18", + "tokenIn": "932986871256060603263574", + "quoteOut": "904056664383095550", + "decimals": "18/18", + "step": "sellExactIn" + }, + { + "order": "t0", + "dec": "18/18", + "leftHeld": "0", + "principalQuote": "49958055259709451", + "principalToken": "999950895427828628389346150", + "decimals": "18/18", + "step": "reverse" + }, + { + "order": "t0", + "dec": "18/18", + "token": "108", + "quote": "2", + "decimals": "18/18", + "step": "dust" + }, + { + "order": "t0", + "dec": "18/18", + "requested": "999950896427828628389346150", + "got": "999950895427828628389346150", + "principalToken": "0", + "principalQuote": "10083609766202278612140", + "decimals": "18/18", + "step": "exhaust" + }, + { + "order": "t0", + "dec": "18/18", + "positionTokenBefore": "999950895427828628389346150", + "delivered": "999950895427828628389346150", + "residueToken": "0", + "decimals": "18/18", + "step": "upperResidue" + }, + { + "order": "t0", + "dec": "18/18", + "token": "108", + "quote": "2", + "decimals": "18/18", + "step": "dustExhausted" + }, + { + "order": "t0", + "dec": "18/6", + "quoteIn": "1000000", + "gross": "983667637211529436923507", + "fee": "49183381860576471803516", + "net": "934484255350952965119991", + "decimals": "18/6", + "step": "buyExactIn" + }, + { + "order": "t0", + "dec": "18/6", + "tokenOut": "934484255350952965119991", + "calcQuote": "951642", + "fee": "50087", + "paid": "1001729", + "decimals": "18/6", + "step": "buyExactOut" + }, + { + "order": "t0", + "dec": "18/6", + "tokenIn": "934484255350952965119991", + "quoteOut": "904058", + "decimals": "18/6", + "step": "sellExactIn" + }, + { + "order": "t0", + "dec": "18/6", + "leftHeld": "0", + "principalQuote": "49957", + "principalToken": "999950816618139422912052817", + "decimals": "18/6", + "step": "reverse" + }, + { + "order": "t0", + "dec": "18/6", + "token": "104251056", + "quote": "2", + "decimals": "18/6", + "step": "dust" + }, + { + "order": "t0", + "dec": "18/6", + "requested": "999950817618139422912052817", + "got": "999950816618139422912052817", + "principalToken": "0", + "principalQuote": "10067437871", + "decimals": "18/6", + "step": "exhaust" + }, + { + "order": "t0", + "dec": "18/6", + "positionTokenBefore": "999950816618139422912052817", + "delivered": "999950816618139422912052817", + "residueToken": "0", + "decimals": "18/6", + "step": "upperResidue" + }, + { + "order": "t0", + "dec": "18/6", + "token": "104251056", + "quote": "2", + "decimals": "18/6", + "step": "dustExhausted" + }, + { + "order": "t1", + "dec": "18/18", + "quoteIn": "1000000000000000000", + "gross": "982091443427432213710037", + "fee": "49104572171371610642911", + "net": "932986871256060603067126", + "decimals": "18/18", + "step": "buyExactIn" + }, + { + "order": "t1", + "dec": "18/18", + "tokenOut": "932986871256060603067126", + "calcQuote": "951638594087469001", + "fee": "50086241794077316", + "paid": "1001724835881546317", + "decimals": "18/18", + "step": "buyExactOut" + }, + { + "order": "t1", + "dec": "18/18", + "tokenIn": "932986871256060603067126", + "quoteOut": "904056664383095550", + "decimals": "18/18", + "step": "sellExactIn" + }, + { + "order": "t1", + "dec": "18/18", + "leftHeld": "0", + "principalQuote": "49958055259709452", + "principalToken": "999950895427828628388961338", + "decimals": "18/18", + "step": "reverse" + }, + { + "order": "t1", + "dec": "18/18", + "token": "395310", + "quote": "2", + "decimals": "18/18", + "step": "dust" + }, + { + "order": "t1", + "dec": "18/18", + "requested": "999950896427828628388961338", + "got": "999950895427828628388961338", + "principalToken": "0", + "principalQuote": "10083609766202278612147", + "decimals": "18/18", + "step": "exhaust" + }, + { + "order": "t1", + "dec": "18/18", + "positionTokenBefore": "999950895427828628388961338", + "delivered": "999950895427828628388961338", + "residueToken": "0", + "decimals": "18/18", + "step": "upperResidue" + }, + { + "order": "t1", + "dec": "18/18", + "token": "395310", + "quote": "2", + "decimals": "18/18", + "step": "dustExhausted" + }, + { + "order": "t1", + "dec": "18/6", + "quoteIn": "1000000", + "gross": "983667120985587257409989", + "fee": "49183356049279362827840", + "net": "934483764936307894582149", + "decimals": "18/6", + "step": "buyExactIn" + }, + { + "order": "t1", + "dec": "18/6", + "tokenOut": "934483764936307894582149", + "calcQuote": "951641", + "fee": "50087", + "paid": "1001728", + "decimals": "18/6", + "step": "buyExactOut" + }, + { + "order": "t1", + "dec": "18/6", + "tokenIn": "934483764936307894582149", + "quoteOut": "904058", + "decimals": "18/6", + "step": "sellExactIn" + }, + { + "order": "t1", + "dec": "18/6", + "leftHeld": "0", + "principalQuote": "49957", + "principalToken": "999950816643950720086181245", + "decimals": "18/6", + "step": "reverse" + }, + { + "order": "t1", + "dec": "18/6", + "token": "2", + "quote": "3", + "decimals": "18/6", + "step": "dust" + }, + { + "order": "t1", + "dec": "18/6", + "requested": "999950817643950720086181245", + "got": "999950816643950720086181245", + "principalToken": "0", + "principalQuote": "10067437871", + "decimals": "18/6", + "step": "exhaust" + }, + { + "order": "t1", + "dec": "18/6", + "positionTokenBefore": "999950816643950720086181245", + "delivered": "999950816643950720086181245", + "residueToken": "0", + "decimals": "18/6", + "step": "upperResidue" + }, + { + "order": "t1", + "dec": "18/6", + "token": "2", + "quote": "3", + "decimals": "18/6", + "step": "dustExhausted" + } + ], + "fee": [ + { + "decayRateX32": "68913100933", + "t_minus_start": "-1", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "0", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "1", + "tick_magnitude": "16", + "fee_q64": "922330267740917652" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "10", + "tick_magnitude": "160", + "fee_q64": "922267846736753839" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "60", + "tick_magnitude": "962", + "fee_q64": "921920278629180106" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "600", + "tick_magnitude": "9627", + "fee_q64": "918173943809149156" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "1800", + "tick_magnitude": "28881", + "fee_q64": "909907303121253143" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "3600", + "tick_magnitude": "57762", + "fee_q64": "897655606672062181" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "43200", + "tick_magnitude": "693147", + "fee_q64": "668399831593786699" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86399", + "tick_magnitude": "1386279", + "fee_q64": "488842197715736884" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86400", + "tick_magnitude": "1386295", + "fee_q64": "488838729715618946" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "86401", + "tick_magnitude": "1386311", + "fee_q64": "488835261743244884" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "172800", + "tick_magnitude": "2772590", + "fee_q64": "272089486849531874" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "604800", + "tick_magnitude": "9704065", + "fee_q64": "62113647347197278" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "2592000", + "tick_magnitude": "41588851", + "fee_q64": "55340233028582759" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529596", + "tick_magnitude": "88722819", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529597", + "tick_magnitude": "88722835", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "5529598", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "18446744073709551615", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "68913100933", + "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "-1", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "0", + "tick_magnitude": null, + "fee_q64": "922337203685477580" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "1", + "tick_magnitude": "2310", + "fee_q64": "921336400758785753" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "10", + "tick_magnitude": "23104", + "fee_q64": "912379287313991670" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "60", + "tick_magnitude": "138629", + "fee_q64": "864277214588907100" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "600", + "tick_magnitude": "1386295", + "fee_q64": "488838729715618946" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "1800", + "tick_magnitude": "4158885", + "fee_q64": "163714862475909380" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "3600", + "tick_magnitude": "8317770", + "fee_q64": "68887062105693473" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "43200", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86399", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86400", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "86401", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "172800", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "604800", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "2592000", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38398", + "tick_magnitude": "88718262", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38399", + "tick_magnitude": "88720572", + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "38400", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "18446744073709551615", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + }, + { + "decayRateX32": "9923486534471", + "t_minus_start": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "tick_magnitude": null, + "fee_q64": "55340232221128654" + } + ], + "router": [ + { + "order": "t0", + "paid": "1000000000000000000", + "got": "932986871256060603263574" + }, + { + "order": "t1", + "paid": "1000000000000000000", + "got": "932986871256060603067126" + } + ], + "gas": [ + { + "order": "t0", + "create": "1145023", + "firstBuy": "102296", + "secondBuy": "34078", + "sell": "34167", + "claim": "70066" + }, + { + "order": "t1", + "create": "1104007", + "firstBuy": "84187", + "secondBuy": "34686", + "sell": "38502", + "claim": "79588" + } + ], + "other": [ + { + "raw": "VEC|recapture|t0|quoteIn=10e18|netOfFee=9256308150479412625085330|afterClaim=9743482263662539604908183" + }, + { + "raw": "VEC|dust|t0|quoteIn=1|net=933793|fee=49148" + } + ], + "gas_note": "In-test warm gas, t0 on the plain 18-decimal test ERC-20 quote and t1 on native ETH, through the periphery (create, claim) and the forwarding test locker (swaps), as logged by test_gas_vectors at this revision (forge 1.8.3, solc 0.8.33, via_ir, optimizer 9999999, evm osaka).", + "residue_metrics": { + "definitions": { + "core_unbooked_dust": "Per token: Core balance - position amount (rounded down) - creator fee ledger, accumulated over all swaps; raw units of that token. Unowned: not creator principal, not creator fees, unreachable by claims.", + "upper_bound_residue": "Raw launch-token units the position held before the exhausting buy that the buy did not deliver when it stopped at the upper bound. Becomes Core unbooked dust: dust_after.token = dust_before.token + residue (asserted)." + }, + "scenarios": [ + { + "order": "t0", + "decimals": "18/18", + "position_token_before_exhaust": "999950895427828628389346150", + "delivered_raw_token": "999950895427828628389346150", + "upper_residue_raw_token": "0", + "core_dust_before_exhaust": { + "raw_token": "108", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "108", + "raw_quote": "2" + } + }, + { + "order": "t0", + "decimals": "18/6", + "position_token_before_exhaust": "999950816618139422912052817", + "delivered_raw_token": "999950816618139422912052817", + "upper_residue_raw_token": "0", + "core_dust_before_exhaust": { + "raw_token": "104251056", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "104251056", + "raw_quote": "2" + } + }, + { + "order": "t1", + "decimals": "18/18", + "position_token_before_exhaust": "999950895427828628388961338", + "delivered_raw_token": "999950895427828628388961338", + "upper_residue_raw_token": "0", + "core_dust_before_exhaust": { + "raw_token": "395310", + "raw_quote": "2" + }, + "core_dust_after_exhaust": { + "raw_token": "395310", + "raw_quote": "2" + } + }, + { + "order": "t1", + "decimals": "18/6", + "position_token_before_exhaust": "999950816643950720086181245", + "delivered_raw_token": "999950816643950720086181245", + "upper_residue_raw_token": "0", + "core_dust_before_exhaust": { + "raw_token": "2", + "raw_quote": "3" + }, + "core_dust_after_exhaust": { + "raw_token": "2", + "raw_quote": "3" + } + } + ] + }, + "start": { + "contract": [ + { + "block_time": 5000, + "tradingStart": "4999", + "create": "InvalidTradingStart (create and preview)", + "trade_at_block_time": null, + "trade_at_start": null + }, + { + "block_time": 5000, + "tradingStart": "5000", + "create": "ok", + "trade_at_block_time": "ok", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "5001", + "create": "ok", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "91400", + "create": "ok", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "ok" + }, + { + "block_time": 5000, + "tradingStart": "18446744073709551615", + "create": "ok (contract has no cap; effectively indefinite self-lock)", + "trade_at_block_time": "TradingNotStarted", + "trade_at_start": "not exercised (astronomically distant timestamp)" + } + ], + "contract_mined_late": { + "previewed_at": 5000, + "tradingStart": 5000, + "mined_at": 5001, + "create": "InvalidTradingStart, no token and no pool left" + }, + "producer_proposal": { + "status": "local reference proposal for CTO freeze; not a live/hosted selection", + "rule": "buildTime + minMargin <= tradingStart <= buildTime + maxDelay and buildTime < expiry <= tradingStart; never rewrite a signed start", + "minMargin_s": 300, + "maxDelay_s": 86400, + "vectors": [ + { + "buildTime": 1000, + "tradingStart": 1000, + "expiry": 1000, + "verdict": "refuse: start-now (start <= buildTime)" + }, + { + "buildTime": 1000, + "tradingStart": 1299, + "expiry": 1299, + "verdict": "refuse: start below margin" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1300, + "verdict": "accept: minimum start, expiry at start" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1301, + "verdict": "refuse: expiry after start" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1000, + "verdict": "refuse: expiry not after buildTime" + }, + { + "buildTime": 1000, + "tradingStart": 1300, + "expiry": 1001, + "verdict": "accept" + }, + { + "buildTime": 1000, + "tradingStart": 87400, + "expiry": 1300, + "verdict": "accept: maximum start" + }, + { + "buildTime": 1000, + "tradingStart": 87400, + "expiry": 87400, + "verdict": "accept" + }, + { + "buildTime": 1000, + "tradingStart": 87401, + "expiry": 1300, + "verdict": "refuse: start above cap" + }, + { + "buildTime": 1000, + "tradingStart": 18446744073709551615, + "expiry": 1300, + "verdict": "refuse: start above cap (contract alone would accept and self-lock)" + } + ], + "why": "Bounds the requested start to maxDelay after buildTime. Submission before expiry does not guarantee mining before start: late mining reverts atomically. No inclusion-latency guarantee." + } + }, + "claim_recipient_refusals": { + "error": "InvalidRecipient() (periphery)", + "refused": [ + "address(0)", + "Core", + "extension", + "periphery", + "poolKey.token0", + "poolKey.token1" + ], + "accepted_examples": [ + "owner", + "EOA", + "other contract (ETH-receiving for a native quote)" + ], + "atomicity": "both assets or neither; a reverting native recipient or a refused ERC-20 transfer rolls the ledger, principal and Core balances back; retry to another recipient pays both in full", + "reentrancy": "the ledger is moved onto the lock before Core pays; an owner contract re-entering claimFees during the payment claims zero" + }, + "metadata": { + "encoding": "Canonical LibString.packOne: first byte length 1..31, next length bytes content, all remaining bytes zero", + "unit": "bytes, not characters; UTF-8 is a consumer policy, not contract validation", + "error": "InvalidMetadata()", + "checked_in": [ + "create", + "previewInstallation" + ], + "decimals": 18, + "examples": { + "Zero Seed": "0x095a65726f205365656400000000000000000000000000000000000000000000", + "ZERO": "0x045a45524f000000000000000000000000000000000000000000000000000000" + }, + "refused": [ + "zero", + "length 0", + "length >31", + "nonzero tail" + ], + "note": "LibString.packOne assumes memory padding; producers must explicitly zero the tail. No truncation." + }, + "collision": { + "error": "LaunchTokenExists(address token)", + "checked_in": [ + "create", + "previewInstallation" + ], + "rule": "CREATE2(extension, keccak256(abi.encode(controller,salt)), keccak256(LaunchToken.creationCode ++ abi.encode(bytes32 name,bytes32 symbol))); logo is excluded" + }, + "fee_curve": { + "note": "Tick-rate schedule. Digests: SHA-256 chain h = sha256(h || fee as 8-byte big-endian) over the sampled seconds t = 0, step, 2*step, ..., last inclusive; all from the independent Python oracle (exact port of the tick table).", + "rates_q64": { + "5": "922337203685477580", + "50": "9223372036854775808", + "75": "13835058055282163712", + "90": "16602069666338596454", + "99": "18262276632972456099", + "99.9": "18428297329635842064", + "max": "18446744073709551615" + }, + "final_fee_q64": "55340232221128654", + "decay_rates_x32": { + "default_one_day": "68913100933", + "600s": "9923486534471", + "two_ticks_per_second": "8589934592", + "quarter_tick_per_second": "1073741824", + "tenth_tick_per_second_floor": "429496729", + "min": "1", + "max": "18446744073709551615" + }, + "half_life_s": { + "default_one_day": "86400.00000104363", + "600s": "600.00000000005238", + "two_ticks_per_second": "693151.52713347783 (8.0225408233 days)", + "quarter_tick_per_second": "5545180.2170678226 (64.18 days)", + "min": "5954091920683119 (about 1.89e8 years)", + "max": "0.0000000003229 (saturates within the first second)" + }, + "saturation_second": { + "default_one_day": "5529597", + "600s": "38399", + "two_ticks_per_second": "44361417", + "quarter_tick_per_second": "354891343", + "min": "381061679028371455", + "max": "0" + }, + "saturation_note": "saturation_second = MAX_DECAY_PRODUCT / decayRateX32 is the last elapsed second computed through the helper; from the next second the fee is exactly finalFee. On the 50% -> 0.3% fixtures the floored excess is already zero earlier (rounding), so the two mechanisms coincide at the floor; a full-span excess (2**64 - 1 -> 0) still leaves 1 raw unit when the magnitude is exactly 88,722,835 at saturation_second, which is why the guard rather than a clamp to the minimum tick yields finalFee.", + "digests_sha256": [ + { + "label": "default 50% -> 0.3%, every second 0..3600", + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "last": 3600, + "step": 1, + "digest": "c3de4d91d476acc61f73455efe7fb8c750b89de3759a0c500bbde0bdf9bcd062" + }, + { + "label": "default 50% -> 0.3%, every minute 0..30 days", + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "last": 2592000, + "step": 60, + "digest": "b2c022f5cbbc90cedca9834bcf64f636b97c45dee08c730ab4dbf17d8d5acc74" + }, + { + "label": "600 s half-life 5% -> 0.3%, every second 0..3600", + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "9923486534471", + "last": 3600, + "step": 1, + "digest": "c4db35cd45f48222463cb9a940c506599715162db6c37b01428331ff5263bdc6" + }, + { + "label": "2 ticks/s 99.9% -> 0, every hour 0..30 days", + "initialFee": "18428297329635842064", + "finalFee": "0", + "decayRateX32": "8589934592", + "last": 2592000, + "step": 3600, + "digest": "8fdefff5fe4cfb98c43bbbf6ed6f0b9a0ae398f1f810ffea0522d5504e1321da" + }, + { + "label": "1/4 tick/s max -> 0, every second 0..3600", + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "1073741824", + "last": 3600, + "step": 1, + "digest": "11ff0a905ab22707579e714aba31b895ed3234788a0dab10b0daa0881a9ccf36" + } + ], + "tables": { + "default_one_day_50_to_0.3": [ + { + "t": "0", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000" + }, + { + "t": "1", + "fee_q64": "9223298692930386783", + "pct": "49.9996024018", + "tick_magnitude": "16" + }, + { + "t": "60", + "fee_q64": "9218963276153076996", + "pct": "49.9761000603", + "tick_magnitude": "962" + }, + { + "t": "3600", + "fee_q64": "8962377276946957658", + "pct": "48.5851445715", + "tick_magnitude": "57762" + }, + { + "t": "43200", + "fee_q64": "6538119400055406284", + "pct": "35.4432162875", + "tick_magnitude": "693147" + }, + { + "t": "86400", + "fee_q64": "4639356258918185573", + "pct": "25.1500006743", + "tick_magnitude": "1386295" + }, + { + "t": "172800", + "fee_q64": "2347348307759775472", + "pct": "12.7250006743", + "tick_magnitude": "2772590" + }, + { + "t": "604800", + "fee_q64": "126965494298918151", + "pct": "0.6882813237", + "tick_magnitude": "9704065" + }, + { + "t": "2592000", + "fee_q64": "55340240759526319", + "pct": "0.3000000463", + "tick_magnitude": "41588851" + }, + { + "t": "5529597", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "88722835" + }, + { + "t": "5529598", + "fee_q64": "55340232221128654", + "pct": "0.3000000000" + } + ], + "two_ticks_per_second_50_to_0.3": [ + { + "t": "0", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000" + }, + { + "t": "1", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "60", + "fee_q64": "9222821971723649262", + "pct": "49.9970180909", + "tick_magnitude": "120" + }, + { + "t": "3600", + "fee_q64": "9190426476420833039", + "pct": "49.8214017590", + "tick_magnitude": "7200" + }, + { + "t": "43200", + "fee_q64": "8835746255246636721", + "pct": "47.8986764273", + "tick_magnitude": "86400" + }, + { + "t": "86400", + "fee_q64": "8464509350746382498", + "pct": "45.8861971355", + "tick_magnitude": "172800" + }, + { + "t": "172800", + "fee_q64": "7768459773126180960", + "pct": "42.1129048144", + "tick_magnitude": "345600" + }, + { + "t": "604800", + "fee_q64": "5062770843274302024", + "pct": "27.4453357354", + "tick_magnitude": "1209600" + }, + { + "t": "2592000", + "fee_q64": "741753037726786584", + "pct": "4.0210512748", + "tick_magnitude": "5184000" + }, + { + "t": "44361417", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "88722834" + }, + { + "t": "44361418", + "fee_q64": "55340232221128654", + "pct": "0.3000000000" + } + ], + "two_ticks_per_second_99.9_to_0": [ + { + "t": "0", + "fee_q64": "18428297329635842064", + "pct": "99.9000000000" + }, + { + "t": "1", + "fee_q64": "18428278901356940707", + "pct": "99.8999001001", + "tick_magnitude": "2" + }, + { + "t": "60", + "fee_q64": "18427191665519151079", + "pct": "99.8940061828", + "tick_magnitude": "120" + }, + { + "t": "3600", + "fee_q64": "18362074764497997343", + "pct": "99.5410067550", + "tick_magnitude": "7200" + }, + { + "t": "43200", + "fee_q64": "17649146110668978989", + "pct": "95.6762127785", + "tick_magnitude": "86400" + }, + { + "t": "86400", + "fee_q64": "16902937523957200381", + "pct": "91.6310079243", + "tick_magnitude": "172800" + }, + { + "t": "172800", + "fee_q64": "15503835857875547793", + "pct": "84.0464625948", + "tick_magnitude": "345600" + }, + { + "t": "604800", + "fee_q64": "10065237787609899792", + "pct": "54.5637633795", + "tick_magnitude": "1209600" + }, + { + "t": "2592000", + "fee_q64": "1379731172434914028", + "pct": "7.4795376730", + "tick_magnitude": "5184000" + } + ], + "default_one_day_max_to_0": [ + { + "t": "0", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000" + }, + { + "t": "1", + "fee_q64": "18446596500421042511", + "pct": "99.9992000036", + "tick_magnitude": "16" + }, + { + "t": "60", + "fee_q64": "18437873327830881974", + "pct": "99.9519115902", + "tick_magnitude": "962" + }, + { + "t": "3600", + "fee_q64": "17921603711721989946", + "pct": "97.1532083934", + "tick_magnitude": "57762" + }, + { + "t": "43200", + "fee_q64": "13043821263248043520", + "pct": "70.7106967556", + "tick_magnitude": "693147" + }, + { + "t": "86400", + "fee_q64": "9223372287116814725", + "pct": "50.0000013567", + "tick_magnitude": "1386295" + }, + { + "t": "172800", + "fee_q64": "4611686268689430216", + "pct": "25.0000013567", + "tick_magnitude": "2772590" + }, + { + "t": "604800", + "fee_q64": "144115215448268606", + "pct": "0.7812501484", + "tick_magnitude": "9704065" + }, + { + "t": "2592000", + "fee_q64": "17179874578", + "pct": "9.31E-8", + "tick_magnitude": "41588851" + }, + { + "t": "5529597", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "5529598", + "fee_q64": "0", + "pct": "0E-10" + } + ], + "min_rate_max_to_0": [ + { + "t": "0", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000" + }, + { + "t": "4294967295", + "fee_q64": "18446744073709551615", + "pct": "100.0000000000", + "tick_magnitude": "0" + }, + { + "t": "4294967296", + "fee_q64": "18446734850344432283", + "pct": "99.9999500000", + "tick_magnitude": "1" + }, + { + "t": "381061679028371454", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "381061679028371455", + "fee_q64": "1", + "pct": "0E-10", + "tick_magnitude": "88722835" + }, + { + "t": "381061679028371456", + "fee_q64": "0", + "pct": "0E-10" + }, + { + "t": "18446744073709551615", + "fee_q64": "0", + "pct": "0E-10" + } + ] + }, + "tick_steps": [ + { + "decayRateX32": "1073741824", + "note": "1/4 tick/s: the fee steps every 4 s", + "rows": [ + { + "t": "1", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "3", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "4", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "5", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "7", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "8", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "9", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + } + ] + }, + { + "decayRateX32": "429496729", + "note": "floor(0.1 * 2**32), slightly below one tick per 10 s: first step at 11 s", + "rows": [ + { + "t": "9", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "10", + "fee_q64": "9223372036854775808", + "pct": "50.0000000000", + "tick_magnitude": "0" + }, + { + "t": "11", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "20", + "fee_q64": "9223367452842311500", + "pct": "49.9999751500", + "tick_magnitude": "1" + }, + { + "t": "21", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "30", + "fee_q64": "9223362868832139196", + "pct": "49.9999503000", + "tick_magnitude": "2" + }, + { + "t": "31", + "fee_q64": "9223358284824258897", + "pct": "49.9999254501", + "tick_magnitude": "3" + } + ] + }, + { + "decayRateX32": "68913100933", + "note": "default: 16 whole ticks per second, 1,386,295 after one day", + "rows": [ + { + "t": "1", + "fee_q64": "9223298692930386783", + "pct": "49.9996024018", + "tick_magnitude": "16" + }, + { + "t": "2", + "fee_q64": "9223225349592746513", + "pct": "49.9992048068", + "tick_magnitude": "32" + }, + { + "t": "86400", + "fee_q64": "4639356258918185573", + "pct": "25.1500006743", + "tick_magnitude": "1386295" + } + ] + }, + { + "decayRateX32": "68913100933", + "note": "a one-unit excess is gone after the first whole tick", + "rows": [ + { + "t": "0", + "fee_q64": "55340232221128655", + "pct": "0.3000000000" + }, + { + "t": "1", + "fee_q64": "55340232221128654", + "pct": "0.3000000000", + "tick_magnitude": "16" + } + ] + } + ], + "oracle_error_bounds": { + "note": "Integer result minus the real-number F + (I - F) * 1.000001**(-(decayRateX32 / 2**32) * t / 2) at 150 digits, default 50% -> 0.3%, every second of the first hour then every 10 minutes to 30 days. The error is dominated by the whole-tick floor of m (one tick = 5e-7 of the excess), not by the helper (which is within a few units of 2**-128 of the real sqrt ratio) or the final floor.", + "max_abs_q64": "4574692217333.041", + "at_t": "244", + "max_abs_fraction_of_excess": "4.98983E-7", + "one_tick_of_excess_fraction": "5.00000E-7" + } + }, + "creator_fee_range": { + "type": "uint64, Q0.64 (2**64 = 100%)", + "min_q64": "0", + "max_q64": "18446744073709551615", + "max_fraction": "1 - 2**-64: just under 100%, not 100%", + "constant": "MAX_CREATOR_FEE = type(uint64).max (src/extensions/ZeroSeedLaunch.sol); no ceiling comparison in _validate", + "schedule_rules": [ + "finalFee <= initialFee" + ], + "error": "InvalidFeeSchedule() 0xf75a271a", + "checked_in": [ + "create", + "previewInstallation", + "forwarded create payload" + ], + "unencodable": { + "value": "18446744073709551616", + "meaning": "exactly 100% = 2**64", + "verdict": "ABI decoding failure in create(parameters,logoUrl), previewInstallation and forwarded create for either fee word; no truncation", + "dirty_high_bits": { + "value": "36893488147419103231", + "note": "2**64 + (2**64 - 1)", + "verdict": "same ABI decoding failure" + } + }, + "vectors": [ + { + "initialFee": "18446744073709551615", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "verdict": "accept", + "note": "max -> 0.3% at the default one-day half-life" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551615", + "decayRateX32": "1", + "verdict": "accept", + "note": "flat at the maximum, slowest rate" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551615", + "decayRateX32": "18446744073709551615", + "verdict": "accept", + "note": "flat at the maximum, fastest rate" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "18446744073709551615", + "verdict": "accept", + "note": "max -> 0 at the fastest rate: saturated to 0 from t = 1" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "0", + "decayRateX32": "1", + "verdict": "accept", + "note": "max -> 0 at the slowest rate: one raw unit at t = MAX_DECAY_PRODUCT (minimum tick residual), 0 from t = MAX_DECAY_PRODUCT + 1" + }, + { + "initialFee": "0", + "finalFee": "0", + "decayRateX32": "68913100933", + "verdict": "accept", + "note": "zero schedule" + }, + { + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "0", + "verdict": "accept", + "note": "zero rate: flat at initialFee forever" + }, + { + "initialFee": "18446744073709551614", + "finalFee": "18446744073709551615", + "decayRateX32": "68913100933", + "verdict": "InvalidFeeSchedule", + "note": "final above initial at the top of the range" + }, + { + "initialFee": "0", + "finalFee": "1", + "decayRateX32": "68913100933", + "verdict": "InvalidFeeSchedule" + }, + { + "initialFee": "922337203685477580", + "finalFee": "922337203685477581", + "decayRateX32": "0", + "verdict": "InvalidFeeSchedule", + "note": "shape is checked even at a zero rate" + }, + { + "initialFee": "18446744073709551616", + "finalFee": "18446744073709551615", + "decayRateX32": "68913100933", + "verdict": "abi_decoding_failure", + "note": "2**64 in initialFee" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "18446744073709551616", + "decayRateX32": "68913100933", + "verdict": "abi_decoding_failure", + "note": "2**64 in finalFee" + }, + { + "initialFee": "18446744073709551615", + "finalFee": "55340232221128654", + "decayRateX32": "18446744073709551616", + "verdict": "abi_decoding_failure", + "note": "2**64 in decayRateX32" + } + ], + "reference_schedules": { + "fixture": { + "initialFee": "922337203685477580", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "note": "5% -> 0.3% at the default one-day half-life: this fixture's local reference parameters" + }, + "consumer_default": { + "initialFee": "9223372036854775808", + "finalFee": "55340232221128654", + "decayRateX32": "68913100933", + "half_life_s": "86400.0000010436", + "fee_after_one_day_q64": "4639356258918185573", + "fee_after_one_day_pct": "25.1500006743", + "note": "50% -> 0.3% with a one-day half-life of the excess: the consumer-side default; a creation parameter choice, not a contract constant. About 25.15% after one day, 12.73% after two, 0.69% after seven; the floor is approached, not scheduled." + } + }, + "decay_rate_rules": [ + "any uint64 decayRateX32 is accepted, including 0 (flat at initialFee) and 2**64 - 1 (finalFee from the first second)" + ] + }, + "max_rate": { + "note": "Launches created through ZeroSeedLaunchPeriphery.create at initialFee = 2**64 - 1 (no storage injection), finalFee = 0.3%, default one-day half-life rate 68913100933, traded at tradingStart. Exact input: net = floor(gross / 2**64), fee = gross - net = ceil(gross * (2**64 - 1) / 2**64). Exact output: paid = calc * 2**64, fee = paid - calc. Settled by the local test lockers against locally compiled Core; not live evidence.", + "first_buy_note": "The first whole-quote-unit buy is identical for the max -> 0.3% / default rate, max -> max / rate 1 and max -> 0 / rate 2**64 - 1 schedules (the rate at tradingStart is the initial rate in every case).", + "trades": [ + { + "rate": "max", + "order": "t0", + "dec": "18/18", + "gross": "982941", + "fee": "982941", + "net": "0", + "decimals": "18/18", + "step": "oneUnit" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/18", + "quoteIn": "1000000000000000000", + "gross": "982091443427432213915088", + "fee": "982091443427432213861849", + "net": "53239", + "decimals": "18/18", + "step": "buyExactIn" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/18", + "tokenOut": "53239", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616", + "decimals": "18/18", + "step": "buyExactOut" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/18", + "tokenIn": "106478", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0", + "roundTripLoss": "19446744073709551617", + "decimals": "18/18", + "step": "sellAll" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/6", + "gross": "984538387033333867", + "fee": "984538387033333867", + "net": "0", + "decimals": "18/6", + "step": "oneUnit" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/6", + "quoteIn": "1000000", + "gross": "983667635470798139337767", + "fee": "983667635470798139284443", + "net": "53324", + "decimals": "18/6", + "step": "buyExactIn" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/6", + "tokenOut": "53324", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616", + "decimals": "18/6", + "step": "buyExactOut" + }, + { + "rate": "max", + "order": "t0", + "dec": "18/6", + "tokenIn": "106648", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0", + "roundTripLoss": "18446744073710551617", + "decimals": "18/6", + "step": "sellAll" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/18", + "gross": "729761", + "fee": "729761", + "net": "0", + "decimals": "18/18", + "step": "oneUnit" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/18", + "quoteIn": "1000000000000000000", + "gross": "982091443427432212980275", + "fee": "982091443427432212927036", + "net": "53239", + "decimals": "18/18", + "step": "buyExactIn" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/18", + "tokenOut": "53239", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616", + "decimals": "18/18", + "step": "buyExactOut" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/18", + "tokenIn": "106478", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0", + "roundTripLoss": "19446744073709551617", + "decimals": "18/18", + "step": "sellAll" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/6", + "gross": "467398106332855075", + "fee": "467398106332855075", + "net": "0", + "decimals": "18/6", + "step": "oneUnit" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/6", + "quoteIn": "1000000", + "gross": "983666653587480924554914", + "fee": "983666653587480924501590", + "net": "53324", + "decimals": "18/6", + "step": "buyExactIn" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/6", + "tokenOut": "53324", + "calcQuote": "1", + "fee": "18446744073709551615", + "paid": "18446744073709551616", + "decimals": "18/6", + "step": "buyExactOut" + }, + { + "rate": "max", + "order": "t1", + "dec": "18/6", + "tokenIn": "106648", + "grossQuote": "0", + "fee": "0", + "quoteOut": "0", + "roundTripLoss": "18446744073710551617", + "decimals": "18/6", + "step": "sellAll" + } + ], + "trades_note": "Actual maximum-rate fills on the final geometry; oneUnit buys net zero, exact output grosses up the input; see trade rows for exact amounts", + "exact_output_bounds": { + "note": "Bisection over the requested launch-token output on the pinned fixture: the last request that executes, the first that fails the signed int128 conversion of the fee-inclusive input (calc * 2**64 >= 2**127), and the first that fails the uint128 gross-up helper (calc >= 2**64). The last executable request has calc = 2**63 - 1 exactly. Every failed probe rolls back pool state, fee ledger, position liquidity and Core balances.", + "signed_bound_calc": "9223372036854775807", + "helper_bound_calc": "18446744073709551615", + "errors": { + "signed": "SafeCastLib.Overflow() 0x35278d12", + "helper": "AmountBeforeFeeOverflow() 0x0d88f526" + }, + "vectors": [ + { + "rate": "max", + "order": "t0", + "lastValidTokenOut": "8992894608587101713726987", + "calc": "9223372036854775807", + "paid": "170141183460469231713240559642174554112", + "firstSignedOverflowTokenOut": "8992894608587101713726988", + "lastSignedOverflowTokenOut": "17841529728048229186238638", + "firstHelperOverflowTokenOut": "17841529728048229186238639", + "decimals": "18/18" + }, + { + "rate": "max", + "order": "t1", + "lastValidTokenOut": "8992894608587101713646726", + "calc": "9223372036854775807", + "paid": "170141183460469231713240559642174554112", + "firstSignedOverflowTokenOut": "8992894608587101713646727", + "lastSignedOverflowTokenOut": "17841529728048229186058949", + "firstHelperOverflowTokenOut": "17841529728048229186058950", + "decimals": "18/18" + } + ] + } + }, + "forward_schema": { + "swap": { + "payload": "abi.encode(PoolKey, SwapParameters)", + "length": 128, + "returns": "abi.encode(PoolBalanceUpdate, PoolState)", + "first_word": "PoolKey.token0 < 2**160" + }, + "create": { + "tag": "0x224a5a98239f599168b798c4205db155bfff4332b4931a7fb5bc60c2e2eeee30", + "tag_source": "keccak256(\"IZeroSeedLaunch#CREATE\")", + "payload": "abi.encode(ZERO_SEED_LAUNCH_CREATE, LaunchParameters, string logoUrl)", + "returns": "abi.encode(Installation)", + "controller": "Locker.addr() of the forwarding locker", + "lock_debt_after": "zero" + }, + "claim_fees": { + "tag": "0xe52d8a05fd0f2d32b280675736d8117fee83069a0eac7ab9279aa52ff05a614c", + "tag_source": "keccak256(\"IZeroSeedLaunch#CLAIM_FEES\")", + "payload": "abi.encode(tag, PoolKey)", + "returns": "abi.encode(uint128 amount0, uint128 amount1)", + "authorization": "launch.controller == Locker.addr()", + "lock_debt_after": "credit of (amount0, amount1) for the locker to withdraw" + }, + "unknown": "any first word >= 2**160 that is not a tag, or fewer than 32 bytes, reverts UnknownForwardAction(); a first word < 2**160 is decoded as a swap" + }, + "abi": { + "ZeroSeedLaunch": { + "selectors": { + "afterCollectFees(bytes32,(address,address,bytes32),bytes32,uint128,uint128)": "0x751fd5df", + "afterInitializePool(address,(address,address,bytes32),int32,uint96)": "0x948374ff", + "afterSwap(bytes32,(address,address,bytes32),bytes32,bytes32,bytes32)": "0xa4e8f288", + "afterUpdatePosition(bytes32,(address,address,bytes32),bytes32,int128,bytes32,bytes32)": "0x25fa4e69", + "beforeCollectFees(bytes32,(address,address,bytes32),bytes32)": "0xdf65d8d1", + "beforeInitializePool(address,(address,address,bytes32),int32)": "0x1fbbb462", + "beforeSwap(bytes32,(address,address,bytes32),bytes32)": "0xca11dba7", + "beforeUpdatePosition(bytes32,(address,address,bytes32),bytes32,int128)": "0x0035c723", + "creatorFees((address,address,bytes32))": "0xb4ce9a2e", + "feeAt(bytes32,uint256)": "0xe81084ec", + "forwarded_2374103877(bytes32)": "0x00000001", + "getLaunch(bytes32)": "0x7d1d589c", + "previewInstallation(address,(bytes32,bytes32,uint128,address,int32,int32,uint64,uint64,uint64,uint64,bytes32))": "0xd215dcb2" + }, + "errors": { + "BaseForwardeeAccountantOnly()": "0x588e1740", + "BoundsOrder()": "0x68651c5c", + "BoundsTickSpacing()": "0x89fd41b1", + "CallPointNotImplemented()": "0xc6f26286", + "ControllerOnly()": "0xac8ff76a", + "CoreOnly()": "0x62279cff", + "InitializeThroughCreateOnly()": "0x028eb443", + "InvalidFeeSchedule()": "0xf75a271a", + "InvalidMetadata()": "0xbcecb64a", + "InvalidSupply()": "0x15ae6727", + "InvalidTick(int32)": "0x073ee172", + "InvalidTicks()": "0x1434ed7f", + "InvalidTradingStart()": "0xe0839259", + "LaunchTokenExists(address)": "0xf68475cd", + "LiquidityAboveTickCap(uint128,uint128)": "0x20ea795f", + "MinMaxBounds()": "0xabfa4a31", + "PositionsThroughCreateOnly()": "0x74a99003", + "StableswapMustBeFullRange()": "0x72b1019d", + "SwapsThroughForwardOnly()": "0xf17f54b3", + "TradingNotStarted()": "0xacf75be8", + "UnknownForwardAction()": "0xe5c78d9a", + "UnknownLaunch()": "0xf6806bf8", + "UnsupportedQuoteToken()": "0x658d8331", + "ZeroLiquidity()": "0x10074548" + }, + "events": { + "CreatorFeesClaimed(bytes32,address,uint128,uint128)": "0xf0ffdfa84530baef2e9f8bbb160545a59bfef8da319fa27aa69e4f3c8f05a41d", + "LaunchCreated(bytes32,address,address,(address,address,bytes32),bytes32,uint128,(bytes32,bytes32,uint128,address,int32,int32,uint64,uint64,uint64,uint64,bytes32),string)": "0x0a54c7540832cb23832d30bbf27c5597cfb779d070a1c253a7aee90571c2da4c", + "LaunchSwapped(bytes32,address,int128,int128,uint64,uint128,bool)": "0x3e58795f52a8e88efdec2da89ea3798175f8764145ccd7bf5e40633a7d571f6d" + } + }, + "ZeroSeedLaunchPeriphery": { + "selectors": { + "LAUNCH()": "0x096e3c3e", + "claimFees((address,address,bytes32),address)": "0x6906b4c0", + "create((bytes32,bytes32,uint128,address,int32,int32,uint64,uint64,uint64,uint64,bytes32),string)": "0x9a09bd5d", + "launchOwner(bytes32)": "0x60ac0b66", + "locked_6416899205(uint256)": "0x00000000", + "previewInstallation(address,(bytes32,bytes32,uint128,address,int32,int32,uint64,uint64,uint64,uint64,bytes32))": "0xd215dcb2", + "transferLaunch(bytes32,address)": "0x8b1cb36f" + }, + "errors": { + "BaseLockerAccountantOnly()": "0x3ed2a9c0", + "ExpectedRevertWithinLock()": "0x4c816e2b", + "InvalidOwner()": "0x49e27cff", + "InvalidRecipient()": "0x9c8d2cd2", + "OwnerOnly()": "0x596dcdb8" + }, + "events": { + "LaunchFeesClaimed(bytes32,address,address,uint128,uint128)": "0x908c6b2c19765fe47aef218b4f71ea1a10a9e156d9d5c3500c3a298e01428805", + "LaunchOwnerChanged(bytes32,address,address)": "0x478004cf03e8c3b648608ce0259ce479c813aa671a383230ee7763fe5da22391" + } + }, + "LaunchToken": { + "selectors": { + "DOMAIN_SEPARATOR()": "0x3644e515", + "allowance(address,address)": "0xdd62ed3e", + "approve(address,uint256)": "0x095ea7b3", + "balanceOf(address)": "0x70a08231", + "decimals()": "0x313ce567", + "mint(address,uint128,uint128)": "0x97622870", + "name()": "0x06fdde03", + "nonces(address)": "0x7ecebe00", + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": "0xd505accf", + "symbol()": "0x95d89b41", + "totalSupply()": "0x18160ddd", + "transfer(address,uint256)": "0xa9059cbb", + "transferFrom(address,address,uint256)": "0x23b872dd" + }, + "errors": { + "AllowanceOverflow()": "0xf9067066", + "AllowanceUnderflow()": "0x8301ab38", + "InsufficientAllowance()": "0x13be252b", + "InsufficientBalance()": "0xf4d678b8", + "InvalidPermit()": "0xddafbaef", + "MintClosed()": "0x589ed34b", + "Permit2AllowanceIsFixedAtInfinity()": "0x3f68539a", + "PermitExpired()": "0x1a15a3cc", + "TotalSupplyOverflow()": "0xe5cfe957" + }, + "events": { + "Approval(address,address,uint256)": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "Transfer(address,address,uint256)": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + } + } + }, + "pins": { + "runtime_templates": { + "ZeroSeedLaunch": { + "bytes": 18366, + "keccak256": "0x707be0d4e2c25c21d6d9aafec5bb58e634118c1edade48d0dc1fd5be5de2624c" + }, + "ZeroSeedLaunchPeriphery": { + "bytes": 6123, + "keccak256": "0xe6c0d086bff1c96e4f0a2e8068022795a0c74f96d15d15cd041cbea2d2e9b529" + }, + "LaunchToken": { + "bytes": 3012, + "keccak256": "0xec01758e39962fca797fc966e3e541ac87c2c72814b9e1ab86d6193597b182b6" + } + }, + "creationCode": { + "ZeroSeedLaunch": { + "bytes": 18884, + "keccak256": "0x6ec3a947c32774eab196724fd1a98c4eb927af430dec7ed1b111be414d85dc20" + }, + "ZeroSeedLaunchPeriphery": { + "bytes": 6343, + "keccak256": "0x4698c93153732a8f4371252ca5af048a7fe5a181dbd6e26ee4e76f2c942abaf2" + }, + "LaunchToken": { + "bytes": 3172, + "keccak256": "0xb92240f5b935a76e949aab04e7e9e4a0a28adc3ff1a89a349c67dcaab13ea432" + } + } + }, + "supply": { + "min": "1", + "max": "1000000000000000000000000000000", + "type": "uint128", + "error": "InvalidSupply()" + }, + "spacing": { + "rule": "s = ceil((upperTick-askTick)/256); ask = ceil(askTick/s)*s; upper = floor(upperTick/s)*s", + "max": 693148, + "skipAhead": 1, + "reference": "zero-seed-launch-installation.json" + }, + "logo": { + "location": "second argument of create and final field of LaunchCreated", + "default": "", + "stored": false, + "in_preview_or_token_or_pool_identity": false + } +} From c05481aee2d563a72d02a7c9d13d76ec141a91f8 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sun, 25 Oct 2026 19:40:58 -0400 Subject: [PATCH 16/19] Replay the indexer's final launch chain through the launch routes replay.postgres.test.ts builds a loopback Postgres with the indexer's own migrations, processors and DAO from its 853b608 local anvil chain and checks every route against the raw logs and recorded chain state, including logos, claims counted once, owner history, list filters, a reorg and an identical second replay. It replaces the harness-splicing runner and writes the v6 golden responses. --- src/routes/launches/README.md | 338 +-- ...o-seed-launch-api-v5-tick-rate.golden.json | 1697 ----------- .../zero-seed-launch-api-v5.golden.json | 2133 -------------- .../zero-seed-launch-api-v6.golden.json | 2590 +++++++++++++++++ src/routes/launches/golden.test.ts | 286 +- src/routes/launches/replay.postgres.test.ts | 783 +++++ 6 files changed, 3539 insertions(+), 4288 deletions(-) delete mode 100644 src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json delete mode 100644 src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json create mode 100644 src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json create mode 100644 src/routes/launches/replay.postgres.test.ts diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 24a0e95..3d6090d 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,224 +1,146 @@ # Zero-seed launch reads (draft/local) -Draft response contract: `zero-seed-launch-api/v5`, for the forward-only -`ZeroSeedLaunch` and its separate `ZeroSeedLaunchPeriphery` of evm-contracts -`43d6628841b3ce91dafa048b7ceef64bed55fc8e` (ABI sha256 of `jq -cS .abi`: -ZeroSeedLaunch `efae9388aa6b1342479204ddf436fd2a310df16bf06c80c990b2d22c90d1e41e`, -periphery `df24d9f07d45323da7e46c0f6e64ce24f8f522442d60cb07880ddf742ebe7305`). -Since v5 it also serves the tick-rate revision, -`3d6378f585591dc7eabeea6976932c30524bb8ea` (ABI sha256 `89ff4d15…aadd` and -`a6d43b36…db07`). Indexer schema `zero-seed-launch-index/v4` (migration 00140, -after main's `00139_mev_capture_v21`). Live instance and quote lists are empty. - -Changes from v4: - -- `fee_schedule` is a union on `model`, the fee model of the launch's - deployment, bound to the instance pin (`LaunchInstance.feeModel`) and never - inferred from a row. List and detail SQL screen `fee_model` with the rest of - the instance, so a reader with the other model for a deployment serves none - of its launches. -- `model: "fixed_base_duration_v1"` (43d6628, the earlier revision): the v4 - schedule unchanged (`fee_duration`, `fee_end`, `decay`, phases - `before_trading_start | fee_decay | final_fee`). -- `model: "tick_rate_v2"` (3d6378f): `trading_start`, `initial_fee`, - `final_fee` (the long-run floor), `decay_rate_x32` (canonical decimal - uint64, unsigned Q32.32 ticks per second, never a JSON number), - `half_life_seconds` (display: 6 fractional digits, floored, from the exact - integer `HALF_LIFE_X32_E30 = 5954091920683119788732747343471285361579197626`; - `null` for rate 0), `saturation_elapsed_seconds` (`floor(381061679028371455 / - rate)`, after which the fee is exactly the floor by numerical saturation; - `null` for rate 0; not an end), `phase` (`before_trading_start | decaying | - at_floor | constant`), `fee_rate` (the contract's `feeAt` at the indexed block - time) and `evaluated_at`. There is no `fee_end`, `fee_duration` or `decay`. - `fee_rate = final + floor((initial - final) * toFixed(tickToSqrtRatio(-m)) / - 2^128)`, `m = floor(rate * elapsed / 2^32)`, with saturation checked first; - `policy.test.ts` checks it against the contract's frozen vectors v7 - (`vectors/zero-seed-launch-v2-vectors.json`, verbatim: fee rows, curve - tables, tick steps, saturation seconds and the five SHA-256 chains). -- Malformed indexed schedules fail as 500: an unknown model, a v2 row with a - duration or without a rate, a rate outside uint64, a v1 row with a rate. -- Golden examples: `fixtures/zero-seed-launch-api-v5.golden.json` (fixed-base - fixture, previously the v4 golden) and - `fixtures/zero-seed-launch-api-v5-tick-rate.golden.json` (tick-rate fixture: - rates 0, 1, one day, 600 s and 2^64 - 1). - -Changes from v3 (v4): - -- Every `LaunchSummary` (list and detail) has - `supply: { requested, installed, sink, sink_holder }`, decimal strings of - raw launch-token units. `requested` is `LaunchParameters.supply` as - submitted and the token's `totalSupply`; `installed` is what Core took into - the position at creation (the launch-token delta of Core's - `PositionUpdated`), all of the circulating inventory; `sink` is - `requested - installed`, minted to `sink_holder` (address 0) and never - spendable. It is not a burn: `totalSupply` stays `requested`. The API - refuses (500) indexed rows outside `0 < installed <= requested` or with - `sink != requested - installed`. -- `principal.installed_supply` is removed (use `supply.installed`); - `principal.liquidity` is the liquidity the extension computed and - installed. -- The quote is per launch (`LaunchParameters.quoteToken`): one extension and - periphery serve native ETH and ERC-20 quotes. `quote_token` is the launch's - own quote; an instance has no quote. Native ETH is address 0, always token0 - (the launch token is token1), 18 decimals, null name and symbol. An ERC-20 - quote above the launch token's address makes the launch token token0. - -Creator fees span the whole uint64 range. `initial_fee`, `final_fee` and -both `fee_rate`s are Q0.64 rates as canonical decimal strings from `0` to -`18446744073709551615` (2^64 - 1, the maximum, just under 100%; the fee -fraction is value / 2^64, never exactly 100%), never JSON numbers. The -contract checks only `final_fee <= initial_fee` and a nonzero duration; there -is no ceiling below 2^64 - 1 and no `FeeTooHigh` error. The API refuses (500) an -indexed fee above 2^64 - 1 instead of wrapping, truncating or serving it. -The response shape is unchanged, so the contract stays v4. - -At `43d6628` the extension installs the exact largest token0 liquidity the -requested supply pays for (an ERC-20 quote above the launch token), where it -previously installed less at low prices and sank the difference. The API -serves the indexed liquidity and Core's position delta and never recomputes -the installation, so nothing here changes; `sink` can now be `"0"` when the -requested supply is exactly installable (no mint to address 0 happens). - -Unchanged from v3: the fixed-base fee table (`fee_schedule.decay` is -`{ kind: "fixed_base_q128", half_life_seconds: 600 }` and `fee_rate` the -contract's `feeAt` at the indexed block time, checked in `policy.test.ts` -against all 25,212 rows of the protocol's independent all-second vectors), -`creator` (owner at creation, never changes) versus `owner` (current, -transferable holder of unclaimed and future fees) and `controller` (the -periphery), periphery claim payouts linked by `ledger_event_id` and counted -once, and the `/owners` history and `owner`/`creator` list filters. - -`fixtures/zero-seed-launch-api-v5.golden.json` is a machine-readable example of -every response, written by the actual handlers over the replayed local fixture: -a native-ETH (launch token1, sink 578) and two ERC-20 (launch token0, sink 790) -launches on one extension, ownership transfers, claims, swaps and a reorg, -plus two launches created at the maximum rate: `maxNativeDetail` (native, -2^64 - 1 decaying to 0.3% over 3600 s) and `maxErc20Detail` (ERC-20, flat at -2^64 - 1), with their exact-input, exact-output and decayed swaps and claims, -and two ERC-20 launches at low-price token0 geometry: `lowPriceErc20Detail` -(1e30 over ticks -88080384..-85983232 at spacing 524288; liquidity -115073586432, sink 8589236567713153558) and `fullWidthErc20Detail` (an -int128-scale supply over the full width at spacing 1 that installs all of it, -sink `"0"`). +Response contract `zero-seed-launch-api/v6`, for the final `ZeroSeedLaunch` +and `ZeroSeedLaunchPeriphery` of the evm-contracts source freeze +`853b608ec26d4c80bdad6e394cb8ba686e1ae615` (compiled ABI file sha256 +`6a131c2d…56eb` and `5d05426f…c839`). It reads the indexer's +`zero_seed_launch_*` tables of migration `00140_zero_seed_launch` (final-only). +No deployment of the final contract exists, so the hosted instance and quote +lists are empty and every launch route answers 404 when hosted. + +## Changes from v5 + +- Final model only. The fixed-base (`fixed_base_duration_v1`) and earlier + tick-rate drafts, the `fee_schedule.model` discriminator, their pins, + vectors and goldens are removed. There is one fee curve: the shifted + exponential of `decayRateX32` toward the `final_fee` floor. +- New on every launch: `logo_url`, `parameters` (every `LaunchParameters` + field: numbers as exact decimal strings, words as 32-byte hex), `range` + (derived `tick_spacing`, `aligned_ask_tick`, `aligned_upper_tick`, pool + ticks), `trading_start`, `status`, `market`. `token.name`/`symbol` are + decoded from the packed words and `token.decimals` is always 18. + Top-level `ask_tick`/`upper_tick` moved into `parameters` as strings. +- Removed: `inventory` (see `status` and `market`), `principal.tick_*`, + `principal.reserves_basis`, `fee_ledger.transferable`. +- Claims are one row per claim keyed on the extension's `CreatorFeesClaimed` + (`event`), with the periphery `LaunchFeesClaimed` of the same transaction + as `payout_event_id`, `owner` and `recipient`; the page has `totals` + (claimed per asset, from the ledger). `ledger_event_id` is gone. +- The list spans chains (`chainId` optional), filters `status`, `owner` + (current) and `creator` (original), orders by `market_cap` (default) or + `created`, pages by `offset`, and reports `total`, `totals`, + `next_offset`, `evaluated_at` and one head per chain (`heads`). + +## Shapes + +`LaunchSummary` (list item; `LaunchDetail` adds `principal` and `fee_ledger`): + +```text +schema_version "zero-seed-launch-api/v6" +chain_id decimal string +pool_id 0x + 64 hex +pool_key { core_address, token0, token1, config: bytes32, fee: "0", tick_spacing: int, extension } +token { address, name: string|null, symbol: string|null, decimals: 18 } +quote_token { address, name|null, symbol|null, decimals: int|null, kind: "native"|"erc20" } +launch_token_is_token1 bool +controller address (the periphery) +creator address (owner at creation) +owner address (current owner; holds unclaimed and future fees) +logo_url string|null (null when "" or not valid UTF-8; otherwise exactly as emitted) +parameters { name: bytes32, symbol: bytes32, supply, quote_token: address, ask_tick, upper_tick, + trading_start, decay_rate_x32, initial_fee, final_fee: decimal strings, salt: bytes32 } +range { tick_spacing, aligned_ask_tick, aligned_upper_tick, pool_tick_lower, pool_tick_upper: int } +supply { requested, installed, sink: decimal strings, sink_holder: 0x0…0 } +trading_start int (unix seconds) +status "upcoming" (evaluated_at < trading_start) | "trading" +fee_schedule { trading_start: int, initial_fee, final_fee, decay_rate_x32: decimal strings, + half_life_seconds: "s.uuuuuu"|null, saturation_elapsed_seconds: string|null, + phase: before_trading_start|decaying|at_floor|constant, fee_rate: decimal string, + evaluated_at: int } +market { price_tick: int, price_x128, market_cap: decimal strings, market_cap_usd: string|null, + raised, sold: decimal strings, sold_share: "d.dddddddddddddddddd", trade_count: int } +created { block_number, block_hash, block_time, event_id, transaction_index, event_index, transaction_hash } +as_of { block_number, block_hash, block_time } (the launch chain's indexed head) +principal { position_id, liquidity, locked: true, withdrawable: false, + pool_reserves: { launch_token, quote_token } } +fee_ledger { holder, launch_token: { accrued, claimed, unclaimed }, quote_token: {…}, + swap_count, claim_count, owner_change_count: int, last_event_id } +``` + +`LaunchList`: `{ schema_version, data: LaunchSummary[], total, totals: { all, +trading, upcoming }, next_offset: int|null, evaluated_at: int, heads: [{ +chain_id, block_number, block_hash, block_time }] }`. `total` counts every +filter; `totals` counts the non-status filters by status. + +Market: `price_x128` is raw quote per raw launch token times 2^128, floored, +from Core's sqrt ratio; `market_cap = floor(price * totalSupply)` in raw quote +units (the sink is not a burn and is included); `market_cap_usd` uses the +indexed quote price (6 decimals) and is null without one; `raised` is the +quote the pool holds; `sold = installed - launch tokens in the pool`; +`trade_count` counts `LaunchSwapped`, including zero fills. + +Status and `fee_rate` are evaluated at the request's wall-clock second +(`evaluated_at`); `as_of`/`heads` are the indexed heads. Fee math: +`fee = final + floor((initial - final) * toFixed(tickToSqrtRatio(-m)) / 2^128)`, +`m = floor(rate * elapsed / 2^32)`, initial before and at the start and for +rate 0, `final` once `elapsed > 381061679028371455 / rate`; checked against the +freeze's fee rows, curve tables, tick steps, saturation seconds and SHA-256 +chains (`policy.test.ts`). ## Requests -- `GET /launches?chainId=31337&limit=50&before_event_id=...&creator=0x...&owner=0x...` +- `GET /launches?chainId=&status=all|trading|upcoming&owner=&creator=&order_by=market_cap|created&offset=0&limit=50` - `GET /launches/{chainId}/{poolId}` -- `GET /launches/{chainId}/{poolId}/swaps?limit=50&after_event_id=...` -- `GET /launches/{chainId}/{poolId}/claims?limit=50&after_event_id=...` -- `GET /launches/{chainId}/{poolId}/owners?limit=50&after_event_id=...` - -One chain per list request gives an unambiguous indexed `as_of`. Pool IDs are -32-byte hex. Cursors are signed decimal event IDs: newest-first creations use -`before_event_id`; oldest-first swaps/claims/owners use `after_event_id`. -Limits are 1–200. A non-null `next_cursor` means another row was observed in -that snapshot. Cursor continuation is not a frozen historical snapshot: reorgs -may change pages. +- `GET /launches/{chainId}/{poolId}/swaps?limit=50&after_event_id=…` +- `GET /launches/{chainId}/{poolId}/claims?limit=50&after_event_id=…` +- `GET /launches/{chainId}/{poolId}/owners?limit=50&after_event_id=…` -No stats, lifecycle status, staged funding, advances, terminal migration, user -counts or connected-wallet analytics exist. Forwarding lockers are contracts, -not traders. Fee phases are schedule labels, not completion/exhaustion states. +Limits are 1–200, offsets 0–100000. Histories are oldest first with signed +decimal event-id cursors. Validation errors are 400 and name the parameter +(`Invalid query: status: …`). Missing/unsupported launches are 404; a missing, +mismatched or stale head of a chain in scope is 503 naming the chain; +malformed indexed rows (non-canonical packed name, inconsistent range or +supply, fee outside uint64, claim without its payout) fail as 500. ## Supported instances and consistency -All hosted query delegates construct a reader with **no enabled instances**. -They refuse launch reads with 404 without querying Postgres; missing database -configuration also returns 404. There is no environment-binding enable switch. -Only the explicit local test harness can pass the full reviewed instance -manifest and quote list. Extension and periphery addresses and runtimes, Core -and both ABIs must match an instance together. Quote support is per launch: -native ETH is always eligible; an ERC-20 quote only if it is in the reviewed -quote list by chain, address and runtime codehash. The local list holds only -the fixture's plain test ERC-20 (evm-contracts `test/TestToken.sol`, -relocated to `0xff…ff`); the hosted list is empty, so no ERC-20 quote launch -can be served there even if launch reads were enabled. - -List SQL screens the extension, controller (periphery), Core, the launch's own -quote and kind against the supported quote list, ordered pair, zero Core fee -and spacing before pagination, so an unsupported quote never occupies a page. -Detail and all histories use that same selection: an unsupported launch is 404. -Missing/unsupported launches return 404. A missing, mismatched or stale cursor -head returns 503. Parameters return 400; malformed internal indexed output -fails as 500, never a success or client-validation fallback. - -Each read uses one read-only repeatable-read transaction for the coherent head, -configuration, current Core balance, creator ledger and event page. Responses -are `no-store`. Local test freshness is 60 seconds from cursor `last_updated`; -this is not an approved live/hosted parameter. Fee rates and phases depend only -on the indexed block timestamp. HTTP does not accept a wall-clock fee override. - -Principal is the immutable installed liquidity and Core event-accounted pool -reserves (including rounding dust). Creator accrued/claimed/unclaimed ledgers -are separate, per asset. Upper-bound leftover token residue and Core unowned -token/quote dust are different quantities; no shared <=90 bound or fabricated -claimable-funds estimate is exposed. Both assets are claimed atomically in full. - -The installed supply is not emitted by `LaunchCreated`; the indexer takes it -from Core's position update of the creation and refuses a creation without -exactly one consistent update. Build/expiry times are not emitted onchain and -are not served. Producer/owner surfaces separately bind a preview's -requested supply and computed installation, 31 UTF-8 byte metadata, all -calldata and start/expiry constraints; this read API does not. - -## Local verification and stop/reset - -Run `bun run format:check`, `bun run lint`, `bun run typecheck` and -`bun test` (or `bun test src/routes/launches src/responseSchemas.test.ts -src/openapiSnapshot.test.ts` for the launch routes). `bun run openapi` -regenerates `openapi.json`. - -For actual emitted-state/Postgres/handler replay, provide the exact pinned -indexer checkout (EkuboProtocol/indexer -`7f373e59ca79eadb5d7ce47a29eb7f02decf31da`, fixtures -`tests/fixtures/zero-seed-launch/local-chain.json` sha256 -`ec308953b8412e3ad76e7b6bf74b1e3ae56f6cbe0798032dbc6673bfedfae7d9` and -`local-chain-v2.json` sha256 -`52377ccd26930d893aa656d7835375536c7e13375faa437d1b8d220f1b1d1d2f`), a -disposable loopback Postgres 18 and a scratch directory: +Hosted readers enable no instance and no ERC-20 quote. Only the explicit local +harness may pass the reviewed local manifest (`LOCAL_INSTANCES`, the +indexer fixture's instance, and `LOCAL_QUOTES`, its relocated test ERC-20). +SQL screens chain, extension, periphery controller, Core, ordered pair, zero +fee, spacing and the launch's own quote before ordering and paging. Each read +is one read-only repeatable-read transaction. Responses are `no-store`. +Logos and names are untrusted creator input: they are never fetched, +rewritten or sanitized here; clients must escape them and must not fetch +logo URLs unsafely. + +## Local verification + +`bun run format:check`, `bun run lint`, `bun run typecheck`, `bun test`. +`bun run openapi` regenerates `openapi.json`. + +`replay.postgres.test.ts` populates a disposable loopback Postgres through the +indexer's own migrations, stream grouping, processors and DAO from its local +anvil chain (`tests/fixtures/zero-seed-launch/local-chain.json`, sha256 +pinned in the test) and reads every route through the app: ```sh -docker run -d --rm --name launch-replay-pg -p 127.0.0.1:55453:5432 \ - --tmpfs /var/lib/postgresql:rw \ - -e POSTGRES_USER=fixture -e POSTGRES_PASSWORD=fixture postgres:18 -ZERO_SEED_INDEXER_WORKTREE=/path/to/pinned/indexer \ -TEST_PG_CONNECTION_STRING=postgres://fixture:fixture@127.0.0.1:55453/postgres \ -LAUNCH_REPLAY_SCRATCH_DIR=/path/to/scratch \ -LAUNCH_GOLDEN_DIR=/path/to/scratch \ -bun scripts/verifyLaunchReplay.ts -docker stop launch-replay-pg +docker run -d --rm --name launch-api-pg -p 127.0.0.1:55491:5432 \ + -e POSTGRES_PASSWORD=postgres postgres:18 +ZERO_SEED_INDEXER_WORKTREE=/path/to/indexer \ +TEST_PG_CONNECTION_STRING=postgres://postgres:postgres@127.0.0.1:55491/postgres \ +bun test src/routes/launches/replay.postgres.test.ts ``` -The runner runs both fee models' harnesses. With `LAUNCH_GOLDEN_DIR` set, it -writes `actual-handler-golden.json` and `actual-handler-golden-tick-rate.json`; -copy them to `fixtures/zero-seed-launch-api-v5.golden.json` and -`fixtures/zero-seed-launch-api-v5-tick-rate.golden.json` and run +It checks after every block each launch's parameters and packed metadata +against the raw `LaunchCreated` log, logo, range, supply, ledger, owner, +creator, controller and `feeAt` against the chain state; claims once per +extension claim (counts and sums equal both the extension and the periphery +logs); owner history and paging; list filters, totals, offsets, market-cap +order and quote screening; a reorg; and that a second straight replay and a +redelivery serve identical bodies. With `LAUNCH_GOLDEN_DIR` set it writes +`zero-seed-launch-api-v6.golden.json`; copy it to `fixtures/` and run `bun run format`. -The runner checks source/fixture pins, creates a scratch copy of the reviewed -indexer test harness, appends API tests (`scripts/launchReplayCases.txt`), -and executes actual stream/DAO/SQL replay without editing the indexer -checkout. The API cases check every launch's supply against the chain -(`totalSupply`, `balanceOf(address(0))`, Core's position delta), that -ERC-20 quote launches are 404 and absent from lists when their quote is not -in the reviewed list (empty, wrong runtime or wrong chain) while the native -launches are served, that the local instance and quote pins equal the -fixture's, that the maximum-rate launches serve 2^64 - 1 exactly (schedule, -swap rates, exact-input net floor(gross / 2^64), exact-output paid -calculated * 2^64, ledgers), and that a malformed indexed supply or an indexed -fee of 2^64 fails as 500. Test-only query-method spies wire -the actual HTTP handlers to real local SQL with the reviewed local manifest; -they do not stub response rows. All temporary databases are dropped by the -harness. No RPC, fork URL, provider/secret read or production migration is used. - -Safe stop is launch support OFF. A pre-issuance source rollback to the PR base -without launch routes is safe; the old staged, constructor-quote or -arbitrary-quote drafts are not. Reset only the dedicated disposable local test databases/container. -Application rollback cannot refund, withdraw, rebalance or migrate immutable -principal. No merge/deploy/live issuance or authority change is authorized. - -Live provenance/registration/state, production query performance/capacity/costs, -served artifacts, mined UserOps, ERC-20 quote metadata (not indexed for the -local quote, so its name, symbol and decimals are null), downstream -whole-stack QA/Sage, external audit and legal/provider/wallet/board/release -controls remain NOT PROVEN/HOLD. +Safe stop is launch support off. No deployment, live database, merge or +release is authorized by this draft. diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json deleted file mode 100644 index 52cc43a..0000000 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v5-tick-rate.golden.json +++ /dev/null @@ -1,1697 +0,0 @@ -{ - "schema_version": "zero-seed-launch-api/v5", - "source": { - "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain-v2.json", - "chain_id": "31337", - "fee_model": "tick_rate_v2" - }, - "list": { - "schema_version": "zero-seed-launch-api/v5", - "data": [ - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x29595065df87855bacc2f4179d99eeb807c399ab331ca954aaacef214b58e39e", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x3c77e927fa2c58c5a0db186896b1e189e9ed7c19", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000001", - "fee": "0", - "tick_spacing": 1, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x3c77e927fa2c58c5a0db186896b1e189e9ed7c19", - "name": "Zero Seed l8", - "symbol": "L8", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "170141183460469231694794709727778658247", - "installed": "170141183460469231694794709727778658247", - "sink": "0", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -88722835, - "upper_tick": 88722835, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800005200, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay_rate_x32": "68913100933", - "half_life_seconds": "86400.000001", - "saturation_elapsed_seconds": "5529597", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0xa36617d0b2108090723abd4a735681fcc39fe2f5fb9ff51deda8d47acd5142aa", - "block_time": 1800005100, - "event_id": "-9223371968135233522", - "transaction_index": 1, - "event_index": 13, - "transaction_hash": "0xae4a7c761089548fce3b1f4d0d81a6dc2e0054afa06a6f86df1705a8be3b6170" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x78584afa19c71aadb11a78c8c7c7c5176868f6f8384b6fd75d6bc1c28ca98ee7", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xe51554275bef011ccb7648b1a0b5fc080bd78915", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080080000", - "fee": "0", - "tick_spacing": 524288, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0xe51554275bef011ccb7648b1a0b5fc080bd78915", - "name": "Zero Seed l7", - "symbol": "L7", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000000", - "installed": "999999999991410763432286846442", - "sink": "8589236567713153558", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -88080384, - "upper_tick": -85983232, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800005200, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay_rate_x32": "68913100933", - "half_life_seconds": "86400.000001", - "saturation_elapsed_seconds": "5529597", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0xa36617d0b2108090723abd4a735681fcc39fe2f5fb9ff51deda8d47acd5142aa", - "block_time": 1800005100, - "event_id": "-9223371968135299065", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0xb2e095b629cac40fce28c5062e93df91a506b6374816e7e84d2f70c3de7d6d72" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", - "name": "Zero Seed l6", - "symbol": "L6", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "18446744073709551615", - "final_fee": "18446744073709551615", - "decay_rate_x32": "1", - "half_life_seconds": "5954091920683119.788732", - "saturation_elapsed_seconds": "381061679028371455", - "phase": "at_floor", - "fee_rate": "18446744073709551615", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494775266", - "transaction_index": 4, - "event_index": 29, - "transaction_hash": "0xf1b11401ab1b29b9ea159a52ee41d4596615bf8c0b36378ea4bfcdd9dda04be3" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", - "name": "Zero Seed l5", - "symbol": "L5", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "18446744073709551615", - "final_fee": "55340232221128654", - "decay_rate_x32": "18446744073709551615", - "half_life_seconds": "0.000322", - "saturation_elapsed_seconds": "0", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494840810", - "transaction_index": 3, - "event_index": 21, - "transaction_hash": "0xfb8a73d444fe116725219fdf0baa4189238f39591f79bbcc294509fafe1304a2" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x77cd1963269215c42f374efcf48022b890dfc5fc", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x77cd1963269215c42f374efcf48022b890dfc5fc", - "name": "Zero Seed l4", - "symbol": "L4", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay_rate_x32": "0", - "half_life_seconds": null, - "saturation_elapsed_seconds": null, - "phase": "constant", - "fee_rate": "922337203685477580", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494906354", - "transaction_index": 2, - "event_index": 13, - "transaction_hash": "0x93a1efb9f3c33ae3481826d1c4c0d3f553fdbf1b06b9b9e5322ee728c30e6ab5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", - "name": "Zero Seed l2", - "symbol": "L2", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001000, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay_rate_x32": "68913100933", - "half_life_seconds": "86400.000001", - "saturation_elapsed_seconds": "5529597", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "3", - "block_hash": "0xbebd0fe88adcee7372dc5d0419b91c2ad80857d487d2e7df40d6c116368aedd0", - "block_time": 1800000020, - "event_id": "-9223372023969873913", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x257e44a4017f805ed4d37d0eefd62524566bea81c795639a05ece3aca4eccca5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", - "name": "Zero Seed l1", - "symbol": "L1", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001000, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay_rate_x32": "9923486534471", - "half_life_seconds": "600.000000", - "saturation_elapsed_seconds": "38399", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "2", - "block_hash": "0x8c5adbc2d0283c6baf44f585cb424483f85690a15c71d68ac3b5627947c0e068", - "block_time": 1800000010, - "event_id": "-9223372028264710136", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x8ed8c19664e464cbea5f81952f478d29b0f5023f93c05f032a3161c313c164ae" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - } - ], - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - } - }, - "l1": { - "detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x74ef529d73da3acdd7190dd42dfd9c4083777d8c", - "name": "Zero Seed l1", - "symbol": "L1", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001000, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay_rate_x32": "9923486534471", - "half_life_seconds": "600.000000", - "saturation_elapsed_seconds": "38399", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "2", - "block_hash": "0x8c5adbc2d0283c6baf44f585cb424483f85690a15c71d68ac3b5627947c0e068", - "block_time": 1800000010, - "event_id": "-9223372028264710136", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x8ed8c19664e464cbea5f81952f478d29b0f5023f93c05f032a3161c313c164ae" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", - "tick_lower": 9210300, - "tick_upper": 13815500, - "liquidity": "1111119116089565312898036", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "984239888128788178301567600", - "quote_token": "15987150838889926474" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "transferable": true, - "launch_token": { - "accrued": "1476890214302222101466582", - "claimed": "1476890214302222101466582", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "9323380801075558014", - "claimed": "9323380801075558014", - "unclaimed": "0" - }, - "swap_count": 10, - "claim_count": 4, - "owner_change_count": 3, - "last_event_id": "-9223371959545233400" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13786928 - } - }, - "swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "5", - "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", - "block_time": 1800001000, - "event_id": "-9223372015379939327", - "transaction_index": 0, - "event_index": 0, - "transaction_hash": "0xb740e721a93924ade13abaa5c1bd9fe4874b5f8fe97320b39368683b64276718" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "zero_fill", - "launch_token_delta": "0", - "quote_token_delta": "0", - "fee_rate": "9223372036854775808", - "fee_amount": "0", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "5", - "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", - "block_time": 1800001000, - "event_id": "-9223372015379873789", - "transaction_index": 1, - "event_index": 2, - "transaction_hash": "0x07377cf406760f8d87ef013be1c43bc5744edb633f2347c0264bfe334c63cf13" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-499541686868404598624947", - "quote_token_delta": "1000000000000000000", - "fee_rate": "9223372036854775808", - "fee_amount": "499541686868404598624948", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", - "block_time": 1800001060, - "event_id": "-9223372011084972030", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x33c300d33e2a9c7689025d6e365f0107920b78fe881091a86596dbcc646de604" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000000000000000000", - "quote_token_delta": "18956647379605107490", - "fee_rate": "8609418535131466691", - "fee_amount": "8847399338429899765", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", - "block_time": 1800001060, - "event_id": "-9223372011084906491", - "transaction_index": 1, - "event_index": 4, - "transaction_hash": "0xf48a29f8f803a7c9e1786654b21208e3cd008e713ccde7c84c26097c3cb6d113" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "1000000000000000000000000", - "quote_token_delta": "-543514932567036288", - "fee_rate": "8609418535131466691", - "fee_amount": "475672734038619372", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002495037438", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x66d6057deddc325eb595f0f7ce06b1b41ad7f4f7dcab36cc66a81f1874cdfd72" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-1273636192113237800407828", - "quote_token_delta": "2000000000000000000", - "fee_rate": "6463658066361875532", - "fee_amount": "686997392968332076936929", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "11", - "block_hash": "0xea823a1624c421642c343588291bd967f268db38e038f0809c0f8705b1983ca1", - "block_time": 1800001756, - "event_id": "-9223371989610135550", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x163513f8082eda07081632f98c943eafc988bb96237f730011cdbdfb171d083e" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-771875785591178897799846", - "quote_token_delta": "1000000000000000000", - "fee_rate": "3883398005406982927", - "fee_amount": "205825012475042142878670", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "12", - "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", - "block_time": 1800002800, - "event_id": "-9223371985315168254", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x1540407af5aac9d170164a1d32a5cd5a59225d90db43f39495e03494fd4cfd55" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-912403406016545423674081", - "quote_token_delta": "1000000000000000000", - "fee_rate": "1201344301085512086", - "fee_amount": "63559595402884915790647", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "13", - "block_hash": "0x76483275a4dfbc11e23bca9aa51c92a1e3ea83bd33c6b59c2b873ab1fb56bdb5", - "block_time": 1800004599, - "event_id": "-9223371981020200958", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0xf8e1689d52a97cc7cb223992d412dfeb115e02d46c4b9096d34671b269739926" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-963732892605571480403853", - "quote_token_delta": "1000000000000000000", - "fee_rate": "198756374052614459", - "fee_amount": "10496941276057644149158", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "14", - "block_hash": "0x50feaeec18c38bc28549e91998d45490e0c9d8bfd16f22cc21fc689741814179", - "block_time": 1800004600, - "event_id": "-9223371976725233662", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x26203ced636676caa5cd62a3b898a27db36f8c0807d2544f4e97559b45ba16fc" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-962031693714661396054685", - "quote_token_delta": "1000000000000000000", - "fee_rate": "198590752489824724", - "fee_amount": "10469585311500723086230", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000, - "event_id": "-9223371959545364478", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0xd43bd9849a86b3449b90992ef3804c2b2f4fa5ab543a3a0d03903656fbd25e7f" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "100000000000000000000000", - "quote_token_delta": "-102600807072586714", - "fee_rate": "55340232221128654", - "fee_amount": "308728607038877", - "fee_asset": "quote_token" - } - ] - }, - "claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x9c44a83d1d3fc23fea333998060e0077740ede07333bf7efa8b837f0b68e69f3", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "6", - "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", - "block_time": 1800001060, - "event_id": "-9223372011084775411", - "transaction_index": 3, - "event_index": 12, - "transaction_hash": "0x75bd7b0541a3fc2796be5b4c5ee7c72a464df2acb042a159708f93dd0d4dd979" - }, - "ledger_event_id": "-9223372011084775413", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "499541686868404598624948", - "quote_token_amount": "9323072072468519137" - }, - { - "event": { - "block_number": "12", - "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", - "block_time": 1800002800, - "event_id": "-9223371985315037174", - "transaction_index": 2, - "event_index": 9, - "transaction_hash": "0xb7d006f1252f06680349d35b6185d8f8973ed0917c298bdab48f881fcbc504c8" - }, - "ledger_event_id": "-9223371985315037176", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "956382000846259135606246", - "quote_token_amount": "0" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000, - "event_id": "-9223371959545298938", - "transaction_index": 1, - "event_index": 5, - "transaction_hash": "0x853e87c2ec6e66032bf916d5f786b1339b40cc83149a5b5623bfa658ddf75911" - }, - "ledger_event_id": "-9223371959545298940", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "20966526587558367235388", - "quote_token_amount": "308728607038877" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000, - "event_id": "-9223371959545233400", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x40d82cfe0b5597f9470552c2759a6b9b0068eda77293b4ebfd54fdbf4e0664f1" - }, - "ledger_event_id": "-9223371959545233401", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "0", - "quote_token_amount": "0" - } - ] - } - }, - "l2": { - "detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0xa2dd7bc8d58bb4edbcc063eb2f6d7ae504aa2a45", - "name": "Zero Seed l2", - "symbol": "L2", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001000, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay_rate_x32": "68913100933", - "half_life_seconds": "86400.000001", - "saturation_elapsed_seconds": "5529597", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "3", - "block_hash": "0xbebd0fe88adcee7372dc5d0419b91c2ad80857d487d2e7df40d6c116368aedd0", - "block_time": 1800000020, - "event_id": "-9223372023969873913", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x257e44a4017f805ed4d37d0eefd62524566bea81c795639a05ece3aca4eccca5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "991072549435525463122574599", - "quote_token": "8999918293703352734" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "transferable": true, - "launch_token": { - "accrued": "395582709290336348407848", - "claimed": "248368656637862963393881", - "unclaimed": "147214052652473385013967" - }, - "quote_token": { - "accrued": "52679910194913302", - "claimed": "52679910194913302", - "unclaimed": "0" - }, - "swap_count": 4, - "claim_count": 1, - "owner_change_count": 2, - "last_event_id": "-9223371993905037306" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13799366 - } - }, - "swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "5", - "block_hash": "0xab48c0529e581e2e49afe5979d5787d5abb28d3642e85081c363d43228f31fbe", - "block_time": 1800001000, - "event_id": "-9223372015379808250", - "transaction_index": 2, - "event_index": 5, - "transaction_hash": "0x17bceb9d20d5b0f29fa26b7d9870da1554bd9bec488f54f9cf227b75918f7a66" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-1000000000000000000000000", - "quote_token_delta": "1053598203898266036", - "fee_rate": "922337203685477580", - "fee_amount": "52679910194913302", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0xfd096b56498d8cb44ab33f9c88b4152e9f9307a91b32207f6a1bf4dcb7179718", - "block_time": 1800001060, - "event_id": "-9223372011084840952", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x5f23c76ea0a42880df580b44252b368631b183316cc8b905f38de0fdd56d4a6e" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-4720262100638779571525175", - "quote_token_delta": "5000000000000000000", - "fee_rate": "921920278629180106", - "fee_amount": "248316639408682001285794", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "7", - "block_hash": "0xad431b2a1b6c312c0db23e80576cc2f11c20d7283f8f4cadb3e67e27646e1c04", - "block_time": 1800001120, - "event_id": "-9223372006789939197", - "transaction_index": 1, - "event_index": 2, - "transaction_hash": "0x8c9a128abee97b9164113587ee6c7cb46ab9681a5249f74c495690f25efec3a9" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "1041286233626528999764", - "quote_token_delta": "-1000000000000000", - "fee_rate": "921503120984176012", - "fee_amount": "52017229180962108087", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "10", - "block_hash": "0x89c2b4ce768a54f7579e51e8c43b4974ab38c838c5bfa7fe0a10fb64a8d85ebd", - "block_time": 1800001700, - "event_id": "-9223371993905037306", - "transaction_index": 1, - "event_index": 5, - "transaction_hash": "0x6ed38440160ae3559e16c8fe1257b67a374d99dd9def326eac3869ce764a2d29" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-2812647040779047486491352", - "quote_token_delta": "3000000000000000000", - "fee_rate": "917482228933054387", - "fee_amount": "147214052652473385013967", - "fee_asset": "launch_token" - } - ] - }, - "claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa716f882522a4765cd89450f76b60b9572e3ff3f6f1fcc079863b633af260534", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494971897", - "transaction_index": 1, - "event_index": 6, - "transaction_hash": "0x70d19d941019367fa98fb0b82c2eba90a04139aab1300b78f2e0ad2f825bac4e" - }, - "ledger_event_id": "-9223372002494971900", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "248368656637862963393881", - "quote_token_amount": "52679910194913302" - } - ] - } - }, - "l4": { - "detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x77cd1963269215c42f374efcf48022b890dfc5fc", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x77cd1963269215c42f374efcf48022b890dfc5fc", - "name": "Zero Seed l4", - "symbol": "L4", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay_rate_x32": "0", - "half_life_seconds": null, - "saturation_elapsed_seconds": null, - "phase": "constant", - "fee_rate": "922337203685477580", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494906354", - "transaction_index": 2, - "event_index": 13, - "transaction_hash": "0x93a1efb9f3c33ae3481826d1c4c0d3f553fdbf1b06b9b9e5322ee728c30e6ab5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "992953944708816951043107488", - "quote_token": "7091146575104629573" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "transferable": true, - "launch_token": { - "accrued": "347802764559152447542916", - "claimed": "347802764559152447542916", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "5836673075303073", - "claimed": "5836673075303073", - "unclaimed": "0" - }, - "swap_count": 4, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371976725168121" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13802777 - } - }, - "swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998200004606", - "transaction_index": 1, - "event_index": 1, - "transaction_hash": "0x908c6b2456b4a9205e02ece20f19d1863025c94ce19601de8a0aafb31e9fff7a" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "zero_fill", - "launch_token_delta": "0", - "quote_token_delta": "0", - "fee_rate": "922337203685477580", - "fee_amount": "0", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998199939068", - "transaction_index": 2, - "event_index": 3, - "transaction_hash": "0x93b6871f568324d65df1f8d158464a6f11f08fda81319a9bb5124a7b8ee1365a" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-6608252526623896509348806", - "quote_token_delta": "7000000000000000000", - "fee_rate": "922337203685477580", - "fee_amount": "347802764559152447542916", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "10", - "block_hash": "0x89c2b4ce768a54f7579e51e8c43b4974ab38c838c5bfa7fe0a10fb64a8d85ebd", - "block_time": 1800001700, - "event_id": "-9223371993905102846", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x82420f8ee003d082329376306c0cd7b232796347e390864165635526a77d6cea" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-100000000000000000000000", - "quote_token_delta": "106605146979841542", - "fee_rate": "922337203685477580", - "fee_amount": "5330257348992078", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "12", - "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", - "block_time": 1800002800, - "event_id": "-9223371985315102715", - "transaction_index": 1, - "event_index": 4, - "transaction_hash": "0xff24cfc8d7ac5bfa3c01a2a8fe72feb613d4c524ea9c3bcce6c3231a4947d9b7" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "10000000000000000000000", - "quote_token_delta": "-9621898799908896", - "fee_rate": "922337203685477580", - "fee_amount": "506415726310995", - "fee_asset": "quote_token" - } - ] - }, - "claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x105fa6f55830ac11bdef3bc7e263dcb9261d32c2d89c1e3327bfa507a90d97ca", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "14", - "block_hash": "0x50feaeec18c38bc28549e91998d45490e0c9d8bfd16f22cc21fc689741814179", - "block_time": 1800004600, - "event_id": "-9223371976725168121", - "transaction_index": 1, - "event_index": 6, - "transaction_hash": "0x28a1274e0f4a187c38a7f664b3c0e00cb91d28ecb38c4a483537e18d3db6b7f1" - }, - "ledger_event_id": "-9223371976725168124", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "347802764559152447542916", - "quote_token_amount": "5836673075303073" - } - ] - } - }, - "l5": { - "detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x5857834a2f4b4ec1304541c48e13685f02a8dc19", - "name": "Zero Seed l5", - "symbol": "L5", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "18446744073709551615", - "final_fee": "55340232221128654", - "decay_rate_x32": "18446744073709551615", - "half_life_seconds": "0.000322", - "saturation_elapsed_seconds": "0", - "phase": "at_floor", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494840810", - "transaction_index": 3, - "event_index": 21, - "transaction_hash": "0xfb8a73d444fe116725219fdf0baa4189238f39591f79bbcc294509fafe1304a2" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", - "tick_lower": 9210300, - "tick_upper": 13815500, - "liquidity": "1111119116089565312898036", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "997008130316076698400737225", - "quote_token": "2999999999999999984" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "transferable": true, - "launch_token": { - "accrued": "1005061732667368674310164", - "claimed": "1005061732667368674310164", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "184817928874495997630716", - "claimed": "184817928874495997630716", - "unclaimed": "0" - }, - "swap_count": 5, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371972430135287" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13810107 - } - }, - "swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998199742454", - "transaction_index": 5, - "event_index": 9, - "transaction_hash": "0xa61e2d30a5d3abd9ebfc40d412eec3177d8dcdcd1c9dd2288967a6aaf1815469" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-54160", - "quote_token_delta": "1000000000000000000", - "fee_rate": "18446744073709551615", - "fee_amount": "999083373736809197195735", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998199676915", - "transaction_index": 6, - "event_index": 12, - "transaction_hash": "0xbb6f8443b1a49a5ec7615b27695cb840213bb021abcd37b9e64fe2fc6a03b12c" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000", - "quote_token_delta": "184817928874495997640704", - "fee_rate": "18446744073709551615", - "fee_amount": "184817928874495997630685", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "12", - "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", - "block_time": 1800002800, - "event_id": "-9223371985314971636", - "transaction_index": 3, - "event_index": 11, - "transaction_hash": "0x8f948e22790515db6f88dbe271506af7476641a283898341d11fbf61a3114e1b" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-994296418585455187411754", - "quote_token_delta": "1000000000000000000", - "fee_rate": "55340232221128654", - "fee_amount": "2991864850307287378526", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "15", - "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", - "block_time": 1800005000, - "event_id": "-9223371972430266366", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x0010589c2f13c2baa1198c925b9c2f0d797d7853dc9552956d17cb8ee4e35b4d" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "10000000000", - "quote_token_delta": "-10004", - "fee_rate": "55340232221128654", - "fee_amount": "31", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "15", - "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", - "block_time": 1800005000, - "event_id": "-9223371972430200827", - "transaction_index": 1, - "event_index": 4, - "transaction_hash": "0x2f56541851d9db177cb8daca8d3e0406c4e911c2b238c8340440370653a00686" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-992511532670477737486119", - "quote_token_delta": "1000000000000000000", - "fee_rate": "55340232221128654", - "fee_amount": "2986494080252189735903", - "fee_asset": "launch_token" - } - ] - }, - "claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xe3f89a991b0a4c220151cf79dd2298af10a6774b53d3cec2a4908276cbff0659", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "15", - "block_hash": "0xca5fccf33fafbad3f3bb69b0b49cd563ba9ed6d8a0840f3d44c3bfe7070f8fc4", - "block_time": 1800005000, - "event_id": "-9223371972430135287", - "transaction_index": 2, - "event_index": 8, - "transaction_hash": "0x31c86889cab5b68293fcf87ab88a7858f1f23839018ca20a1e405177bf91f05f" - }, - "ledger_event_id": "-9223371972430135289", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "1005061732667368674310164", - "quote_token_amount": "184817928874495997630716" - } - ] - } - }, - "l6": { - "detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x5135fd3e9a6056937eaf6b99454bbc2198c2263d" - }, - "token": { - "address": "0x6db16cdce5cd1f9e0efeed470380273aa53b1e8c", - "name": "Zero Seed l6", - "symbol": "L6", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0xfb2e7927d3e4984dca3e390526d43c6f5a3461e9", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "tick_rate_v2", - "trading_start": 1800001400, - "initial_fee": "18446744073709551615", - "final_fee": "18446744073709551615", - "decay_rate_x32": "1", - "half_life_seconds": "5954091920683119.788732", - "saturation_elapsed_seconds": "381061679028371455", - "phase": "at_floor", - "fee_rate": "18446744073709551615", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x2e4c8261afca423cdfeedcef518e15e6d9fcaf54071e3cb72645503abe73d40f", - "block_time": 1800001310, - "event_id": "-9223372002494775266", - "transaction_index": 4, - "event_index": 29, - "transaction_hash": "0xf1b11401ab1b29b9ea159a52ee41d4596615bf8c0b36378ea4bfcdd9dda04be3" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "999000916626263180802490332", - "quote_token": "1000000000000010019" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "transferable": true, - "launch_token": { - "accrued": "999083373736809197454718", - "claimed": "999083373736809197454718", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "184817928874495997630685", - "claimed": "184817928874495997630685", - "unclaimed": "0" - }, - "swap_count": 2, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371985314906095" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13813701 - } - }, - "swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998199611376", - "transaction_index": 7, - "event_index": 15, - "transaction_hash": "0x5e2d078babc19e43eb6ac6facefed693888d2b3be2801641d5763fba8b4c795a" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-54160", - "quote_token_delta": "1000000000000000000", - "fee_rate": "18446744073709551615", - "fee_amount": "999083373736809197454718", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "9", - "block_hash": "0x6d788e5dc0ed1c512f8700d70ca3ed54c47d89d0c27b4df51aff2fc81798a46f", - "block_time": 1800001400, - "event_id": "-9223371998199545836", - "transaction_index": 8, - "event_index": 19, - "transaction_hash": "0xc899747848e1e0407a0f86124ce268a72ba5e155a225446528f80e0e78ca7e89" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000", - "quote_token_delta": "184817928874495997640704", - "fee_rate": "18446744073709551615", - "fee_amount": "184817928874495997630685", - "fee_asset": "quote_token" - } - ] - }, - "claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xb6470ca6bd9e220a61c2246f6d0b235e01e03307bf5087cb805268b0eafcacc1", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x16de4ff4d984f18b483a63986bdad6b3f2b10c9948eb152c5b3c15b1ca3dc588", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "12", - "block_hash": "0xba95c8c5102398db04979c8e950bc9b3dae57cb0293778ac144d9bca3b677c0d", - "block_time": 1800002800, - "event_id": "-9223371985314906095", - "transaction_index": 4, - "event_index": 16, - "transaction_hash": "0x1da96d809d89d9b64dc2d110c55cebeef6315ad00e8468ccae6fd771db4dc3db" - }, - "ledger_event_id": "-9223371985314906098", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "999083373736809197454718", - "quote_token_amount": "184817928874495997630685" - } - ] - } - } -} diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json deleted file mode 100644 index def794c..0000000 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v5.golden.json +++ /dev/null @@ -1,2133 +0,0 @@ -{ - "schema_version": "zero-seed-launch-api/v5", - "source": { - "fixture": "indexer tests/fixtures/zero-seed-launch/local-chain.json", - "chain_id": "31337" - }, - "list": { - "schema_version": "zero-seed-launch-api/v5", - "data": [ - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4b680c35b2badd8287a7bd61a44c5105c047e06496b6aa83cc95255893c7f5b3", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x7dba39967ec66d5ab5d69322dd4a0185fce5616d", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x7dba39967ec66d5ab5d69322dd4a0185fce5616d", - "name": "Zero Seed l3", - "symbol": "L3", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001300, - "fee_duration": 3600, - "fee_end": 1800004900, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "fee_decay", - "fee_rate": "8599671045411758433", - "evaluated_at": 1800001360 - }, - "created": { - "block_number": "8", - "block_hash": "0xbe7197377e734384f7b383e0d9b1368626e44974650a34baea70919defbae284", - "block_time": 1800001300, - "event_id": "-9223372002494906358", - "transaction_index": 2, - "event_index": 9, - "transaction_hash": "0x46b30a084c978e5760a9afdd6a47a93c2c83018d3c177dcdb88daeb191902910" - }, - "as_of": { - "block_number": "9", - "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", - "block_time": 1800001360 - } - } - ], - "next_cursor": "-9223372002494906358", - "as_of": { - "block_number": "9", - "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", - "block_time": 1800001360 - } - }, - "second": { - "schema_version": "zero-seed-launch-api/v5", - "data": [ - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "name": "Zero Seed l2", - "symbol": "L2", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 600, - "fee_end": 1800001600, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "fee_decay", - "fee_rate": "332352623220129400", - "evaluated_at": 1800001360 - }, - "created": { - "block_number": "3", - "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", - "block_time": 1800000020, - "event_id": "-9223372023969873913", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" - }, - "as_of": { - "block_number": "9", - "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", - "block_time": 1800001360 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "name": "Zero Seed l1", - "symbol": "L1", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 3600, - "fee_end": 1800004600, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "fee_decay", - "fee_rate": "6054471406081085352", - "evaluated_at": 1800001360 - }, - "created": { - "block_number": "2", - "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", - "block_time": 1800000010, - "event_id": "-9223372028264710136", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" - }, - "as_of": { - "block_number": "9", - "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", - "block_time": 1800001360 - } - } - ], - "next_cursor": null, - "as_of": { - "block_number": "9", - "block_hash": "0x617446af1b3c49d46301128dbd018fcd675f964bed452dbf561a76d175ff7d7e", - "block_time": 1800001360 - } - }, - "canonicalList": { - "schema_version": "zero-seed-launch-api/v5", - "data": [ - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000001", - "fee": "0", - "tick_spacing": 1, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", - "name": "Zero Seed l8", - "symbol": "L8", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "170141183460469231694794709727778658247", - "installed": "170141183460469231694794709727778658247", - "sink": "0", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -88722835, - "upper_tick": 88722835, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800005200, - "fee_duration": 3600, - "fee_end": 1800008800, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", - "block_time": 1800005100, - "event_id": "-9223371968135233522", - "transaction_index": 1, - "event_index": 13, - "transaction_hash": "0xd7641c80752a761d38a809de9b96f57c3e55e14e611a63f3820002394d1c1b6a" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080080000", - "fee": "0", - "tick_spacing": 524288, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", - "name": "Zero Seed l7", - "symbol": "L7", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000000", - "installed": "999999999991410763432286846442", - "sink": "8589236567713153558", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -88080384, - "upper_tick": -85983232, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800005200, - "fee_duration": 3600, - "fee_end": 1800008800, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", - "block_time": 1800005100, - "event_id": "-9223371968135299065", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x4d42f78bdb07af1928ff236c63ba1c30d30f26a57f57f0faa75b0c5a26b0c456" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x4c955b66e40af689e262687acc83b95067e4233f", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x4c955b66e40af689e262687acc83b95067e4233f", - "name": "Zero Seed l6", - "symbol": "L6", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 1, - "fee_end": 1800001401, - "initial_fee": "18446744073709551615", - "final_fee": "18446744073709551615", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "18446744073709551615", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494775266", - "transaction_index": 4, - "event_index": 29, - "transaction_hash": "0x3d8f9644ad6ca9f2e28ecf0f767897c5f301f6a70b59ca64b4fdff8a1c59c3c5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf69b28f6d9353d0ba18323fac2879eec12803414", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xf69b28f6d9353d0ba18323fac2879eec12803414", - "name": "Zero Seed l5", - "symbol": "L5", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 3600, - "fee_end": 1800005000, - "initial_fee": "18446744073709551615", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494840810", - "transaction_index": 3, - "event_index": 21, - "transaction_hash": "0x643a2714881f72d7158b0912c7cd24879b85683c0bc17841c723f2866385d0f8" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x15c551f561ac16b237e0fd3304b02998d968662c", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x15c551f561ac16b237e0fd3304b02998d968662c", - "name": "Zero Seed l4", - "symbol": "L4", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 600, - "fee_end": 1800002000, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494906354", - "transaction_index": 2, - "event_index": 13, - "transaction_hash": "0x1c1833a17444efbe77b5a25bc119f47b12074cbfd83cd2d3ea0d78af1ef48e90" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "name": "Zero Seed l2", - "symbol": "L2", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 600, - "fee_end": 1800001600, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "3", - "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", - "block_time": 1800000020, - "event_id": "-9223372023969873913", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "name": "Zero Seed l1", - "symbol": "L1", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 3600, - "fee_end": 1800004600, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "2", - "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", - "block_time": 1800000010, - "event_id": "-9223372028264710136", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - } - ], - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - } - }, - "nativeDetail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x51328cf1d8d7bd92938e33232d5184e25a80fbb6", - "name": "Zero Seed l1", - "symbol": "L1", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 3600, - "fee_end": 1800004600, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "2", - "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", - "block_time": 1800000010, - "event_id": "-9223372028264710136", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", - "tick_lower": 9210300, - "tick_upper": 13815500, - "liquidity": "1111119116089565312898036", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "984239888128788178301567600", - "quote_token": "15987150838889926474" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "transferable": true, - "launch_token": { - "accrued": "1445887341297225653249490", - "claimed": "1445887341297225653249490", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "9304077310520618614", - "claimed": "9304077310520618614", - "unclaimed": "0" - }, - "swap_count": 10, - "claim_count": 4, - "owner_change_count": 3, - "last_event_id": "-9223371959545233400" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13786928 - } - }, - "nativeSwaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "5", - "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", - "block_time": 1800001000, - "event_id": "-9223372015379939327", - "transaction_index": 0, - "event_index": 0, - "transaction_hash": "0x7101a2ad1381043e769e388ac9ee21d094d4dbe8c93c84d38bf072d108029e23" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "zero_fill", - "launch_token_delta": "0", - "quote_token_delta": "0", - "fee_rate": "9223372036854775808", - "fee_amount": "0", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "5", - "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", - "block_time": 1800001000, - "event_id": "-9223372015379873789", - "transaction_index": 1, - "event_index": 2, - "transaction_hash": "0xa14ca4de32c2672ab646c4d823ce141e9657299870ae99fce26e2467d0272dd5" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-499541686868404598624947", - "quote_token_delta": "1000000000000000000", - "fee_rate": "9223372036854775808", - "fee_amount": "499541686868404598624948", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", - "block_time": 1800001060, - "event_id": "-9223372011084972030", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x720e06e34823d2a562377aa6a0a16a8de28b5cb15d4d61aa1cf4a2f8d9a353b2" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000000000000000000", - "quote_token_delta": "18937882440529117203", - "fee_rate": "8599671045411758433", - "fee_amount": "8828634399353909478", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", - "block_time": 1800001060, - "event_id": "-9223372011084906491", - "transaction_index": 1, - "event_index": 4, - "transaction_hash": "0xdf37d43acac819505ea91f11619c0f7e9e5d84eb1cccc384398e0cce7d50359d" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "1000000000000000000000000", - "quote_token_delta": "-544053484045985401", - "fee_rate": "8599671045411758433", - "fee_amount": "475134182559670259", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002495037438", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x6b98b5033142e35944c23543c97bbeb9b4879a3a075557b3f903b289e13476ce" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-1278292209449944997565916", - "quote_token_delta": "2000000000000000000", - "fee_rate": "6419851635182426398", - "fee_amount": "682341375631624879778841", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "11", - "block_hash": "0x6ea9295face4622cbdf33529eae79521a13eefdf71888967138a7a639db30643", - "block_time": 1800001756, - "event_id": "-9223371989610135550", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x19401fd497b838f5503198c57f20c5422b2ea245a8dcb5e3c48f83cd66e1c7c4" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-776368331230381396567584", - "quote_token_delta": "1000000000000000000", - "fee_rate": "3798635018806438810", - "fee_amount": "201332466835839644110932", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "12", - "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", - "block_time": 1800002800, - "event_id": "-9223371985315168254", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0xe76a36ccc6d284264f86273a7514e8d8b9d7d79ed9525556ddae58bdce198e74" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-919140266670121284225811", - "quote_token_delta": "1000000000000000000", - "fee_rate": "1074010432735978337", - "fee_amount": "56822734749309055238917", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "13", - "block_hash": "0x0fc5cfd47afcb3edb9cdd04bccd5769cf75ccca7139bd9d19c15dff629fd9038", - "block_time": 1800004599, - "event_id": "-9223371981020200958", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0xe5a32b0292d8ae36aedbf269823ddad51a5a33e27a5633551e9956066a5dd375" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-971298260506660135369876", - "quote_token_delta": "1000000000000000000", - "fee_rate": "55508445646640394", - "fee_amount": "2931573374968989183135", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "14", - "block_hash": "0x421411fb521b7492f6b6c2f9f4e33ad574b3ab9a15667650b47e934599e6ba70", - "block_time": 1800004600, - "event_id": "-9223371976725233662", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x0a72fdbffef404ac2c7b84ff25e82a7a29e01767c42eff6dfbf03d4375b5f0ad" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-969583775189083632828198", - "quote_token_delta": "1000000000000000000", - "fee_rate": "55340232221128654", - "fee_amount": "2917503837078486312717", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000, - "event_id": "-9223371959545364478", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0x8e947f7865f3615194b0a725a8331516296870c2e4ba77682995c780a6c012f8" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "100000000000000000000000", - "quote_token_delta": "-102600807072586714", - "fee_rate": "55340232221128654", - "fee_amount": "308728607038877", - "fee_asset": "quote_token" - } - ] - }, - "nativeClaims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "6", - "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", - "block_time": 1800001060, - "event_id": "-9223372011084775411", - "transaction_index": 3, - "event_index": 12, - "transaction_hash": "0xde312b3f9fad6f43239d2c82f024b2b46a7f815b7e50d93107fa577afba2a3f3" - }, - "ledger_event_id": "-9223372011084775413", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "499541686868404598624948", - "quote_token_amount": "9303768581913579737" - }, - { - "event": { - "block_number": "12", - "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", - "block_time": 1800002800, - "event_id": "-9223371985315037174", - "transaction_index": 2, - "event_index": 9, - "transaction_hash": "0x04231beabae417154fd0367d2ba8d2bc15e55a4f76d363bb118788ec79a740a5" - }, - "ledger_event_id": "-9223371985315037176", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "940496577216773579128690", - "quote_token_amount": "0" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000, - "event_id": "-9223371959545298938", - "transaction_index": 1, - "event_index": 5, - "transaction_hash": "0x8035e454bddab3d597bd744221fe8da62e7e42b4510908f8497431433b6588e8" - }, - "ledger_event_id": "-9223371959545298940", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "5849077212047475495852", - "quote_token_amount": "308728607038877" - }, - { - "event": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000, - "event_id": "-9223371959545233400", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0xa812559a27a75f1acfcd2b44c8bcb69b023e817631a2ebea0b4b4fb84fd8d9c9" - }, - "ledger_event_id": "-9223371959545233401", - "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "0", - "quote_token_amount": "0" - } - ] - }, - "nativeOwners": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa82ee3b56f026133e77e98cc48b9f80d54c934f3e38acbb50e46c1be66e3903f", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "2", - "block_hash": "0x3c4f4edb3e6e8ff86363c7836f18dc1ea00ad972802486db05657e3ace93ae37", - "block_time": 1800000010, - "event_id": "-9223372028264710135", - "transaction_index": 2, - "event_index": 8, - "transaction_hash": "0x092b2a46b90cc18f7d6ee774c3ca918c8a5f7e7b669c3e3c2fb00f2d02d98562" - }, - "previous_owner": null, - "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" - }, - { - "event": { - "block_number": "10", - "block_hash": "0x8ab8e6f901763a260512c10e8a3a39516cbfcfa57ef761c750b168308ca24a84", - "block_time": 1800001700, - "event_id": "-9223371993904971767", - "transaction_index": 2, - "event_index": 8, - "transaction_hash": "0x44cbd6d884fb3b0c0938c1796adcb3e909a23d5698975dd48200c203bda2f799" - }, - "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" - }, - { - "event": { - "block_number": "11", - "block_hash": "0x6ea9295face4622cbdf33529eae79521a13eefdf71888967138a7a639db30643", - "block_time": 1800001756, - "event_id": "-9223371989610070012", - "transaction_index": 1, - "event_index": 3, - "transaction_hash": "0x06896be8fb3348efa4c8fab6fe02e1ebe9abf60f232b0fca2b12a3a09de1d5bf" - }, - "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" - } - ] - }, - "erc20Detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xb21c8dbf60916c56a8676a6fd7e2a751719ae1b3", - "name": "Zero Seed l2", - "symbol": "L2", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001000, - "fee_duration": 600, - "fee_end": 1800001600, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "3", - "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", - "block_time": 1800000020, - "event_id": "-9223372023969873913", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "991072549435525463122574599", - "quote_token": "8999918293703352734" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "transferable": true, - "launch_token": { - "accrued": "226070717291339801421992", - "claimed": "217191134011045238943541", - "unclaimed": "8879583280294562478451" - }, - "quote_token": { - "accrued": "52679910194913302", - "claimed": "52679910194913302", - "unclaimed": "0" - }, - "swap_count": 4, - "claim_count": 1, - "owner_change_count": 2, - "last_event_id": "-9223371993905037306" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13799366 - } - }, - "erc20Swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "5", - "block_hash": "0xf3a9e260acbaf15cac43311dbc76ab25c665eb868731d932f2e42d8f9b010795", - "block_time": 1800001000, - "event_id": "-9223372015379808250", - "transaction_index": 2, - "event_index": 5, - "transaction_hash": "0xbb3aca3565c12f4da3bc017b3d214c3f7cb6e96f30821b339ddf212447e32120" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-1000000000000000000000000", - "quote_token_delta": "1053598203898266036", - "fee_rate": "922337203685477580", - "fee_amount": "52679910194913302", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "6", - "block_hash": "0x49c6e0b64cb92aa7c0914ef0ba44ef132767996b1d30bc3ad1e1623742c2c7b7", - "block_time": 1800001060, - "event_id": "-9223372011084840952", - "transaction_index": 2, - "event_index": 7, - "transaction_hash": "0x1cfc924a5fb6f41ec7d2f434ad3047f6f90c0ef3dba9387d8fdbfd9df23ad3ff" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-4751426503371359422372847", - "quote_token_delta": "5000000000000000000", - "fee_rate": "806216816634246792", - "fee_amount": "217152236676102150438122", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "7", - "block_hash": "0xff33c4024127fbdc44d4efa422b1338004300bd9ec2bcc192559f8e841c95abf", - "block_time": 1800001120, - "event_id": "-9223372006789939197", - "transaction_index": 1, - "event_index": 2, - "transaction_hash": "0x8a15e755864852c0b63f4d0f2e59654806784d006e1dd03d936cf194d264dfbc" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "1028166339388655397096", - "quote_token_delta": "-1000000000000000", - "fee_rate": "697872664525424975", - "fee_amount": "38897334943088505419", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "10", - "block_hash": "0x8ab8e6f901763a260512c10e8a3a39516cbfcfa57ef761c750b168308ca24a84", - "block_time": 1800001700, - "event_id": "-9223371993905037306", - "transaction_index": 1, - "event_index": 5, - "transaction_hash": "0x62ab75a1f1b6314c79e488b2c68edf69bde7bd2809167bc757789c77d0232203" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-2950981510151226309026868", - "quote_token_delta": "3000000000000000000", - "fee_rate": "55340232221128654", - "fee_amount": "8879583280294562478451", - "fee_asset": "launch_token" - } - ] - }, - "erc20Claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494971897", - "transaction_index": 1, - "event_index": 6, - "transaction_hash": "0x75d5bd663c7deb7aa1ae4a89ec006586b2655a5bb98b899b5f9fe63a047edd39" - }, - "ledger_event_id": "-9223372002494971900", - "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", - "launch_token_amount": "217191134011045238943541", - "quote_token_amount": "52679910194913302" - } - ] - }, - "erc20Owners": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x4673d72a7244db78d26305352371c9e68a1ffc56366c25cb49c31f7fa88a8610", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "3", - "block_hash": "0x04ee649ffeea97fa2182b62f0d536b4a309d20ce0ab55288046f4f450248dbd3", - "block_time": 1800000020, - "event_id": "-9223372023969873912", - "transaction_index": 0, - "event_index": 7, - "transaction_hash": "0x573c7be09f63ec1bce5f3e0e744058118a436c250571e0050c76997a0eb55521" - }, - "previous_owner": null, - "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" - }, - { - "event": { - "block_number": "7", - "block_hash": "0xff33c4024127fbdc44d4efa422b1338004300bd9ec2bcc192559f8e841c95abf", - "block_time": 1800001120, - "event_id": "-9223372006790004735", - "transaction_index": 0, - "event_index": 0, - "transaction_hash": "0x36028e6bf502a2ccc0a0a5a10351189682a3f16cb0b4d7204308f7e45f3757f8" - }, - "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" - } - ] - }, - "erc20TraderOwnedDetail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x31ac68f6276a3805adde8ec41aeee006226b3e64d29234d4091392923136f6c0", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x15c551f561ac16b237e0fd3304b02998d968662c", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x15c551f561ac16b237e0fd3304b02998d968662c", - "name": "Zero Seed l4", - "symbol": "L4", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 600, - "fee_end": 1800002000, - "initial_fee": "922337203685477580", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494906354", - "transaction_index": 2, - "event_index": 13, - "transaction_hash": "0x1c1833a17444efbe77b5a25bc119f47b12074cbfd83cd2d3ea0d78af1ef48e90" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "992953944708816951043107488", - "quote_token": "7091146575104629573" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "transferable": true, - "launch_token": { - "accrued": "347802764559152447542916", - "claimed": "347802764559152447542916", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "2358132882502121", - "claimed": "2358132882502121", - "unclaimed": "0" - }, - "swap_count": 4, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371976725168121" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13802777 - } - }, - "maxNativeDetail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x0000000000000000000000000000000000000000", - "token1": "0xf69b28f6d9353d0ba18323fac2879eec12803414", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xf69b28f6d9353d0ba18323fac2879eec12803414", - "name": "Zero Seed l5", - "symbol": "L5", - "decimals": 18 - }, - "quote_token": { - "address": "0x0000000000000000000000000000000000000000", - "name": null, - "symbol": null, - "decimals": 18, - "kind": "native" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999422", - "sink": "578", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": true, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 3600, - "fee_end": 1800005000, - "initial_fee": "18446744073709551615", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494840810", - "transaction_index": 3, - "event_index": 21, - "transaction_hash": "0x643a2714881f72d7158b0912c7cd24879b85683c0bc17841c723f2866385d0f8" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000008c89bc00d2cecc", - "tick_lower": 9210300, - "tick_upper": 13815500, - "liquidity": "1111119116089565312898036", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "997008130316076698400737225", - "quote_token": "2999999999999999984" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "transferable": true, - "launch_token": { - "accrued": "1189704288668331578335754", - "claimed": "1189704288668331578335754", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "184817928874495997630716", - "claimed": "184817928874495997630716", - "unclaimed": "0" - }, - "swap_count": 5, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371972430135287" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13810107 - } - }, - "maxNativeSwaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "9", - "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", - "block_time": 1800001400, - "event_id": "-9223371998199742454", - "transaction_index": 5, - "event_index": 9, - "transaction_hash": "0x8690243222241437f36d68d2d865f9d5061b360d4c3af753bb0a7af0b4bb07e9" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-54160", - "quote_token_delta": "1000000000000000000", - "fee_rate": "18446744073709551615", - "fee_amount": "999083373736809197195735", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "9", - "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", - "block_time": 1800001400, - "event_id": "-9223371998199676915", - "transaction_index": 6, - "event_index": 12, - "transaction_hash": "0x40cef279c3f5f1666d679d795f7a8f6b084232ed79eb73c45d1a7f9e405ac76a" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000", - "quote_token_delta": "184817928874495997640704", - "fee_rate": "18446744073709551615", - "fee_amount": "184817928874495997630685", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "12", - "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", - "block_time": 1800002800, - "event_id": "-9223371985314971636", - "transaction_index": 3, - "event_index": 11, - "transaction_hash": "0xc6e5e1b2ea18a8d937090b4014607429ce989f394942831d05882813b796fed3" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-809653862584492283386164", - "quote_token_delta": "1000000000000000000", - "fee_rate": "3470655575073783216", - "fee_amount": "187634420851270191404116", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "15", - "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", - "block_time": 1800005000, - "event_id": "-9223371972430266366", - "transaction_index": 0, - "event_index": 1, - "transaction_hash": "0xe4311d28091142c8375515c91e761ab90b1fa35e43dbc8acc4b33b56dd4f277d" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "sell", - "launch_token_delta": "10000000000", - "quote_token_delta": "-10004", - "fee_rate": "55340232221128654", - "fee_amount": "31", - "fee_asset": "quote_token" - }, - { - "event": { - "block_number": "15", - "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", - "block_time": 1800005000, - "event_id": "-9223371972430200827", - "transaction_index": 1, - "event_index": 4, - "transaction_hash": "0x71a097485a947e72963b3169d74c554f30d88c53432ed63915a202d3ca048dd7" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-992511532670477737486119", - "quote_token_delta": "1000000000000000000", - "fee_rate": "55340232221128654", - "fee_amount": "2986494080252189735903", - "fee_asset": "launch_token" - } - ] - }, - "maxNativeClaims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xa144205e6ed4ab091df980d7dee97229264eb018bda8e3c8b1ecc2db3db4e8d2", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "15", - "block_hash": "0x465c1f4f66810b0b5084fda1f9d6c982b4d08de4eaa5858131b8b414624a016a", - "block_time": 1800005000, - "event_id": "-9223371972430135287", - "transaction_index": 2, - "event_index": 8, - "transaction_hash": "0xbc018ffb338fb8c9a8b26eea8dc3c2e465d7e7812def80586cdec74b5f9ecd1d" - }, - "ledger_event_id": "-9223371972430135289", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "1189704288668331578335754", - "quote_token_amount": "184817928874495997630716" - } - ] - }, - "maxErc20Detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x4c955b66e40af689e262687acc83b95067e4233f", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000064", - "fee": "0", - "tick_spacing": 100, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x4c955b66e40af689e262687acc83b95067e4233f", - "name": "Zero Seed l6", - "symbol": "L6", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000", - "installed": "999999999999999999999999210", - "sink": "790", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -13815500, - "upper_tick": -9210300, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800001400, - "fee_duration": 1, - "fee_end": 1800001401, - "initial_fee": "18446744073709551615", - "final_fee": "18446744073709551615", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "18446744073709551615", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "8", - "block_hash": "0x53759b2d6153d6465851ef178e9af14621e4d6ca5b355b08a0e8cd914b274b9d", - "block_time": 1800001310, - "event_id": "-9223372002494775266", - "transaction_index": 4, - "event_index": 29, - "transaction_hash": "0x3d8f9644ad6ca9f2e28ecf0f767897c5f301f6a70b59ca64b4fdff8a1c59c3c5" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000ff2d3134ff737644", - "tick_lower": -13815500, - "tick_upper": -9210300, - "liquidity": "1111119116089565312897934", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "999000916626263180802490332", - "quote_token": "1000000000000010019" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "transferable": true, - "launch_token": { - "accrued": "999083373736809197454718", - "claimed": "999083373736809197454718", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "184817928874495997630685", - "claimed": "184817928874495997630685", - "unclaimed": "0" - }, - "swap_count": 2, - "claim_count": 1, - "owner_change_count": 1, - "last_event_id": "-9223371985314906095" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -13813701 - } - }, - "maxErc20Swaps": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "9", - "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", - "block_time": 1800001400, - "event_id": "-9223371998199611376", - "transaction_index": 7, - "event_index": 15, - "transaction_hash": "0x47478bc3ca05cb40f297a8c445abb490ff9d7a1de77dfe0b2c27ed36074cb975" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-54160", - "quote_token_delta": "1000000000000000000", - "fee_rate": "18446744073709551615", - "fee_amount": "999083373736809197454718", - "fee_asset": "launch_token" - }, - { - "event": { - "block_number": "9", - "block_hash": "0xdd1ca9362a099961418636b290fbdb07754cecccaa4e89aedb8a18f3fb954fe3", - "block_time": 1800001400, - "event_id": "-9223371998199545836", - "transaction_index": 8, - "event_index": 19, - "transaction_hash": "0x59356010bcdd4c01e4d82b52f04c8ab1a9e44eaec2a2a5e83bad5f26f9932ad9" - }, - "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", - "direction": "buy", - "launch_token_delta": "-10000000000", - "quote_token_delta": "184817928874495997640704", - "fee_rate": "18446744073709551615", - "fee_amount": "184817928874495997630685", - "fee_asset": "quote_token" - } - ] - }, - "maxErc20Claims": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0x0d0812c3541da20168eab45062e125694e7c9ffcc7f31bedb8163f92f27681c1", - "next_cursor": null, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "data": [ - { - "event": { - "block_number": "12", - "block_hash": "0x1038d421b110a26b12772a94fc3be15ae7f221eb9bb4776ac1e902b2edbdd06b", - "block_time": 1800002800, - "event_id": "-9223371985314906095", - "transaction_index": 4, - "event_index": 16, - "transaction_hash": "0x01487b24cd7bfb68cd5eed42a8fb2a826af16a3f93d707f8214ce30e2e73287f" - }, - "ledger_event_id": "-9223371985314906098", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", - "launch_token_amount": "999083373736809197454718", - "quote_token_amount": "184817928874495997630685" - } - ] - }, - "lowPriceErc20Detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xdaa8ec7b6dd214d63110b3a3933dbf7d339f907ef4c298f91f21d6b378272202", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080080000", - "fee": "0", - "tick_spacing": 524288, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0xbcb69a20b6c616f514eb785ba76b4473ef65fa3e", - "name": "Zero Seed l7", - "symbol": "L7", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "1000000000000000000000000000000", - "installed": "999999999991410763432286846442", - "sink": "8589236567713153558", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "ask_tick": -88080384, - "upper_tick": -85983232, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800005200, - "fee_duration": 3600, - "fee_end": 1800008800, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", - "block_time": 1800005100, - "event_id": "-9223371968135299065", - "transaction_index": 0, - "event_index": 6, - "transaction_hash": "0x4d42f78bdb07af1928ff236c63ba1c30d30f26a57f57f0faa75b0c5a26b0c456" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000fac00000fae00000", - "tick_lower": -88080384, - "tick_upper": -85983232, - "liquidity": "115073586432", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "999999999891410763432286846442", - "quote_token": "1" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", - "transferable": true, - "launch_token": { - "accrued": "0", - "claimed": "0", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "1", - "claimed": "0", - "unclaimed": "1" - }, - "swap_count": 1, - "claim_count": 0, - "owner_change_count": 1, - "last_event_id": "-9223371963840331774" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -88080384 - } - }, - "fullWidthErc20Detail": { - "schema_version": "zero-seed-launch-api/v5", - "chain_id": "31337", - "pool_id": "0xd56a8e6fcf9b121ec98770105e1a1afb7e7ed2b5bbf6777efc128d987cecf6bf", - "pool_key": { - "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", - "token0": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", - "token1": "0xffffffffffffffffffffffffffffffffffffffff", - "config": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f000000000000000080000001", - "fee": "0", - "tick_spacing": 1, - "extension": "0x519c5f793d3f6cf9ee439e4cc38d8285cf08211f" - }, - "token": { - "address": "0x7398b4a4430315dcb5fdfbc49d98df5b0e6dfd2f", - "name": "Zero Seed l8", - "symbol": "L8", - "decimals": 18 - }, - "quote_token": { - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "name": null, - "symbol": null, - "decimals": null, - "kind": "erc20" - }, - "supply": { - "requested": "170141183460469231694794709727778658247", - "installed": "170141183460469231694794709727778658247", - "sink": "0", - "sink_holder": "0x0000000000000000000000000000000000000000" - }, - "launch_token_is_token1": false, - "controller": "0x0df4e2a76640b52e51479b435f52d0eb0e7e8ef4", - "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "ask_tick": -88722835, - "upper_tick": 88722835, - "fee_schedule": { - "model": "fixed_base_duration_v1", - "trading_start": 1800005200, - "fee_duration": 3600, - "fee_end": 1800008800, - "initial_fee": "9223372036854775808", - "final_fee": "55340232221128654", - "decay": { - "kind": "fixed_base_q128", - "half_life_seconds": 600 - }, - "phase": "final_fee", - "fee_rate": "55340232221128654", - "evaluated_at": 2115361000 - }, - "created": { - "block_number": "16", - "block_hash": "0x3b73a71369297791099c2db3654eb1980eac267caf3567b8d8e09aa27e3666a5", - "block_time": 1800005100, - "event_id": "-9223371968135233522", - "transaction_index": 1, - "event_index": 13, - "transaction_hash": "0xd7641c80752a761d38a809de9b96f57c3e55e14e611a63f3820002394d1c1b6a" - }, - "as_of": { - "block_number": "18", - "block_hash": "0x0480f077de5465facbcd151c9555caf652ea0a713bc042a958e7f55f9e7522b1", - "block_time": 2115361000 - }, - "principal": { - "position_id": "0x000000000000000000000000000000000000000000000000fab6326d0549cd93", - "tick_lower": -88722835, - "tick_upper": 88722835, - "liquidity": "9223595582101085260", - "locked": true, - "withdrawable": false, - "pool_reserves": { - "launch_token": "170141183460469231594794709727778658247", - "quote_token": "1" - }, - "reserves_basis": "core_pool_balance" - }, - "fee_ledger": { - "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", - "transferable": true, - "launch_token": { - "accrued": "0", - "claimed": "0", - "unclaimed": "0" - }, - "quote_token": { - "accrued": "1", - "claimed": "0", - "unclaimed": "1" - }, - "swap_count": 1, - "claim_count": 0, - "owner_change_count": 1, - "last_event_id": "-9223371963840266234" - }, - "inventory": { - "trading_open": true, - "no_bid": false, - "price_tick": -88722835 - } - } -} diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json new file mode 100644 index 0000000..8925a2a --- /dev/null +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json @@ -0,0 +1,2590 @@ +{ + "source": { + "indexer_commit": "942a060da475039518a471c0ac02fe200a69fe2d", + "fixture_sha256": "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6", + "evaluated_at": 2115361000 + }, + "list": { + "schema_version": "zero-seed-launch-api/v6", + "data": [ + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c0000000000000000800a939c", + "fee": "0", + "tick_spacing": 693148, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3800000000000000000000000000000000000000", + "symbol": "0x024c380000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88722835", + "upper_tick": "88722835", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x114620bab65d343f8f17a1536ca597c5641c5e6cbe4398524e79a406de0df42b" + }, + "range": { + "tick_spacing": 693148, + "aligned_ask_tick": -88029796, + "aligned_upper_tick": 88029796, + "pool_tick_lower": -88029796, + "pool_tick_upper": 88029796 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999987511320520150058769", + "sink": "12488679479849941231", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -88029796, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1 + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135233525", + "transaction_index": 1, + "event_index": 10, + "transaction_hash": "0xb0a431eab9a08a25bb3542b81016c59e96d2c1ce978384640542b18d436e8153" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080002000", + "fee": "0", + "tick_spacing": 8192, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3700000000000000000000000000000000000000", + "symbol": "0x024c370000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88080384", + "upper_tick": "-85983232", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x3bb0077653d024b802a78f862b2d959f8be504b7f42df81a445180edd449da4e" + }, + "range": { + "tick_spacing": 8192, + "aligned_ask_tick": -88080384, + "aligned_upper_tick": -85983232, + "pool_tick_lower": -88080384, + "pool_tick_upper": -85983232 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -88080384, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1 + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135299067", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0xddc99d9b6de8f631e839ffe8c6cf47600c972b909e9793ac88a6e5b288ca1b1b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "name": null, + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x035aff0000000000000000000000000000000000000000000000000000000000", + "symbol": "0x024c360000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "1", + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "salt": "0x87f22775ea9a15cba089cab3a4d0d92b968f5f1280cec686b6126b32bdfa72f8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay_rate_x32": "1", + "half_life_seconds": "5954091920683119.788732", + "saturation_elapsed_seconds": "381061679028371455", + "phase": "at_floor", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13796564, + "price_x128": "346793678803132136179439976012385", + "market_cap": "1019135025834901745761", + "market_cap_usd": null, + "raised": "1000000000000010192", + "sold": "982091443427442213916824", + "sold_share": "0.000982091443427442", + "trade_count": 2 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494775272", + "transaction_index": 4, + "event_index": 23, + "transaction_hash": "0x8d219e66a3a734e1655a128a681f1f3ac6f5811ee4c1d1924cb8b2fe7a14254c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "name": "Zéro 🚀", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": "ipfs://bafyélogo 🚀", + "parameters": { + "name": "0x0a5ac3a9726f20f09f9a80000000000000000000000000000000000000000000", + "symbol": "0x024c350000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "18446744073709551615", + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "salt": "0xd0018fa3949db0f7478425347db6277a1a4e81620122b9ef181f47b2ade9574e" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay_rate_x32": "18446744073709551615", + "half_life_seconds": "0.000322", + "saturation_elapsed_seconds": "0", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13793034, + "price_x128": "348019647004654940455006368355745", + "market_cap": "1022737822572846286160", + "market_cap_usd": null, + "raised": "2999999999999999983", + "sold": "2941080334312777133468723", + "sold_share": "0.002941080334312777", + "trade_count": 5 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494840814", + "transaction_index": 3, + "event_index": 17, + "transaction_hash": "0xce4a04be8c2b72e774db76672444ff684141564fcfc9ea16844b62bc8bfc25e3" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3400000000000000000000000000000000000000", + "symbol": "0x024c340000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "0", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x64778ffaf8f1b3afdbfda9ef8a1483b1ae9316267a82c4aa2fa77f3f5c800ab7" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "0", + "half_life_seconds": null, + "saturation_elapsed_seconds": null, + "phase": "constant", + "fee_rate": "922337203685477580", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13785833, + "price_x128": "350535152092893641763330226765819", + "market_cap": "1030130227624569569284", + "market_cap_usd": null, + "raised": "7092704171850925118", + "sold": "6928409872974093301683354", + "sold_share": "0.006928409872974093", + "trade_count": 4 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494906356", + "transaction_index": 2, + "event_index": 11, + "transaction_hash": "0x953e3e4c3901fd7081e1a41e36d189eec944f7424dc1b31b329e5aec8339e31c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3200000000000000000000000000000000000000", + "symbol": "0x024c320000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "68913100933", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x706d1c2beda46be1cbaa68fe362b46cabc10d357d951c50e9fa8faa1112679d8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13782455, + "price_x128": "351721170929431300655620967870695", + "market_cap": "1033615623730366669443", + "market_cap_usd": null, + "raised": "9017251531406779206", + "sold": "8793513662736047941471448", + "sold_share": "0.008793513662736047", + "trade_count": 4 + }, + "created": { + "block_number": "3", + "block_hash": "0x3d50b3ccba7b4ab3df647ceb94e60b76a6ee464eb63bb44151074c3108b7a8b2", + "block_time": 1800000020, + "event_id": "-9223372023969873915", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0x9d7d2a206587b1f14e48d669379d1adc8d06f9c245e973f27fec2d73786720dc" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "logo_url": "https://example.invalid/l1.svg", + "parameters": { + "name": "0x0c5a65726f2053656564206c3100000000000000000000000000000000000000", + "symbol": "0x024c310000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "9923486534471", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x4ef3c22030c48a824508eaa92d5871f0ef42a8a80ef512ae3934836eed27ebaf" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "9923486534471", + "half_life_seconds": "600.000000", + "saturation_elapsed_seconds": "38399", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13769998, + "price_x128": "356129566344924627381574169859136", + "market_cap": "1046570733498124860716", + "market_cap_usd": null, + "raised": "16142442253306885264", + "sold": "15644177672570445471331431", + "sold_share": "0.015644177672570445", + "trade_count": 10 + }, + "created": { + "block_number": "2", + "block_hash": "0x570fcc84e9f797809ba3e5f9138a969cbac2cbb913ad2d9a2a4881b6965ae3b9", + "block_time": 1800000010, + "event_id": "-9223372028264710138", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0x9166164828ca58da84d325963c28bd5cf66ddf894017ca90970c4b3efea7215b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + } + ], + "total": 7, + "totals": { + "all": 7, + "trading": 7, + "upcoming": 0 + }, + "next_offset": null, + "evaluated_at": 2115361000, + "heads": [ + { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000, + "chain_id": "31337" + } + ] + }, + "l1": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "logo_url": "https://example.invalid/l1.svg", + "parameters": { + "name": "0x0c5a65726f2053656564206c3100000000000000000000000000000000000000", + "symbol": "0x024c310000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "9923486534471", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x4ef3c22030c48a824508eaa92d5871f0ef42a8a80ef512ae3934836eed27ebaf" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "9923486534471", + "half_life_seconds": "600.000000", + "saturation_elapsed_seconds": "38399", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13769998, + "price_x128": "356129566344924627381574169859136", + "market_cap": "1046570733498124860716", + "market_cap_usd": null, + "raised": "16142442253306885264", + "sold": "15644177672570445471331431", + "sold_share": "0.015644177672570445", + "trade_count": 10 + }, + "created": { + "block_number": "2", + "block_hash": "0x570fcc84e9f797809ba3e5f9138a969cbac2cbb913ad2d9a2a4881b6965ae3b9", + "block_time": 1800000010, + "event_id": "-9223372028264710138", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0x9166164828ca58da84d325963c28bd5cf66ddf894017ca90970c4b3efea7215b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c8c0000d28bba", + "liquidity": "1121810267768648385525007", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "984355822327429554528668128", + "quote_token": "16142442253306885264" + } + }, + "fee_ledger": { + "holder": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token": { + "accrued": "1451851403496426596373491", + "claimed": "1451851403496426596373491", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "9484447569588169453", + "claimed": "9484447569588169453", + "unclaimed": "0" + }, + "swap_count": 10, + "claim_count": 4, + "owner_change_count": 3, + "last_event_id": "-9223371959545233400" + } + }, + "l1/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0x29be75ab412bc11d154ff2c50069b7e3cbc1f5b5a4590b2ec8928f32d2b101b2", + "block_time": 1800001000, + "event_id": "-9223372015379939327", + "transaction_index": 0, + "event_index": 0, + "transaction_hash": "0xbf35c3ce1d9dcb32c0e1ad29e91c1b076c9a6557326dca5c60e7c7c03581a98c" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "zero_fill", + "launch_token_delta": "0", + "quote_token_delta": "0", + "fee_rate": "9223372036854775808", + "fee_amount": "0", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "5", + "block_hash": "0x29be75ab412bc11d154ff2c50069b7e3cbc1f5b5a4590b2ec8928f32d2b101b2", + "block_time": 1800001000, + "event_id": "-9223372015379873789", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x84a9f43120ccfeee868b3f4501eb58adc0430988c763bda18d7b5e0ad14f060a" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-491045721713716106855018", + "quote_token_delta": "1000000000000000000", + "fee_rate": "9223372036854775808", + "fee_amount": "491045721713716106855019", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xe0e8ffa78a9a26df9896fce1605238a067be6f8f7a5027755a3a633b6278afbd", + "block_time": 1800001060, + "event_id": "-9223372011084972030", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x3b163179d42cea5cca890c19e760266fc7d322b23fb6e0519ba65ad0bcb8bfa3" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000000000000000000", + "quote_token_delta": "19284142593632098659", + "fee_rate": "8609418535131466691", + "fee_amount": "9000247090561359127", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xe0e8ffa78a9a26df9896fce1605238a067be6f8f7a5027755a3a633b6278afbd", + "block_time": 1800001060, + "event_id": "-9223372011084906491", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0x22bd5e41d1139149a482dac060b4494aae73aa3d4c0c3c20279b6ef52576ff20" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "1000000000000000000000000", + "quote_token_delta": "-552900152304903791", + "fee_rate": "8609418535131466691", + "fee_amount": "483886479171949322", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002495037438", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x3313823e8938f44020f896287a94d1770b8b6ed238ef41e266426dbb4bc70351" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-1252055731769336718170443", + "quote_token_delta": "2000000000000000000", + "fee_rate": "6463658066361875532", + "fee_amount": "675356926022494550268670", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "11", + "block_hash": "0x788d4382b82ff9647c5fcdbbdc9a2df58f23fb876561fd223e2db5fc1e18f019", + "block_time": 1800001756, + "event_id": "-9223371989610135550", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x5439c6d78575c90da40b015dfe632cc3e6372af474a9eb5b3b135d87e06df267" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-758833536048878826029642", + "quote_token_delta": "1000000000000000000", + "fee_rate": "3883398005406982927", + "fee_amount": "202347223399575124000988", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xab1e4aacf8998e3270a5ff019a545e6f03e23e2dd56633f2f3fd1816ebd394ad", + "block_time": 1800002800, + "event_id": "-9223371985315168254", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xfdaf942b6745c37a1d21832a8525a9c453b99573e69a6a094ae1c1f6ac5bf3b4" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-897015291833690654902106", + "quote_token_delta": "1000000000000000000", + "fee_rate": "1201344301085512086", + "fee_amount": "62487632820296849412243", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "13", + "block_hash": "0xf1649bb7ef047768e402763e94305bda221d84b4984fc2774d4dcb68fca3a0c1", + "block_time": 1800004599, + "event_id": "-9223371981020200958", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xa4d1f861846f4a5a6ee79197b4b2f65d4ff62c87bc72d7d8532d23d29e7f1057" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-947509245325636381702945", + "quote_token_delta": "1000000000000000000", + "fee_rate": "198756374052614459", + "fee_amount": "10320233939317764424278", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "14", + "block_hash": "0xd440f23ba0ca0c61434085221a45e74c1d4fbec9da6c08c68669829d6d61f9f8", + "block_time": 1800004600, + "event_id": "-9223371976725233662", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xb8c5cdfa979425ec890a09269ce40d7caaeec1caa3ff8a6a73760d7fe2596c3e" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-945866742382760187297786", + "quote_token_delta": "1000000000000000000", + "fee_rate": "198590752489824724", + "fee_amount": "10293665601026201412293", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000, + "event_id": "-9223371959545364478", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x9fe48e9dcf8c17fea8fe10ac8a9e81126bbf878b284a7663ccfbfcbf8e71721b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "100000000000000000000000", + "quote_token_delta": "-104352618432140151", + "fee_rate": "55340232221128654", + "fee_amount": "313999854861004", + "fee_asset": "quote_token" + } + ] + }, + "l1/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "6", + "block_hash": "0xe0e8ffa78a9a26df9896fce1605238a067be6f8f7a5027755a3a633b6278afbd", + "block_time": 1800001060, + "event_id": "-9223372011084775413", + "transaction_index": 3, + "event_index": 10, + "transaction_hash": "0xb24950ad479de42d8e046f303eeab022cc8bdda19b2f4b4ef3c50971179400fd" + }, + "payout_event_id": "-9223372011084775411", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "491045721713716106855019", + "quote_token_amount": "9484133569733308449" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xab1e4aacf8998e3270a5ff019a545e6f03e23e2dd56633f2f3fd1816ebd394ad", + "block_time": 1800002800, + "event_id": "-9223371985315037176", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0x44d19cc71cfedf0937bac15b9b7e2b56a1b8ed049ec156cd52d56b2cb69be97c" + }, + "payout_event_id": "-9223371985315037174", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "940191782242366523681901", + "quote_token_amount": "0" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000, + "event_id": "-9223371959545298940", + "transaction_index": 1, + "event_index": 3, + "transaction_hash": "0xacbf491054ae9332743f0f88ea6e6aa278cc588d220e5dc57d1dfcf60fb5d6ab" + }, + "payout_event_id": "-9223371959545298938", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "20613899540343965836571", + "quote_token_amount": "313999854861004" + }, + { + "event": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000, + "event_id": "-9223371959545233401", + "transaction_index": 2, + "event_index": 6, + "transaction_hash": "0xbcde3793eab80cca03ab163e517b25b7bd8bba94336dfefd8073a5d491c244d2" + }, + "payout_event_id": "-9223371959545233400", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "0", + "quote_token_amount": "0" + } + ], + "totals": { + "launch_token": "1451851403496426596373491", + "quote_token": "9484447569588169453" + } + }, + "l1/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "2", + "block_hash": "0x570fcc84e9f797809ba3e5f9138a969cbac2cbb913ad2d9a2a4881b6965ae3b9", + "block_time": 1800000010, + "event_id": "-9223372028264710137", + "transaction_index": 2, + "event_index": 6, + "transaction_hash": "0x9166164828ca58da84d325963c28bd5cf66ddf894017ca90970c4b3efea7215b" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + }, + { + "event": { + "block_number": "10", + "block_hash": "0xca53b68272a9a116f305f46fbf57bef1cade347bfb86a7fadd3649471cf6a22e", + "block_time": 1800001700, + "event_id": "-9223371993904971767", + "transaction_index": 2, + "event_index": 8, + "transaction_hash": "0xc334b52ed56a9e20678955fc7d4452e106e04d655b83b5cf4c65f81b06a32d9d" + }, + "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" + }, + { + "event": { + "block_number": "11", + "block_hash": "0x788d4382b82ff9647c5fcdbbdc9a2df58f23fb876561fd223e2db5fc1e18f019", + "block_time": 1800001756, + "event_id": "-9223371989610070012", + "transaction_index": 1, + "event_index": 3, + "transaction_hash": "0xdcd51ca1e16b4a0187fb9fb759d9be4d626b36f192f24b500e4c43443455778f" + }, + "previous_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "new_owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc" + } + ] + }, + "l2": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3200000000000000000000000000000000000000", + "symbol": "0x024c320000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "68913100933", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x706d1c2beda46be1cbaa68fe362b46cabc10d357d951c50e9fa8faa1112679d8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13782455, + "price_x128": "351721170929431300655620967870695", + "market_cap": "1033615623730366669443", + "market_cap_usd": null, + "raised": "9017251531406779206", + "sold": "8793513662736047941471448", + "sold_share": "0.008793513662736047", + "trade_count": 4 + }, + "created": { + "block_number": "3", + "block_hash": "0x3d50b3ccba7b4ab3df647ceb94e60b76a6ee464eb63bb44151074c3108b7a8b2", + "block_time": 1800000020, + "event_id": "-9223372023969873915", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0x9d7d2a206587b1f14e48d669379d1adc8d06f9c245e973f27fec2d73786720dc" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d7446ff737400", + "liquidity": "1121810267768648385524834", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "991206486337263952058528060", + "quote_token": "9017251531406779206" + } + }, + "fee_ledger": { + "holder": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token": { + "accrued": "388899155321923602869114", + "claimed": "244160676971611651450703", + "unclaimed": "144738478350311951418411" + }, + "quote_token": { + "accrued": "53592185863514696", + "claimed": "53592185863514696", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 2, + "last_event_id": "-9223371993905037306" + } + }, + "l2/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "5", + "block_hash": "0x29be75ab412bc11d154ff2c50069b7e3cbc1f5b5a4590b2ec8928f32d2b101b2", + "block_time": 1800001000, + "event_id": "-9223372015379808250", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0xd35603d3fc9f3cc0185bf6167b06d644139152bca3933792c9ee9e4beaaa04a7" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-1000000000000000000000000", + "quote_token_delta": "1071843717270293902", + "fee_rate": "922337203685477580", + "fee_amount": "53592185863514696", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "6", + "block_hash": "0xe0e8ffa78a9a26df9896fce1605238a067be6f8f7a5027755a3a633b6278afbd", + "block_time": 1800001060, + "event_id": "-9223372011084840952", + "transaction_index": 2, + "event_index": 7, + "transaction_hash": "0xc616880dbb7e4b136ff60831fb48ad93e7a8d823fcca2d2ebbe3eab813873918" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-4640289101439344548241033", + "quote_token_delta": "5000000000000000000", + "fee_rate": "921920278629180106", + "fee_amount": "244109536925548806220012", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "7", + "block_hash": "0x4db4904b50455c80829239f5dcb927952782f5c76c325f3b4473efc5b04a4e67", + "block_time": 1800001120, + "event_id": "-9223372006789939197", + "transaction_index": 1, + "event_index": 2, + "transaction_hash": "0x938369e6e99027852c823cd426402e2d7d631ee4fd7543469aa52bb1b9a3aea1" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "1023726691919932397035", + "quote_token_delta": "-1000000000000000", + "fee_rate": "921503120984176012", + "fee_amount": "51140046062845230691", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "10", + "block_hash": "0xca53b68272a9a116f305f46fbf57bef1cade347bfb86a7fadd3649471cf6a22e", + "block_time": 1800001700, + "event_id": "-9223371993905037306", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0x373f3322390d772dee9f13e0b6b9e61c1e0efd1a9c6b838679a0eea9dd8e7b6d" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-2765349132666699722758336", + "quote_token_delta": "3000000000000000000", + "fee_rate": "917482228933054387", + "fee_amount": "144738478350311951418411", + "fee_asset": "launch_token" + } + ] + }, + "l2/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494971900", + "transaction_index": 1, + "event_index": 3, + "transaction_hash": "0xf3e28dcc77895f7d14615e7946c262afd537dcfa78e2ed6fafa60b91567b583e" + }, + "payout_event_id": "-9223372002494971897", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "recipient": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "launch_token_amount": "244160676971611651450703", + "quote_token_amount": "53592185863514696" + } + ], + "totals": { + "launch_token": "244160676971611651450703", + "quote_token": "53592185863514696" + } + }, + "l2/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "3", + "block_hash": "0x3d50b3ccba7b4ab3df647ceb94e60b76a6ee464eb63bb44151074c3108b7a8b2", + "block_time": 1800000020, + "event_id": "-9223372023969873914", + "transaction_index": 0, + "event_index": 5, + "transaction_hash": "0x9d7d2a206587b1f14e48d669379d1adc8d06f9c245e973f27fec2d73786720dc" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + }, + { + "event": { + "block_number": "7", + "block_hash": "0x4db4904b50455c80829239f5dcb927952782f5c76c325f3b4473efc5b04a4e67", + "block_time": 1800001120, + "event_id": "-9223372006790004735", + "transaction_index": 0, + "event_index": 0, + "transaction_hash": "0x6b1553a70841f6999c6985312e57b1639bb7ea5fe95209d0b29d1fe9a3b63cee" + }, + "previous_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "new_owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906" + } + ] + }, + "l4": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3400000000000000000000000000000000000000", + "symbol": "0x024c340000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "0", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x64778ffaf8f1b3afdbfda9ef8a1483b1ae9316267a82c4aa2fa77f3f5c800ab7" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "0", + "half_life_seconds": null, + "saturation_elapsed_seconds": null, + "phase": "constant", + "fee_rate": "922337203685477580", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13785833, + "price_x128": "350535152092893641763330226765819", + "market_cap": "1030130227624569569284", + "market_cap_usd": null, + "raised": "7092704171850925118", + "sold": "6928409872974093301683354", + "sold_share": "0.006928409872974093", + "trade_count": 4 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494906356", + "transaction_index": 2, + "event_index": 11, + "transaction_hash": "0x953e3e4c3901fd7081e1a41e36d189eec944f7424dc1b31b329e5aec8339e31c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d7446ff737400", + "liquidity": "1121810267768648385524834", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "993071590127025906698316154", + "quote_token": "7092704171850925118" + } + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "launch_token": { + "accrued": "341920493648704664787599", + "claimed": "341920493648704664787599", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "5936415422760581", + "claimed": "5936415422760581", + "unclaimed": "0" + }, + "swap_count": 4, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371976725168121" + } + }, + "l4/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998200004606", + "transaction_index": 1, + "event_index": 1, + "transaction_hash": "0x1a270a5cb7d827d0026e49262e4d819354dded39a477f4c526aeb00a845ac4ff" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "zero_fill", + "launch_token_delta": "0", + "quote_token_delta": "0", + "fee_rate": "922337203685477580", + "fee_amount": "0", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998199939068", + "transaction_index": 2, + "event_index": 3, + "transaction_hash": "0x7d23c2ee3d129457826ad71710ab4108d3b5608ee7b1fc2b010790b7aa59aeb3" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-6496489379325388636895755", + "quote_token_delta": "7000000000000000000", + "fee_rate": "922337203685477580", + "fee_amount": "341920493648704664787599", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "10", + "block_hash": "0xca53b68272a9a116f305f46fbf57bef1cade347bfb86a7fadd3649471cf6a22e", + "block_time": 1800001700, + "event_id": "-9223371993905102846", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0xfb0fd212addcc91010e949b9d9445affdf5041b7a9899a7e3e1921aec77ded8c" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-100000000000000000000000", + "quote_token_delta": "108426912977506016", + "fee_rate": "922337203685477580", + "fee_amount": "5421345648875301", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xab1e4aacf8998e3270a5ff019a545e6f03e23e2dd56633f2f3fd1816ebd394ad", + "block_time": 1800002800, + "event_id": "-9223371985315102715", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0xaf474ffefe422c04233218084510d20f77d7d54dd4ee17d6c058dc6c36b2a313" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "10000000000000000000000", + "quote_token_delta": "-9786325703820317", + "fee_rate": "922337203685477580", + "fee_amount": "515069773885280", + "fee_asset": "quote_token" + } + ] + }, + "l4/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "14", + "block_hash": "0xd440f23ba0ca0c61434085221a45e74c1d4fbec9da6c08c68669829d6d61f9f8", + "block_time": 1800004600, + "event_id": "-9223371976725168124", + "transaction_index": 1, + "event_index": 3, + "transaction_hash": "0x82a2f4ea905b275b9e8ac2c0a899231bb72c5679d92888e06eef2ce0b0d4641b" + }, + "payout_event_id": "-9223371976725168121", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "341920493648704664787599", + "quote_token_amount": "5936415422760581" + } + ], + "totals": { + "launch_token": "341920493648704664787599", + "quote_token": "5936415422760581" + } + }, + "l4/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494906355", + "transaction_index": 2, + "event_index": 12, + "transaction_hash": "0x953e3e4c3901fd7081e1a41e36d189eec944f7424dc1b31b329e5aec8339e31c" + }, + "previous_owner": null, + "new_owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + } + ] + }, + "l5": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "name": "Zéro 🚀", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": "ipfs://bafyélogo 🚀", + "parameters": { + "name": "0x0a5ac3a9726f20f09f9a80000000000000000000000000000000000000000000", + "symbol": "0x024c350000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "18446744073709551615", + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "salt": "0xd0018fa3949db0f7478425347db6277a1a4e81620122b9ef181f47b2ade9574e" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay_rate_x32": "18446744073709551615", + "half_life_seconds": "0.000322", + "saturation_elapsed_seconds": "0", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13793034, + "price_x128": "348019647004654940455006368355745", + "market_cap": "1022737822572846286160", + "market_cap_usd": null, + "raised": "2999999999999999983", + "sold": "2941080334312777133468723", + "sold_share": "0.002941080334312777", + "trade_count": 5 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494840814", + "transaction_index": 3, + "event_index": 17, + "transaction_hash": "0xce4a04be8c2b72e774db76672444ff684141564fcfc9ea16844b62bc8bfc25e3" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000008c8c0000d28bba", + "liquidity": "1121810267768648385525007", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "997058919665687222866530836", + "quote_token": "2999999999999999983" + } + }, + "fee_ledger": { + "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "launch_token": { + "accrued": "987968410100088248326020", + "claimed": "987968410100088248326020", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "188009215599247750060111", + "claimed": "188009215599247750060111", + "unclaimed": "0" + }, + "swap_count": 5, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371972430135287" + } + }, + "l5/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998199742454", + "transaction_index": 5, + "event_index": 9, + "transaction_hash": "0xf778751bd389e81580d5381a5b322ce140f1ebb12cfe0a558e79227e13b9223e" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-53239", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "982091443427432213656798", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998199676915", + "transaction_index": 6, + "event_index": 12, + "transaction_hash": "0xa6f23cf7006ee13f513604f9de7222d400a221bdfec475aaed47f1392a8990b4" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "188009215599247750070272", + "fee_rate": "18446744073709551615", + "fee_amount": "188009215599247750060080", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "12", + "block_hash": "0xab1e4aacf8998e3270a5ff019a545e6f03e23e2dd56633f2f3fd1816ebd394ad", + "block_time": 1800002800, + "event_id": "-9223371985314971636", + "transaction_index": 3, + "event_index": 11, + "transaction_hash": "0x6d1511a15c2cf5a9c10e43f1f9457608b3c5ebdb6e9166a152247f78804c266b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-977417508254004529376415", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2941075752018067746301", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0x30fa40e7ca5f3c2bb44f3e4ab5d37bc729665bfd2c113bd44bf5b3bd2c23db71", + "block_time": 1800005000, + "event_id": "-9223371972430266366", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x049f0728be79d24e94ad0a49b876a3cbc210c06224fabeab319585dee4eba517" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "sell", + "launch_token_delta": "10000000000", + "quote_token_delta": "-10178", + "fee_rate": "55340232221128654", + "fee_amount": "31", + "fee_asset": "quote_token" + }, + { + "event": { + "block_number": "15", + "block_hash": "0x30fa40e7ca5f3c2bb44f3e4ab5d37bc729665bfd2c113bd44bf5b3bd2c23db71", + "block_time": 1800005000, + "event_id": "-9223371972430200827", + "transaction_index": 1, + "event_index": 4, + "transaction_hash": "0x16aff27ba3f402bb6b08377892c2fff3629670e70b3d9057282989bcef35d007" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-975694415958684355713049", + "quote_token_delta": "1000000000000000000", + "fee_rate": "55340232221128654", + "fee_amount": "2935890920637966922921", + "fee_asset": "launch_token" + } + ] + }, + "l5/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "15", + "block_hash": "0x30fa40e7ca5f3c2bb44f3e4ab5d37bc729665bfd2c113bd44bf5b3bd2c23db71", + "block_time": 1800005000, + "event_id": "-9223371972430135289", + "transaction_index": 2, + "event_index": 6, + "transaction_hash": "0x9dc3cc14ccb7e1faa7040a5653a737b9664a5bd06a4d8b5d8e787c2c821bbd94" + }, + "payout_event_id": "-9223371972430135287", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "987968410100088248326020", + "quote_token_amount": "188009215599247750060111" + } + ], + "totals": { + "launch_token": "987968410100088248326020", + "quote_token": "188009215599247750060111" + } + }, + "l5/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494840813", + "transaction_index": 3, + "event_index": 18, + "transaction_hash": "0xce4a04be8c2b72e774db76672444ff684141564fcfc9ea16844b62bc8bfc25e3" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + } + ] + }, + "l6": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "name": null, + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x035aff0000000000000000000000000000000000000000000000000000000000", + "symbol": "0x024c360000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "1", + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "salt": "0x87f22775ea9a15cba089cab3a4d0d92b968f5f1280cec686b6126b32bdfa72f8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay_rate_x32": "1", + "half_life_seconds": "5954091920683119.788732", + "saturation_elapsed_seconds": "381061679028371455", + "phase": "at_floor", + "fee_rate": "18446744073709551615", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -13796564, + "price_x128": "346793678803132136179439976012385", + "market_cap": "1019135025834901745761", + "market_cap_usd": null, + "raised": "1000000000000010192", + "sold": "982091443427442213916824", + "sold_share": "0.000982091443427442", + "trade_count": 2 + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494775272", + "transaction_index": 4, + "event_index": 23, + "transaction_hash": "0x8d219e66a3a734e1655a128a681f1f3ac6f5811ee4c1d1924cb8b2fe7a14254c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000ff2d7446ff737400", + "liquidity": "1121810267768648385524834", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999017908556572557786082684", + "quote_token": "1000000000000010192" + } + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "launch_token": { + "accrued": "982091443427432213863585", + "claimed": "982091443427432213863585", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "188009215599247750060080", + "claimed": "188009215599247750060080", + "unclaimed": "0" + }, + "swap_count": 2, + "claim_count": 1, + "owner_change_count": 1, + "last_event_id": "-9223371985314906095" + } + }, + "l6/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998199611376", + "transaction_index": 7, + "event_index": 15, + "transaction_hash": "0xf9048041ca7112458c58a9414a73121c6b4b2877a27cfbc57b6ab710e14d9b25" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-53239", + "quote_token_delta": "1000000000000000000", + "fee_rate": "18446744073709551615", + "fee_amount": "982091443427432213863585", + "fee_asset": "launch_token" + }, + { + "event": { + "block_number": "9", + "block_hash": "0x645b2482c9f83ac034a48b288f1c1325154e9f3fa6635a5f3e3948aa1533ed2e", + "block_time": 1800001400, + "event_id": "-9223371998199545836", + "transaction_index": 8, + "event_index": 19, + "transaction_hash": "0x7d3e67c4b16ec29c35c1238ec1697f58ca567cd03485bef19a7ed0515f4cfb0b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-10000000000", + "quote_token_delta": "188009215599247750070272", + "fee_rate": "18446744073709551615", + "fee_amount": "188009215599247750060080", + "fee_asset": "quote_token" + } + ] + }, + "l6/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "12", + "block_hash": "0xab1e4aacf8998e3270a5ff019a545e6f03e23e2dd56633f2f3fd1816ebd394ad", + "block_time": 1800002800, + "event_id": "-9223371985314906098", + "transaction_index": 4, + "event_index": 13, + "transaction_hash": "0x272380861fc975c0eeddd59d15f59009fc0dee24fa016408169a89154c5e0a3c" + }, + "payout_event_id": "-9223371985314906095", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "recipient": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "launch_token_amount": "982091443427432213863585", + "quote_token_amount": "188009215599247750060080" + } + ], + "totals": { + "launch_token": "982091443427432213863585", + "quote_token": "188009215599247750060080" + } + }, + "l6/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494775271", + "transaction_index": 4, + "event_index": 24, + "transaction_hash": "0x8d219e66a3a734e1655a128a681f1f3ac6f5811ee4c1d1924cb8b2fe7a14254c" + }, + "previous_owner": null, + "new_owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + } + ] + }, + "l7": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080002000", + "fee": "0", + "tick_spacing": 8192, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3700000000000000000000000000000000000000", + "symbol": "0x024c370000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88080384", + "upper_tick": "-85983232", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x3bb0077653d024b802a78f862b2d959f8be504b7f42df81a445180edd449da4e" + }, + "range": { + "tick_spacing": 8192, + "aligned_ask_tick": -88080384, + "aligned_upper_tick": -85983232, + "pool_tick_lower": -88080384, + "pool_tick_upper": -85983232 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -88080384, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1 + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135299067", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0xddc99d9b6de8f631e839ffe8c6cf47600c972b909e9793ac88a6e5b288ca1b1b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000fac00000fae00000", + "liquidity": "115073586432", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999999999891410763432286846442", + "quote_token": "1" + } + }, + "fee_ledger": { + "holder": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "launch_token": { + "accrued": "0", + "claimed": "0", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "1", + "claimed": "0", + "unclaimed": "1" + }, + "swap_count": 1, + "claim_count": 0, + "owner_change_count": 1, + "last_event_id": "-9223371963840331774" + } + }, + "l7/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "17", + "block_hash": "0x8f5572aaa27b0d285fff6b77cbaf21dc5e39a2c9e5f6d70e2149a512a7944857", + "block_time": 1800005200, + "event_id": "-9223371963840331774", + "transaction_index": 0, + "event_index": 1, + "transaction_hash": "0x016f254daa714fd812183aceb232e96481a537b0021716745f0e1086faef690b" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-100000000000000000000", + "quote_token_delta": "2", + "fee_rate": "9223372036854775808", + "fee_amount": "1", + "fee_asset": "quote_token" + } + ] + }, + "l7/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [], + "totals": { + "launch_token": "0", + "quote_token": "0" + } + }, + "l7/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135299066", + "transaction_index": 0, + "event_index": 5, + "transaction_hash": "0xddc99d9b6de8f631e839ffe8c6cf47600c972b909e9793ac88a6e5b288ca1b1b" + }, + "previous_owner": null, + "new_owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8" + } + ] + }, + "l8": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c0000000000000000800a939c", + "fee": "0", + "tick_spacing": 693148, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3800000000000000000000000000000000000000", + "symbol": "0x024c380000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88722835", + "upper_tick": "88722835", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x114620bab65d343f8f17a1536ca597c5641c5e6cbe4398524e79a406de0df42b" + }, + "range": { + "tick_spacing": 693148, + "aligned_ask_tick": -88029796, + "aligned_upper_tick": 88029796, + "pool_tick_lower": -88029796, + "pool_tick_upper": 88029796 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999987511320520150058769", + "sink": "12488679479849941231", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 2115361000 + }, + "market": { + "price_tick": -88029796, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1 + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135233525", + "transaction_index": 1, + "event_index": 10, + "transaction_hash": "0xb0a431eab9a08a25bb3542b81016c59e96d2c1ce978384640542b18d436e8153" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "principal": { + "position_id": "0x000000000000000000000000000000000000000000000000fac0c59c053f3a64", + "liquidity": "76662368857", + "locked": true, + "withdrawable": false, + "pool_reserves": { + "launch_token": "999999999887511320520150058769", + "quote_token": "1" + } + }, + "fee_ledger": { + "holder": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "launch_token": { + "accrued": "0", + "claimed": "0", + "unclaimed": "0" + }, + "quote_token": { + "accrued": "1", + "claimed": "0", + "unclaimed": "1" + }, + "swap_count": 1, + "claim_count": 0, + "owner_change_count": 1, + "last_event_id": "-9223371963840266234" + } + }, + "l8/swaps?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "17", + "block_hash": "0x8f5572aaa27b0d285fff6b77cbaf21dc5e39a2c9e5f6d70e2149a512a7944857", + "block_time": 1800005200, + "event_id": "-9223371963840266234", + "transaction_index": 1, + "event_index": 5, + "transaction_hash": "0x695c24f02302bcc35a29cadb6dba02d60b20e0a8e892cfc237d0a54fe7f48d1d" + }, + "forwarding_locker": "0x5fbdb2315678afecb367f032d93f642f64180aa3", + "direction": "buy", + "launch_token_delta": "-100000000000000000000", + "quote_token_delta": "2", + "fee_rate": "9223372036854775808", + "fee_amount": "1", + "fee_asset": "quote_token" + } + ] + }, + "l8/claims?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [], + "totals": { + "launch_token": "0", + "quote_token": "0" + } + }, + "l8/owners?limit=200": { + "schema_version": "zero-seed-launch-api/v6", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "next_cursor": null, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + }, + "data": [ + { + "event": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135233524", + "transaction_index": 1, + "event_index": 11, + "transaction_hash": "0xb0a431eab9a08a25bb3542b81016c59e96d2c1ce978384640542b18d436e8153" + }, + "previous_owner": null, + "new_owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" + } + ] + } +} diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 4999da0..15a367d 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -1,7 +1,5 @@ import { expect, test } from "bun:test"; -import golden from "./fixtures/zero-seed-launch-api-v5.golden.json"; -import tickRate from "./fixtures/zero-seed-launch-api-v5-tick-rate.golden.json"; -import { halfLifeSeconds, MAX_DECAY_PRODUCT } from "./policy"; +import golden from "./fixtures/zero-seed-launch-api-v6.golden.json"; import { ClaimPage, LaunchDetail, @@ -11,256 +9,44 @@ import { SwapPage, } from "./schema"; -// Written by scripts/verifyLaunchReplay.ts (LAUNCH_GOLDEN_DIR) from the actual -// handlers over the indexer's replayed local fixtures; a machine-readable -// example of every v5 response for integrators, per fee model. -test("the emitted-state golden responses parse with the v5 schemas", () => { - expect(golden.schema_version).toBe(SCHEMA_VERSION); - for (const list of [golden.list, golden.second, golden.canonicalList]) - LaunchList.parse(list); - for (const detail of [ - golden.nativeDetail, - golden.erc20Detail, - golden.erc20TraderOwnedDetail, - golden.maxNativeDetail, - golden.maxErc20Detail, - golden.lowPriceErc20Detail, - golden.fullWidthErc20Detail, - ]) - LaunchDetail.parse(detail); - for (const swaps of [ - golden.nativeSwaps, - golden.erc20Swaps, - golden.maxNativeSwaps, - golden.maxErc20Swaps, - ]) - SwapPage.parse(swaps); - for (const claims of [ - golden.nativeClaims, - golden.erc20Claims, - golden.maxNativeClaims, - golden.maxErc20Claims, - ]) - ClaimPage.parse(claims); - for (const owners of [golden.nativeOwners, golden.erc20Owners]) - OwnerPage.parse(owners); - expect(golden.nativeDetail.quote_token).toEqual({ - address: "0x0000000000000000000000000000000000000000", - name: null, - symbol: null, - decimals: 18, - kind: "native", - }); - expect(golden.erc20Detail.quote_token.kind).toBe("erc20"); -}); +// Written by replay.postgres.test.ts (LAUNCH_GOLDEN_DIR) from the actual +// handlers over the indexer's replayed local chain: a machine-readable +// example of every v6 response for integrators. +const responses = golden as unknown as Record; -test("native token1 and ERC-20 token0 launches share one extension and split requested/installed/sink exactly", () => { - const sink = "0x0000000000000000000000000000000000000000"; - const native = golden.nativeDetail; - const erc20 = golden.erc20Detail; - expect(native.pool_key.extension).toBe(erc20.pool_key.extension); - expect([native.launch_token_is_token1, erc20.launch_token_is_token1]).toEqual( - [true, false], - ); - expect(native.supply).toEqual({ - requested: "1000000000000000000000000000", - installed: "999999999999999999999999422", - sink: "578", - sink_holder: sink, - }); - expect(erc20.supply).toEqual({ - requested: "1000000000000000000000000000", - installed: "999999999999999999999999210", - sink: "790", - sink_holder: sink, - }); - for (const launch of [ - ...golden.canonicalList.data, - native, - erc20, - golden.erc20TraderOwnedDetail, - ]) { - const { requested, installed, sink } = launch.supply; - expect(BigInt(requested) - BigInt(installed)).toBe(BigInt(sink)); - expect(launch).not.toHaveProperty("principal.installed_supply"); - } - // creator stays; owner follows transfers - for (const [detail, owners] of [ - [native, golden.nativeOwners], - [erc20, golden.erc20Owners], - ] as const) { - expect(detail.creator).not.toBe(detail.owner); - expect(owners.data[0].new_owner).toBe(detail.creator); - expect(owners.data.at(-1)!.new_owner).toBe(detail.owner); +test("every golden response parses with the closed v6 schemas", () => { + LaunchList.parse(golden.list); + expect(golden.list.data.length).toBe(7); + for (const [key, body] of Object.entries(responses)) { + if (key === "source" || key === "list") continue; + const schema = key.endsWith("/swaps?limit=200") + ? SwapPage + : key.endsWith("/claims?limit=200") + ? ClaimPage + : key.endsWith("/owners?limit=200") + ? OwnerPage + : LaunchDetail; + const parsed = schema.parse(body) as { schema_version: string }; + expect(parsed.schema_version).toBe(SCHEMA_VERSION); } }); -const MAX = "18446744073709551615"; -const FINAL = "55340232221128654"; - -test("launches created at the maximum rate 2^64 - 1 are served exactly, never as 2^64", () => { - const l5 = golden.maxNativeDetail; - const l6 = golden.maxErc20Detail; - // l5: native quote, max -> 0.3% over 3600 s; l6: ERC-20 quote, flat max - expect([l5.quote_token.kind, l5.launch_token_is_token1]).toEqual([ - "native", - true, - ]); - expect([l6.quote_token.kind, l6.launch_token_is_token1]).toEqual([ - "erc20", - false, - ]); - expect(l5.fee_schedule).toMatchObject({ - model: "fixed_base_duration_v1", - initial_fee: MAX, - final_fee: FINAL, - fee_duration: 3600, - phase: "final_fee", - fee_rate: FINAL, - }); - expect(l6.fee_schedule).toMatchObject({ - initial_fee: MAX, - final_fee: MAX, - fee_duration: 1, - phase: "final_fee", - fee_rate: MAX, - }); - expect( - golden.canonicalList.data - .filter((l) => l.fee_schedule.initial_fee === MAX) - .map((l) => l.pool_id), - ).toEqual([l6.pool_id, l5.pool_id]); - // l5 decays from the maximum; its swaps carry the contract's feeAt - expect(golden.maxNativeSwaps.data.map((s) => s.fee_rate)).toEqual([ - MAX, - MAX, - "3470655575073783216", - FINAL, - FINAL, - ]); - expect(golden.maxErc20Swaps.data.map((s) => s.fee_rate)).toEqual([MAX, MAX]); - for (const [detail, swaps, claims] of [ - [l5, golden.maxNativeSwaps, golden.maxNativeClaims], - [l6, golden.maxErc20Swaps, golden.maxErc20Claims], - ] as const) { - // exact input at the maximum nets floor(gross / 2^64); exact output - // pays calculated * 2^64 - const [exactIn, exactOut] = swaps.data; - const net = -BigInt(exactIn.launch_token_delta); - expect(net).toBe(54160n); - expect((net + BigInt(exactIn.fee_amount)) / 2n ** 64n).toBe(net); - expect(BigInt(exactOut.quote_token_delta)).toBe(10019n * 2n ** 64n); - expect( - BigInt(exactOut.quote_token_delta) - BigInt(exactOut.fee_amount), - ).toBe(10019n); - // every fee accrues once to its asset's creator ledger and is claimed - for (const asset of ["launch_token", "quote_token"] as const) { - const accrued = swaps.data - .filter((s) => s.fee_asset === asset) - .reduce((n, s) => n + BigInt(s.fee_amount), 0n); - expect(detail.fee_ledger[asset].accrued).toBe(accrued.toString()); - expect(detail.fee_ledger[asset].unclaimed).toBe("0"); - expect( - claims.data.reduce((n, c) => n + BigInt(c[`${asset}_amount`]), 0n), - ).toBe(accrued); - } - } -}); - -test("low-price ERC-20 token0 launches serve the exact installation, including an empty sink", () => { - const sinkHolder = "0x0000000000000000000000000000000000000000"; - const l7 = golden.lowPriceErc20Detail; - const l8 = golden.fullWidthErc20Detail; - // 1e30 over ticks -88080384..-85983232 at spacing 524288: the largest - // affordable liquidity, 115073586432 (the shared helper gave 106052588672) - expect([ - l7.supply, - l7.principal.liquidity, - l7.launch_token_is_token1, - ]).toEqual([ - { - requested: "1000000000000000000000000000000", - installed: "999999999991410763432286846442", - sink: "8589236567713153558", - sink_holder: sinkHolder, - }, - "115073586432", - false, - ]); - // an int128-scale supply over the full width at spacing 1 installs exactly - expect([ - l8.supply, - l8.principal.liquidity, - l8.launch_token_is_token1, - ]).toEqual([ - { - requested: "170141183460469231694794709727778658247", - installed: "170141183460469231694794709727778658247", - sink: "0", - sink_holder: sinkHolder, - }, - "9223595582101085260", - false, - ]); - for (const detail of [l7, l8]) - expect(BigInt(detail.supply.installed) + BigInt(detail.supply.sink)).toBe( - BigInt(detail.supply.requested), - ); -}); - -test("tick-rate golden: every response parses, rates are decimal strings and schedules have a floor, not an end", () => { - expect(tickRate.schema_version).toBe(SCHEMA_VERSION); - expect(tickRate.source.fee_model).toBe("tick_rate_v2"); - LaunchList.parse(tickRate.list); - const launches = [ - tickRate.l1, - tickRate.l2, - tickRate.l4, - tickRate.l5, - tickRate.l6, - ]; - for (const { detail, swaps, claims } of launches) { - LaunchDetail.parse(detail); - SwapPage.parse(swaps); - ClaimPage.parse(claims); - } - for (const launch of [ - ...tickRate.list.data, - ...launches.map((l) => l.detail), - ]) { - const schedule = launch.fee_schedule; - expect(schedule.model).toBe("tick_rate_v2"); - expect(schedule).not.toHaveProperty("fee_end"); - expect(schedule).not.toHaveProperty("fee_duration"); - if (schedule.model !== "tick_rate_v2") throw new Error("model"); - const rate = BigInt(schedule.decay_rate_x32); - expect(schedule.half_life_seconds).toBe(halfLifeSeconds(rate)); - expect(schedule.saturation_elapsed_seconds).toBe( - rate === 0n ? null : (MAX_DECAY_PRODUCT / rate).toString(), - ); +test("golden: logos, statuses and claims as served", () => { + expect(golden.l1.logo_url).toBe("https://example.invalid/l1.svg"); + expect(golden.l2.logo_url).toBeNull(); + expect(golden.l4.logo_url).toBeNull(); + expect(golden.l1.creator).not.toBe(golden.l1.owner); + expect(golden.l1.fee_schedule.evaluated_at).toBe(golden.l1.as_of.block_time); + for (const label of ["l1", "l2", "l4", "l5", "l6"] as const) { + const detail = golden[label]; + const claims = responses[`${label}/claims?limit=200`] as { + data: unknown[]; + totals: unknown; + }; + expect(claims.data.length).toBe(detail.fee_ledger.claim_count); + expect(claims.totals).toEqual({ + launch_token: detail.fee_ledger.launch_token.claimed, + quote_token: detail.fee_ledger.quote_token.claimed, + }); } - expect( - tickRate.list.data.map((l) => l.fee_schedule.decay_rate_x32).sort(), - ).toEqual( - [ - "0", - "1", - "68913100933", - "68913100933", - "68913100933", - "9923486534471", - MAX, - ].sort(), - ); - // rate 0 is flat at the initial fee; every other launch is at its floor by the head - expect(tickRate.l4.detail.fee_schedule).toMatchObject({ - decay_rate_x32: "0", - phase: "constant", - half_life_seconds: null, - }); - expect(tickRate.l1.detail.fee_schedule).toMatchObject({ - decay_rate_x32: "9923486534471", - half_life_seconds: "600.000000", - phase: "at_floor", - }); }); diff --git a/src/routes/launches/replay.postgres.test.ts b/src/routes/launches/replay.postgres.test.ts new file mode 100644 index 0000000..3467925 --- /dev/null +++ b/src/routes/launches/replay.postgres.test.ts @@ -0,0 +1,783 @@ +// Actual-handler replay: a local Postgres is populated by the indexer's own +// migrations, stream grouping, processors and DAO from its emitted local +// anvil chain (evm-contracts 853b608), and every launch route is read +// through the app and checked against the raw logs and the chain state the +// fixture recorded after each block. Skipped unless both are given: +// +// ZERO_SEED_INDEXER_WORKTREE=/path/to/indexer (at INDEXER_COMMIT) +// TEST_PG_CONNECTION_STRING=postgres://...@127.0.0.1:port/postgres +// +// Only loopback Postgres is accepted; the test creates and drops its own +// databases. No RPC, provider or remote database is used. +import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test"; +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import postgres from "postgres"; +import { app } from "../../router"; +import { Queries } from "../../queries"; +import { LOCAL_INSTANCES, LOCAL_QUOTES, utf8OrNull } from "./policy"; +import { LaunchQueries } from "./queries"; + +const ROOT = process.env.ZERO_SEED_INDEXER_WORKTREE; +const SERVER = process.env.TEST_PG_CONNECTION_STRING; +// The indexer revision and fixture this replay was written against. +export const INDEXER_COMMIT = "942a060da475039518a471c0ac02fe200a69fe2d"; +export const FIXTURE_SHA256 = + "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6"; + +type Hex = `0x${string}`; +type Body = Record; +interface RawLog { + address: Hex; + topics: Hex[]; + data: Hex; + blockNumber: Hex; + transactionHash: Hex; + logIndex: Hex; +} +interface Pool { + label: string; + poolId: Hex; + creatorFees: [string, string]; + launchOwner: Hex; + controller: Hex; + feeAtBlockTime: string; + totalSupply: string; + sinkBalance: string; +} +interface Branch { + blocks: { number: number; timestamp: number }[]; + logs: RawLog[]; + states: { blockNumber: number; pools: Pool[] }[]; +} +interface Launch { + poolId: Hex; + token: Hex; + quote: Hex; + quoteKind: "native" | "erc20"; + tokenIsToken1: boolean; + owner: Hex; + requestedSupply: string; + installedSupply: string; + sinkSupply: string; + liquidity: string; + logoUrl: Hex; + tickSpacing: number; + alignedAskTick: number; + alignedUpperTick: number; +} +interface Fixture { + chainId: number; + addresses: Record; + instances: { + extension: Hex; + periphery: Hex; + extensionRuntimeKeccak: Hex; + peripheryRuntimeKeccak: Hex; + deploymentBlock: number; + }[]; + launches: Record; + installations: Record; + forkPoint: number; + branches: { prefix: Branch; alternate: Branch; canonical: Branch }; +} +interface Dao { + begin(fn: (tx: Dao) => Promise): Promise; + deleteOldBlockNumbers(n: number): Promise; + insertBlock(b: { + number: number; + hash: bigint; + time: Date; + baseFeePerGas: null; + numEvents: number; + }): Promise; +} +interface StreamBlock { + header: { blockNumber: bigint }; + logs: { filterIds: number[] }[]; +} +interface Processor { + address: Hex; + filter: { topics: (Hex | null)[]; strict: boolean }; +} +interface Indexer { + DAO: new (sql: unknown, chainId: bigint) => Dao; + NumericIntegerType: unknown; + parseEvmBlockHeader(block: unknown): { + header: { number: number; hash: bigint; timestamp: number }; + block: unknown; + }; + processEvmBlock( + processors: Processor[], + block: unknown, + n: number, + dao: Dao, + ): Promise; + createLogProcessorsV3(config: Record): Processor[]; + groupLogsByBlock(logs: RawLog[], filters: unknown[]): StreamBlock[]; + shift(o: { sql: unknown; path: string }): Promise; + postgres: typeof postgres; +} + +const lower = (a: string) => a.toLowerCase(); +const word = (data: Hex, i: number) => + BigInt(`0x${data.slice(2 + 64 * i, 66 + 64 * i)}`); +const hex32 = (n: bigint) => `0x${n.toString(16).padStart(64, "0")}`; +const signed32 = (n: bigint) => Number(BigInt.asIntN(32, n)); +const addr = (t: Hex) => `0x${t.slice(26)}`; + +// The frozen topics (evm-contracts 853b608, vectors/zero-seed-launch.json). +const VECTORS = JSON.parse( + readFileSync( + resolve(import.meta.dir, "vectors/zero-seed-launch.json"), + "utf8", + ), +) as { abi: Record }> }; +const topicOf = (contract: string, prefix: string) => + Object.entries(VECTORS.abi[contract].events).find(([s]) => + s.startsWith(`${prefix}(`), + )![1]; +const T = { + created: topicOf("ZeroSeedLaunch", "LaunchCreated"), + swapped: topicOf("ZeroSeedLaunch", "LaunchSwapped"), + claimed: topicOf("ZeroSeedLaunch", "CreatorFeesClaimed"), + paid: topicOf("ZeroSeedLaunchPeriphery", "LaunchFeesClaimed"), + owner: topicOf("ZeroSeedLaunchPeriphery", "LaunchOwnerChanged"), +}; + +describe.skipIf(!ROOT || !SERVER)( + "launch routes over the indexer's replayed local chain", + () => { + let ix: Indexer; + let fx: Fixture; + let admin: ReturnType; + const template = `zsl_api_${process.pid}_${Date.now()}`; + const created: string[] = []; + let CHAIN: bigint; + let processors: Processor[]; + + const urlFor = (db: string) => { + const url = new URL(SERVER); + url.pathname = `/${db}`; + return url.toString(); + }; + + beforeAll(async () => { + const url = new URL(SERVER); + if (!["127.0.0.1", "localhost"].includes(url.hostname)) + throw new Error("Only loopback test Postgres is permitted"); + const bytes = readFileSync( + resolve(ROOT, "tests/fixtures/zero-seed-launch/local-chain.json"), + ); + expect(createHash("sha256").update(bytes).digest("hex")).toBe( + FIXTURE_SHA256, + ); + fx = JSON.parse(bytes.toString("utf8")) as Fixture; + CHAIN = BigInt(fx.chainId); + const load = async (path: string) => + (await import(resolve(ROOT, path))) as Record; + const dao = await load("src/_shared/dao.ts"); + const evm = await load("src/evm.ts"); + const v3 = await load("src/evm/logProcessorsV3.ts"); + const stream = await load("src/evm/logStream.ts"); + const shiftModule = (await import( + Bun.resolveSync("postgres-shift", ROOT) + )) as { default: Indexer["shift"] }; + const pg = (await import(Bun.resolveSync("postgres", ROOT))) as { + default: typeof postgres; + }; + ix = { + ...(dao as unknown as Pick), + ...(evm as unknown as Pick< + Indexer, + "parseEvmBlockHeader" | "processEvmBlock" + >), + ...(v3 as unknown as Pick), + ...(stream as unknown as Pick), + shift: shiftModule.default, + postgres: pg.default, + }; + const unused = (n: number) => `0x${n.toString(16).padStart(40, "0")}`; + const instance = fx.instances[0]; + processors = ix.createLogProcessorsV3({ + mevCaptureAddress: unused(1), + boostedFeesConcentratedAddress: unused(2), + boostedFeesStableswapAddress: unused(3), + coreAddress: fx.addresses.core, + oracleAddress: unused(4), + incentivesAddress: unused(5), + tokenWrapperFactoryAddress: unused(6), + auctionsAddress: unused(7), + twammAddresses: [], + ordersAddresses: [], + positionsContracts: [], + zeroSeedLaunchPins: [ + { + chainId: CHAIN, + address: instance.extension, + runtimeCodehash: instance.extensionRuntimeKeccak, + periphery: instance.periphery, + core: fx.addresses.core, + deploymentBlock: instance.deploymentBlock, + local: true, + source: "tests/fixtures/zero-seed-launch/local-chain.json", + }, + ], + }); + admin = postgres(SERVER, { max: 1, onnotice: () => {} }); + await admin.unsafe(`CREATE DATABASE ${template}`); + const sql = ix.postgres(urlFor(template), { max: 1, onnotice: () => {} }); + await ix.shift({ sql, path: resolve(ROOT, "migrations") }); + await sql.end(); + }, 300_000); + + afterAll(async () => { + for (const name of [...created, template]) + await admin?.unsafe(`DROP DATABASE IF EXISTS ${name} WITH (FORCE)`); + await admin?.end(); + }, 60_000); + + // ---- the indexer side ---------------------------------------------------- + + let counter = 0; + async function freshDb() { + const name = `${template}_${++counter}`; + await admin.unsafe(`CREATE DATABASE ${name} TEMPLATE ${template}`); + created.push(name); + const sql = ix.postgres(urlFor(name), { + max: 1, + onnotice: () => {}, + types: { + bigint: ix.postgres.BigInt, + numeric: ix.NumericIntegerType as never, + }, + }); + await sql`INSERT INTO indexer_cursor (chain_id, order_key, unique_key, last_updated, fork_counter) + VALUES (${CHAIN.toString()}, 0, NULL, NOW(), 0)`; + return { name, sql, dao: new ix.DAO(sql, CHAIN) }; + } + type Db = Awaited>; + + const filters = () => + processors.map((p, i) => ({ + id: i + 1, + address: p.address, + topics: p.filter.topics, + strict: p.filter.strict, + })); + + /** The runtime's per-block transaction, then the cursor head the API reads. */ + async function replay( + db: Db, + branch: Branch, + check?: (state: Branch["states"][number], time: bigint) => Promise, + ) { + const blocks = ix.groupLogsByBlock(branch.logs, filters()); + for (const fixtureBlock of branch.blocks) { + const block = blocks.find( + (b) => Number(b.header.blockNumber) === fixtureBlock.number, + ); + if (block) { + const parsed = ix.parseEvmBlockHeader(block); + await db.dao.begin(async (tx) => { + await tx.deleteOldBlockNumbers(parsed.header.number); + await tx.insertBlock({ + number: parsed.header.number, + hash: parsed.header.hash, + time: new Date(parsed.header.timestamp), + baseFeePerGas: null, + numEvents: block.logs.reduce((n, l) => n + l.filterIds.length, 0), + }); + await ix.processEvmBlock( + processors, + parsed.block, + parsed.header.number, + tx, + ); + }); + } + await setHead(db); + const state = branch.states.find( + (s) => s.blockNumber === fixtureBlock.number, + ); + if (check && state) await check(state, BigInt(fixtureBlock.timestamp)); + } + } + + async function setHead(db: Db) { + await db.sql`UPDATE indexer_cursor SET + head_block_number = b.block_number, head_block_hash = b.block_hash, + head_block_time = b.block_time, last_updated = NOW() + FROM (SELECT * FROM blocks WHERE chain_id = ${CHAIN.toString()} + ORDER BY block_number DESC LIMIT 1) b + WHERE indexer_cursor.chain_id = ${CHAIN.toString()}`; + } + + // ---- the API side -------------------------------------------------------- + + /** Routes the app's query delegate to a local reader over the API's own connection settings. */ + function serve(db: Db, now: () => bigint, quotes = LOCAL_QUOTES) { + const apiSql = postgres(urlFor(db.name), { + max: 1, + fetch_types: false, + onnotice: () => {}, + types: { bigint: postgres.BigInt }, + }); + const reader = new LaunchQueries(apiSql as never, { + localHarness: true, + instances: LOCAL_INSTANCES, + quotes, + maxIndexAgeSeconds: 60, + now, + }); + const spies = [ + spyOn(Queries.prototype, "readZeroSeedList").mockImplementation((i) => + reader.list(i), + ), + spyOn(Queries.prototype, "readZeroSeedDetail").mockImplementation( + (c, p) => reader.detail(c, p), + ), + spyOn(Queries.prototype, "readZeroSeedSwaps").mockImplementation((i) => + reader.swaps(i), + ), + spyOn(Queries.prototype, "readZeroSeedClaims").mockImplementation((i) => + reader.claims(i), + ), + spyOn(Queries.prototype, "readZeroSeedOwners").mockImplementation((i) => + reader.owners(i), + ), + ]; + const get = async (path: string, status = 200) => { + const response = await app.fetch( + new Request(`http://localhost${path}`), + { PG_CONNECTION_STRING: urlFor(db.name) }, + ); + const body = (await response.json()) as never; + if (response.status !== status) + throw new Error( + `${path}: ${response.status} ${JSON.stringify(body)}`, + ); + return body as Body; + }; + const stop = async () => { + spies.forEach((s) => s.mockRestore()); + await apiSql.end(); + }; + return { get, stop }; + } + + const path = (label: string) => + `/launches/${CHAIN}/${fx.launches[label].poolId}`; + const logsOf = ( + branches: Branch[], + topic: Hex, + poolId: Hex, + upTo = Number.MAX_SAFE_INTEGER, + ) => + branches + .flatMap((b) => b.logs) + .filter( + (l) => + l.topics[0] === topic && + l.topics[1] === poolId && + Number(l.blockNumber) <= upTo, + ); + + /** One launch at one block: detail versus the raw LaunchCreated log and the chain state. */ + async function expectLaunch( + get: ReturnType["get"], + pool: Pool, + branches: Branch[], + upTo: number, + ) { + const launch = fx.launches[pool.label]; + const body = await get(path(pool.label)); + const [log] = logsOf(branches, T.created, launch.poolId); + const d = log.data; + expect(body.schema_version).toBe("zero-seed-launch-api/v6"); + expect(body.token.address).toBe(lower(addr(log.topics[2]))); + expect(body.controller).toBe(lower(addr(log.topics[3]))); + expect(body.parameters).toEqual({ + name: hex32(word(d, 5)), + symbol: hex32(word(d, 6)), + supply: word(d, 7).toString(), + quote_token: `0x${word(d, 8).toString(16).padStart(40, "0")}`, + ask_tick: signed32(word(d, 9)).toString(), + upper_tick: signed32(word(d, 10)).toString(), + trading_start: word(d, 11).toString(), + decay_rate_x32: word(d, 12).toString(), + initial_fee: word(d, 13).toString(), + final_fee: word(d, 14).toString(), + salt: hex32(word(d, 15)), + }); + // decoded from the packed words: length byte, then content + const text = (w: bigint) => { + const length = Number(w >> 248n); + return utf8OrNull( + Buffer.from(hex32(w).slice(4, 4 + 2 * length), "hex"), + ); + }; + expect([body.token.name, body.token.symbol, body.token.decimals]).toEqual( + [text(word(d, 5)), text(word(d, 6)), 18], + ); + // logo: null when empty or not valid UTF-8, otherwise exactly as emitted + const logo = Buffer.from(launch.logoUrl.slice(2), "hex"); + expect(body.logo_url).toBe(utf8OrNull(logo)); + expect(body.range).toMatchObject({ + tick_spacing: launch.tickSpacing, + aligned_ask_tick: launch.alignedAskTick, + aligned_upper_tick: launch.alignedUpperTick, + }); + expect(body.pool_key.tick_spacing).toBe(launch.tickSpacing); + expect(body.supply).toEqual({ + requested: pool.totalSupply, + installed: launch.installedSupply, + sink: pool.sinkBalance, + sink_holder: "0x0000000000000000000000000000000000000000", + }); + const is1 = body.launch_token_is_token1 as boolean; + expect(is1).toBe(launch.tokenIsToken1); + expect(body.fee_ledger.launch_token.unclaimed).toBe( + pool.creatorFees[is1 ? 1 : 0], + ); + expect(body.fee_ledger.quote_token.unclaimed).toBe( + pool.creatorFees[is1 ? 0 : 1], + ); + expect([body.owner, body.fee_ledger.holder, body.creator]).toEqual([ + lower(pool.launchOwner), + lower(pool.launchOwner), + lower(fx.installations[pool.label].owner), + ]); + expect(body.controller).toBe(lower(pool.controller)); + expect(body.quote_token.kind).toBe(launch.quoteKind); + // fee rate at the head block equals the contract's feeAt there + expect(body.fee_schedule.fee_rate).toBe(pool.feeAtBlockTime); + expect(body.status).toBe( + body.as_of.block_time < body.trading_start ? "upcoming" : "trading", + ); + expect(body.market.trade_count).toBe(body.fee_ledger.swap_count); + expect(body.market.trade_count).toBe( + logsOf(branches, T.swapped, launch.poolId, upTo).length, + ); + expect(BigInt(body.market.sold)).toBe( + BigInt(launch.installedSupply) - + BigInt(body.principal.pool_reserves.launch_token), + ); + expect(body.market.raised).toBe(body.principal.pool_reserves.quote_token); + return body; + } + + /** Claims: one row per extension claim, never the payout twice. */ + async function expectClaims( + get: ReturnType["get"], + label: string, + branches: Branch[], + ) { + const launch = fx.launches[label]; + const detail = await get(path(label)); + const claims = await get(`${path(label)}/claims?limit=200`); + const ledgerLogs = logsOf(branches, T.claimed, launch.poolId); + const payoutLogs = logsOf(branches, T.paid, launch.poolId); + expect(claims.data.length).toBe(ledgerLogs.length); + expect(claims.data.length).toBe(payoutLogs.length); + expect(claims.data.length).toBe(detail.fee_ledger.claim_count); + const is1 = launch.tokenIsToken1; + const sum = (logs: RawLog[], i: number) => + logs.reduce((n, l) => n + word(l.data, i), 0n).toString(); + const [tokenWord, quoteWord] = is1 ? [1, 0] : [0, 1]; + for (const logs of [ledgerLogs, payoutLogs]) { + expect(sum(logs, tokenWord)).toBe( + detail.fee_ledger.launch_token.claimed, + ); + expect(sum(logs, quoteWord)).toBe( + detail.fee_ledger.quote_token.claimed, + ); + } + expect(claims.totals).toEqual({ + launch_token: detail.fee_ledger.launch_token.claimed, + quote_token: detail.fee_ledger.quote_token.claimed, + }); + // owner and recipient come from the payout of the same transaction + claims.data.forEach((c: Body, i: number) => { + const payout = payoutLogs[i]; + expect(c.event.transaction_hash).toBe(ledgerLogs[i].transactionHash); + expect(c.payout_event_id).not.toBe(c.event.event_id); + expect([c.owner, c.recipient]).toEqual([ + lower(addr(payout.topics[2])), + lower(addr(payout.topics[3])), + ]); + }); + return claims; + } + + async function everyResponse(get: ReturnType["get"]) { + const out: Record = { + list: await get(`/launches?order_by=created&limit=200`), + }; + for (const label of ["l1", "l2", "l4", "l5", "l6", "l7", "l8"]) + for (const suffix of [ + "", + "/swaps?limit=200", + "/claims?limit=200", + "/owners?limit=200", + ]) + out[`${label}${suffix}`] = await get(`${path(label)}${suffix}`); + return out; + } + + test("every block: detail equals the raw logs and chain state; claims once; owners; lists; reorg and a second replay are identical", async () => { + const db = await freshDb(); + let time = 0n; + const { get, stop } = serve(db, () => time); + const { prefix, alternate, canonical } = fx.branches; + try { + const at = + (branches: Branch[]) => + async (state: Branch["states"][number], blockTime: bigint) => { + time = blockTime; + for (const pool of state.pools) + await expectLaunch(get, pool, branches, state.blockNumber); + }; + await replay(db, prefix, at([prefix])); + await replay(db, alternate, at([prefix, alternate])); + const forked = await get(path("l3")); + expect(forked.creator).toBe(lower(fx.addresses.newOwner)); + // reorg onto the canonical branch: l3 and the alternate transfers vanish + await db.dao.begin((tx) => tx.deleteOldBlockNumbers(fx.forkPoint + 1)); + await replay(db, canonical, at([prefix, canonical])); + for (const suffix of ["", "/swaps", "/claims", "/owners"]) + await get(`${path("l3")}${suffix}`, 404); + for (const label of ["l1", "l2", "l4", "l5", "l6"]) + await expectClaims(get, label, [prefix, canonical]); + + // logos as emitted: empty and invalid UTF-8 are null, the rest raw + const logos = Object.fromEntries( + await Promise.all( + ["l1", "l2", "l4", "l5"].map(async (l) => [ + l, + (await get(path(l))).logo_url, + ]), + ), + ); + expect(logos).toEqual({ + l1: "https://example.invalid/l1.svg", + l2: null, + l4: null, + l5: "ipfs://bafyélogo 🚀", + }); + expect(fx.launches.l4.logoUrl).not.toBe("0x"); + // owners: the full history from the raw logs, creator first, paged + for (const label of ["l1", "l2"]) { + const owners = await get(`${path(label)}/owners`); + const logs = logsOf( + [prefix, canonical], + T.owner, + fx.launches[label].poolId, + ); + expect( + owners.data.map((o: Record) => [ + o.previous_owner, + o.new_owner, + ]), + ).toEqual( + logs.map((l) => [ + BigInt(l.topics[2]) === 0n ? null : lower(addr(l.topics[2])), + lower(addr(l.topics[3])), + ]), + ); + const first = await get(`${path(label)}/owners?limit=1`); + const rest = await get( + `${path(label)}/owners?after_event_id=${first.next_cursor}`, + ); + expect([...first.data, ...rest.data]).toEqual(owners.data); + const detail = await get(path(label)); + expect(detail.owner).toBe(owners.data.at(-1).new_owner); + expect(detail.creator).toBe(owners.data[0].new_owner); + expect(detail.owner).not.toBe(detail.creator); + } + // l1 was transferred twice and then claimed by its new owner + const l1Claims = await get(`${path("l1")}/claims`); + expect( + l1Claims.data.map((c: Record) => c.owner), + ).toContain(lower(fx.addresses.feeRecipient)); + // swaps and claims page without loss or overlap + for (const suffix of ["swaps", "claims"]) { + const all = await get(`${path("l1")}/${suffix}?limit=200`); + const pages = []; + let cursor: string | null = null; + do { + const page = await get( + `${path("l1")}/${suffix}?limit=2${cursor ? `&after_event_id=${cursor}` : ""}`, + ); + pages.push(...page.data); + cursor = page.next_cursor; + } while (cursor); + expect(pages).toEqual(all.data); + } + + // lists: across chains (chainId omitted) equals the chain's own list + const created = await get(`/launches?order_by=created`); + expect( + created.data.map((l: Record) => l.pool_id), + ).toEqual( + ["l8", "l7", "l6", "l5", "l4", "l2", "l1"].map( + (l) => fx.launches[l].poolId, + ), + ); + expect( + await get(`/launches?chainId=${CHAIN}&order_by=created`), + ).toEqual(created); + expect([created.total, created.totals, created.next_offset]).toEqual([ + 7, + { all: 7, trading: 7, upcoming: 0 }, + null, + ]); + expect(created.heads).toEqual([ + { chain_id: CHAIN.toString(), ...created.data[0].as_of }, + ]); + // offset paging + const first = await get(`/launches?order_by=created&limit=3`); + const second = await get( + `/launches?order_by=created&limit=3&offset=${first.next_offset}`, + ); + const third = await get( + `/launches?order_by=created&limit=3&offset=${second.next_offset}`, + ); + expect([ + first.next_offset, + second.next_offset, + third.next_offset, + ]).toEqual([3, 6, null]); + expect([...first.data, ...second.data, ...third.data]).toEqual( + created.data, + ); + // status: before l7/l8's trading start they are upcoming + const l7Start = BigInt(fx.installations.l7.tradingStart); + time = l7Start - 1n; + const upcoming = await get(`/launches?status=upcoming`); + expect( + upcoming.data.map((l: Record) => l.pool_id).sort(), + ).toEqual([fx.launches.l7.poolId, fx.launches.l8.poolId].sort()); + expect( + upcoming.data.every( + (l: Record) => l.status === "upcoming", + ), + ).toBe(true); + expect([upcoming.total, upcoming.totals]).toEqual([ + 2, + { all: 7, trading: 5, upcoming: 2 }, + ]); + const trading = await get(`/launches?status=trading`); + expect(trading.total).toBe(5); + expect((await get(path("l7"))).fee_schedule.phase).toBe( + "before_trading_start", + ); + time = l7Start; + expect((await get(`/launches?status=upcoming`)).total).toBe(0); + expect((await get(path("l7"))).status).toBe("trading"); + // owner filter is the current owner; creator the original one + const owned = await get(`/launches?owner=${fx.addresses.feeRecipient}`); + expect( + owned.data.map((l: Record) => l.pool_id), + ).toEqual([fx.launches.l1.poolId]); + expect( + ( + await get( + `/launches?owner=${BigInt(fx.addresses.creator).toString()}&order_by=created`, + ) + ).data.map((l: Record) => l.pool_id), + ).toEqual(["l7", "l5"].map((l) => fx.launches[l].poolId)); + expect( + ( + await get( + `/launches?creator=${fx.addresses.creator}&order_by=created`, + ) + ).total, + ).toBe(4); + expect((await get(`/launches?owner=0x${"9".repeat(40)}`)).data).toEqual( + [], + ); + // market-cap order: by USD when the quote has a price, the rest after + const byCap = await get(`/launches`); + expect( + byCap.data.every((l: Body) => l.market.market_cap_usd === null), + ).toBe(true); + await db.sql`INSERT INTO erc20_tokens_latest_price (chain_id, token_address, source, "timestamp", value, confidence, valid_until) + VALUES (${CHAIN.toString()}, 0, 'tst', NOW(), 2000, 100, NOW() + INTERVAL '1 hour')`; + const priced = await get(`/launches`); + const native = priced.data.filter( + (l: Body) => l.quote_token.kind === "native", + ); + expect(priced.data.slice(0, native.length)).toEqual(native); + const usd = native.map((l: Body) => Number(l.market.market_cap_usd)); + expect([...usd].sort((a, b) => b - a)).toEqual(usd); + for (const l of native) { + const mc = BigInt(l.market.market_cap); + expect(Number(l.market.market_cap_usd)).toBeCloseTo( + (Number(mc) / 1e18) * 2000, + 0, + ); + } + await db.sql`DELETE FROM erc20_tokens_latest_price WHERE source = 'tst'`; + // unsupported ERC-20 quotes are screened before paging + await stop(); + const nativeOnly = serve(db, () => time, []); + const page = await nativeOnly.get(`/launches?order_by=created&limit=2`); + expect( + page.data.map((l: Record) => l.pool_id), + ).toEqual(["l5", "l1"].map((l) => fx.launches[l].poolId)); + expect(page.total).toBe(2); + await nativeOnly.get(path("l2"), 404); + await nativeOnly.stop(); + + // a straight second replay into a fresh database serves identical bodies, + // evaluated at the head block + time = BigInt(canonical.blocks.at(-1)!.timestamp); + const reread = serve(db, () => time); + const first1 = await everyResponse(reread.get); + if (process.env.LAUNCH_GOLDEN_DIR) + await Bun.write( + resolve( + process.env.LAUNCH_GOLDEN_DIR, + "zero-seed-launch-api-v6.golden.json", + ), + `${JSON.stringify( + { + source: { + indexer_commit: INDEXER_COMMIT, + fixture_sha256: FIXTURE_SHA256, + evaluated_at: Number(time), + }, + ...first1, + }, + null, + 2, + )}\n`, + ); + await reread.stop(); + const db2 = await freshDb(); + await replay(db2, prefix); + await replay(db2, canonical); + // block heads are identical; restore the same evaluation time + const again = serve(db2, () => time); + expect(await everyResponse(again.get)).toEqual(first1); + // redelivery: re-applying the last blocks changes nothing + await replay(db2, { ...canonical, blocks: canonical.blocks.slice(-3) }); + expect(await everyResponse(again.get)).toEqual(first1); + await again.stop(); + await db2.sql.end(); + + // stale or incoherent heads are 503, naming the chain + const late = serve(db, () => time); + await db.sql`UPDATE indexer_cursor SET last_updated = NOW() - INTERVAL '2 minutes'`; + await late.get(path("l1"), 503); + await late.get(`/launches`, 503); + await late.stop(); + } finally { + await stop().catch(() => {}); + await db.sql.end(); + } + }, 300_000); + }, +); From a0738c4d18436faa6d571b242b2f665f80ee432d Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sun, 25 Oct 2026 19:48:46 -0400 Subject: [PATCH 17/19] Pin the launch replay to the indexer's documented final launch commit --- .../launches/fixtures/zero-seed-launch-api-v6.golden.json | 2 +- src/routes/launches/replay.postgres.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json index 8925a2a..0ba845f 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json @@ -1,6 +1,6 @@ { "source": { - "indexer_commit": "942a060da475039518a471c0ac02fe200a69fe2d", + "indexer_commit": "2435c89bb52fdcc7f1660031f16b8d11b8d9ffdd", "fixture_sha256": "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6", "evaluated_at": 2115361000 }, diff --git a/src/routes/launches/replay.postgres.test.ts b/src/routes/launches/replay.postgres.test.ts index 3467925..dbd0d94 100644 --- a/src/routes/launches/replay.postgres.test.ts +++ b/src/routes/launches/replay.postgres.test.ts @@ -22,7 +22,7 @@ import { LaunchQueries } from "./queries"; const ROOT = process.env.ZERO_SEED_INDEXER_WORKTREE; const SERVER = process.env.TEST_PG_CONNECTION_STRING; // The indexer revision and fixture this replay was written against. -export const INDEXER_COMMIT = "942a060da475039518a471c0ac02fe200a69fe2d"; +export const INDEXER_COMMIT = "2435c89bb52fdcc7f1660031f16b8d11b8d9ffdd"; export const FIXTURE_SHA256 = "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6"; From 90d13a6e83de5f0df69539586f243a050e573f32 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sun, 25 Oct 2026 21:05:20 -0400 Subject: [PATCH 18/19] Serve launch volume, list orders and market aggregates as contract v7 Adds market.volume (|quote delta| summed over every LaunchSwapped, raw quote units) and market.volume_usd, list order_by volume, sold_share and trade_count ranked in SQL over the whole filtered set, and list aggregates: per-quote raw totals, a USD total over priced launches only and the count of launches left out of it. --- openapi.json | 190 +++- src/contract/cases/launches.ts | 27 +- src/routes/launches/README.md | 51 +- ...on => zero-seed-launch-api-v7.golden.json} | 896 +++++++++++++++++- src/routes/launches/golden.test.ts | 37 +- src/routes/launches/index.ts | 16 +- src/routes/launches/policy.test.ts | 9 +- src/routes/launches/policy.ts | 4 + src/routes/launches/queries.ts | 124 ++- src/routes/launches/replay.postgres.test.ts | 206 +++- src/routes/launches/routes.test.ts | 3 +- src/routes/launches/schema.ts | 76 +- 12 files changed, 1532 insertions(+), 107 deletions(-) rename src/routes/launches/fixtures/{zero-seed-launch-api-v6.golden.json => zero-seed-launch-api-v7.golden.json} (74%) diff --git a/openapi.json b/openapi.json index 56f6730..9c4f6ce 100644 --- a/openapi.json +++ b/openapi.json @@ -9941,7 +9941,7 @@ "Launches" ], "operationId": "get_ListLaunches", - "summary": "List supported zero-seed launches across chains, with status, owner and creator filters", + "summary": "List supported zero-seed launches across chains, with status, owner and creator filters, ordering and market aggregates", "parameters": [ { "schema": { @@ -10015,11 +10015,16 @@ "type": "string", "enum": [ "market_cap", + "volume", + "sold_share", + "trade_count", "created" ], - "default": "market_cap" + "default": "market_cap", + "description": "Descending, over every launch matching the filters before offset and limit; ties newest first. market_cap and volume rank launches with a USD value first, by USD, then the rest by whole quote units (raw / 10^decimals); created is newest first" }, "required": false, + "description": "Descending, over every launch matching the filters before offset and limit; ties newest first. market_cap and volume rank launches with a USD value first, by USD, then the rest by whole quote units (raw / 10^decimals); created is newest first", "name": "order_by", "in": "query" }, @@ -10060,7 +10065,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "data": { @@ -10071,7 +10076,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "chain_id": { @@ -10461,7 +10466,8 @@ "string", "null" ], - "description": "market_cap in USD from the indexed quote price, 6 decimals; null without a price" + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "market_cap / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null when the quote has no indexed price or no known decimals" }, "raised": { "type": "string", @@ -10480,7 +10486,21 @@ }, "trade_count": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "LaunchSwapped events of the launch, zero fills included" + }, + "volume": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Sum of |quote delta| over every LaunchSwapped of the launch, buys and sells, fee-inclusive, in raw quote base units" + }, + "volume_usd": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "volume / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null exactly when market_cap_usd is" } }, "required": [ @@ -10491,7 +10511,9 @@ "raised", "sold", "sold_share", - "trade_count" + "trade_count", + "volume", + "volume_usd" ], "additionalProperties": false }, @@ -10614,6 +10636,128 @@ ], "additionalProperties": false }, + "aggregates": { + "type": "object", + "properties": { + "launch_count": { + "type": "integer", + "minimum": 0 + }, + "trade_count": { + "type": "integer", + "minimum": 0 + }, + "by_quote": { + "type": "array", + "items": { + "type": "object", + "properties": { + "chain_id": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$" + }, + "quote_token": { + "type": "string", + "pattern": "^0x[0-9a-f]{40}$" + }, + "launch_count": { + "type": "integer", + "minimum": 0 + }, + "trade_count": { + "type": "integer", + "minimum": 0 + }, + "volume": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Sum of market.volume, raw quote units" + }, + "market_cap": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Sum of market.market_cap, raw quote units" + }, + "usd_priced": { + "type": "boolean", + "description": "The quote has an indexed USD price and known decimals" + }, + "volume_usd": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$" + }, + "market_cap_usd": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$" + } + }, + "required": [ + "chain_id", + "quote_token", + "launch_count", + "trade_count", + "volume", + "market_cap", + "usd_priced", + "volume_usd", + "market_cap_usd" + ], + "additionalProperties": false + } + }, + "usd": { + "type": "object", + "properties": { + "volume": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Sum of market.volume_usd over priced launches; null when none is priced" + }, + "market_cap": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "Sum of market.market_cap_usd over priced launches; null when none is priced" + }, + "priced_launch_count": { + "type": "integer", + "minimum": 0 + }, + "excluded_launch_count": { + "type": "integer", + "minimum": 0, + "description": "Launches left out of the USD sums because their quote has no indexed price" + } + }, + "required": [ + "volume", + "market_cap", + "priced_launch_count", + "excluded_launch_count" + ], + "additionalProperties": false + } + }, + "required": [ + "launch_count", + "trade_count", + "by_quote", + "usd" + ], + "additionalProperties": false, + "description": "Over every launch matching all filters (the set total counts), not one page. by_quote is ordered by chain_id, then quote_token" + }, "next_offset": { "type": [ "integer", @@ -10662,6 +10806,7 @@ "data", "total", "totals", + "aggregates", "next_offset", "evaluated_at", "heads" @@ -10784,7 +10929,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "chain_id": { @@ -11050,7 +11195,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "chain_id": { @@ -11285,7 +11430,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "chain_id": { @@ -11532,7 +11677,7 @@ "schema_version": { "type": "string", "enum": [ - "zero-seed-launch-api/v6" + "zero-seed-launch-api/v7" ] }, "chain_id": { @@ -11922,7 +12067,8 @@ "string", "null" ], - "description": "market_cap in USD from the indexed quote price, 6 decimals; null without a price" + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "market_cap / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null when the quote has no indexed price or no known decimals" }, "raised": { "type": "string", @@ -11941,7 +12087,21 @@ }, "trade_count": { "type": "integer", - "minimum": 0 + "minimum": 0, + "description": "LaunchSwapped events of the launch, zero fills included" + }, + "volume": { + "type": "string", + "pattern": "^(0|[1-9][0-9]*)$", + "description": "Sum of |quote delta| over every LaunchSwapped of the launch, buys and sells, fee-inclusive, in raw quote base units" + }, + "volume_usd": { + "type": [ + "string", + "null" + ], + "pattern": "^(0|[1-9][0-9]*)\\.[0-9]{6}$", + "description": "volume / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null exactly when market_cap_usd is" } }, "required": [ @@ -11952,7 +12112,9 @@ "raised", "sold", "sold_share", - "trade_count" + "trade_count", + "volume", + "volume_usd" ], "additionalProperties": false }, diff --git a/src/contract/cases/launches.ts b/src/contract/cases/launches.ts index 95179ca..85df16a 100644 --- a/src/contract/cases/launches.ts +++ b/src/contract/cases/launches.ts @@ -2,7 +2,7 @@ import { ContractCase, stubQuery } from "../harness"; import { LOCAL_INSTANCES } from "../../routes/launches/policy"; const NATIVE = "0x0000000000000000000000000000000000000000" as const; -const VERSION = "zero-seed-launch-api/v6" as const; +const VERSION = "zero-seed-launch-api/v7" as const; const pin = LOCAL_INSTANCES[0]; const owner = `0x${"2".repeat(40)}`; @@ -88,6 +88,8 @@ const summary = { sold: "0", sold_share: "0.000000000000000000", trade_count: 0, + volume: "0", + volume_usd: null, }, created: { ...as_of, @@ -124,6 +126,29 @@ export const launchesCases: ContractCase[] = [ ], total: 2, totals: { all: 2, trading: 1, upcoming: 1 }, + aggregates: { + launch_count: 2, + trade_count: 0, + by_quote: [ + { + chain_id: "31337", + quote_token: NATIVE, + launch_count: 2, + trade_count: 0, + volume: "0", + market_cap: "2000000000000000000000", + usd_priced: false, + volume_usd: null, + market_cap_usd: null, + }, + ], + usd: { + volume: null, + market_cap: null, + priced_launch_count: 0, + excluded_launch_count: 2, + }, + }, next_offset: null, evaluated_at: 1000, heads: [{ chain_id: "31337", ...as_of }], diff --git a/src/routes/launches/README.md b/src/routes/launches/README.md index 3d6090d..414539b 100644 --- a/src/routes/launches/README.md +++ b/src/routes/launches/README.md @@ -1,6 +1,6 @@ # Zero-seed launch reads (draft/local) -Response contract `zero-seed-launch-api/v6`, for the final `ZeroSeedLaunch` +Response contract `zero-seed-launch-api/v7`, for the final `ZeroSeedLaunch` and `ZeroSeedLaunchPeriphery` of the evm-contracts source freeze `853b608ec26d4c80bdad6e394cb8ba686e1ae615` (compiled ABI file sha256 `6a131c2d…56eb` and `5d05426f…c839`). It reads the indexer's @@ -8,6 +8,31 @@ and `ZeroSeedLaunchPeriphery` of the evm-contracts source freeze No deployment of the final contract exists, so the hosted instance and quote lists are empty and every launch route answers 404 when hosted. +## Changes from v6 + +- `market.volume` (sum of |quote delta| over every `LaunchSwapped`, buys and + sells, fee-inclusive, raw quote base units) and `market.volume_usd` + (`volume / 10^decimals * indexed quote price`, 6 decimals, null without a + price or known decimals, exactly when `market_cap_usd` is null). + `market_cap_usd` is now constrained to the same 6-decimal format. +- `order_by` adds `volume`, `sold_share` and `trade_count` to `market_cap` + (default) and `created`. Every order is descending and applied in SQL to + the whole filtered set before `offset`/`limit`; ties are newest first + (`block_time DESC, chain_id, event_id DESC`). `market_cap` and `volume` + rank launches with a USD value first by USD, then the others by whole + quote units (`raw / 10^decimals`; unknown decimals last), as the interface + does. `market_cap` previously put every unpriced launch last in creation + order. +- `aggregates` on the list, over the same set `total` counts (all filters, + status included, never one page): + `{ launch_count, trade_count, by_quote: [{ chain_id, quote_token, + launch_count, trade_count, volume, market_cap, usd_priced, volume_usd, + market_cap_usd }], usd: { volume, market_cap, priced_launch_count, + excluded_launch_count } }`. Raw amounts are summed per (chain, quote + token) only; the USD sums add the served per-launch USD values of priced + launches and are null when none is priced; `excluded_launch_count` counts + launches without a USD value. + ## Changes from v5 - Final model only. The fixed-base (`fixed_base_duration_v1`) and earlier @@ -36,7 +61,7 @@ lists are empty and every launch route answers 404 when hosted. `LaunchSummary` (list item; `LaunchDetail` adds `principal` and `fee_ledger`): ```text -schema_version "zero-seed-launch-api/v6" +schema_version "zero-seed-launch-api/v7" chain_id decimal string pool_id 0x + 64 hex pool_key { core_address, token0, token1, config: bytes32, fee: "0", tick_spacing: int, extension } @@ -58,7 +83,8 @@ fee_schedule { trading_start: int, initial_fee, final_fee, decay_rate_x32: d phase: before_trading_start|decaying|at_floor|constant, fee_rate: decimal string, evaluated_at: int } market { price_tick: int, price_x128, market_cap: decimal strings, market_cap_usd: string|null, - raised, sold: decimal strings, sold_share: "d.dddddddddddddddddd", trade_count: int } + raised, sold: decimal strings, sold_share: "d.dddddddddddddddddd", trade_count: int, + volume: decimal string, volume_usd: "d.dddddd"|null } created { block_number, block_hash, block_time, event_id, transaction_index, event_index, transaction_hash } as_of { block_number, block_hash, block_time } (the launch chain's indexed head) principal { position_id, liquidity, locked: true, withdrawable: false, @@ -68,16 +94,18 @@ fee_ledger { holder, launch_token: { accrued, claimed, unclaimed }, quote_ ``` `LaunchList`: `{ schema_version, data: LaunchSummary[], total, totals: { all, -trading, upcoming }, next_offset: int|null, evaluated_at: int, heads: [{ -chain_id, block_number, block_hash, block_time }] }`. `total` counts every -filter; `totals` counts the non-status filters by status. +trading, upcoming }, aggregates, next_offset: int|null, evaluated_at: int, +heads: [{ chain_id, block_number, block_hash, block_time }] }`. `total` counts +every filter; `totals` counts the non-status filters by status; `aggregates` +(see Changes from v6) covers the same set as `total`. Market: `price_x128` is raw quote per raw launch token times 2^128, floored, from Core's sqrt ratio; `market_cap = floor(price * totalSupply)` in raw quote units (the sink is not a burn and is included); `market_cap_usd` uses the indexed quote price (6 decimals) and is null without one; `raised` is the quote the pool holds; `sold = installed - launch tokens in the pool`; -`trade_count` counts `LaunchSwapped`, including zero fills. +`trade_count` counts `LaunchSwapped`, including zero fills; `volume` sums +`|quote delta|` over them (fee-inclusive, raw quote units). Status and `fee_rate` are evaluated at the request's wall-clock second (`evaluated_at`); `as_of`/`heads` are the indexed heads. Fee math: @@ -89,7 +117,7 @@ chains (`policy.test.ts`). ## Requests -- `GET /launches?chainId=&status=all|trading|upcoming&owner=&creator=&order_by=market_cap|created&offset=0&limit=50` +- `GET /launches?chainId=&status=all|trading|upcoming&owner=&creator=&order_by=market_cap|volume|sold_share|trade_count|created&offset=0&limit=50` - `GET /launches/{chainId}/{poolId}` - `GET /launches/{chainId}/{poolId}/swaps?limit=50&after_event_id=…` - `GET /launches/{chainId}/{poolId}/claims?limit=50&after_event_id=…` @@ -137,9 +165,12 @@ against the raw `LaunchCreated` log, logo, range, supply, ledger, owner, creator, controller and `feeAt` against the chain state; claims once per extension claim (counts and sums equal both the extension and the periphery logs); owner history and paging; list filters, totals, offsets, market-cap -order and quote screening; a reorg; and that a second straight replay and a +order, every `order_by` against the interface ranking across offset pages, +aggregates against the served launches for each filter (no price, native +priced, mixed quote decimals, both quotes priced), volume and trade count +against each launch's swaps, and quote screening; a reorg; and that a second straight replay and a redelivery serve identical bodies. With `LAUNCH_GOLDEN_DIR` set it writes -`zero-seed-launch-api-v6.golden.json`; copy it to `fixtures/` and run +`zero-seed-launch-api-v7.golden.json`; copy it to `fixtures/` and run `bun run format`. Safe stop is launch support off. No deployment, live database, merge or diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json similarity index 74% rename from src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json rename to src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json index 0ba845f..4114f89 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v6.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json @@ -5,10 +5,10 @@ "evaluated_at": 2115361000 }, "list": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "data": [ { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", "pool_key": { @@ -85,7 +85,9 @@ "raised": "1", "sold": "100000000000000000000", "sold_share": "0.000000000100000000", - "trade_count": 1 + "trade_count": 1, + "volume": "2", + "volume_usd": null }, "created": { "block_number": "16", @@ -103,7 +105,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", "pool_key": { @@ -180,7 +182,9 @@ "raised": "1", "sold": "100000000000000000000", "sold_share": "0.000000000100000000", - "trade_count": 1 + "trade_count": 1, + "volume": "2", + "volume_usd": null }, "created": { "block_number": "16", @@ -198,7 +202,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", "pool_key": { @@ -275,7 +279,9 @@ "raised": "1000000000000010192", "sold": "982091443427442213916824", "sold_share": "0.000982091443427442", - "trade_count": 2 + "trade_count": 2, + "volume": "188010215599247750070272", + "volume_usd": null }, "created": { "block_number": "8", @@ -293,7 +299,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", "pool_key": { @@ -370,7 +376,9 @@ "raised": "2999999999999999983", "sold": "2941080334312777133468723", "sold_share": "0.002941080334312777", - "trade_count": 5 + "trade_count": 5, + "volume": "188012215599247750080450", + "volume_usd": null }, "created": { "block_number": "8", @@ -388,7 +396,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", "pool_key": { @@ -465,7 +473,9 @@ "raised": "7092704171850925118", "sold": "6928409872974093301683354", "sold_share": "0.006928409872974093", - "trade_count": 4 + "trade_count": 4, + "volume": "7118213238681326333", + "volume_usd": null }, "created": { "block_number": "8", @@ -483,7 +493,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", "pool_key": { @@ -560,7 +570,9 @@ "raised": "9017251531406779206", "sold": "8793513662736047941471448", "sold_share": "0.008793513662736047", - "trade_count": 4 + "trade_count": 4, + "volume": "9072843717270293902", + "volume_usd": null }, "created": { "block_number": "3", @@ -578,7 +590,7 @@ } }, { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", "pool_key": { @@ -655,7 +667,9 @@ "raised": "16142442253306885264", "sold": "15644177672570445471331431", "sold_share": "0.015644177672570445", - "trade_count": 10 + "trade_count": 10, + "volume": "26941395364369142601", + "volume_usd": null }, "created": { "block_number": "2", @@ -679,6 +693,40 @@ "trading": 7, "upcoming": 0 }, + "aggregates": { + "launch_count": 7, + "trade_count": 27, + "by_quote": [ + { + "chain_id": "31337", + "quote_token": "0x0000000000000000000000000000000000000000", + "launch_count": 2, + "trade_count": 15, + "volume": "188039156994612119223051", + "market_cap": "2069308556070971146876", + "usd_priced": false, + "volume_usd": null, + "market_cap_usd": null + }, + { + "chain_id": "31337", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "launch_count": 5, + "trade_count": 12, + "volume": "188026406656203701690511", + "market_cap": "3082880877189837984488", + "usd_priced": false, + "volume_usd": null, + "market_cap_usd": null + } + ], + "usd": { + "volume": null, + "market_cap": null, + "priced_launch_count": 0, + "excluded_launch_count": 7 + } + }, "next_offset": null, "evaluated_at": 2115361000, "heads": [ @@ -691,7 +739,7 @@ ] }, "l1": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", "pool_key": { @@ -768,7 +816,9 @@ "raised": "16142442253306885264", "sold": "15644177672570445471331431", "sold_share": "0.015644177672570445", - "trade_count": 10 + "trade_count": 10, + "volume": "26941395364369142601", + "volume_usd": null }, "created": { "block_number": "2", @@ -813,7 +863,7 @@ } }, "l1/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", "next_cursor": null, @@ -1006,7 +1056,7 @@ ] }, "l1/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", "next_cursor": null, @@ -1087,7 +1137,7 @@ } }, "l1/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", "next_cursor": null, @@ -1139,7 +1189,7 @@ ] }, "l2": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", "pool_key": { @@ -1216,7 +1266,9 @@ "raised": "9017251531406779206", "sold": "8793513662736047941471448", "sold_share": "0.008793513662736047", - "trade_count": 4 + "trade_count": 4, + "volume": "9072843717270293902", + "volume_usd": null }, "created": { "block_number": "3", @@ -1261,7 +1313,7 @@ } }, "l2/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", "next_cursor": null, @@ -1346,7 +1398,7 @@ ] }, "l2/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", "next_cursor": null, @@ -1379,7 +1431,7 @@ } }, "l2/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", "next_cursor": null, @@ -1418,7 +1470,7 @@ ] }, "l4": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", "pool_key": { @@ -1495,7 +1547,9 @@ "raised": "7092704171850925118", "sold": "6928409872974093301683354", "sold_share": "0.006928409872974093", - "trade_count": 4 + "trade_count": 4, + "volume": "7118213238681326333", + "volume_usd": null }, "created": { "block_number": "8", @@ -1540,7 +1594,7 @@ } }, "l4/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", "next_cursor": null, @@ -1625,7 +1679,7 @@ ] }, "l4/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", "next_cursor": null, @@ -1658,7 +1712,7 @@ } }, "l4/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", "next_cursor": null, @@ -1684,7 +1738,7 @@ ] }, "l5": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", "pool_key": { @@ -1761,7 +1815,9 @@ "raised": "2999999999999999983", "sold": "2941080334312777133468723", "sold_share": "0.002941080334312777", - "trade_count": 5 + "trade_count": 5, + "volume": "188012215599247750080450", + "volume_usd": null }, "created": { "block_number": "8", @@ -1806,7 +1862,7 @@ } }, "l5/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", "next_cursor": null, @@ -1909,7 +1965,7 @@ ] }, "l5/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", "next_cursor": null, @@ -1942,7 +1998,7 @@ } }, "l5/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", "next_cursor": null, @@ -1968,7 +2024,7 @@ ] }, "l6": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", "pool_key": { @@ -2045,7 +2101,9 @@ "raised": "1000000000000010192", "sold": "982091443427442213916824", "sold_share": "0.000982091443427442", - "trade_count": 2 + "trade_count": 2, + "volume": "188010215599247750070272", + "volume_usd": null }, "created": { "block_number": "8", @@ -2090,7 +2148,7 @@ } }, "l6/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", "next_cursor": null, @@ -2139,7 +2197,7 @@ ] }, "l6/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", "next_cursor": null, @@ -2172,7 +2230,7 @@ } }, "l6/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", "next_cursor": null, @@ -2198,7 +2256,7 @@ ] }, "l7": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", "pool_key": { @@ -2275,7 +2333,9 @@ "raised": "1", "sold": "100000000000000000000", "sold_share": "0.000000000100000000", - "trade_count": 1 + "trade_count": 1, + "volume": "2", + "volume_usd": null }, "created": { "block_number": "16", @@ -2320,7 +2380,7 @@ } }, "l7/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", "next_cursor": null, @@ -2351,7 +2411,7 @@ ] }, "l7/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", "next_cursor": null, @@ -2367,7 +2427,7 @@ } }, "l7/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", "next_cursor": null, @@ -2393,7 +2453,7 @@ ] }, "l8": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", "pool_key": { @@ -2470,7 +2530,9 @@ "raised": "1", "sold": "100000000000000000000", "sold_share": "0.000000000100000000", - "trade_count": 1 + "trade_count": 1, + "volume": "2", + "volume_usd": null }, "created": { "block_number": "16", @@ -2515,7 +2577,7 @@ } }, "l8/swaps?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", "next_cursor": null, @@ -2546,7 +2608,7 @@ ] }, "l8/claims?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", "next_cursor": null, @@ -2562,7 +2624,7 @@ } }, "l8/owners?limit=200": { - "schema_version": "zero-seed-launch-api/v6", + "schema_version": "zero-seed-launch-api/v7", "chain_id": "31337", "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", "next_cursor": null, @@ -2586,5 +2648,739 @@ "new_owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266" } ] + }, + "list_by_volume_native_priced": { + "schema_version": "zero-seed-launch-api/v7", + "data": [ + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x8386120fbe81ba3df690a19c0616abc9579e65ac1d038c28854355d67d1548be", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5b756793d33bbc96a4f36fc574abfa1c1a16344", + "name": "Zéro 🚀", + "symbol": "L5", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": "ipfs://bafyélogo 🚀", + "parameters": { + "name": "0x0a5ac3a9726f20f09f9a80000000000000000000000000000000000000000000", + "symbol": "0x024c350000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "18446744073709551615", + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "salt": "0xd0018fa3949db0f7478425347db6277a1a4e81620122b9ef181f47b2ade9574e" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "55340232221128654", + "decay_rate_x32": "18446744073709551615", + "half_life_seconds": "0.000322", + "saturation_elapsed_seconds": "0", + "phase": "at_floor", + "fee_rate": "55340232221128654", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -13793034, + "price_x128": "348019647004654940455006368355745", + "market_cap": "1022737822572846286160", + "market_cap_usd": "2045475.645146", + "raised": "2999999999999999983", + "sold": "2941080334312777133468723", + "sold_share": "0.002941080334312777", + "trade_count": 5, + "volume": "188012215599247750080450", + "volume_usd": "376024431.198496" + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494840814", + "transaction_index": 3, + "event_index": 17, + "transaction_hash": "0xce4a04be8c2b72e774db76672444ff684141564fcfc9ea16844b62bc8bfc25e3" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x411e52638869caa3f0808fb6975961407fead4e4704e639680b634c2ca6ef6b1", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x0000000000000000000000000000000000000000", + "token1": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa6a58be5a2606e8f0a5fd28c5cc5cace90aafe44", + "name": "Zero Seed l1", + "symbol": "L1", + "decimals": 18 + }, + "quote_token": { + "address": "0x0000000000000000000000000000000000000000", + "name": null, + "symbol": null, + "decimals": 18, + "kind": "native" + }, + "launch_token_is_token1": true, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", + "logo_url": "https://example.invalid/l1.svg", + "parameters": { + "name": "0x0c5a65726f2053656564206c3100000000000000000000000000000000000000", + "symbol": "0x024c310000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0x0000000000000000000000000000000000000000", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "9923486534471", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x4ef3c22030c48a824508eaa92d5871f0ef42a8a80ef512ae3934836eed27ebaf" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": 9210880, + "pool_tick_upper": 13798330 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999559", + "sink": "441", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "9923486534471", + "half_life_seconds": "600.000000", + "saturation_elapsed_seconds": "38399", + "phase": "decaying", + "fee_rate": "126965494298918151", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -13769998, + "price_x128": "356129566344924627381574169859136", + "market_cap": "1046570733498124860716", + "market_cap_usd": "2093141.466996", + "raised": "16142442253306885264", + "sold": "15644177672570445471331431", + "sold_share": "0.015644177672570445", + "trade_count": 10, + "volume": "26941395364369142601", + "volume_usd": "53882.790729" + }, + "created": { + "block_number": "2", + "block_hash": "0x570fcc84e9f797809ba3e5f9138a969cbac2cbb913ad2d9a2a4881b6965ae3b9", + "block_time": 1800000010, + "event_id": "-9223372028264710138", + "transaction_index": 2, + "event_index": 5, + "transaction_hash": "0x9166164828ca58da84d325963c28bd5cf66ddf894017ca90970c4b3efea7215b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x5d58fbe502d86c8606cbc21129514b9989eb79999066f1139904926fd0994384", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c0000000000000000800a939c", + "fee": "0", + "tick_spacing": 693148, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xdec85268897bdaf4ae3fac868429e05f9bf90659", + "name": "Zero Seed l8", + "symbol": "L8", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3800000000000000000000000000000000000000", + "symbol": "0x024c380000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88722835", + "upper_tick": "88722835", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x114620bab65d343f8f17a1536ca597c5641c5e6cbe4398524e79a406de0df42b" + }, + "range": { + "tick_spacing": 693148, + "aligned_ask_tick": -88029796, + "aligned_upper_tick": 88029796, + "pool_tick_lower": -88029796, + "pool_tick_upper": 88029796 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999987511320520150058769", + "sink": "12488679479849941231", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "decaying", + "fee_rate": "9223372036854775808", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -88029796, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1, + "volume": "2", + "volume_usd": null + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135233525", + "transaction_index": 1, + "event_index": 10, + "transaction_hash": "0xb0a431eab9a08a25bb3542b81016c59e96d2c1ce978384640542b18d436e8153" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x4092ccb2f914fc005db61f2f29235260e289a2dd31010bcd2e655a8b874743a0", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080002000", + "fee": "0", + "tick_spacing": 8192, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x1979be2e532c9712f3c4f5aebbb112b3363d2a69", + "name": "Zero Seed l7", + "symbol": "L7", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3700000000000000000000000000000000000000", + "symbol": "0x024c370000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-88080384", + "upper_tick": "-85983232", + "trading_start": "1800005200", + "decay_rate_x32": "68913100933", + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "salt": "0x3bb0077653d024b802a78f862b2d959f8be504b7f42df81a445180edd449da4e" + }, + "range": { + "tick_spacing": 8192, + "aligned_ask_tick": -88080384, + "aligned_upper_tick": -85983232, + "pool_tick_lower": -88080384, + "pool_tick_upper": -85983232 + }, + "supply": { + "requested": "1000000000000000000000000000000", + "installed": "999999999991410763432286846442", + "sink": "8589236567713153558", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800005200, + "status": "trading", + "fee_schedule": { + "trading_start": 1800005200, + "initial_fee": "9223372036854775808", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "decaying", + "fee_rate": "9223372036854775808", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -88080384, + "price_x128": "1", + "market_cap": "0", + "market_cap_usd": null, + "raised": "1", + "sold": "100000000000000000000", + "sold_share": "0.000000000100000000", + "trade_count": 1, + "volume": "2", + "volume_usd": null + }, + "created": { + "block_number": "16", + "block_hash": "0xb89b0e6f054aa84266808b0849968c584d94470768e9ae7bd047e1c7013955d6", + "block_time": 1800005100, + "event_id": "-9223371968135299067", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0xddc99d9b6de8f631e839ffe8c6cf47600c972b909e9793ac88a6e5b288ca1b1b" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x8d332c8ca02dbd982b355bfd93b157abb06c657dab38c91f789c6c1a5927c14d", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0xa5c8be0f39023a470ad73955bd023d52636399e4", + "name": null, + "symbol": "L6", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x035aff0000000000000000000000000000000000000000000000000000000000", + "symbol": "0x024c360000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "1", + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "salt": "0x87f22775ea9a15cba089cab3a4d0d92b968f5f1280cec686b6126b32bdfa72f8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "18446744073709551615", + "final_fee": "18446744073709551615", + "decay_rate_x32": "1", + "half_life_seconds": "5954091920683119.788732", + "saturation_elapsed_seconds": "381061679028371455", + "phase": "at_floor", + "fee_rate": "18446744073709551615", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -13796564, + "price_x128": "346793678803132136179439976012385", + "market_cap": "1019135025834901745761", + "market_cap_usd": null, + "raised": "1000000000000010192", + "sold": "982091443427442213916824", + "sold_share": "0.000982091443427442", + "trade_count": 2, + "volume": "188010215599247750070272", + "volume_usd": null + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494775272", + "transaction_index": 4, + "event_index": 23, + "transaction_hash": "0x8d219e66a3a734e1655a128a681f1f3ac6f5811ee4c1d1924cb8b2fe7a14254c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0xfd819e34709dc79b7af4a674ead3a1fe88eb0e22871e57e78edc580425076cad", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x99f831f2f9542301bae6ffa6916a7624d1d75545", + "name": "Zero Seed l4", + "symbol": "L4", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3400000000000000000000000000000000000000", + "symbol": "0x024c340000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001400", + "decay_rate_x32": "0", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x64778ffaf8f1b3afdbfda9ef8a1483b1ae9316267a82c4aa2fa77f3f5c800ab7" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001400, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001400, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "0", + "half_life_seconds": null, + "saturation_elapsed_seconds": null, + "phase": "constant", + "fee_rate": "922337203685477580", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -13785833, + "price_x128": "350535152092893641763330226765819", + "market_cap": "1030130227624569569284", + "market_cap_usd": null, + "raised": "7092704171850925118", + "sold": "6928409872974093301683354", + "sold_share": "0.006928409872974093", + "trade_count": 4, + "volume": "7118213238681326333", + "volume_usd": null + }, + "created": { + "block_number": "8", + "block_hash": "0x33c435c8135e24ee87180569416cba7a868940e77ba3cbfdacd7e45e933c17f0", + "block_time": 1800001310, + "event_id": "-9223372002494906356", + "transaction_index": 2, + "event_index": 11, + "transaction_hash": "0x953e3e4c3901fd7081e1a41e36d189eec944f7424dc1b31b329e5aec8339e31c" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + }, + { + "schema_version": "zero-seed-launch-api/v7", + "chain_id": "31337", + "pool_id": "0x46f469a9f61648d57a55f79be9216890b8618427c03ebe6ab71ae0557e9b5fb3", + "pool_key": { + "core_address": "0x00000000000014aa86c5d3c41765bb24e11bd701", + "token0": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "token1": "0xffffffffffffffffffffffffffffffffffffffff", + "config": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c000000000000000080004646", + "fee": "0", + "tick_spacing": 17990, + "extension": "0x51c4ad945fbef67859d92f1690cbf86df12e3b7c" + }, + "token": { + "address": "0x9e621d5e87e699eaebe56bc54a6ce2fc1d6e8380", + "name": "Zero Seed l2", + "symbol": "L2", + "decimals": 18 + }, + "quote_token": { + "address": "0xffffffffffffffffffffffffffffffffffffffff", + "name": null, + "symbol": null, + "decimals": null, + "kind": "erc20" + }, + "launch_token_is_token1": false, + "controller": "0x6963de1652c96fb0eaeaf5167de3c89e187284f2", + "creator": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + "owner": "0x90f79bf6eb2c4f870365e785982e1f101e93b906", + "logo_url": null, + "parameters": { + "name": "0x0c5a65726f2053656564206c3200000000000000000000000000000000000000", + "symbol": "0x024c320000000000000000000000000000000000000000000000000000000000", + "supply": "1000000000000000000000000000", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "ask_tick": "-13815500", + "upper_tick": "-9210300", + "trading_start": "1800001000", + "decay_rate_x32": "68913100933", + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "salt": "0x706d1c2beda46be1cbaa68fe362b46cabc10d357d951c50e9fa8faa1112679d8" + }, + "range": { + "tick_spacing": 17990, + "aligned_ask_tick": -13798330, + "aligned_upper_tick": -9210880, + "pool_tick_lower": -13798330, + "pool_tick_upper": -9210880 + }, + "supply": { + "requested": "1000000000000000000000000000", + "installed": "999999999999999999999999508", + "sink": "492", + "sink_holder": "0x0000000000000000000000000000000000000000" + }, + "trading_start": 1800001000, + "status": "trading", + "fee_schedule": { + "trading_start": 1800001000, + "initial_fee": "922337203685477580", + "final_fee": "55340232221128654", + "decay_rate_x32": "68913100933", + "half_life_seconds": "86400.000001", + "saturation_elapsed_seconds": "5529597", + "phase": "decaying", + "fee_rate": "893610866128455423", + "evaluated_at": 1800005200 + }, + "market": { + "price_tick": -13782455, + "price_x128": "351721170929431300655620967870695", + "market_cap": "1033615623730366669443", + "market_cap_usd": null, + "raised": "9017251531406779206", + "sold": "8793513662736047941471448", + "sold_share": "0.008793513662736047", + "trade_count": 4, + "volume": "9072843717270293902", + "volume_usd": null + }, + "created": { + "block_number": "3", + "block_hash": "0x3d50b3ccba7b4ab3df647ceb94e60b76a6ee464eb63bb44151074c3108b7a8b2", + "block_time": 1800000020, + "event_id": "-9223372023969873915", + "transaction_index": 0, + "event_index": 4, + "transaction_hash": "0x9d7d2a206587b1f14e48d669379d1adc8d06f9c245e973f27fec2d73786720dc" + }, + "as_of": { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000 + } + } + ], + "total": 7, + "totals": { + "all": 7, + "trading": 7, + "upcoming": 0 + }, + "aggregates": { + "launch_count": 7, + "trade_count": 27, + "by_quote": [ + { + "chain_id": "31337", + "quote_token": "0x0000000000000000000000000000000000000000", + "launch_count": 2, + "trade_count": 15, + "volume": "188039156994612119223051", + "market_cap": "2069308556070971146876", + "usd_priced": true, + "volume_usd": "376078313.989225", + "market_cap_usd": "4138617.112142" + }, + { + "chain_id": "31337", + "quote_token": "0xffffffffffffffffffffffffffffffffffffffff", + "launch_count": 5, + "trade_count": 12, + "volume": "188026406656203701690511", + "market_cap": "3082880877189837984488", + "usd_priced": false, + "volume_usd": null, + "market_cap_usd": null + } + ], + "usd": { + "volume": "376078313.989225", + "market_cap": "4138617.112142", + "priced_launch_count": 2, + "excluded_launch_count": 5 + } + }, + "next_offset": null, + "evaluated_at": 1800005200, + "heads": [ + { + "block_number": "18", + "block_hash": "0x41f1936846fcb71cee30f7dd9070915b4fc7dff73f4e94cea11e54c0166606ba", + "block_time": 2115361000, + "chain_id": "31337" + } + ] } } diff --git a/src/routes/launches/golden.test.ts b/src/routes/launches/golden.test.ts index 15a367d..7b2a286 100644 --- a/src/routes/launches/golden.test.ts +++ b/src/routes/launches/golden.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import golden from "./fixtures/zero-seed-launch-api-v6.golden.json"; +import golden from "./fixtures/zero-seed-launch-api-v7.golden.json"; import { ClaimPage, LaunchDetail, @@ -11,14 +11,15 @@ import { // Written by replay.postgres.test.ts (LAUNCH_GOLDEN_DIR) from the actual // handlers over the indexer's replayed local chain: a machine-readable -// example of every v6 response for integrators. +// example of every v7 response for integrators. const responses = golden as unknown as Record; -test("every golden response parses with the closed v6 schemas", () => { +test("every golden response parses with the closed v7 schemas", () => { LaunchList.parse(golden.list); + LaunchList.parse(golden.list_by_volume_native_priced); expect(golden.list.data.length).toBe(7); for (const [key, body] of Object.entries(responses)) { - if (key === "source" || key === "list") continue; + if (key === "source" || key.startsWith("list")) continue; const schema = key.endsWith("/swaps?limit=200") ? SwapPage : key.endsWith("/claims?limit=200") @@ -50,3 +51,31 @@ test("golden: logos, statuses and claims as served", () => { }); } }); + +test("golden: list aggregates never add different quote tokens", () => { + for (const list of [golden.list, golden.list_by_volume_native_priced]) { + const { aggregates } = list; + expect(aggregates.launch_count).toBe(list.total); + expect(aggregates.by_quote.map((g) => g.quote_token)).toEqual([ + "0x0000000000000000000000000000000000000000", + "0xffffffffffffffffffffffffffffffffffffffff", + ]); + for (const group of aggregates.by_quote) { + const launches = list.data.filter( + (l) => l.quote_token.address === group.quote_token, + ); + expect(group.volume).toBe( + launches.reduce((n, l) => n + BigInt(l.market.volume), 0n).toString(), + ); + } + } + // native priced, the ERC-20 not: USD covers the native launches only + const priced = LaunchList.parse( + golden.list_by_volume_native_priced, + ).aggregates; + expect(priced.usd.volume).toBe(priced.by_quote[0].volume_usd); + expect(priced.usd.excluded_launch_count).toBe( + priced.by_quote[1].launch_count, + ); + expect(golden.list.aggregates.usd.volume).toBeNull(); +}); diff --git a/src/routes/launches/index.ts b/src/routes/launches/index.ts index 4f7b5fe..cf3235a 100644 --- a/src/routes/launches/index.ts +++ b/src/routes/launches/index.ts @@ -77,7 +77,7 @@ export const ListLaunches = defineRoute( tags: ["Launches"], operationId: "get_ListLaunches", summary: - "List supported zero-seed launches across chains, with status, owner and creator filters", + "List supported zero-seed launches across chains, with status, owner and creator filters, ordering and market aggregates", request: { query: z.object({ // omitted: every chain with launch support @@ -87,7 +87,19 @@ export const ListLaunches = defineRoute( owner: address.optional(), // owner at creation creator: address.optional(), - order_by: z.enum(["market_cap", "created"]).default("market_cap"), + order_by: z + .enum([ + "market_cap", + "volume", + "sold_share", + "trade_count", + "created", + ]) + .default("market_cap") + .openapi({ + description: + "Descending, over every launch matching the filters before offset and limit; ties newest first. market_cap and volume rank launches with a USD value first, by USD, then the rest by whole quote units (raw / 10^decimals); created is newest first", + }), offset: z.coerce.number().int().min(0).max(100_000).default(0), limit, }), diff --git a/src/routes/launches/policy.test.ts b/src/routes/launches/policy.test.ts index bac30bf..b673147 100644 --- a/src/routes/launches/policy.test.ts +++ b/src/routes/launches/policy.test.ts @@ -412,6 +412,8 @@ test("market data: exact price, market cap, raised and sold share", () => { quoteReserve: 77n, marketCapUsd: null, tradeCount: 3n, + volume: 154n, + volumeUsd: null, }); expect(market).toEqual({ price_tick: -1386295, @@ -422,6 +424,8 @@ test("market data: exact price, market cap, raised and sold share", () => { sold: "300", sold_share: "0.333333333333333333", trade_count: 3, + volume: "154", + volume_usd: null, }); // dust above the installed amount never makes sold negative expect( @@ -432,11 +436,13 @@ test("market data: exact price, market cap, raised and sold share", () => { quoteReserve: 0n, marketCapUsd: "1.000000", tradeCount: 0n, + volume: 0n, + volumeUsd: "0.000000", }).sold, ).toBe("0"); }); -test("v6 schemas are final-only and closed", () => { +test("v7 schemas are final-only and closed", () => { const keys = Object.keys(LaunchSummary.shape); for (const key of [ "logo_url", @@ -458,6 +464,7 @@ test("v6 schemas are final-only and closed", () => { "data", "total", "totals", + "aggregates", "next_offset", "evaluated_at", "heads", diff --git a/src/routes/launches/policy.ts b/src/routes/launches/policy.ts index 3ed1bf5..5c5dd55 100644 --- a/src/routes/launches/policy.ts +++ b/src/routes/launches/policy.ts @@ -360,6 +360,8 @@ export function marketData(input: { quoteReserve: bigint; marketCapUsd: string | null; tradeCount: bigint; + volume: bigint; + volumeUsd: string | null; }) { const price = priceX128(input.sqrtRatio, input.launchIsToken1); const left = @@ -376,5 +378,7 @@ export function marketData(input: { sold: sold.toString(), sold_share: share18(sold, input.installed), trade_count: Number(input.tradeCount), + volume: input.volume.toString(), + volume_usd: input.volumeUsd, }; } diff --git a/src/routes/launches/queries.ts b/src/routes/launches/queries.ts index 6c04ed2..6dc32a4 100644 --- a/src/routes/launches/queries.ts +++ b/src/routes/launches/queries.ts @@ -89,6 +89,8 @@ interface LaunchRow extends EventRow { claim_count: bigint; last_event_id: bigint; market_cap_usd: string | null; + volume: Numeric; + volume_usd: string | null; } const decimal = (n: Numeric) => n.toString(); const hex = (n: Numeric, width: number) => @@ -204,6 +206,8 @@ function summary(row: LaunchRow, asOf: Head, time: bigint) { quoteReserve: BigInt(quoteReserve), marketCapUsd: row.market_cap_usd, tradeCount: row.swap_count, + volume: BigInt(row.volume), + volumeUsd: row.volume_usd, }), created: event(row), as_of: asOf, @@ -267,7 +271,12 @@ function detail(row: LaunchRow, asOf: Head, time: bigint) { } export type LaunchStatusFilter = "all" | "trading" | "upcoming"; -export type LaunchOrder = "market_cap" | "created"; +export type LaunchOrder = + | "market_cap" + | "volume" + | "sold_share" + | "trade_count" + | "created"; export interface LaunchReadOptions { chainId: bigint; @@ -421,14 +430,22 @@ export class LaunchQueries { OR (c.quote_kind = 'erc20' AND c.quote_token <> 0 AND (${erc20}))) `; // price_x128 = raw quote per raw launch token * 2^128 (Core's sqrt ratio - // is Q128 token1 per token0); market cap = price * totalSupply, in USD - // when the quote has an indexed price. + // is Q128 token1 per token0); market cap = price * totalSupply. volume is + // |quote delta| summed over every LaunchSwapped, fee-inclusive. The USD + // values use the indexed quote price; the *_quote columns (whole quote + // units) and sold_share_e18 exist only to order by. return tx` - SELECT l.*, + SELECT l.*, m.market_cap::text AS market_cap, CASE WHEN l.quote_price IS NULL THEN NULL - ELSE ROUND(DIV(l.price_x128 * l.requested_supply, 340282366920938463463374607431768211456) - / POWER(10::numeric, l.quote_decimals_effective) - * l.quote_price::numeric, 6)::text END AS market_cap_usd + ELSE ROUND(m.market_cap / POWER(10::numeric, l.quote_decimals_effective) + * l.quote_price::numeric, 6)::text END AS market_cap_usd, + CASE WHEN l.quote_price IS NULL THEN NULL + ELSE ROUND(l.volume / POWER(10::numeric, l.quote_decimals_effective) + * l.quote_price::numeric, 6)::text END AS volume_usd, + m.market_cap / POWER(10::numeric, l.quote_decimals_effective) AS market_cap_quote, + l.volume / POWER(10::numeric, l.quote_decimals_effective) AS volume_quote, + DIV((l.installed_supply - LEAST(l.token_reserve, l.installed_supply)) + * 1000000000000000000, NULLIF(l.installed_supply, 0)) AS sold_share_e18 FROM ( SELECT c.*, b.block_hash, b.block_time, pk.core_address, pk.token0, pk.token1, qt.token_name AS quote_name, qt.token_symbol AS quote_symbol, @@ -437,7 +454,8 @@ export class LaunchQueries { qp.value AS quote_price, s.accrued0, s.accrued1, s.claimed0, s.claimed1, s.swap_count, s.claim_count, s.last_event_id, s.initial_owner, s.current_owner, s.owner_change_count, - pt.balance0, pt.balance1, ps.tick, ps.sqrt_ratio, + pt.balance0, pt.balance1, ps.tick, ps.sqrt_ratio, v.volume, + CASE WHEN c.token_is_token1 THEN pt.balance1 ELSE pt.balance0 END AS token_reserve, CASE WHEN c.token_is_token1 THEN DIV(POWER(2::numeric, 384), ps.sqrt_ratio * ps.sqrt_ratio) ELSE DIV(ps.sqrt_ratio * ps.sqrt_ratio, 340282366920938463463374607431768211456) END AS price_x128 @@ -451,6 +469,10 @@ export class LaunchQueries { LEFT JOIN erc20_tokens qt ON qt.chain_id = c.chain_id AND qt.token_address = c.quote_token AND c.quote_kind = 'erc20' LEFT JOIN erc20_tokens_latest_price qp ON qp.chain_id = c.chain_id AND qp.token_address = c.quote_token + CROSS JOIN LATERAL ( + SELECT COALESCE(SUM(ABS(CASE WHEN c.token_is_token1 THEN w.delta0 ELSE w.delta1 END)), 0) AS volume + FROM zero_seed_launch_swapped w WHERE w.pool_key_id = c.pool_key_id + ) v WHERE c.chain_id IN (${chainList}) AND pk.chain_id = c.chain_id AND pk.pool_extension = c.emitter AND pk.fee = 0 AND pk.token0 = LEAST(c.token, c.quote_token) @@ -459,6 +481,9 @@ export class LaunchQueries { AND s.current_owner IS NOT NULL AND s.initial_owner IS NOT NULL AND (${instance}) AND ${quote} AND ${filter} ) l + CROSS JOIN LATERAL ( + SELECT DIV(l.price_x128 * l.requested_supply, 340282366920938463463374607431768211456) AS market_cap + ) m `; } @@ -477,10 +502,6 @@ export class LaunchQueries { trading: tx`l.trading_start <= ${now}::numeric`, upcoming: tx`l.trading_start > ${now}::numeric`, }[input.status]; - const order = - input.orderBy === "market_cap" - ? tx`(market_cap_usd)::numeric DESC NULLS LAST, block_time DESC, chain_id, event_id DESC` - : tx`block_time DESC, chain_id, event_id DESC`; const [counts] = await tx< { all: number; trading: number; upcoming: number }[] >` @@ -489,10 +510,23 @@ export class LaunchQueries { (COUNT(*) FILTER (WHERE l.trading_start > ${now}::numeric))::int AS upcoming FROM (${this.rows(tx, chains, filter)}) l `; + const groups = await tx` + SELECT l.chain_id, l.quote_token, COUNT(*)::int AS launch_count, + SUM(l.swap_count)::text AS trade_count, SUM(l.volume)::text AS volume, + SUM(l.market_cap::numeric)::text AS market_cap, + SUM(l.volume_usd::numeric)::text AS volume_usd, + SUM(l.market_cap_usd::numeric)::text AS market_cap_usd + FROM (${this.rows(tx, chains, filter)}) l + WHERE ${status} + GROUP BY l.chain_id, l.quote_token ORDER BY l.chain_id, l.quote_token + `; + // The whole filtered set is ordered before the page is cut; the + // creation order breaks ties. const rows = await tx` SELECT * FROM (${this.rows(tx, chains, filter)}) l WHERE ${status} - ORDER BY ${order} OFFSET ${input.offset} LIMIT ${input.limit + 1} + ORDER BY ${orderBy(tx, input.orderBy)}, block_time DESC, chain_id, event_id DESC + OFFSET ${input.offset} LIMIT ${input.limit + 1} `; const page = rows.slice(0, input.limit); return parseIndexed(LaunchList, { @@ -502,6 +536,7 @@ export class LaunchQueries { ), total: counts[input.status], totals: counts, + aggregates: aggregates(groups), next_offset: rows.length > input.limit ? input.offset + input.limit : null, evaluated_at: seconds(time), @@ -696,6 +731,69 @@ export class LaunchQueries { } } +// Launches without a USD value rank after those with one, by whole quote +// units, so launches on different quotes still compare. +function orderBy(tx: DB, key: LaunchOrder) { + return { + market_cap: tx`(market_cap_usd IS NULL), market_cap_usd::numeric DESC, market_cap_quote DESC NULLS LAST`, + volume: tx`(volume_usd IS NULL), volume_usd::numeric DESC, volume_quote DESC NULLS LAST`, + sold_share: tx`sold_share_e18 DESC NULLS LAST`, + trade_count: tx`swap_count DESC`, + created: tx`block_time DESC`, + }[key]; +} + +interface QuoteGroup { + chain_id: bigint; + quote_token: Numeric; + launch_count: number; + trade_count: string; + volume: string; + market_cap: string; + volume_usd: string | null; + market_cap_usd: string | null; +} + +const sumUsd = (values: (string | null)[]) => { + const priced = values.filter((v): v is string => v !== null); + if (priced.length === 0) return null; + const micros = priced.reduce( + (sum, v) => sum + BigInt(v.replace(".", "")), + 0n, + ); + return `${micros / 1_000_000n}.${(micros % 1_000_000n).toString().padStart(6, "0")}`; +}; + +// Per-quote sums and USD sums over priced quotes only: raw amounts of +// different quote tokens are never added together. +function aggregates(groups: QuoteGroup[]) { + const byQuote = groups.map((g) => ({ + chain_id: decimal(g.chain_id), + quote_token: hex(g.quote_token, 20), + launch_count: g.launch_count, + trade_count: Number(g.trade_count), + volume: g.volume, + market_cap: g.market_cap, + usd_priced: g.volume_usd !== null, + volume_usd: g.volume_usd, + market_cap_usd: g.market_cap_usd, + })); + const count = (rows: typeof byQuote) => + rows.reduce((n, g) => n + g.launch_count, 0); + const priced = byQuote.filter((g) => g.usd_priced); + return { + launch_count: count(byQuote), + trade_count: byQuote.reduce((n, g) => n + g.trade_count, 0), + by_quote: byQuote, + usd: { + volume: sumUsd(priced.map((g) => g.volume_usd)), + market_cap: sumUsd(priced.map((g) => g.market_cap_usd)), + priced_launch_count: count(priced), + excluded_launch_count: count(byQuote) - count(priced), + }, + }; +} + function swapDirection(token: bigint, quote: bigint) { if (token === 0n && quote === 0n) return "zero_fill"; if (token <= 0n && quote >= 0n) return "buy"; diff --git a/src/routes/launches/replay.postgres.test.ts b/src/routes/launches/replay.postgres.test.ts index dbd0d94..a65ecdc 100644 --- a/src/routes/launches/replay.postgres.test.ts +++ b/src/routes/launches/replay.postgres.test.ts @@ -26,6 +26,15 @@ export const INDEXER_COMMIT = "2435c89bb52fdcc7f1660031f16b8d11b8d9ffdd"; export const FIXTURE_SHA256 = "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6"; +const ORDERS = [ + "market_cap", + "volume", + "sold_share", + "trade_count", + "created", +] as const; +const cmpBigint = (a: bigint, b: bigint) => (a < b ? -1 : a > b ? 1 : 0); + type Hex = `0x${string}`; type Body = Record; interface RawLog { @@ -395,7 +404,7 @@ describe.skipIf(!ROOT || !SERVER)( const body = await get(path(pool.label)); const [log] = logsOf(branches, T.created, launch.poolId); const d = log.data; - expect(body.schema_version).toBe("zero-seed-launch-api/v6"); + expect(body.schema_version).toBe("zero-seed-launch-api/v7"); expect(body.token.address).toBe(lower(addr(log.topics[2]))); expect(body.controller).toBe(lower(addr(log.topics[3]))); expect(body.parameters).toEqual({ @@ -511,6 +520,168 @@ describe.skipIf(!ROOT || !SERVER)( return claims; } + // The interface's launch-list ranking, recomputed from served fields: + // descending; for market cap and volume, launches with a USD value + // first by USD, then the rest by whole quote units; ties newest first. + const micros = (usd: string) => BigInt(usd.replace(".", "")); + function rankCompare(key: string, created: string[]) { + const quoteUnits = (l: Body, field: string) => + l.quote_token.decimals === null + ? null + : { raw: BigInt(l.market[field]), decimals: l.quote_token.decimals }; + const cmpBig = (a: bigint, b: bigint) => (a > b ? -1 : a < b ? 1 : 0); + const byUsdThenQuote = (field: string) => (a: Body, b: Body) => { + const [ua, ub] = [a.market[`${field}_usd`], b.market[`${field}_usd`]]; + if ((ua === null) !== (ub === null)) return ua === null ? 1 : -1; + if (ua !== null) return cmpBig(micros(ua), micros(ub)); + const [qa, qb] = [quoteUnits(a, field), quoteUnits(b, field)]; + if (qa === null || qb === null) + return qa === qb ? 0 : qa === null ? 1 : -1; + return cmpBig( + qa.raw * 10n ** BigInt(qb.decimals), + qb.raw * 10n ** BigInt(qa.decimals), + ); + }; + const primary: Record number> = { + market_cap: byUsdThenQuote("market_cap"), + volume: byUsdThenQuote("volume"), + sold_share: (a, b) => + a.market.sold_share < b.market.sold_share + ? 1 + : a.market.sold_share > b.market.sold_share + ? -1 + : 0, + trade_count: (a, b) => b.market.trade_count - a.market.trade_count, + created: () => 0, + }; + return (a: Body, b: Body) => + primary[key](a, b) || + created.indexOf(a.pool_id) - created.indexOf(b.pool_id); + } + + function expectedAggregates(data: Body[]) { + const groups = new Map(); + for (const l of data) { + const k = `${l.chain_id}:${l.quote_token.address}`; + groups.set(k, [...(groups.get(k) ?? []), l]); + } + const sum = (ls: Body[], f: (l: Body) => bigint) => + ls.reduce((n, l) => n + f(l), 0n); + const usd = (v: bigint | null) => + v === null + ? null + : `${v / 1_000_000n}.${(v % 1_000_000n).toString().padStart(6, "0")}`; + const usdSum = (ls: Body[], field: string) => + ls.some((l) => l.market[field] === null) + ? null + : sum(ls, (l) => micros(l.market[field])); + const byQuote = [...groups.entries()] + .sort(([a], [b]) => { + const [ca, qa] = a.split(":"); + const [cb, qb] = b.split(":"); + return ( + cmpBigint(BigInt(ca), BigInt(cb)) || + cmpBigint(BigInt(qa), BigInt(qb)) + ); + }) + .map(([, ls]) => ({ + chain_id: ls[0].chain_id, + quote_token: ls[0].quote_token.address, + launch_count: ls.length, + trade_count: ls.reduce((n, l) => n + l.market.trade_count, 0), + volume: sum(ls, (l) => BigInt(l.market.volume)).toString(), + market_cap: sum(ls, (l) => BigInt(l.market.market_cap)).toString(), + usd_priced: ls[0].market.volume_usd !== null, + volume_usd: usd(usdSum(ls, "volume_usd")), + market_cap_usd: usd(usdSum(ls, "market_cap_usd")), + })); + const priced = data.filter((l) => l.market.volume_usd !== null); + return { + launch_count: data.length, + trade_count: data.reduce((n, l) => n + l.market.trade_count, 0), + by_quote: byQuote, + usd: { + volume: priced.length + ? usd(sum(priced, (l) => micros(l.market.volume_usd))) + : null, + market_cap: priced.length + ? usd(sum(priced, (l) => micros(l.market.market_cap_usd))) + : null, + priced_launch_count: priced.length, + excluded_launch_count: data.length - priced.length, + }, + }; + } + + async function expectOrdersAndAggregates( + get: ReturnType["get"], + label: string, + ) { + const creator = fx.addresses.creator; + for (const filter of [ + "", + "&status=trading", + "&status=upcoming", + `&owner=${creator}`, + `&creator=${creator}`, + ]) { + const created = ( + await get(`/launches?order_by=created&limit=200${filter}`) + ).data.map((l: Body) => l.pool_id); + let aggregates: unknown; + for (const key of ORDERS) { + const full = await get( + `/launches?order_by=${key}&limit=200${filter}`, + ); + const ctx = `${label} ${key}${filter}`; + expect([ctx, full.data.length]).toEqual([ctx, full.total]); + // the order is the interface ranking over the whole filtered set + expect([ctx, full.data.map((l: Body) => l.pool_id)]).toEqual([ + ctx, + [...full.data] + .sort(rankCompare(key, created)) + .map((l: Body) => l.pool_id), + ]); + // pages concatenate to the full order: ordered before offset/limit + const pages: Body[] = []; + let offset: number | null = 0; + while (offset !== null) { + const page = await get( + `/launches?order_by=${key}&limit=2&offset=${offset}${filter}`, + ); + expect(page.aggregates).toEqual(full.aggregates); + pages.push(...page.data); + offset = page.next_offset; + } + expect(pages).toEqual(full.data); + // aggregates cover the filtered set, never one page + expect([ctx, full.aggregates]).toEqual([ + ctx, + expectedAggregates(full.data), + ]); + aggregates ??= full.aggregates; + expect(full.aggregates).toEqual(aggregates); + } + } + // volume and trade count equal the launch's served swaps + const all = await get(`/launches?order_by=created&limit=200`); + for (const l of all.data) { + const swaps = await get( + `/launches/${l.chain_id}/${l.pool_id}/swaps?limit=200`, + ); + expect(swaps.next_cursor).toBeNull(); + expect(swaps.data.length).toBe(l.market.trade_count); + const volume = swaps.data.reduce((n: bigint, s: Body) => { + const d = BigInt(s.quote_token_delta); + return n + (d < 0n ? -d : d); + }, 0n); + expect(l.market.volume).toBe(volume.toString()); + expect(l.market.volume_usd === null).toBe( + l.market.market_cap_usd === null, + ); + } + } + async function everyResponse(get: ReturnType["get"]) { const out: Record = { list: await get(`/launches?order_by=created&limit=200`), @@ -531,6 +702,7 @@ describe.skipIf(!ROOT || !SERVER)( let time = 0n; const { get, stop } = serve(db, () => time); const { prefix, alternate, canonical } = fx.branches; + let pricedList: Body | undefined; try { const at = (branches: Branch[]) => @@ -719,7 +891,36 @@ describe.skipIf(!ROOT || !SERVER)( 0, ); } + await expectOrdersAndAggregates(get, "native priced"); + pricedList = await get(`/launches?order_by=volume&limit=200`); + expect(pricedList.aggregates.usd.excluded_launch_count).toBe( + pricedList.data.length - native.length, + ); + await db.sql`DELETE FROM erc20_tokens_latest_price WHERE source = 'tst'`; + await expectOrdersAndAggregates(get, "unpriced"); + // mixed quote decimals: unpriced launches rank by whole quote units + const erc20 = BigInt(LOCAL_QUOTES[0].address).toString(); + await db.sql`INSERT INTO erc20_tokens (chain_id, token_address, token_symbol, token_name, token_decimals, visibility_priority, sort_order) + VALUES (${CHAIN.toString()}, ${erc20}, 'TQ', 'Test quote', 6, 0, 0)`; + await expectOrdersAndAggregates(get, "6-decimal erc20 unpriced"); + // both quotes priced: one USD total, raw amounts still per quote + await db.sql`INSERT INTO erc20_tokens_latest_price (chain_id, token_address, source, "timestamp", value, confidence, valid_until) + VALUES (${CHAIN.toString()}, 0, 'tst', NOW(), 2000, 100, NOW() + INTERVAL '1 hour'), + (${CHAIN.toString()}, ${erc20}, 'tst', NOW(), 0.5, 100, NOW() + INTERVAL '1 hour')`; + await expectOrdersAndAggregates(get, "both priced"); + const mixed = (await get(`/launches?limit=200`)).aggregates; + expect(mixed.by_quote.map((g: Body) => g.usd_priced)).toEqual([ + true, + true, + ]); + expect(mixed.usd.excluded_launch_count).toBe(0); + expect( + micros(mixed.usd.volume) === + micros(mixed.by_quote[0].volume_usd) + + micros(mixed.by_quote[1].volume_usd), + ).toBe(true); await db.sql`DELETE FROM erc20_tokens_latest_price WHERE source = 'tst'`; + await db.sql`DELETE FROM erc20_tokens WHERE chain_id = ${CHAIN.toString()} AND token_address = ${erc20}`; // unsupported ERC-20 quotes are screened before paging await stop(); const nativeOnly = serve(db, () => time, []); @@ -740,7 +941,7 @@ describe.skipIf(!ROOT || !SERVER)( await Bun.write( resolve( process.env.LAUNCH_GOLDEN_DIR, - "zero-seed-launch-api-v6.golden.json", + "zero-seed-launch-api-v7.golden.json", ), `${JSON.stringify( { @@ -750,6 +951,7 @@ describe.skipIf(!ROOT || !SERVER)( evaluated_at: Number(time), }, ...first1, + list_by_volume_native_priced: pricedList, }, null, 2, diff --git a/src/routes/launches/routes.test.ts b/src/routes/launches/routes.test.ts index 8dec25e..60943a6 100644 --- a/src/routes/launches/routes.test.ts +++ b/src/routes/launches/routes.test.ts @@ -69,7 +69,8 @@ test("invalid arguments return 400 naming the parameter", async () => { ["/launches?offset=1.5", "offset"], ["/launches?status=live", "status"], ["/launches?status=", "status"], - ["/launches?order_by=volume", "order_by"], + ["/launches?order_by=price", "order_by"], + ["/launches?order_by=sold", "order_by"], [`/launches?owner=0x1${"0".repeat(40)}`, "owner"], ["/launches?owner=alice", "owner"], ["/launches?creator=owner", "creator"], diff --git a/src/routes/launches/schema.ts b/src/routes/launches/schema.ts index 83d9ead..fa11e81 100644 --- a/src/routes/launches/schema.ts +++ b/src/routes/launches/schema.ts @@ -1,11 +1,12 @@ import { z } from "@hono/zod-openapi"; -// v6: the final zero-seed launch (evm-contracts 853b608) only. One fee model, +// v7: the final zero-seed launch (evm-contracts 853b608) only. One fee model, // so the schedule has no discriminator; launches carry their logo, every // LaunchParameters field, the derived spacing and aligned bounds, a status -// and market data, and the list spans chains with status/owner filters, -// market-cap ordering and totals. -export const SCHEMA_VERSION = "zero-seed-launch-api/v6" as const; +// and market data with volume, and the list spans chains with status/owner +// filters, market-cap, volume, sold-share, trade-count and creation order, +// totals and market aggregates. +export const SCHEMA_VERSION = "zero-seed-launch-api/v7" as const; export const SchemaVersion = z.literal(SCHEMA_VERSION); const UINT64_MAX = (1n << 64n) - 1n; const U = z.string().regex(/^(0|[1-9][0-9]*)$/); @@ -126,8 +127,11 @@ const Supply = z.strictObject({ sink: U, sink_holder: z.literal("0x0000000000000000000000000000000000000000"), }); -// Computed from the indexed Core pool state and balances. Raw units; price -// is raw quote per raw launch token, times 2^128 and floored. +// USD with exactly 6 decimals, from the indexed quote price. +const Usd = z.string().regex(/^(0|[1-9][0-9]*)\.[0-9]{6}$/); +// Computed from the indexed Core pool state, balances and LaunchSwapped +// events. Raw units; price is raw quote per raw launch token, times 2^128 +// and floored. const Market = z.strictObject({ price_tick: Tick, price_x128: U, @@ -135,9 +139,9 @@ const Market = z.strictObject({ description: "floor(price * totalSupply) in raw quote units; totalSupply includes the sink", }), - market_cap_usd: z.string().nullable().openapi({ + market_cap_usd: Usd.nullable().openapi({ description: - "market_cap in USD from the indexed quote price, 6 decimals; null without a price", + "market_cap / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null when the quote has no indexed price or no known decimals", }), raised: U.openapi({ description: "Quote held by the launch pool (buyers' net input)", @@ -149,7 +153,17 @@ const Market = z.strictObject({ .string() .regex(/^[01]\.[0-9]{18}$/) .openapi({ description: "sold / installed, 18 decimals, floored" }), - trade_count: z.number().int().nonnegative(), + trade_count: z.number().int().nonnegative().openapi({ + description: "LaunchSwapped events of the launch, zero fills included", + }), + volume: U.openapi({ + description: + "Sum of |quote delta| over every LaunchSwapped of the launch, buys and sells, fee-inclusive, in raw quote base units", + }), + volume_usd: Usd.nullable().openapi({ + description: + "volume / 10^quote decimals * the indexed quote USD price, rounded to 6 decimals; null exactly when market_cap_usd is", + }), }); export const LaunchSummary = z.strictObject({ schema_version: SchemaVersion, @@ -206,12 +220,56 @@ const Totals = z.strictObject({ trading: z.number().int().nonnegative(), upcoming: z.number().int().nonnegative(), }); +const Count = z.number().int().nonnegative(); +// One row per (chain, quote token): amounts of different quote tokens are +// never added together. +const QuoteAggregate = z.strictObject({ + chain_id: U, + quote_token: Address, + launch_count: Count, + trade_count: Count, + volume: U.openapi({ description: "Sum of market.volume, raw quote units" }), + market_cap: U.openapi({ + description: "Sum of market.market_cap, raw quote units", + }), + usd_priced: z.boolean().openapi({ + description: "The quote has an indexed USD price and known decimals", + }), + volume_usd: Usd.nullable(), + market_cap_usd: Usd.nullable(), +}); +export const Aggregates = z + .strictObject({ + launch_count: Count, + trade_count: Count, + by_quote: z.array(QuoteAggregate), + usd: z.strictObject({ + volume: Usd.nullable().openapi({ + description: + "Sum of market.volume_usd over priced launches; null when none is priced", + }), + market_cap: Usd.nullable().openapi({ + description: + "Sum of market.market_cap_usd over priced launches; null when none is priced", + }), + priced_launch_count: Count, + excluded_launch_count: Count.openapi({ + description: + "Launches left out of the USD sums because their quote has no indexed price", + }), + }), + }) + .openapi({ + description: + "Over every launch matching all filters (the set total counts), not one page. by_quote is ordered by chain_id, then quote_token", + }); export const LaunchList = z.strictObject({ schema_version: SchemaVersion, data: z.array(LaunchSummary), // launches matching every filter, and by status matching the others total: z.number().int().nonnegative(), totals: Totals, + aggregates: Aggregates, next_offset: z.number().int().positive().nullable(), evaluated_at: Seconds, heads: z.array(ChainHead), From 75ee5bea794e5dd9fd160a85b913b2c694e9d6c3 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Sun, 25 Oct 2026 21:22:20 -0400 Subject: [PATCH 19/19] Replay launch-token logs through the indexer's discovered-address filters The indexer now follows each launch token named by LaunchCreated and refuses a creation whose mints were not stored first, so the replay builds its filters with logStreamFilters and groups logs with the addresses loadKnownAddresses returns. The v7 golden is regenerated against indexer 4a2587e; only source.indexer_commit changes. --- .../zero-seed-launch-api-v7.golden.json | 2 +- src/routes/launches/replay.postgres.test.ts | 37 +++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json b/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json index 4114f89..c132980 100644 --- a/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json +++ b/src/routes/launches/fixtures/zero-seed-launch-api-v7.golden.json @@ -1,6 +1,6 @@ { "source": { - "indexer_commit": "2435c89bb52fdcc7f1660031f16b8d11b8d9ffdd", + "indexer_commit": "4a2587e5b733fb4f9f5c4dd185abc66493698749", "fixture_sha256": "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6", "evaluated_at": 2115361000 }, diff --git a/src/routes/launches/replay.postgres.test.ts b/src/routes/launches/replay.postgres.test.ts index a65ecdc..8274aa5 100644 --- a/src/routes/launches/replay.postgres.test.ts +++ b/src/routes/launches/replay.postgres.test.ts @@ -22,7 +22,7 @@ import { LaunchQueries } from "./queries"; const ROOT = process.env.ZERO_SEED_INDEXER_WORKTREE; const SERVER = process.env.TEST_PG_CONNECTION_STRING; // The indexer revision and fixture this replay was written against. -export const INDEXER_COMMIT = "2435c89bb52fdcc7f1660031f16b8d11b8d9ffdd"; +export const INDEXER_COMMIT = "4a2587e5b733fb4f9f5c4dd185abc66493698749"; export const FIXTURE_SHA256 = "b818cb5a4d939a67bd91a672f394ff32146edb9557cdf597e02ecad04d9a64d6"; @@ -101,6 +101,7 @@ interface Dao { baseFeePerGas: null; numEvents: number; }): Promise; + loadDiscoveredAddresses(source: unknown): Promise; } interface StreamBlock { header: { blockNumber: bigint }; @@ -124,7 +125,16 @@ interface Indexer { dao: Dao, ): Promise; createLogProcessorsV3(config: Record): Processor[]; - groupLogsByBlock(logs: RawLog[], filters: unknown[]): StreamBlock[]; + logStreamFilters(processors: Processor[]): unknown[]; + loadKnownAddresses( + filters: unknown[], + load: (source: unknown) => Promise, + ): Promise; + groupLogsByBlock( + logs: RawLog[], + filters: unknown[], + known: unknown, + ): StreamBlock[]; shift(o: { sql: unknown; path: string }): Promise; postgres: typeof postgres; } @@ -190,6 +200,7 @@ describe.skipIf(!ROOT || !SERVER)( const evm = await load("src/evm.ts"); const v3 = await load("src/evm/logProcessorsV3.ts"); const stream = await load("src/evm/logStream.ts"); + const shared = await load("src/evm/logProcessorsShared.ts"); const shiftModule = (await import( Bun.resolveSync("postgres-shift", ROOT) )) as { default: Indexer["shift"] }; @@ -203,7 +214,11 @@ describe.skipIf(!ROOT || !SERVER)( "parseEvmBlockHeader" | "processEvmBlock" >), ...(v3 as unknown as Pick), - ...(stream as unknown as Pick), + ...(stream as unknown as Pick< + Indexer, + "groupLogsByBlock" | "loadKnownAddresses" + >), + ...(shared as unknown as Pick), shift: shiftModule.default, postgres: pg.default, }; @@ -268,21 +283,19 @@ describe.skipIf(!ROOT || !SERVER)( } type Db = Awaited>; - const filters = () => - processors.map((p, i) => ({ - id: i + 1, - address: p.address, - topics: p.filter.topics, - strict: p.filter.strict, - })); - /** The runtime's per-block transaction, then the cursor head the API reads. */ async function replay( db: Db, branch: Branch, check?: (state: Branch["states"][number], time: bigint) => Promise, ) { - const blocks = ix.groupLogsByBlock(branch.logs, filters()); + // as the stream does: the tokens already indexed, plus those the + // branch creates + const filters = ix.logStreamFilters(processors); + const known = await ix.loadKnownAddresses(filters, (source) => + db.dao.loadDiscoveredAddresses(source), + ); + const blocks = ix.groupLogsByBlock(branch.logs, filters, known); for (const fixtureBlock of branch.blocks) { const block = blocks.find( (b) => Number(b.header.blockNumber) === fixtureBlock.number,