diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7ac95b0b..f2cdf60b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,5 +27,6 @@ jobs: run: bun test env: RPC_ENDPOINT: https://api.devnet.solana.com/ + BACKUP_RPC_ENDPOINT: https://api.devnet.solana.com/ INDEXER_URL: https://staging-indexer.metadao.fi INDEXER_WSS_URL: wss://staging-indexer.metadao.fi diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..62ce040c --- /dev/null +++ b/TODO.md @@ -0,0 +1,7 @@ +TODO + +[] Insert conditional prices when new proposal is indexed - DONE +[] Manage database connections better - probably a connection pool with dedicated channels for each major component +[] Add telemetry - probably log calltrace when indexer crashes +[] Add logging for db connections and health checks +[] Unify v3 and v4 indexers to both either use croner or regular intervals (currently v3 uses cron and v4 uses regular intervals) \ No newline at end of file diff --git a/packages/database/drizzle/0014_lonely_wolfsbane.sql b/packages/database/drizzle/0014_lonely_wolfsbane.sql new file mode 100644 index 00000000..4ac776e3 --- /dev/null +++ b/packages/database/drizzle/0014_lonely_wolfsbane.sql @@ -0,0 +1,2 @@ +ALTER TABLE "markets" ADD COLUMN "base_amount" numeric;--> statement-breakpoint +ALTER TABLE "markets" ADD COLUMN "quote_amount" numeric; \ No newline at end of file diff --git a/packages/database/drizzle/meta/0014_snapshot.json b/packages/database/drizzle/meta/0014_snapshot.json new file mode 100644 index 00000000..d3558fb9 --- /dev/null +++ b/packages/database/drizzle/meta/0014_snapshot.json @@ -0,0 +1,4291 @@ +{ + "id": "59efc177-30cc-4541-9011-fcd90ddb6028", + "prevId": "fe8c29f9-489c-4e52-acba-3124dfb13447", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.candles": { + "name": "candles", + "schema": "", + "columns": { + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "candle_duration": { + "name": "candle_duration", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "volume": { + "name": "volume", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "open": { + "name": "open", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "high": { + "name": "high", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "low": { + "name": "low", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "close": { + "name": "close", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "candle_average": { + "name": "candle_average", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "cond_market_twap": { + "name": "cond_market_twap", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "candles_market_acct_markets_market_acct_fk": { + "name": "candles_market_acct_markets_market_acct_fk", + "tableFrom": "candles", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "candles_market_acct_candle_duration_timestamp_pk": { + "name": "candles_market_acct_candle_duration_timestamp_pk", + "columns": [ + "market_acct", + "candle_duration", + "timestamp" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.comments": { + "name": "comments", + "schema": "", + "columns": { + "comment_id": { + "name": "comment_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "commentor_acct": { + "name": "commentor_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responding_comment_id": { + "name": "responding_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "comments_proposal_acct_proposals_proposal_acct_fk": { + "name": "comments_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "comments", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "comments_responding_comment_id_comments_comment_id_fk": { + "name": "comments_responding_comment_id_comments_comment_id_fk", + "tableFrom": "comments", + "tableTo": "comments", + "columnsFrom": [ + "responding_comment_id" + ], + "columnsTo": [ + "comment_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "comments_comment_id_unique": { + "name": "comments_comment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "comment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conditional_vaults": { + "name": "conditional_vaults", + "schema": "", + "columns": { + "cond_vault_acct": { + "name": "cond_vault_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "settlement_authority": { + "name": "settlement_authority", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "underlying_mint_acct": { + "name": "underlying_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "underlying_token_acct": { + "name": "underlying_token_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "nonce": { + "name": "nonce", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "cond_finalize_token_mint_acct": { + "name": "cond_finalize_token_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "cond_revert_token_mint_acct": { + "name": "cond_revert_token_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "conditional_vaults_underlying_mint_acct_tokens_mint_acct_fk": { + "name": "conditional_vaults_underlying_mint_acct_tokens_mint_acct_fk", + "tableFrom": "conditional_vaults", + "tableTo": "tokens", + "columnsFrom": [ + "underlying_mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dao_details": { + "name": "dao_details", + "schema": "", + "columns": { + "dao_id": { + "name": "dao_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "x_account": { + "name": "x_account", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "github": { + "name": "github", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "creator_acct": { + "name": "creator_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "admin_accts": { + "name": "admin_accts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "token_image_url": { + "name": "token_image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "pass_token_image_url": { + "name": "pass_token_image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "fail_token_image_url": { + "name": "fail_token_image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "lp_token_image_url": { + "name": "lp_token_image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "is_hide": { + "name": "is_hide", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socials": { + "name": "socials", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "base_mint": { + "name": "base_mint", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "quote_mint": { + "name": "quote_mint", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "dao_details_organization_id_organizations_organization_id_fk": { + "name": "dao_details_organization_id_organizations_organization_id_fk", + "tableFrom": "dao_details", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "organization_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dao_details_base_mint_tokens_mint_acct_fk": { + "name": "dao_details_base_mint_tokens_mint_acct_fk", + "tableFrom": "dao_details", + "tableTo": "tokens", + "columnsFrom": [ + "base_mint" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dao_details_quote_mint_tokens_mint_acct_fk": { + "name": "dao_details_quote_mint_tokens_mint_acct_fk", + "tableFrom": "dao_details", + "tableTo": "tokens", + "columnsFrom": [ + "quote_mint" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dao_details_name_unique": { + "name": "dao_details_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "dao_details_slug_unique": { + "name": "dao_details_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "dao_details_url_unique": { + "name": "dao_details_url_unique", + "nullsNotDistinct": false, + "columns": [ + "url" + ] + }, + "dao_details_x_account_unique": { + "name": "dao_details_x_account_unique", + "nullsNotDistinct": false, + "columns": [ + "x_account" + ] + }, + "dao_details_github_unique": { + "name": "dao_details_github_unique", + "nullsNotDistinct": false, + "columns": [ + "github" + ] + }, + "id_name_url": { + "name": "id_name_url", + "nullsNotDistinct": false, + "columns": [ + "dao_id", + "url", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.daos": { + "name": "daos", + "schema": "", + "columns": { + "dao_acct": { + "name": "dao_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "program_acct": { + "name": "program_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "dao_id": { + "name": "dao_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "base_acct": { + "name": "base_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "quote_acct": { + "name": "quote_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "treasury_acct": { + "name": "treasury_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "slots_per_proposal": { + "name": "slots_per_proposal", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "pass_threshold_bps": { + "name": "pass_threshold_bps", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "twap_initial_observation": { + "name": "twap_initial_observation", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "twap_max_observation_change_per_update": { + "name": "twap_max_observation_change_per_update", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "min_quote_futarchic_liquidity": { + "name": "min_quote_futarchic_liquidity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "min_base_futarchic_liquidity": { + "name": "min_base_futarchic_liquidity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "organization_id": { + "name": "organization_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "colors": { + "name": "colors", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "daos_program_acct_programs_program_acct_fk": { + "name": "daos_program_acct_programs_program_acct_fk", + "tableFrom": "daos", + "tableTo": "programs", + "columnsFrom": [ + "program_acct" + ], + "columnsTo": [ + "program_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "daos_dao_id_dao_details_dao_id_fk": { + "name": "daos_dao_id_dao_details_dao_id_fk", + "tableFrom": "daos", + "tableTo": "dao_details", + "columnsFrom": [ + "dao_id" + ], + "columnsTo": [ + "dao_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "daos_base_acct_tokens_mint_acct_fk": { + "name": "daos_base_acct_tokens_mint_acct_fk", + "tableFrom": "daos", + "tableTo": "tokens", + "columnsFrom": [ + "base_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "daos_quote_acct_tokens_mint_acct_fk": { + "name": "daos_quote_acct_tokens_mint_acct_fk", + "tableFrom": "daos", + "tableTo": "tokens", + "columnsFrom": [ + "quote_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "daos_organization_id_organizations_organization_id_fk": { + "name": "daos_organization_id_organizations_organization_id_fk", + "tableFrom": "daos", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "organization_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "daos_treasury_acct_unique": { + "name": "daos_treasury_acct_unique", + "nullsNotDistinct": false, + "columns": [ + "treasury_acct" + ] + }, + "dao_acct_program": { + "name": "dao_acct_program", + "nullsNotDistinct": false, + "columns": [ + "dao_acct", + "program_acct" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.indexer_account_dependencies": { + "name": "indexer_account_dependencies", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "acct": { + "name": "acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "latest_tx_sig_processed": { + "name": "latest_tx_sig_processed", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'active'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "indexer_account_dependencies_name_indexers_name_fk": { + "name": "indexer_account_dependencies_name_indexers_name_fk", + "tableFrom": "indexer_account_dependencies", + "tableTo": "indexers", + "columnsFrom": [ + "name" + ], + "columnsTo": [ + "name" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "indexer_account_dependencies_latest_tx_sig_processed_transactions_tx_sig_fk": { + "name": "indexer_account_dependencies_latest_tx_sig_processed_transactions_tx_sig_fk", + "tableFrom": "indexer_account_dependencies", + "tableTo": "transactions", + "columnsFrom": [ + "latest_tx_sig_processed" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "indexer_account_dependencies_name_acct_pk": { + "name": "indexer_account_dependencies_name_acct_pk", + "columns": [ + "name", + "acct" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.indexers": { + "name": "indexers", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": true, + "notNull": true + }, + "implementation": { + "name": "implementation", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "latest_slot_processed": { + "name": "latest_slot_processed", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "latest_tx_sig_processed": { + "name": "latest_tx_sig_processed", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "indexer_type": { + "name": "indexer_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.makes": { + "name": "makes", + "schema": "", + "columns": { + "order_tx_sig": { + "name": "order_tx_sig", + "type": "varchar(88)", + "primaryKey": true, + "notNull": true + }, + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "unfilled_base_amount": { + "name": "unfilled_base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "filled_base_amount": { + "name": "filled_base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "quote_price": { + "name": "quote_price", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "market_index": { + "name": "market_index", + "columns": [ + { + "expression": "market_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "makes_order_tx_sig_orders_order_tx_sig_fk": { + "name": "makes_order_tx_sig_orders_order_tx_sig_fk", + "tableFrom": "makes", + "tableTo": "orders", + "columnsFrom": [ + "order_tx_sig" + ], + "columnsTo": [ + "order_tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "makes_market_acct_markets_market_acct_fk": { + "name": "makes_market_acct_markets_market_acct_fk", + "tableFrom": "makes", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.markets": { + "name": "markets", + "schema": "", + "columns": { + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "market_type": { + "name": "market_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "create_tx_sig": { + "name": "create_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "base_mint_acct": { + "name": "base_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "quote_mint_acct": { + "name": "quote_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "base_lot_size": { + "name": "base_lot_size", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "quote_lot_size": { + "name": "quote_lot_size", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "quote_tick_size": { + "name": "quote_tick_size", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "bids_token_acct": { + "name": "bids_token_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "asks_token_acct": { + "name": "asks_token_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "base_maker_fee": { + "name": "base_maker_fee", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "base_taker_fee": { + "name": "base_taker_fee", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "quote_maker_fee": { + "name": "quote_maker_fee", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "quote_taker_fee": { + "name": "quote_taker_fee", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "active_slot": { + "name": "active_slot", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "inactive_slot": { + "name": "inactive_slot", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "base_amount": { + "name": "base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "quote_amount": { + "name": "quote_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "markets_proposal_acct_proposals_proposal_acct_fk": { + "name": "markets_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "markets", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "markets_base_mint_acct_tokens_mint_acct_fk": { + "name": "markets_base_mint_acct_tokens_mint_acct_fk", + "tableFrom": "markets", + "tableTo": "tokens", + "columnsFrom": [ + "base_mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "markets_quote_mint_acct_tokens_mint_acct_fk": { + "name": "markets_quote_mint_acct_tokens_mint_acct_fk", + "tableFrom": "markets", + "tableTo": "tokens", + "columnsFrom": [ + "quote_mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "markets_bids_token_acct_token_accts_token_acct_fk": { + "name": "markets_bids_token_acct_token_accts_token_acct_fk", + "tableFrom": "markets", + "tableTo": "token_accts", + "columnsFrom": [ + "bids_token_acct" + ], + "columnsTo": [ + "token_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "markets_asks_token_acct_token_accts_token_acct_fk": { + "name": "markets_asks_token_acct_token_accts_token_acct_fk", + "tableFrom": "markets", + "tableTo": "token_accts", + "columnsFrom": [ + "asks_token_acct" + ], + "columnsTo": [ + "token_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.orders": { + "name": "orders", + "schema": "", + "columns": { + "order_tx_sig": { + "name": "order_tx_sig", + "type": "varchar(88)", + "primaryKey": true, + "notNull": true + }, + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "actor_acct": { + "name": "actor_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "unfilled_base_amount": { + "name": "unfilled_base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "filled_base_amount": { + "name": "filled_base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "quote_price": { + "name": "quote_price", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "order_block": { + "name": "order_block", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "order_time": { + "name": "order_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cancel_tx_sig": { + "name": "cancel_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "cancel_block": { + "name": "cancel_block", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "cancel_time": { + "name": "cancel_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "actor_index": { + "name": "actor_index", + "columns": [ + { + "expression": "market_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "orders_order_tx_sig_transactions_tx_sig_fk": { + "name": "orders_order_tx_sig_transactions_tx_sig_fk", + "tableFrom": "orders", + "tableTo": "transactions", + "columnsFrom": [ + "order_tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "orders_market_acct_markets_market_acct_fk": { + "name": "orders_market_acct_markets_market_acct_fk", + "tableFrom": "orders", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "orders_actor_acct_users_user_acct_fk": { + "name": "orders_actor_acct_users_user_acct_fk", + "tableFrom": "orders", + "tableTo": "users", + "columnsFrom": [ + "actor_acct" + ], + "columnsTo": [ + "user_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "creator_acct": { + "name": "creator_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "admin_accts": { + "name": "admin_accts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_hide": { + "name": "is_hide", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "socials": { + "name": "socials", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_name_unique": { + "name": "organizations_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + }, + "organizations_url_unique": { + "name": "organizations_url_unique", + "nullsNotDistinct": false, + "columns": [ + "url" + ] + }, + "id_name_url": { + "name": "id_name_url", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "url", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.prices": { + "name": "prices", + "schema": "", + "columns": { + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "updated_slot": { + "name": "updated_slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "base_amount": { + "name": "base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "quote_amount": { + "name": "quote_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prices_type": { + "name": "prices_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "market_by_slot_index": { + "name": "market_by_slot_index", + "columns": [ + { + "expression": "market_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "prices_market_acct_markets_market_acct_fk": { + "name": "prices_market_acct_markets_market_acct_fk", + "tableFrom": "prices", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "prices_created_at_market_acct_pk": { + "name": "prices_created_at_market_acct_pk", + "columns": [ + "created_at", + "market_acct" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.program_system": { + "name": "program_system", + "schema": "", + "columns": { + "system_version": { + "name": "system_version", + "type": "double precision", + "primaryKey": true, + "notNull": true + }, + "autocrat_acct": { + "name": "autocrat_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "conditional_vault_acct": { + "name": "conditional_vault_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "pricing_model_acct": { + "name": "pricing_model_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "migrator_acct": { + "name": "migrator_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "program_system_autocrat_acct_programs_program_acct_fk": { + "name": "program_system_autocrat_acct_programs_program_acct_fk", + "tableFrom": "program_system", + "tableTo": "programs", + "columnsFrom": [ + "autocrat_acct" + ], + "columnsTo": [ + "program_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_system_conditional_vault_acct_programs_program_acct_fk": { + "name": "program_system_conditional_vault_acct_programs_program_acct_fk", + "tableFrom": "program_system", + "tableTo": "programs", + "columnsFrom": [ + "conditional_vault_acct" + ], + "columnsTo": [ + "program_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_system_pricing_model_acct_programs_program_acct_fk": { + "name": "program_system_pricing_model_acct_programs_program_acct_fk", + "tableFrom": "program_system", + "tableTo": "programs", + "columnsFrom": [ + "pricing_model_acct" + ], + "columnsTo": [ + "program_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "program_system_migrator_acct_programs_program_acct_fk": { + "name": "program_system_migrator_acct_programs_program_acct_fk", + "tableFrom": "program_system", + "tableTo": "programs", + "columnsFrom": [ + "migrator_acct" + ], + "columnsTo": [ + "program_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.programs": { + "name": "programs", + "schema": "", + "columns": { + "program_acct": { + "name": "program_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "program_name": { + "name": "program_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "program_version": { + "name": "program_version", + "nullsNotDistinct": false, + "columns": [ + "program_acct", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proposal_details": { + "name": "proposal_details", + "schema": "", + "columns": { + "proposal_id": { + "name": "proposal_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_acct": { + "name": "proposer_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "base_cond_vault_acct": { + "name": "base_cond_vault_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "quote_cond_vault_acct": { + "name": "quote_cond_vault_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "pass_market_acct": { + "name": "pass_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "fail_market_acct": { + "name": "fail_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "proposal_details_proposal_acct_proposals_proposal_acct_fk": { + "name": "proposal_details_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "proposal_details", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "proposal_details_slug_unique": { + "name": "proposal_details_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proposals": { + "name": "proposals", + "schema": "", + "columns": { + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "dao_acct": { + "name": "dao_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "proposal_num": { + "name": "proposal_num", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "autocrat_version": { + "name": "autocrat_version", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "proposer_acct": { + "name": "proposer_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "initial_slot": { + "name": "initial_slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "end_slot": { + "name": "end_slot", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description_url": { + "name": "description_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "pricing_model_pass_acct": { + "name": "pricing_model_pass_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "pricing_model_fail_acct": { + "name": "pricing_model_fail_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "pass_market_acct": { + "name": "pass_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "fail_market_acct": { + "name": "fail_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "base_vault": { + "name": "base_vault", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "quote_vault": { + "name": "quote_vault", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "duration_in_slots": { + "name": "duration_in_slots", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "pass_threshold_bps": { + "name": "pass_threshold_bps", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "twap_initial_observation": { + "name": "twap_initial_observation", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "twap_max_observation_change_per_update": { + "name": "twap_max_observation_change_per_update", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "min_quote_futarchic_liquidity": { + "name": "min_quote_futarchic_liquidity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "min_base_futarchic_liquidity": { + "name": "min_base_futarchic_liquidity", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "proposals_dao_acct_daos_dao_acct_fk": { + "name": "proposals_dao_acct_daos_dao_acct_fk", + "tableFrom": "proposals", + "tableTo": "daos", + "columnsFrom": [ + "dao_acct" + ], + "columnsTo": [ + "dao_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "proposals_base_vault_conditional_vaults_cond_vault_acct_fk": { + "name": "proposals_base_vault_conditional_vaults_cond_vault_acct_fk", + "tableFrom": "proposals", + "tableTo": "conditional_vaults", + "columnsFrom": [ + "base_vault" + ], + "columnsTo": [ + "cond_vault_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "proposals_quote_vault_conditional_vaults_cond_vault_acct_fk": { + "name": "proposals_quote_vault_conditional_vaults_cond_vault_acct_fk", + "tableFrom": "proposals", + "tableTo": "conditional_vaults", + "columnsFrom": [ + "quote_vault" + ], + "columnsTo": [ + "cond_vault_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "reaction_id": { + "name": "reaction_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "reactor_acct": { + "name": "reactor_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "comment_id": { + "name": "comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "reaction": { + "name": "reaction", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "reactions_comment_id_comments_comment_id_fk": { + "name": "reactions_comment_id_comments_comment_id_fk", + "tableFrom": "reactions", + "tableTo": "comments", + "columnsFrom": [ + "comment_id" + ], + "columnsTo": [ + "comment_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "reactions_proposal_acct_proposals_proposal_acct_fk": { + "name": "reactions_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "reactions", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_acct": { + "name": "user_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_acct_users_user_acct_fk": { + "name": "sessions_user_acct_users_user_acct_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_acct" + ], + "columnsTo": [ + "user_acct" + ], + "onDelete": "restrict", + "onUpdate": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signature_accounts": { + "name": "signature_accounts", + "schema": "", + "columns": { + "signature": { + "name": "signature", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "account": { + "name": "account", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "inserted_at": { + "name": "inserted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "account_index": { + "name": "account_index", + "columns": [ + { + "expression": "account", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "signature_accounts_signature_account_pk": { + "name": "signature_accounts_signature_account_pk", + "columns": [ + "signature", + "account" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signatures": { + "name": "signatures", + "schema": "", + "columns": { + "signature": { + "name": "signature", + "type": "varchar(88)", + "primaryKey": true, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "did_err": { + "name": "did_err", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "err": { + "name": "err", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_time": { + "name": "block_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "inserted_at": { + "name": "inserted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "seq_num": { + "name": "seq_num", + "type": "bigserial", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "slot_index": { + "name": "slot_index", + "columns": [ + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sequence_num_index": { + "name": "sequence_num_index", + "columns": [ + { + "expression": "seq_num", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "signatures_seq_num_unique": { + "name": "signatures_seq_num_unique", + "nullsNotDistinct": false, + "columns": [ + "seq_num" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.takes": { + "name": "takes", + "schema": "", + "columns": { + "take_id": { + "name": "take_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "order_tx_sig": { + "name": "order_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "base_amount": { + "name": "base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "quote_price": { + "name": "quote_price", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "taker_base_fee": { + "name": "taker_base_fee", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "taker_quote_fee": { + "name": "taker_quote_fee", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maker_order_tx_sig": { + "name": "maker_order_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "maker_base_fee": { + "name": "maker_base_fee", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "maker_quote_fee": { + "name": "maker_quote_fee", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "order_block": { + "name": "order_block", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "order_time": { + "name": "order_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "block_index": { + "name": "block_index", + "columns": [ + { + "expression": "market_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_block", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "time_index": { + "name": "time_index", + "columns": [ + { + "expression": "market_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "maker_index": { + "name": "maker_index", + "columns": [ + { + "expression": "maker_order_tx_sig", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "takes_order_tx_sig_orders_order_tx_sig_fk": { + "name": "takes_order_tx_sig_orders_order_tx_sig_fk", + "tableFrom": "takes", + "tableTo": "orders", + "columnsFrom": [ + "order_tx_sig" + ], + "columnsTo": [ + "order_tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "takes_maker_order_tx_sig_makes_order_tx_sig_fk": { + "name": "takes_maker_order_tx_sig_makes_order_tx_sig_fk", + "tableFrom": "takes", + "tableTo": "makes", + "columnsFrom": [ + "maker_order_tx_sig" + ], + "columnsTo": [ + "order_tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "takes_market_acct_markets_market_acct_fk": { + "name": "takes_market_acct_markets_market_acct_fk", + "tableFrom": "takes", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.token_acct_balances": { + "name": "token_acct_balances", + "schema": "", + "columns": { + "token_acct": { + "name": "token_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "mint_acct": { + "name": "mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "owner_acct": { + "name": "owner_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delta": { + "name": "delta", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "tx_sig": { + "name": "tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "acct_amount_created": { + "name": "acct_amount_created", + "columns": [ + { + "expression": "token_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "amount", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "token_acct_balances_token_acct_token_accts_token_acct_fk": { + "name": "token_acct_balances_token_acct_token_accts_token_acct_fk", + "tableFrom": "token_acct_balances", + "tableTo": "token_accts", + "columnsFrom": [ + "token_acct" + ], + "columnsTo": [ + "token_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "token_acct_balances_mint_acct_tokens_mint_acct_fk": { + "name": "token_acct_balances_mint_acct_tokens_mint_acct_fk", + "tableFrom": "token_acct_balances", + "tableTo": "tokens", + "columnsFrom": [ + "mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "token_acct_balances_tx_sig_transactions_tx_sig_fk": { + "name": "token_acct_balances_tx_sig_transactions_tx_sig_fk", + "tableFrom": "token_acct_balances", + "tableTo": "transactions", + "columnsFrom": [ + "tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "token_acct_balances_token_acct_mint_acct_amount_created_at_pk": { + "name": "token_acct_balances_token_acct_mint_acct_amount_created_at_pk", + "columns": [ + "token_acct", + "mint_acct", + "amount", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.token_accts": { + "name": "token_accts", + "schema": "", + "columns": { + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "mint_acct": { + "name": "mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "owner_acct": { + "name": "owner_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'enabled'" + }, + "token_acct": { + "name": "token_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "token_accts_mint_acct_tokens_mint_acct_fk": { + "name": "token_accts_mint_acct_tokens_mint_acct_fk", + "tableFrom": "token_accts", + "tableTo": "tokens", + "columnsFrom": [ + "mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tokens": { + "name": "tokens", + "schema": "", + "columns": { + "mint_acct": { + "name": "mint_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "symbol": { + "name": "symbol", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "supply": { + "name": "supply", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "decimals": { + "name": "decimals", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_watcher_transactions": { + "name": "transaction_watcher_transactions", + "schema": "", + "columns": { + "watcher_acct": { + "name": "watcher_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "tx_sig": { + "name": "tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "watcher_slot_index": { + "name": "watcher_slot_index", + "columns": [ + { + "expression": "watcher_acct", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transaction_watcher_transactions_watcher_acct_transaction_watchers_acct_fk": { + "name": "transaction_watcher_transactions_watcher_acct_transaction_watchers_acct_fk", + "tableFrom": "transaction_watcher_transactions", + "tableTo": "transaction_watchers", + "columnsFrom": [ + "watcher_acct" + ], + "columnsTo": [ + "acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "transaction_watcher_transactions_tx_sig_transactions_tx_sig_fk": { + "name": "transaction_watcher_transactions_tx_sig_transactions_tx_sig_fk", + "tableFrom": "transaction_watcher_transactions", + "tableTo": "transactions", + "columnsFrom": [ + "tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "transaction_watcher_transactions_watcher_acct_tx_sig_pk": { + "name": "transaction_watcher_transactions_watcher_acct_tx_sig_pk", + "columns": [ + "watcher_acct", + "tx_sig" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction_watchers": { + "name": "transaction_watchers", + "schema": "", + "columns": { + "acct": { + "name": "acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "latest_tx_sig": { + "name": "latest_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "first_tx_sig": { + "name": "first_tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": false + }, + "checked_up_to_slot": { + "name": "checked_up_to_slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "serializer_logic_version": { + "name": "serializer_logic_version", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'disabled'" + }, + "failure_log": { + "name": "failure_log", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_watchers_latest_tx_sig_transactions_tx_sig_fk": { + "name": "transaction_watchers_latest_tx_sig_transactions_tx_sig_fk", + "tableFrom": "transaction_watchers", + "tableTo": "transactions", + "columnsFrom": [ + "latest_tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "transaction_watchers_first_tx_sig_transactions_tx_sig_fk": { + "name": "transaction_watchers_first_tx_sig_transactions_tx_sig_fk", + "tableFrom": "transaction_watchers", + "tableTo": "transactions", + "columnsFrom": [ + "first_tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactions": { + "name": "transactions", + "schema": "", + "columns": { + "tx_sig": { + "name": "tx_sig", + "type": "varchar(88)", + "primaryKey": true, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "block_time": { + "name": "block_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "failed": { + "name": "failed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "serializer_logic_version": { + "name": "serializer_logic_version", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "main_ix_type": { + "name": "main_ix_type", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "txn_slot_index": { + "name": "txn_slot_index", + "columns": [ + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.twaps": { + "name": "twaps", + "schema": "", + "columns": { + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": false + }, + "updated_slot": { + "name": "updated_slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "observation_agg": { + "name": "observation_agg", + "type": "numeric(40, 0)", + "primaryKey": false, + "notNull": true + }, + "last_observation": { + "name": "last_observation", + "type": "numeric(40, 0)", + "primaryKey": false, + "notNull": false + }, + "last_price": { + "name": "last_price", + "type": "numeric(40, 0)", + "primaryKey": false, + "notNull": false + }, + "token_amount": { + "name": "token_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "twaps_market_acct_markets_market_acct_fk": { + "name": "twaps_market_acct_markets_market_acct_fk", + "tableFrom": "twaps", + "tableTo": "markets", + "columnsFrom": [ + "market_acct" + ], + "columnsTo": [ + "market_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "twaps_proposal_acct_proposals_proposal_acct_fk": { + "name": "twaps_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "twaps", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "twaps_updated_slot_market_acct_pk": { + "name": "twaps_updated_slot_market_acct_pk", + "columns": [ + "updated_slot", + "market_acct" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_deposits": { + "name": "user_deposits", + "schema": "", + "columns": { + "tx_sig": { + "name": "tx_sig", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "user_acct": { + "name": "user_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "token_amount": { + "name": "token_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "mint_acct": { + "name": "mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_deposits_tx_sig_transactions_tx_sig_fk": { + "name": "user_deposits_tx_sig_transactions_tx_sig_fk", + "tableFrom": "user_deposits", + "tableTo": "transactions", + "columnsFrom": [ + "tx_sig" + ], + "columnsTo": [ + "tx_sig" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_deposits_user_acct_users_user_acct_fk": { + "name": "user_deposits_user_acct_users_user_acct_fk", + "tableFrom": "user_deposits", + "tableTo": "users", + "columnsFrom": [ + "user_acct" + ], + "columnsTo": [ + "user_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_deposits_mint_acct_tokens_mint_acct_fk": { + "name": "user_deposits_mint_acct_tokens_mint_acct_fk", + "tableFrom": "user_deposits", + "tableTo": "tokens", + "columnsFrom": [ + "mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_performance": { + "name": "user_performance", + "schema": "", + "columns": { + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "user_acct": { + "name": "user_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "dao_acct": { + "name": "dao_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "tokens_bought": { + "name": "tokens_bought", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "tokens_sold": { + "name": "tokens_sold", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "volume_bought": { + "name": "volume_bought", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "volume_sold": { + "name": "volume_sold", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "total_volume": { + "name": "total_volume", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true, + "default": "'0.0'" + }, + "tokens_bought_resolving_market": { + "name": "tokens_bought_resolving_market", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true, + "default": "'0.0'" + }, + "tokens_sold_resolving_market": { + "name": "tokens_sold_resolving_market", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true, + "default": "'0.0'" + }, + "volume_bought_resolving_market": { + "name": "volume_bought_resolving_market", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true, + "default": "'0.0'" + }, + "volume_sold_resolving_market": { + "name": "volume_sold_resolving_market", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true, + "default": "'0.0'" + }, + "buy_orders_count": { + "name": "buy_orders_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sell_orders_count": { + "name": "sell_orders_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_performance_proposal_acct_proposals_proposal_acct_fk": { + "name": "user_performance_proposal_acct_proposals_proposal_acct_fk", + "tableFrom": "user_performance", + "tableTo": "proposals", + "columnsFrom": [ + "proposal_acct" + ], + "columnsTo": [ + "proposal_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_performance_user_acct_users_user_acct_fk": { + "name": "user_performance_user_acct_users_user_acct_fk", + "tableFrom": "user_performance", + "tableTo": "users", + "columnsFrom": [ + "user_acct" + ], + "columnsTo": [ + "user_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_performance_dao_acct_daos_dao_acct_fk": { + "name": "user_performance_dao_acct_daos_dao_acct_fk", + "tableFrom": "user_performance", + "tableTo": "daos", + "columnsFrom": [ + "dao_acct" + ], + "columnsTo": [ + "dao_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_performance_proposal_acct_user_acct_pk": { + "name": "user_performance_proposal_acct_user_acct_pk", + "columns": [ + "proposal_acct", + "user_acct" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "user_acct": { + "name": "user_acct", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_amms": { + "name": "v0_4_amms", + "schema": "", + "columns": { + "amm_addr": { + "name": "amm_addr", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "created_at_slot": { + "name": "created_at_slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "lp_mint_addr": { + "name": "lp_mint_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "base_mint_addr": { + "name": "base_mint_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "quote_mint_addr": { + "name": "quote_mint_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "base_reserves": { + "name": "base_reserves", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "quote_reserves": { + "name": "quote_reserves", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "latest_amm_seq_num_applied": { + "name": "latest_amm_seq_num_applied", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "inserted_at": { + "name": "inserted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "v0_4_amms_lp_mint_addr_tokens_mint_acct_fk": { + "name": "v0_4_amms_lp_mint_addr_tokens_mint_acct_fk", + "tableFrom": "v0_4_amms", + "tableTo": "tokens", + "columnsFrom": [ + "lp_mint_addr" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_amms_base_mint_addr_tokens_mint_acct_fk": { + "name": "v0_4_amms_base_mint_addr_tokens_mint_acct_fk", + "tableFrom": "v0_4_amms", + "tableTo": "tokens", + "columnsFrom": [ + "base_mint_addr" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_amms_quote_mint_addr_tokens_mint_acct_fk": { + "name": "v0_4_amms_quote_mint_addr_tokens_mint_acct_fk", + "tableFrom": "v0_4_amms", + "tableTo": "tokens", + "columnsFrom": [ + "quote_mint_addr" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_conditional_vaults": { + "name": "v0_4_conditional_vaults", + "schema": "", + "columns": { + "conditional_vault_addr": { + "name": "conditional_vault_addr", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "question_addr": { + "name": "question_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "underlying_mint_acct": { + "name": "underlying_mint_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "underlying_token_acct": { + "name": "underlying_token_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "pda_bump": { + "name": "pda_bump", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "latest_vault_seq_num_applied": { + "name": "latest_vault_seq_num_applied", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "v0_4_conditional_vaults_question_addr_v0_4_questions_question_addr_fk": { + "name": "v0_4_conditional_vaults_question_addr_v0_4_questions_question_addr_fk", + "tableFrom": "v0_4_conditional_vaults", + "tableTo": "v0_4_questions", + "columnsFrom": [ + "question_addr" + ], + "columnsTo": [ + "question_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_conditional_vaults_underlying_mint_acct_tokens_mint_acct_fk": { + "name": "v0_4_conditional_vaults_underlying_mint_acct_tokens_mint_acct_fk", + "tableFrom": "v0_4_conditional_vaults", + "tableTo": "tokens", + "columnsFrom": [ + "underlying_mint_acct" + ], + "columnsTo": [ + "mint_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_conditional_vaults_underlying_token_acct_token_accts_token_acct_fk": { + "name": "v0_4_conditional_vaults_underlying_token_acct_token_accts_token_acct_fk", + "tableFrom": "v0_4_conditional_vaults", + "tableTo": "token_accts", + "columnsFrom": [ + "underlying_token_acct" + ], + "columnsTo": [ + "token_acct" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_merges": { + "name": "v0_4_merges", + "schema": "", + "columns": { + "vault_addr": { + "name": "vault_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "vault_seq_num": { + "name": "vault_seq_num", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "signature": { + "name": "signature", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "merge_vault_index": { + "name": "merge_vault_index", + "columns": [ + { + "expression": "vault_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "merge_signature_index": { + "name": "merge_signature_index", + "columns": [ + { + "expression": "signature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "merge_seq_num_vault_index": { + "name": "merge_seq_num_vault_index", + "columns": [ + { + "expression": "vault_seq_num", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vault_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v0_4_merges_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk": { + "name": "v0_4_merges_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk", + "tableFrom": "v0_4_merges", + "tableTo": "v0_4_conditional_vaults", + "columnsFrom": [ + "vault_addr" + ], + "columnsTo": [ + "conditional_vault_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_merges_signature_signatures_signature_fk": { + "name": "v0_4_merges_signature_signatures_signature_fk", + "tableFrom": "v0_4_merges", + "tableTo": "signatures", + "columnsFrom": [ + "signature" + ], + "columnsTo": [ + "signature" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_metric_decisions": { + "name": "v0_4_metric_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "dao_id": { + "name": "dao_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient": { + "name": "recipient", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "outcome_question_addr": { + "name": "outcome_question_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "metric_question_addr": { + "name": "metric_question_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "outcome_vault_addr": { + "name": "outcome_vault_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "metric_vault_addr": { + "name": "metric_vault_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "amm_addr": { + "name": "amm_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "market_opened": { + "name": "market_opened", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "grant_awarded": { + "name": "grant_awarded", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "committee_evaluation": { + "name": "committee_evaluation", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "score_term": { + "name": "score_term", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'effective'" + }, + "score_unit": { + "name": "score_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "score_max_value": { + "name": "score_max_value", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": false + }, + "score_min_value": { + "name": "score_min_value", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": false + }, + "is_binary": { + "name": "is_binary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "v0_4_metric_decisions_dao_id_dao_details_dao_id_fk": { + "name": "v0_4_metric_decisions_dao_id_dao_details_dao_id_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "dao_details", + "columnsFrom": [ + "dao_id" + ], + "columnsTo": [ + "dao_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_metric_decisions_outcome_question_addr_v0_4_questions_question_addr_fk": { + "name": "v0_4_metric_decisions_outcome_question_addr_v0_4_questions_question_addr_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "v0_4_questions", + "columnsFrom": [ + "outcome_question_addr" + ], + "columnsTo": [ + "question_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_metric_decisions_metric_question_addr_v0_4_questions_question_addr_fk": { + "name": "v0_4_metric_decisions_metric_question_addr_v0_4_questions_question_addr_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "v0_4_questions", + "columnsFrom": [ + "metric_question_addr" + ], + "columnsTo": [ + "question_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_metric_decisions_outcome_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk": { + "name": "v0_4_metric_decisions_outcome_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "v0_4_conditional_vaults", + "columnsFrom": [ + "outcome_vault_addr" + ], + "columnsTo": [ + "conditional_vault_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_metric_decisions_metric_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk": { + "name": "v0_4_metric_decisions_metric_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "v0_4_conditional_vaults", + "columnsFrom": [ + "metric_vault_addr" + ], + "columnsTo": [ + "conditional_vault_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_metric_decisions_amm_addr_v0_4_amms_amm_addr_fk": { + "name": "v0_4_metric_decisions_amm_addr_v0_4_amms_amm_addr_fk", + "tableFrom": "v0_4_metric_decisions", + "tableTo": "v0_4_amms", + "columnsFrom": [ + "amm_addr" + ], + "columnsTo": [ + "amm_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_questions": { + "name": "v0_4_questions", + "schema": "", + "columns": { + "question_addr": { + "name": "question_addr", + "type": "varchar(44)", + "primaryKey": true, + "notNull": true + }, + "is_resolved": { + "name": "is_resolved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "oracle_addr": { + "name": "oracle_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "num_outcomes": { + "name": "num_outcomes", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "payout_numerators": { + "name": "payout_numerators", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payout_denominator": { + "name": "payout_denominator", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "question_id": { + "name": "question_id", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_splits": { + "name": "v0_4_splits", + "schema": "", + "columns": { + "vault_addr": { + "name": "vault_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "vault_seq_num": { + "name": "vault_seq_num", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "signature": { + "name": "signature", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "split_vault_index": { + "name": "split_vault_index", + "columns": [ + { + "expression": "vault_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "split_signature_index": { + "name": "split_signature_index", + "columns": [ + { + "expression": "signature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "split_seq_num_vault_index": { + "name": "split_seq_num_vault_index", + "columns": [ + { + "expression": "vault_seq_num", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vault_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v0_4_splits_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk": { + "name": "v0_4_splits_vault_addr_v0_4_conditional_vaults_conditional_vault_addr_fk", + "tableFrom": "v0_4_splits", + "tableTo": "v0_4_conditional_vaults", + "columnsFrom": [ + "vault_addr" + ], + "columnsTo": [ + "conditional_vault_addr" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v0_4_splits_signature_signatures_signature_fk": { + "name": "v0_4_splits_signature_signatures_signature_fk", + "tableFrom": "v0_4_splits", + "tableTo": "signatures", + "columnsFrom": [ + "signature" + ], + "columnsTo": [ + "signature" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v0_4_swaps": { + "name": "v0_4_swaps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "varchar(88)", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "block_time": { + "name": "block_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "swap_type": { + "name": "swap_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "amm_addr": { + "name": "amm_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "user_addr": { + "name": "user_addr", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "amm_seq_num": { + "name": "amm_seq_num", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_amount": { + "name": "input_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "output_amount": { + "name": "output_amount", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "amm_index": { + "name": "amm_index", + "columns": [ + { + "expression": "amm_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "signature_index": { + "name": "signature_index", + "columns": [ + { + "expression": "signature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "seq_num_amm_index": { + "name": "seq_num_amm_index", + "columns": [ + { + "expression": "amm_seq_num", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "amm_addr", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.prices_chart_data": { + "columns": { + "interv": { + "name": "interv", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "base_amount": { + "name": "base_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "quote_amount": { + "name": "quote_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "prices_type": { + "name": "prices_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + } + }, + "definition": "\n SELECT\n TIME_BUCKET('30 SECONDS'::INTERVAL, prices.created_at) AS interv,\n last(price, prices.created_at) FILTER(WHERE prices.created_at IS NOT NULL AND CASE WHEN prices_type = 'spot' THEN TRUE ELSE prices.created_at <= markets.created_at + '5 DAYS'::INTERVAL END) AS price,\n last(base_amount, prices.created_at) FILTER(WHERE prices.created_at IS NOT NULL AND CASE WHEN prices_type = 'spot' THEN TRUE ELSE prices.created_at <= markets.created_at + '5 DAYS'::INTERVAL END) AS base_amount,\n last(quote_amount, prices.created_at) FILTER(WHERE prices.created_at IS NOT NULL AND CASE WHEN prices_type = 'spot' THEN TRUE ELSE prices.created_at <= markets.created_at + '5 DAYS'::INTERVAL END) AS quote_amount,\n prices_type,\n prices.market_acct AS market_acct\n FROM prices\n JOIN markets ON markets.market_acct = prices.market_acct\n WHERE CASE WHEN prices_type = 'spot' THEN TRUE ELSE prices.created_at <= markets.created_at + '5 DAYS'::INTERVAL END\n GROUP BY interv, prices.market_acct, prices_type\n ", + "name": "prices_chart_data", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.proposal_total_trade_volume": { + "columns": { + "proposal_acct": { + "name": "proposal_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "pass_volume": { + "name": "pass_volume", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "fail_volume": { + "name": "fail_volume", + "type": "numeric(40, 20)", + "primaryKey": false, + "notNull": true + }, + "pass_market_acct": { + "name": "pass_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + }, + "fail_market_acct": { + "name": "fail_market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + } + }, + "definition": "\n WITH pass_market AS (\n SELECT\n \t proposal_acct,\n \t orders.market_acct AS pass_market_acct,\n TIME_BUCKET('1 DAYS'::INTERVAL, orders.order_time) AS interv,\n SUM(filled_base_amount * quote_price) FILTER(WHERE orders.order_time IS NOT NULL) AS pass_volume\n FROM proposals\n JOIN orders\n ON proposals.pass_market_acct = orders.market_acct\n GROUP BY proposal_acct, interv, orders.market_acct\n ),\n fail_market AS (\n SELECT\n \t proposal_acct,\n \t orders.market_acct AS fail_market_acct,\n TIME_BUCKET('1 DAYS'::INTERVAL, orders.order_time) AS interv,\n SUM(filled_base_amount * quote_price) FILTER(WHERE orders.order_time IS NOT NULL) AS fail_volume\n FROM proposals\n JOIN orders\n ON proposals.fail_market_acct = orders.market_acct\n GROUP BY proposal_acct, interv, orders.market_acct\n )\n SELECT\n pass_market.proposal_acct AS proposal_acct,\n pass_market_acct,\n fail_market_acct,\n SUM(pass_volume) AS pass_volume,\n SUM(fail_volume) AS fail_volume\n FROM pass_market\n JOIN fail_market ON fail_market.proposal_acct = pass_market.proposal_acct\n GROUP BY pass_market.proposal_acct, pass_market_acct, fail_market_acct\n ", + "name": "proposal_total_trade_volume", + "schema": "public", + "isExisting": false, + "materialized": false + }, + "public.twap_chart_data": { + "columns": { + "interv": { + "name": "interv", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_amount": { + "name": "token_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "market_acct": { + "name": "market_acct", + "type": "varchar(44)", + "primaryKey": false, + "notNull": true + } + }, + "definition": "\n SELECT\n TIME_BUCKET('30 SECONDS'::INTERVAL, \"twaps\".\"created_at\") AS interv,\n last(token_amount, \"twaps\".\"created_at\") FILTER(WHERE \"twaps\".\"created_at\" IS NOT NULL AND \"twaps\".\"created_at\" <= \"markets\".\"created_at\" + '5 DAYS'::INTERVAL) AS token_amount,\n \"twaps\".\"market_acct\" AS market_acct\n FROM \"twaps\"\n JOIN \"markets\" ON \"markets\".\"market_acct\" = \"twaps\".\"market_acct\"\n WHERE \"twaps\".\"created_at\" <= \"markets\".\"created_at\" + '5 DAYS'::INTERVAL\n GROUP BY interv, \"twaps\".\"market_acct\"\n ", + "name": "twap_chart_data", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/drizzle/meta/_journal.json b/packages/database/drizzle/meta/_journal.json index 6d26d96f..7b70e2c5 100644 --- a/packages/database/drizzle/meta/_journal.json +++ b/packages/database/drizzle/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1732920723225, "tag": "0013_military_imperial_guard", "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1733341863425, + "tag": "0014_lonely_wolfsbane", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/database/lib/index.ts b/packages/database/lib/index.ts index e68c5abf..01ed0704 100644 --- a/packages/database/lib/index.ts +++ b/packages/database/lib/index.ts @@ -14,9 +14,9 @@ const ACQUIRE_TIMEOUT = 10000; // 10 second timeout for acquiring connection // Add connection pool configuration const poolConfig = { connectionString: connectionString, - min: 20, - max: 1000, // Reduced from 1000 to a more reasonable number - idleTimeoutMillis: 30 * 1000, + min: 5, + max: 100, // Reduced from 1000 to a more reasonable number + idleTimeoutMillis: 30000, connectionTimeoutMillis: 5000, // Add error handling for the pool async errorHandler(err: Error) { @@ -53,8 +53,14 @@ export async function usingDb( ) ]); - const connection = drizzle(pool, { schema: schemaDefs }); + const connection = drizzle(client, { schema: schemaDefs }); const result = await fn(connection); + + if (client) { + client.release(); + client = undefined; + } + return result; } catch (e) { attempts++; diff --git a/packages/database/lib/schema.ts b/packages/database/lib/schema.ts index 3a9b46cb..444afbcd 100644 --- a/packages/database/lib/schema.ts +++ b/packages/database/lib/schema.ts @@ -221,6 +221,8 @@ export const markets = pgTable("markets", { createdAt: timestamp("created_at", { withTimezone: true }) .notNull() .default(sql`now()`), + baseAmount: biggerTokenAmount("base_amount"), + quoteAmount: biggerTokenAmount("quote_amount"), }); export enum PricesType { diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_markets.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_markets.yaml index 2eac8b27..6b40372e 100644 --- a/packages/hasura/metadata/databases/futarchy/tables/public_markets.yaml +++ b/packages/hasura/metadata/databases/futarchy/tables/public_markets.yaml @@ -64,6 +64,51 @@ array_relationships: table: name: prices schema: public + - name: prices_15m + using: + manual_configuration: + column_mapping: + market_acct: market_acct + insertion_order: null + remote_table: + name: prices_15m + schema: public + - name: prices_1h + using: + manual_configuration: + column_mapping: + market_acct: market_acct + insertion_order: null + remote_table: + name: prices_1h + schema: public + - name: prices_1m + using: + manual_configuration: + column_mapping: + market_acct: market_acct + insertion_order: null + remote_table: + name: prices_chart_data + schema: public + - name: prices_4h + using: + manual_configuration: + column_mapping: + market_acct: market_acct + insertion_order: null + remote_table: + name: prices_4h + schema: public + - name: prices_5m + using: + manual_configuration: + column_mapping: + market_acct: market_acct + insertion_order: null + remote_table: + name: prices_5m + schema: public - name: takes using: foreign_key_constraint_on: diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_prices_15m.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_prices_15m.yaml new file mode 100644 index 00000000..b1f8a541 --- /dev/null +++ b/packages/hasura/metadata/databases/futarchy/tables/public_prices_15m.yaml @@ -0,0 +1,12 @@ +table: + name: prices_15m + schema: public +select_permissions: + - role: anonymous + permission: + columns: + - market_acct + - price + - interv + filter: {} + comment: "" diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_prices_1h.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_prices_1h.yaml new file mode 100644 index 00000000..cdda487c --- /dev/null +++ b/packages/hasura/metadata/databases/futarchy/tables/public_prices_1h.yaml @@ -0,0 +1,12 @@ +table: + name: prices_1h + schema: public +select_permissions: + - role: anonymous + permission: + columns: + - market_acct + - price + - interv + filter: {} + comment: "" diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_prices_4h.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_prices_4h.yaml new file mode 100644 index 00000000..68f19072 --- /dev/null +++ b/packages/hasura/metadata/databases/futarchy/tables/public_prices_4h.yaml @@ -0,0 +1,3 @@ +table: + name: prices_4h + schema: public diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_prices_5m.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_prices_5m.yaml new file mode 100644 index 00000000..6af4e5c2 --- /dev/null +++ b/packages/hasura/metadata/databases/futarchy/tables/public_prices_5m.yaml @@ -0,0 +1,20 @@ +table: + name: prices_5m + schema: public +select_permissions: + - role: anonymous + permission: + columns: + - market_acct + - price + - interv + filter: {} + comment: "" + - role: user + permission: + columns: + - market_acct + - price + - interv + filter: {} + comment: "" diff --git a/packages/hasura/metadata/databases/futarchy/tables/public_proposals.yaml b/packages/hasura/metadata/databases/futarchy/tables/public_proposals.yaml index 7a8fd596..feb7354b 100644 --- a/packages/hasura/metadata/databases/futarchy/tables/public_proposals.yaml +++ b/packages/hasura/metadata/databases/futarchy/tables/public_proposals.yaml @@ -11,6 +11,24 @@ object_relationships: - name: dao using: foreign_key_constraint_on: dao_acct + - name: fail_market + using: + manual_configuration: + column_mapping: + fail_market_acct: market_acct + insertion_order: null + remote_table: + name: markets + schema: public + - name: pass_market + using: + manual_configuration: + column_mapping: + pass_market_acct: market_acct + insertion_order: null + remote_table: + name: markets + schema: public array_relationships: - name: comments using: diff --git a/packages/hasura/metadata/databases/futarchy/tables/tables.yaml b/packages/hasura/metadata/databases/futarchy/tables/tables.yaml index b63139c1..71ba6392 100644 --- a/packages/hasura/metadata/databases/futarchy/tables/tables.yaml +++ b/packages/hasura/metadata/databases/futarchy/tables/tables.yaml @@ -10,6 +10,10 @@ - "!include public_orders.yaml" - "!include public_organizations.yaml" - "!include public_prices.yaml" +- "!include public_prices_15m.yaml" +- "!include public_prices_1h.yaml" +- "!include public_prices_4h.yaml" +- "!include public_prices_5m.yaml" - "!include public_prices_chart_data.yaml" - "!include public_program_system.yaml" - "!include public_programs.yaml" diff --git a/packages/indexer/src/connection.ts b/packages/indexer/src/connection.ts index eafd196f..0ba3694e 100644 --- a/packages/indexer/src/connection.ts +++ b/packages/indexer/src/connection.ts @@ -1,19 +1,39 @@ import { Connection } from "@solana/web3.js"; import { AnchorProvider, Wallet } from "@coral-xyz/anchor"; -import { ConditionalVaultClient, AmmClient } from "@metadaoproject/futarchy/v0.4"; +import { ConditionalVaultClient as V4ConditionalVaultClient, AmmClient as V4AmmClient } from "@metadaoproject/futarchy/v0.4"; +import { + ConditionalVaultClient as V3ConditionalVaultClient +} from "@metadaoproject/futarchy/v0.3"; +import { FutarchyRPCClient, FutarchyIndexerClient } from "@metadaoproject/futarchy-sdk"; +// Environment variables export const RPC_ENDPOINT = process.env.RPC_ENDPOINT ?? ""; +export const BACKUP_RPC_ENDPOINT = process.env.BACKUP_RPC_ENDPOINT ?? ""; +export const INDEXER_URL = process.env.INDEXER_URL ?? ""; +export const INDEXER_WSS_URL = process.env.INDEXER_WSS_URL ?? ""; if (!RPC_ENDPOINT) { throw new Error("RPC_ENDPOINT is not set"); } +// Core connection setup export const connection: Connection = new Connection(RPC_ENDPOINT, "confirmed"); -// the indexer will only be reading, not writing +export const backupConnection: Connection = new Connection(BACKUP_RPC_ENDPOINT, "confirmed"); export const readonlyWallet: Wallet = undefined as unknown as Wallet; export const provider = new AnchorProvider(connection, readonlyWallet, { commitment: "confirmed", }); -export const ammClient = AmmClient.createClient({ provider }); -export const conditionalVaultClient = ConditionalVaultClient.createClient({ provider }); +// V4 clients +export const v4AmmClient = V4AmmClient.createClient({ provider }); +export const v4ConditionalVaultClient = V4ConditionalVaultClient.createClient({ provider }); + +// V3 clients +export const rpcReadClient = FutarchyRPCClient.make(provider, undefined); +export const indexerReadClient = FutarchyIndexerClient.make( + rpcReadClient, + INDEXER_URL, + INDEXER_WSS_URL, + "" +); +export const v3ConditionalVaultClient = V3ConditionalVaultClient.createClient({ provider }); diff --git a/packages/indexer/src/rpc-wrapper.ts b/packages/indexer/src/rpc-wrapper.ts new file mode 100644 index 00000000..87cbd2cf --- /dev/null +++ b/packages/indexer/src/rpc-wrapper.ts @@ -0,0 +1,219 @@ +import { Connection } from "@solana/web3.js"; +import { connection as primaryConnection, backupConnection } from "./connection"; +import { logger } from "./logger"; + +export enum RPCErrorType { + Timeout = "Timeout", + RateLimitExceeded = "RateLimitExceeded", + InvalidResponse = "InvalidResponse", + GeneralError = "GeneralError", + NetworkError = "NetworkError", + InvalidMethod = "InvalidMethod", + ServerError = "ServerError" +} + +interface RPCError { + type: RPCErrorType; + message: string; + originalError?: unknown; +} + +interface RPCConfig { + maxRetries?: number; + baseDelayMs?: number; + maxDelayMs?: number; + failoverThreshold?: number; +} + +const DEFAULT_CONFIG: RPCConfig = { + maxRetries: 3, + baseDelayMs: 500, + maxDelayMs: 5000, + failoverThreshold: 3 +}; + +export class RPCWrapper { + private primaryConnection: Connection; + private backupConnection?: Connection; + private config: RPCConfig; + private consecutiveFailures: number = 0; + private usingBackup: boolean = false; + + constructor( + primaryConnection: Connection, + backupConnection?: Connection, + config: Partial = {} + ) { + this.primaryConnection = primaryConnection; + this.backupConnection = backupConnection; + this.config = { ...DEFAULT_CONFIG, ...config }; + } + + private get activeConnection(): Connection { + return this.usingBackup && this.backupConnection + ? this.backupConnection + : this.primaryConnection; + } + + public getActiveConnection(): Connection { + return this.activeConnection; + } + + /** + * Generic method to execute any RPC function with retry logic + * @param methodName Name of the method to call on the connection + * @param args Arguments to pass to the method + * @param context Description of the operation for logging + * @returns Promise of the operation result + */ + async call( + methodName: string, + args: any[] = [], + context: string + ): Promise { + let lastError: RPCError | null = null; + + for (let attempt = 0; attempt < (this.config.maxRetries ?? 3); attempt++) { + try { + if (!(methodName in this.activeConnection)) { + throw new Error(`Method ${methodName} not found on connection`); + } + + const method = this.activeConnection[methodName as keyof Connection] as (...args: any[]) => Promise; + const result = await method.apply(this.activeConnection, args); + + // Reset failure count on success + this.consecutiveFailures = 0; + if (this.usingBackup) { + logger.info('Successfully failed back to primary connection'); + this.usingBackup = false; + } + + return result; + + } catch (error) { + lastError = this.categorizeError(error); + + logger.error( + `RPC ${context} failed (attempt ${attempt + 1}/${this.config.maxRetries}):`, + lastError + ); + + this.consecutiveFailures++; + + // Check if we should switch to backup connection + if ( + this.backupConnection && + !this.usingBackup && + this.consecutiveFailures >= (this.config.failoverThreshold ?? 3) + ) { + logger.warn('Switching to backup RPC connection'); + this.usingBackup = true; + // Reset retry counter to give the backup connection a fresh start + attempt = 0; + continue; + } + + if (attempt === (this.config.maxRetries ?? 3) - 1) { + throw lastError; + } + + // Calculate delay with exponential backoff + const delay = Math.min( + (this.config.baseDelayMs ?? 1000) * Math.pow(2, attempt), + this.config.maxDelayMs ?? 10000 + ); + + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw lastError; + } + + private categorizeError(error: unknown): RPCError { + // Handle null/undefined errors + if (!error) { + return { + type: RPCErrorType.GeneralError, + message: "Unknown error occurred (null/undefined error)", + originalError: error + }; + } + + // Handle string errors + if (typeof error === 'string') { + return { + type: RPCErrorType.GeneralError, + message: error, + originalError: error + }; + } + + if (error instanceof Error) { + const errorMessage = error.message.toLowerCase(); + + // Network related errors + if ( + errorMessage.includes('network') || + errorMessage.includes('connection') || + errorMessage.includes('offline') || + errorMessage.includes('econnrefused') + ) { + return { + type: RPCErrorType.NetworkError, + message: "Network connection error", + originalError: error + }; + } + + // HTTP status code based errors + if (errorMessage.includes("408") || errorMessage.includes("timeout")) { + return { + type: RPCErrorType.Timeout, + message: "RPC request timed out", + originalError: error + }; + } + + if (errorMessage.includes("429")) { + return { + type: RPCErrorType.RateLimitExceeded, + message: "RPC rate limit exceeded", + originalError: error + }; + } + + if (errorMessage.includes("500") || errorMessage.includes("503")) { + return { + type: RPCErrorType.ServerError, + message: "RPC server error", + originalError: error + }; + } + + // Invalid JSON or unexpected response format + if ( + errorMessage.includes('json') || + errorMessage.includes('parse') || + errorMessage.includes('unexpected') + ) { + return { + type: RPCErrorType.InvalidResponse, + message: "Invalid RPC response format", + originalError: error + }; + } + } + + // Fallback error + return { + type: RPCErrorType.GeneralError, + message: error instanceof Error ? error.message : "Unknown error occurred", + originalError: error + }; + } +} + +// Create a singleton instance +export const rpc = new RPCWrapper(primaryConnection, backupConnection); \ No newline at end of file diff --git a/packages/indexer/src/subscriber.ts b/packages/indexer/src/subscriber.ts index c347340e..52789362 100644 --- a/packages/indexer/src/subscriber.ts +++ b/packages/indexer/src/subscriber.ts @@ -2,9 +2,6 @@ import { connection } from "./connection"; import { Context, Logs, PublicKey } from "@solana/web3.js"; import { AMM_PROGRAM_ID as V4_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V4_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V4_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.4"; import { AMM_PROGRAM_ID as V3_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V3_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V3_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3"; -import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema"; -import { AccountLogsIndexer } from "./account-logs-indexer"; -import { AmmMarketLogsSubscribeIndexer } from "./amm-market/amm-market-logs-subscribe-indexer"; import { logger } from "./logger"; import { indexFromLogs as indexV4 } from "./v4_indexer/indexer"; import { indexFromLogs as indexV3 } from "./v3_indexer/indexer"; diff --git a/packages/indexer/src/v3_indexer/builders/swaps.ts b/packages/indexer/src/v3_indexer/builders/swaps.ts index 905da4ab..c0b08de7 100644 --- a/packages/indexer/src/v3_indexer/builders/swaps.ts +++ b/packages/indexer/src/v3_indexer/builders/swaps.ts @@ -1,5 +1,5 @@ import { Context } from "@solana/web3.js"; -import { Err, Ok, Result, TaggedUnion } from "../match"; +import { Err, Ok, Result, TaggedUnion } from "../utils/match"; import { AmmInstructionIndexerError, SwapPersistableError, @@ -25,9 +25,9 @@ import { import { logger } from "../../logger"; import { getMainIxTypeFromTransaction } from "../transaction/watcher"; import { getHumanPrice } from "../usecases/math"; -import { connection } from "../connection"; -import { AmmMarketAccountUpdateIndexer } from '../indexers/amm-market/amm-market-account-indexer'; -import { PublicKey } from "@solana/web3.js"; +import { AmmMarketAccountUpdateIndexer } from '../indexers/amm/amm-market-account-indexer'; +import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js"; +import { rpc } from "../../rpc-wrapper"; export class SwapPersistable { @@ -256,11 +256,12 @@ export class SwapBuilder { async indexPriceAndTWAPForAccount(account: PublicKey) { console.log("indexing price and twap for account", account.toBase58()); - const accountInfo = await connection.getAccountInfoAndContext( - account - ); + const accountInfo = await rpc.call( + "getAccountInfoAndContext", + [account], + "Get account info for swap" + ) as RpcResponseAndContext | null>; - //index refresh on startup if (accountInfo.value) { const res = await AmmMarketAccountUpdateIndexer.index( accountInfo.value, diff --git a/packages/indexer/src/v3_indexer/cli/txw/populate.ts b/packages/indexer/src/v3_indexer/cli/txw/populate.ts index fa2f3607..778ac203 100644 --- a/packages/indexer/src/v3_indexer/cli/txw/populate.ts +++ b/packages/indexer/src/v3_indexer/cli/txw/populate.ts @@ -19,8 +19,8 @@ import { buildWhirlpoolClient, } from "@orca-so/whirlpools-sdk"; import { PublicKey } from "@solana/web3.js"; -import { connection, readonlyWallet } from "../../connection"; -import { Err, Ok } from "../../match"; +import { connection, readonlyWallet } from "../../../connection"; +import { Err, Ok } from "../../utils/match"; import { JupiterQuoteIndexingError, fetchQuoteFromJupe, @@ -44,7 +44,7 @@ async function populateIndexerAccountDependencies() { try { await populateTokenMintIndexerAccountDependencies(); await populateAmmMarketIndexerAccountDependencies(); - await populateOpenbookMarketIndexerAccountDependencies(); + // await populateOpenbookMarketIndexerAccountDependencies(); await populateSpotPriceMarketIndexerAccountDependencies(); } catch (e) { logger.error("error populating indexers", e); diff --git a/packages/indexer/src/v3_indexer/connection.ts b/packages/indexer/src/v3_indexer/connection.ts deleted file mode 100644 index 47e2bd76..00000000 --- a/packages/indexer/src/v3_indexer/connection.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Connection } from "@solana/web3.js"; -import { AnchorProvider, Wallet } from "@coral-xyz/anchor"; -import { - FutarchyRPCClient, - FutarchyIndexerClient, -} from "@metadaoproject/futarchy-sdk"; -import { ConditionalVaultClient } from "@metadaoproject/futarchy/v0.3"; - -export const RPC_ENDPOINT = process.env.RPC_ENDPOINT ?? ""; -export const INDEXER_URL = process.env.INDEXER_URL ?? ""; -export const INDEXER_WSS_URL = process.env.INDEXER_WSS_URL ?? ""; -export const connection: Connection = new Connection(RPC_ENDPOINT, "confirmed"); -// the indexer will only be reading, not writing -export const readonlyWallet: Wallet = undefined as unknown as Wallet; -export const provider = new AnchorProvider(connection, readonlyWallet, { - commitment: "confirmed", -}); - -export const rpcReadClient = FutarchyRPCClient.make(provider, undefined); - -export const indexerReadClient = FutarchyIndexerClient.make( - rpcReadClient, - INDEXER_URL, - INDEXER_WSS_URL, - "" -); - -export const conditionalVaultClient = ConditionalVaultClient.createClient({ - provider, -}); diff --git a/packages/indexer/src/v3_indexer/indexer.ts b/packages/indexer/src/v3_indexer/indexer.ts index e232d80d..f31ff883 100644 --- a/packages/indexer/src/v3_indexer/indexer.ts +++ b/packages/indexer/src/v3_indexer/indexer.ts @@ -1,22 +1,22 @@ import { Context, Logs, PublicKey } from "@solana/web3.js"; import { AMM_PROGRAM_ID as V3_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V3_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V3_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3"; -import { AmmMarketLogsSubscribeIndexer } from "./indexers/amm-market/amm-market-logs-subscribe-indexer"; +import { AmmMarketLogsSubscribeIndexer } from "./indexers/amm/amm-market-logs-subscribe-indexer"; import { AutocratDaoIndexer } from "./indexers/autocrat/autocrat-dao-indexer"; import { AutocratProposalIndexer } from "./indexers/autocrat/autocrat-proposal-indexer"; export async function indexFromLogs(logs: Logs, ctx: Context, programId: PublicKey) { - console.log("indexFromLogs:: indexing logs", logs); + console.log("indexFromLogs (v3):: indexing logs", logs); if (programId.equals(V3_AMM_PROGRAM_ID)) { await AmmMarketLogsSubscribeIndexer.index(logs, programId, ctx); } else if (programId.equals(V3_CONDITIONAL_VAULT_PROGRAM_ID)) { //TODO: implement - console.log("Conditional vault logs received"); + console.log("indexFromLogs (v3):: conditional vault logs received"); console.log(logs); return; } else if (programId.equals(V3_AUTOCRAT_PROGRAM_ID)) { // return; - console.log("indexFromLogs:: autocrat logs received", logs); + console.log("indexFromLogs (v3):: autocrat logs received", logs); // Parse logs to find instruction type const instructionLog = logs.logs.find(log => log.includes("Instruction:") && diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/test.ts b/packages/indexer/src/v3_indexer/indexers/amm-market/test.ts deleted file mode 100644 index 2598deca..00000000 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { BN } from "@coral-xyz/anchor"; -import { enrichTokenMetadata } from "@metadaoproject/futarchy-sdk"; -import { PriceMath } from "@metadaoproject/futarchy/v0.4"; -import { schema, usingDb, eq, inArray } from "@metadaoproject/indexer-db"; -import { TokenRecord } from "@metadaoproject/indexer-db/lib/schema"; -import { PricesType } from "@metadaoproject/indexer-db/lib/schema"; -import { - TwapRecord, - PricesRecord, -} from "@metadaoproject/indexer-db/lib/schema"; -import { AccountInfo, Context, PublicKey } from "@solana/web3.js"; -import { provider, rpcReadClient } from "../../connection"; -import { Err, Ok, Result, TaggedUnion } from "../../match"; -import { logger } from "../../../logger"; -import { getHumanPrice } from "../../usecases/math"; -import { getMint } from "@solana/spl-token"; -import { connection } from "../../../connection"; - - -async function main() { -let baseToken; -let quoteToken; - - -const accountInfo = await connection.getAccountInfo(new PublicKey("Q8sMdszTYMxhg44Zgc1Ev2EHXrjJS5eBPkQKcRYVnDX")); -console.log("accountInfo", accountInfo); -const ammMarketAccount = await rpcReadClient.markets.amm.decodeMarket( - accountInfo!! -); - -//get base and quote decimals from db -console.log("utils::indexAmmMarketAccountWithContext::getting tokens from db", ammMarketAccount.baseMint.toString(), ammMarketAccount.quoteMint.toString()); -const tokens = await usingDb((db) => - db - .select() - .from(schema.tokens) - .where(inArray(schema.tokens.mintAcct, [ammMarketAccount.baseMint.toString(), ammMarketAccount.quoteMint.toString()])) - .execute() -); -console.log("utils::indexAmmMarketAccountWithContext::tokens", tokens); - -// if (!tokens || tokens.length < 2) { -// fallback if we don't have the tokens in the db for some reason -console.log("utils::indexAmmMarketAccountWithContext::no tokens in db, fetching from rpc"); -baseToken = await enrichTokenMetadata( - ammMarketAccount.baseMint, - provider -); -quoteToken = await enrichTokenMetadata( - ammMarketAccount.quoteMint, - provider -) -console.log("utils::indexAmmMarketAccountWithContext::baseToken", baseToken); -console.log("utils::indexAmmMarketAccountWithContext::quoteToken", quoteToken); -} - -main().catch(console.error); \ No newline at end of file diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-indexer.ts b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-indexer.ts similarity index 88% rename from packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-indexer.ts rename to packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-indexer.ts index 36b323c5..768fe135 100644 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-indexer.ts @@ -1,6 +1,6 @@ -import { AccountInfoIndexer } from "../account-info-indexer"; +import { AccountInfoIndexer } from "../../types/account-info-indexer"; import { AccountInfo, Context, PublicKey } from "@solana/web3.js"; -import { Err, Ok } from "../../match"; +import { Err, Ok } from "../../utils/match"; import { indexAmmMarketAccountWithContext } from "./utils"; import { logger } from "../../../logger"; diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-interval-indexer.ts b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-interval-indexer.ts similarity index 78% rename from packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-interval-indexer.ts rename to packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-interval-indexer.ts index 89724448..ab0c310b 100644 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-account-interval-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-account-interval-indexer.ts @@ -1,8 +1,8 @@ -import { PublicKey } from "@solana/web3.js"; -import { Err, Ok, Result } from "../../match"; +import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js"; +import { Err, Ok, Result } from "../../utils/match"; import { indexAmmMarketAccountWithContext } from "./utils"; -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; -import { connection } from "../../connection"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; +import { rpc } from "../../../rpc-wrapper"; import { logger } from "../../../logger"; import { AmmMarketAccountIndexingErrors } from "./utils"; @@ -23,7 +23,11 @@ export const AmmMarketAccountIntervalFetchIndexer: IntervalFetchIndexer = { } try { const account = new PublicKey(acct); - const resWithContext = await connection.getAccountInfoAndContext(account); + const resWithContext = await rpc.call( + "getAccountInfoAndContext", + [account], + "Get account info for amm market account interval fetcher" + ) as RpcResponseAndContext | null>; if (!resWithContext.value) { return Err({ type: AmmAccountIntervalIndexerError.InvalidRPCResponse, @@ -61,10 +65,5 @@ export const AmmMarketAccountIntervalFetchIndexer: IntervalFetchIndexer = { } return Err({ type: AmmAccountIntervalIndexerError.General }); } - }, - - indexFromLogs: async (logs: string[]) => { - //TODO: implement if needed - return Err({ type: AmmAccountIntervalIndexerError.General }); - }, + } }; diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-instruction-indexer.ts b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-instruction-indexer.ts similarity index 94% rename from packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-instruction-indexer.ts rename to packages/indexer/src/v3_indexer/indexers/amm/amm-market-instruction-indexer.ts index dfd0c879..e9f337f6 100644 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-instruction-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-instruction-indexer.ts @@ -1,8 +1,8 @@ import { VersionedTransactionResponse } from "@solana/web3.js"; -import { Err, Ok, Result, TaggedUnion } from "../../match"; +import { Err, Ok, Result, TaggedUnion } from "../../utils/match"; import { TransactionRecord } from "@metadaoproject/indexer-db/lib/schema"; import { AMM_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3"; -import { InstructionIndexer } from "../instruction-indexer"; +import { InstructionIndexer } from "../../types/instruction-indexer"; import { AmmInstructionIndexerError, SwapPersistableError, diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-logs-subscribe-indexer.ts b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-logs-subscribe-indexer.ts similarity index 93% rename from packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-logs-subscribe-indexer.ts rename to packages/indexer/src/v3_indexer/indexers/amm/amm-market-logs-subscribe-indexer.ts index 478ffaac..8433a720 100644 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/amm-market-logs-subscribe-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/amm/amm-market-logs-subscribe-indexer.ts @@ -1,6 +1,6 @@ import { Context, Logs, PublicKey } from "@solana/web3.js"; -import { Err, Ok } from "../../match"; -import { AccountLogsIndexer } from "../account-logs-indexer"; +import { Err, Ok } from "../../utils/match"; +import { AccountLogsIndexer } from "../../types/account-logs-indexer"; import { SwapBuilder } from "../../builders/swaps"; import { logger } from "../../../logger"; import { SwapPersistableError } from "../../types/errors"; diff --git a/packages/indexer/src/v3_indexer/indexers/amm-market/utils.ts b/packages/indexer/src/v3_indexer/indexers/amm/utils.ts similarity index 74% rename from packages/indexer/src/v3_indexer/indexers/amm-market/utils.ts rename to packages/indexer/src/v3_indexer/indexers/amm/utils.ts index 37170b0d..1170df19 100644 --- a/packages/indexer/src/v3_indexer/indexers/amm-market/utils.ts +++ b/packages/indexer/src/v3_indexer/indexers/amm/utils.ts @@ -1,7 +1,7 @@ import { BN } from "@coral-xyz/anchor"; import { enrichTokenMetadata } from "@metadaoproject/futarchy-sdk"; import { PriceMath } from "@metadaoproject/futarchy/v0.4"; -import { schema, usingDb, eq, inArray } from "@metadaoproject/indexer-db"; +import { schema, usingDb, eq, inArray, or } from "@metadaoproject/indexer-db"; import { TokenRecord } from "@metadaoproject/indexer-db/lib/schema"; import { PricesType } from "@metadaoproject/indexer-db/lib/schema"; import { @@ -9,13 +9,13 @@ import { PricesRecord, } from "@metadaoproject/indexer-db/lib/schema"; import { AccountInfo, Context, PublicKey } from "@solana/web3.js"; -import { provider, rpcReadClient } from "../../connection"; -import { Err, Ok, Result, TaggedUnion } from "../../match"; +import { provider, rpcReadClient } from "../../../connection"; +import { Err, Ok, Result, TaggedUnion } from "../../utils/match"; import { logger } from "../../../logger"; import { getHumanPrice } from "../../usecases/math"; import { getMint } from "@solana/spl-token"; import { connection } from "../../../connection"; - +import { ProposalStatus, IndexerAccountDependencyStatus } from "@metadaoproject/indexer-db/lib/schema"; export enum AmmMarketAccountIndexingErrors { AmmTwapIndexError = "AmmTwapIndexError", MarketMissingError = "MarketMissingError", @@ -38,7 +38,6 @@ export async function indexAmmMarketAccountWithContext( let quoteToken; //get base and quote decimals from db - console.log("utils::indexAmmMarketAccountWithContext::getting tokens from db", ammMarketAccount.baseMint.toString(), ammMarketAccount.quoteMint.toString()); const tokens = await usingDb((db) => db .select() @@ -49,7 +48,6 @@ export async function indexAmmMarketAccountWithContext( if (!tokens || tokens.length < 2) { // fallback if we don't have the tokens in the db for some reason - console.log("utils::indexAmmMarketAccountWithContext::no tokens in db, fetching from rpc"); baseToken = await enrichTokenMetadata( ammMarketAccount.baseMint, provider @@ -98,8 +96,6 @@ export async function indexAmmMarketAccountWithContext( } else { baseToken = tokens.find(token => token.mintAcct === ammMarketAccount.baseMint.toString()); quoteToken = tokens.find(token => token.mintAcct === ammMarketAccount.quoteMint.toString()); - console.log("utils::indexAmmMarketAccountWithContext::baseToken", baseToken); - console.log("utils::indexAmmMarketAccountWithContext::quoteToken", quoteToken); } // if we don't have an oracle.aggregator of 0 let's run this mf @@ -140,7 +136,6 @@ export async function indexAmmMarketAccountWithContext( try{ // TODO batch commits across inserts - maybe with event queue - console.log("utils::indexAmmMarketAccountWithContext::upserting twap", newTwap); const twapUpsertResult = await usingDb((db) => db .insert(schema.twaps) @@ -162,8 +157,48 @@ export async function indexAmmMarketAccountWithContext( if (ammMarketAccount.baseAmount.isZero() || ammMarketAccount.quoteAmount.isZero()) { logger.error("NO RESERVES", ammMarketAccount); + logger.error("account", account.toBase58()); logger.error("baseAmount", ammMarketAccount.baseAmount.toString()); logger.error("quoteAmount", ammMarketAccount.quoteAmount.toString()); + + // Check if the corresponding proposal has been finalized + const proposal = await usingDb((db) => + db + .select() + .from(schema.proposals) + .where( + or( + eq(schema.proposals.passMarketAcct, account.toBase58()), + eq(schema.proposals.failMarketAcct, account.toBase58()) + ) + ) + .execute() + ); + + if (proposal && proposal.length > 0 && proposal[0].status !== ProposalStatus.Pending) { + try { + // Proposal is finalized or failed, disable the indexer account dependency + await usingDb((db) => + db + .update(schema.indexerAccountDependencies) + .set({ + status: IndexerAccountDependencyStatus.Disabled, + updatedAt: new Date(), + }) + .where( + eq(schema.indexerAccountDependencies.acct, account.toBase58()) + ) + .execute() + ); + + logger.info(`Disabled indexing for finalized market: ${account.toBase58()}`); + } catch (e) { + logger.error("error disabling indexing for finalized market", e); + } + } else { + logger.log(`Indexing for market: ${account.toBase58()} is still pending`); + } + return Ok("no price from reserves"); } @@ -181,8 +216,8 @@ export async function indexAmmMarketAccountWithContext( try { conditionalMarketSpotPrice = getHumanPrice( priceFromReserves, - baseToken.decimals!!, - quoteToken.decimals!! + baseToken?.decimals!, + quoteToken?.decimals! ); } catch (e) { logger.error("failed to get human price", e); @@ -216,5 +251,25 @@ export async function indexAmmMarketAccountWithContext( return Err({ type: AmmMarketAccountIndexingErrors.AmmTwapPriceError }); } + try { + const marketReservesUpdateResult = await usingDb((db) => + db + .update(schema.markets) + .set({ + baseAmount: ammMarketAccount.baseAmount.toString(), + quoteAmount: ammMarketAccount.quoteAmount.toString(), + }) + .where(eq(schema.markets.marketAcct, account.toBase58())) + .returning({ marketAcct: schema.markets.marketAcct }) + ); + if (marketReservesUpdateResult === undefined || marketReservesUpdateResult.length === 0) { + logger.error("failed to update market reserves", account.toBase58()); + return Err({ type: AmmMarketAccountIndexingErrors.AmmTwapPriceError }); + } + } catch (e) { + logger.error("error updating market reserves", e); + return Err({ type: AmmMarketAccountIndexingErrors.AmmTwapPriceError }); + } + return Ok(`successfully indexed amm: ${account.toBase58()}`); } diff --git a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-dao-indexer.ts b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-dao-indexer.ts index aa4d1bf0..aed50947 100644 --- a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-dao-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-dao-indexer.ts @@ -1,8 +1,8 @@ -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; -import { rpcReadClient, connection } from "../../connection"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; +import { rpcReadClient, connection } from "../../../connection"; import { usingDb, schema } from "@metadaoproject/indexer-db"; import { Dao } from "@metadaoproject/futarchy-sdk"; -import { Err, Ok } from "../../match"; +import { Err, Ok } from "../../utils/match"; import { PublicKey } from "@solana/web3.js"; import { DaoRecord, TokenRecord } from "@metadaoproject/indexer-db/lib/schema"; import { getMint } from "@solana/spl-token"; @@ -127,105 +127,5 @@ export const AutocratDaoIndexer: IntervalFetchIndexer = { logger.errorWithChatBotAlert(err); return Err({ type: AutocratDaoIndexerError.GeneralError }); } - }, - - indexFromLogs: async (logs: string[]) => { - try { - // Find the relevant log that contains the DAO data - const daoLog = logs.find(log => - log.includes("Instruction:") && - (log.includes("InitializeDao") || log.includes("UpdateDao")) - ); - - if (!daoLog) { - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - // Extract DAO account from logs - const daoAcctMatch = logs.find(log => log.includes("Dao:")); - if (!daoAcctMatch) { - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - const daoAcct = new PublicKey(daoAcctMatch.split(": ")[1]); - - // Fetch the DAO data directly since we need the full account data - const dao = await rpcReadClient.daos.fetchDao(daoAcct); - if (!dao) { - return Err({ type: AutocratDaoIndexerError.NotFoundError }); - } - - // Update database using the same logic as the main indexer - if (dao.baseToken.publicKey == null || dao.quoteToken.publicKey == null) { - logger.error("Unable to determine public key for dao tokens"); - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - const baseTokenMint = await getMint( - connection, - new PublicKey(dao.baseToken.publicKey) - ); - - let token: TokenRecord = { - symbol: dao.baseToken.symbol, - name: dao.baseToken.name ? dao.baseToken.name : dao.baseToken.symbol, - decimals: dao.baseToken.decimals, - mintAcct: dao.baseToken.publicKey, - supply: baseTokenMint.supply.toString(), - updatedAt: new Date(), - }; - - await usingDb((db) => - db.insert(schema.tokens).values(token).onConflictDoNothing().execute() - ); - - let daoToInsert: DaoRecord = { - daoAcct: dao.publicKey.toBase58(), - programAcct: dao.protocol.autocrat.programId.toString(), - baseAcct: dao.baseToken.publicKey, - quoteAcct: dao.quoteToken.publicKey, - slotsPerProposal: dao.daoAccount.slotsPerProposal.toString(), - treasuryAcct: dao.daoAccount.treasury.toBase58(), - minBaseFutarchicLiquidity: - dao.daoAccount.minBaseFutarchicLiquidity - ? dao.daoAccount.minBaseFutarchicLiquidity.toString() - : 0, - minQuoteFutarchicLiquidity: - dao.daoAccount.minQuoteFutarchicLiquidity - ? dao.daoAccount.minQuoteFutarchicLiquidity.toString() - : 0, - passThresholdBps: BigInt(dao.daoAccount.passThresholdBps), - twapInitialObservation: - dao.daoAccount.twapInitialObservation - ? dao.daoAccount.twapInitialObservation.toString() - : 0, - twapMaxObservationChangePerUpdate: - dao.daoAccount.twapMaxObservationChangePerUpdate - ? dao.daoAccount.twapMaxObservationChangePerUpdate.toString() - : 0, - }; - - await usingDb((db) => - db - .insert(schema.daos) - .values(daoToInsert) - .onConflictDoUpdate({ - set: { - minBaseFutarchicLiquidity: daoToInsert.minBaseFutarchicLiquidity, - minQuoteFutarchicLiquidity: daoToInsert.minQuoteFutarchicLiquidity, - twapInitialObservation: daoToInsert.twapInitialObservation, - twapMaxObservationChangePerUpdate: daoToInsert.twapMaxObservationChangePerUpdate, - passThresholdBps: daoToInsert.passThresholdBps, - }, - target: schema.daos.daoAcct, - }) - .execute() - ); - - return Ok({ acct: "Updated dao from logs" }); - } catch (err) { - logger.errorWithChatBotAlert(err); - return Err({ type: AutocratDaoIndexerError.GeneralError }); - } } }; diff --git a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-proposal-indexer.ts b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-proposal-indexer.ts index 430877d2..939418de 100644 --- a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-proposal-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-proposal-indexer.ts @@ -1,9 +1,9 @@ -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; import { rpcReadClient, - conditionalVaultClient, + v3ConditionalVaultClient as conditionalVaultClient, provider, -} from "../../connection"; +} from "../../../connection"; import { usingDb, schema, @@ -16,8 +16,8 @@ import { sql, inArray, } from "@metadaoproject/indexer-db"; -import { Err, Ok } from "../../match"; -import { PublicKey } from "@solana/web3.js"; +import { Err, Ok } from "../../utils/match"; +import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js"; import { ConditionalVaultRecord, DaoRecord, @@ -39,13 +39,14 @@ import { enrichTokenMetadata, } from "@metadaoproject/futarchy-sdk"; import { BN } from "@coral-xyz/anchor"; -import { gte } from "drizzle-orm"; import { desc } from "drizzle-orm"; import { logger } from "../../../logger"; -import { PriceMath, ProposalAccount } from "@metadaoproject/futarchy/v0.3"; -import { UserPerformance, UserPerformanceTotals } from "../../types"; +import { PriceMath } from "@metadaoproject/futarchy/v0.3"; +import { UserPerformanceTotals } from "../../types"; import { alias } from "drizzle-orm/pg-core"; -import { bigint } from "drizzle-orm/mysql-core"; +import { indexAmmMarketAccountWithContext } from "../amm/utils"; +import { rpc } from "../../../rpc-wrapper"; + export enum AutocratDaoIndexerError { GeneralError = "GeneralError", @@ -61,7 +62,6 @@ export const AutocratProposalIndexer: IntervalFetchIndexer = { cronExpression: "5 * * * * *", index: async () => { try { - console.log("AutocratProposalIndexer::index::starting"); const { currentSlot, currentTime } = ( await usingDb((db) => @@ -77,10 +77,8 @@ export const AutocratProposalIndexer: IntervalFetchIndexer = { ) )?.[0] ?? {}; - console.log("currentSlot", currentSlot); if (!currentSlot || !currentTime) return Err({ type: AutocratDaoIndexerError.MissingParamError }); - logger.log("Autocrat proposal indexer"); const dbProposals: ProposalRecord[] = (await usingDb((db) => db.select().from(schema.proposals).execute())) ?? []; @@ -94,12 +92,15 @@ export const AutocratProposalIndexer: IntervalFetchIndexer = { const proposalsToInsert = []; for (const proposal of onChainProposals) { - if ( - !dbProposals.find((dbProposal) => - new PublicKey(dbProposal.proposalAcct).equals(proposal.publicKey) && - dbProposal.endedAt === null - ) - ) { + // Check if proposal exists in DB at all + const existingProposal = dbProposals.find(dbProposal => + new PublicKey(dbProposal.proposalAcct).equals(proposal.publicKey) + ); + + // Only insert if: + // 1. Proposal doesn't exist in DB at all, or + // 2. Proposal exists but is still active (endedAt is null) + if (!existingProposal || !existingProposal.endedAt) { proposalsToInsert.push(proposal); } } @@ -442,215 +443,8 @@ export const AutocratProposalIndexer: IntervalFetchIndexer = { } }, - indexFromLogs: async (logs: string[]) => { - try { - - //TODO: leaving this here for now, maybe one day we will revisit and do it more efficiently. - console.log("AutocratProposalIndexer::indexFromLogs::logs", logs); - // Find the relevant log that contains the proposal data - const proposalLog = logs.find(log => - log.includes("Instruction:") && - (log.includes("InitializeProposal") || - log.includes("FinalizeProposal") || - log.includes("ExecuteProposal")) - ); - console.log("AutocratProposalIndexer::indexFromLogs::proposalLog", proposalLog); - - if (!proposalLog) { - console.log("AutocratProposalIndexer::indexFromLogs::proposalLog not found"); - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - // Extract proposal account from logs - const proposalAcctMatch = logs.find(log => log.includes("Proposal:")); - if (!proposalAcctMatch) { - console.log("AutocratProposalIndexer::indexFromLogs::proposalAcctMatch not found"); - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - const proposalAcct = new PublicKey(proposalAcctMatch.split(": ")[1]); - console.log("AutocratProposalIndexer::indexFromLogs::proposalAcct", proposalAcct); - - // Fetch the proposal data since we need the full account data - const protocolV0_3 = rpcReadClient.futarchyProtocols.find( - (protocol) => protocol.deploymentVersion == "V0.3" - ); - - if (!protocolV0_3) { - return Err({ type: AutocratDaoIndexerError.MissingProtocolError }); - } - - const proposal = await protocolV0_3.autocrat.account.proposal.fetch(proposalAcct); - if (!proposal) { - return Err({ type: AutocratDaoIndexerError.NotFoundError }); - } - console.log("AutocratProposalIndexer::indexFromLogs::proposal", proposal); - - // Get current slot and time for calculations - const { currentSlot, currentTime } = ( - await usingDb((db) => - db - .select({ - currentSlot: schema.prices.updatedSlot, - currentTime: schema.prices.createdAt, - }) - .from(schema.prices) - .orderBy(sql`${schema.prices.updatedSlot} DESC`) - .limit(1) - .execute() - ) - )?.[0] ?? {}; - - if (!currentSlot || !currentTime) { - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - // If this is a new proposal, insert associated accounts data - if (proposalLog.includes("InitializeProposal")) { - console.log("indexFromLogs::inserting associated accounts data for proposal", proposalAcct); - await upsertProposal({ publicKey: proposalAcct, account: proposal }, currentTime); - await insertAssociatedAccountsDataForProposal( - { publicKey: proposalAcct, account: proposal }, - currentTime - ); - } - - // Handle different proposal states - if (proposal.state.pending) { - // Update proposal as pending - if (!proposalLog.includes("InitializeProposal")) { // If this is a new proposal, we dont need to update the status - await updateProposalStatus(proposalAcct, ProposalStatus.Pending, currentTime); - } - } else if (proposal.state.passed) { - // Update proposal as passed - await updateProposalStatus(proposalAcct, ProposalStatus.Passed, currentTime); - await updateVaultStatuses(proposal.baseVault, proposal.quoteVault, "finalized"); - await calculateUserPerformance({ publicKey: proposalAcct, account: proposal }); - } else if (proposal.state.failed) { - // Update proposal as failed - await updateProposalStatus(proposalAcct, ProposalStatus.Failed, currentTime); - await updateVaultStatuses(proposal.baseVault, proposal.quoteVault, "reverted"); - await calculateUserPerformance({ publicKey: proposalAcct, account: proposal }); - } - - console.log("AutocratProposalIndexer::indexFromLogs::done"); - return Ok({ acct: "Updated proposal from logs" }); - } catch (err) { - logger.error("error with proposal indexer:", err); - return Err({ type: AutocratDaoIndexerError.GeneralError }); - } - } }; -// helper function to upsert proposal -async function upsertProposal(proposal: ProposalAccountWithKey, currentTime: Date) { - const daoAcct = proposal.account.dao; - if (!daoAcct) { - console.log("AutocratProposalIndexer::upsertProposal::daoAcct not found"); - return Err({ type: AutocratDaoIndexerError.MissingParamError }); - } - - // Get DAO details - const dbDao: DaoRecord | undefined = ( - await usingDb((db) => - db - .select() - .from(schema.daos) - .where(eq(schema.daos.daoAcct, daoAcct.toBase58())) - .execute() - ) - )?.[0]; - - if (!dbDao) return; - - // Calculate end slot - const initialSlot = proposal.account.slotEnqueued; - const endSlot = initialSlot.add(new BN(dbDao.slotsPerProposal?.toString())); - - // Prepare proposal record - const dbProposal: ProposalRecord = { - proposalAcct: proposal.publicKey.toString(), - proposalNum: BigInt(proposal.account.number.toString()), - autocratVersion: 0.3, - daoAcct: daoAcct.toString(), - proposerAcct: proposal.account.proposer.toString(), - status: ProposalStatus.Pending, - descriptionURL: proposal.account.descriptionUrl, - initialSlot: initialSlot.toString(), - passMarketAcct: proposal.account.passAmm?.toString() ?? null, - failMarketAcct: proposal.account.failAmm?.toString() ?? null, - baseVault: proposal.account.baseVault.toString(), - quoteVault: proposal.account.quoteVault.toString(), - endSlot: endSlot.toString(), - durationInSlots: dbDao.slotsPerProposal, - minBaseFutarchicLiquidity: dbDao.minBaseFutarchicLiquidity ?? null, - minQuoteFutarchicLiquidity: dbDao.minQuoteFutarchicLiquidity ?? null, - passThresholdBps: dbDao.passThresholdBps, - twapInitialObservation: dbDao.twapInitialObservation ?? null, - twapMaxObservationChangePerUpdate: dbDao.twapMaxObservationChangePerUpdate ?? null, - }; - - // Insert or update the proposal - await usingDb((db) => - db - .insert(schema.proposals) - .values([dbProposal]) - .onConflictDoUpdate({ - target: [schema.proposals.proposalAcct], - set: { - status: dbProposal.status, - descriptionURL: dbProposal.descriptionURL, - initialSlot: dbProposal.initialSlot, - endSlot: dbProposal.endSlot, - updatedAt: sql`NOW()`, - }, - }) - .execute() - ); - - return Ok({ acct: "Proposal upserted successfully" }); -} - -// Helper function to update proposal status -async function updateProposalStatus( - proposalAcct: PublicKey, - status: ProposalStatus, - currentTime: Date -) { - await usingDb((db) => - db - .update(schema.proposals) - .set({ - status, - completedAt: status !== ProposalStatus.Pending ? currentTime : null, - updatedAt: sql`NOW()` - }) - .where( - eq(schema.proposals.proposalAcct, proposalAcct.toString()) - ) - .execute() - ); -} - -// Helper function to update vault statuses -async function updateVaultStatuses( - baseVault: PublicKey, - quoteVault: PublicKey, - status: "finalized" | "reverted" -) { - await usingDb((db) => - db - .update(schema.conditionalVaults) - .set({ status }) - .where( - or( - eq(schema.conditionalVaults.condVaultAcct, baseVault.toString()), - eq(schema.conditionalVaults.condVaultAcct, quoteVault.toString()) - ) - ) - .execute() - ); -} async function insertAssociatedAccountsDataForProposal( proposal: ProposalAccountWithKey, @@ -896,6 +690,30 @@ async function insertAssociatedAccountsDataForProposal( .onConflictDoNothing() .execute() ); + + [passMarket, failMarket].map(async (market) => { + try { + console.log("autocrat-proposal-indexer::insertAssociatedAccountsDataForProposal::inserting price for market", market.marketAcct); + const account = new PublicKey(market.marketAcct); + const resWithContext = await rpc.call( + "getAccountInfoAndContext", + [account], + "Get account info for amm market account interval fetcher" + ) as RpcResponseAndContext | null>; + if (!resWithContext.value) { + logger.error("Failed to get account info for market", market.marketAcct); + return; + } + + await indexAmmMarketAccountWithContext( + resWithContext.value, + account, + resWithContext.context + ); + } catch (err) { + logger.error("Failed to index price for market", market.marketAcct, err instanceof Error ? err.message : err); + } + }); } async function calculateUserPerformance( diff --git a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0-indexer.ts b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0-indexer.ts deleted file mode 100644 index f23102f5..00000000 --- a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0-indexer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AUTOCRAT_VERSIONS } from "@metadaoproject/futarchy-sdk/lib/constants"; -import { IDL, AutocratV0 } from "@metadaoproject/futarchy-sdk/lib/idl/autocrat_v0"; -import { Err, InstructionIndexer, Ok } from "../instruction-indexer"; -import { logger } from "../../../logger"; - -const AUTOCRAT_V0 = AUTOCRAT_VERSIONS[AUTOCRAT_VERSIONS.length - 1]; - -if (AUTOCRAT_V0.label !== "V0") { - const error = new Error(`Mistook autocrat ${AUTOCRAT_V0.label} for V0`); - logger.error(error.message); - throw error; -} - -export const AutocratV0Indexer: InstructionIndexer = { - PROGRAM_NAME: "AutocrateV0", - PROGRAM_ID: AUTOCRAT_V0.programId.toBase58(), - PROGRAM_IDL: AUTOCRAT_V0.idl as any, - indexInstruction: async (dbTx, txIdx, txRes, ixIdx, ix) => { - return Ok; - }, -}; diff --git a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_1-indexer.ts b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_1-indexer.ts deleted file mode 100644 index 331c3519..00000000 --- a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_1-indexer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AUTOCRAT_VERSIONS } from "@metadaoproject/futarchy-sdk/lib/constants"; -import { IDL, AutocratV0 } from "@metadaoproject/futarchy-sdk/lib/idl/autocrat_v0.1"; -import { InstructionIndexer, Ok } from "../instruction-indexer"; -import { logger } from "../../../logger"; - -const AUTOCRAT_V0_1 = AUTOCRAT_VERSIONS[AUTOCRAT_VERSIONS.length - 2]; - -if (AUTOCRAT_V0_1.label !== "V0.1") { - const error = new Error(`Mistook autocrat ${AUTOCRAT_V0_1.label} for V0.1`); - logger.error(error.message); - throw error; -} - -export const AutocratV0_1Indexer: InstructionIndexer = { - PROGRAM_NAME: "AutocratV0.1", - PROGRAM_ID: AUTOCRAT_V0_1.programId.toBase58(), - PROGRAM_IDL: IDL, - indexInstruction: async (dbTx, txIdx, txRes, ixIdx, ix) => { - return Ok; - }, -}; diff --git a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_2-indexer.ts b/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_2-indexer.ts deleted file mode 100644 index 68c70cbb..00000000 --- a/packages/indexer/src/v3_indexer/indexers/autocrat/autocrat-v0_2-indexer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { AUTOCRAT_VERSIONS } from "@metadaoproject/futarchy-sdk/lib/constants"; -import { IDL, AutocratV0 } from "@metadaoproject/futarchy-sdk/lib/idl/autocrat_v0.2"; -import { InstructionIndexer, Ok } from "../instruction-indexer"; -import { logger } from "../../../logger"; - -const AUTOCRAT_V0_2 = AUTOCRAT_VERSIONS[AUTOCRAT_VERSIONS.length - 1]; - -if (AUTOCRAT_V0_2.label !== "V0.2") { - const error = new Error(`Mistook autocrat ${AUTOCRAT_V0_2.label} for V0.2`); - logger.error(error.message); - throw error; -} - -export const AutocratV0_1Indexer: InstructionIndexer = { - PROGRAM_NAME: "AutocratV0.2", - PROGRAM_ID: AUTOCRAT_V0_2.programId.toBase58(), - PROGRAM_IDL: IDL, - indexInstruction: async (dbTx, txIdx, txRes, ixIdx, ix) => { - return Ok; - }, -}; diff --git a/packages/indexer/src/v3_indexer/indexers/birdeye/birdeye-prices-indexer.ts b/packages/indexer/src/v3_indexer/indexers/birdeye/birdeye-prices-indexer.ts index 7142fac3..ed64b841 100644 --- a/packages/indexer/src/v3_indexer/indexers/birdeye/birdeye-prices-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/birdeye/birdeye-prices-indexer.ts @@ -1,6 +1,6 @@ import { and, eq, schema, usingDb } from "@metadaoproject/indexer-db"; -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; -import { Err, Ok } from "../../match"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; +import { Err, Ok } from "../../utils/match"; import { IndexerAccountDependencyStatus, PricesRecord, diff --git a/packages/indexer/src/v3_indexer/indexers/common.ts b/packages/indexer/src/v3_indexer/indexers/common.ts index 4462a0dc..06e6f0fd 100644 --- a/packages/indexer/src/v3_indexer/indexers/common.ts +++ b/packages/indexer/src/v3_indexer/indexers/common.ts @@ -1,6 +1,6 @@ import { SolanaParser } from "@debridge-finance/solana-transaction-parser"; import { AmmClient, AMM_PROGRAM_ID } from "@metadaoproject/futarchy/v0.3"; -import { provider } from "../connection"; +import { provider } from "../../connection"; export const ammClient = new AmmClient(provider, AMM_PROGRAM_ID, []); export type IDL = typeof ammClient.program.idl; diff --git a/packages/indexer/src/v3_indexer/indexers/jupiter/jupiter-quotes-indexer.ts b/packages/indexer/src/v3_indexer/indexers/jupiter/jupiter-quotes-indexer.ts index 076487d8..a0e4a6c0 100644 --- a/packages/indexer/src/v3_indexer/indexers/jupiter/jupiter-quotes-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/jupiter/jupiter-quotes-indexer.ts @@ -1,6 +1,6 @@ import { schema, usingDb, eq, and } from "@metadaoproject/indexer-db"; -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; -import { Err, Ok } from "../../match"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; +import { Err, Ok } from "../../utils/match"; import { IndexerAccountDependencyStatus, PricesRecord, diff --git a/packages/indexer/src/v3_indexer/indexers/openbook-twap/openbook-twap-instruction-indexer.ts b/packages/indexer/src/v3_indexer/indexers/openbook-twap/openbook-twap-instruction-indexer.ts deleted file mode 100644 index 894a9013..00000000 --- a/packages/indexer/src/v3_indexer/indexers/openbook-twap/openbook-twap-instruction-indexer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IDL, OpenbookTwap } from "@metadaoproject/futarchy-sdk/lib/idl/openbook_twap"; -import { OPENBOOK_TWAP_PROGRAM_ID } from "@metadaoproject/futarchy-sdk/lib/constants"; -import { Err, InstructionIndexer, Ok } from "../instruction-indexer"; - -// Doing this rather than class implements pattern due to -// https://github.com/microsoft/TypeScript/issues/41399 -export const OpenbookTwapIndexer: InstructionIndexer = { - PROGRAM_NAME: "OpenbookTwap", - PROGRAM_ID: OPENBOOK_TWAP_PROGRAM_ID.toBase58(), - PROGRAM_IDL: IDL, - indexInstruction: async (dbTx, txIdx, txRes, ixIdx, ix) => { - // TODO: in the future we want to switch on the instruction name and index the openbook program instruction - // build the order book, then from that deduce the twap change. - - // For now though, we're going to take a shortcut by just reading the logs. - // We get initial weighted observation aggregator via the createTwapMarket expected value - // see https://github.com/metaDAOproject/openbook-twap/blob/82690c33a091b82e908843a14ad1a571dfba12b1/programs/openbook-twap/src/lib.rs#L68 - // Afterwards we can extract the log line here - // and use it to increment the observation aggregator - // https://github.com/metaDAOproject/openbook-twap/blob/82690c33a091b82e908843a14ad1a571dfba12b1/programs/openbook-twap/src/lib.rs#L120 - // we simply take difference between transaction slot and proposal enqueued slot then divide the obs agg by this - // https://github.com/metaDAOproject/futarchy/blob/d5b91dc103e23d72900817e93b450e42274469c9/programs/autocrat_v0/src/lib.rs#L335 - - return Ok; - } -}; diff --git a/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-account-indexer.ts b/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-account-indexer.ts deleted file mode 100644 index 8518760b..00000000 --- a/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-account-indexer.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { AccountInfoIndexer } from "../account-info-indexer"; -import { rpcReadClient } from "../../connection"; -import { AccountInfo, Context, PublicKey } from "@solana/web3.js"; -import { schema, usingDb } from "@metadaoproject/indexer-db"; -import { Err, Ok } from "../../match"; -import { - OpenbookMarketFetchRequest, - getMidPrice, -} from "@metadaoproject/futarchy-sdk"; -import { - PricesRecord, - PricesType, -} from "@metadaoproject/indexer-db/lib/schema"; -import { logger } from "../../../logger"; - -export enum OpenbookV2MarketAccountIndexerError { - MarketNotFound = "MarketNotFound", - GeneralError = "GeneralError", - OrderbookNotFound = "OrderbookNotFound", - MidPriceNotFound = "MidPriceNotFound", -} - -export const OpenbookV2MarketAccountUpdateIndexer: AccountInfoIndexer = { - index: async ( - _: AccountInfo, - account: PublicKey, - context: Context - ) => { - try { - const market = await rpcReadClient.markets.openbook.fetchMarket( - new OpenbookMarketFetchRequest(account, null as any) - ); - - if (!market) { - logger.error("Failed to fetch market"); - return Err({ - type: OpenbookV2MarketAccountIndexerError.MarketNotFound, - }); - } - - const orderbook = await rpcReadClient.markets.openbook.fetchOrderBook( - market - ); - if (!orderbook) { - logger.error("Failed to fetch order book"); - return Err({ - type: OpenbookV2MarketAccountIndexerError.OrderbookNotFound, - }); - } - - const midPrice = getMidPrice(orderbook); - if (!midPrice) { - logger.error("Failed to calculate mid price"); - return Err({ - type: OpenbookV2MarketAccountIndexerError.MidPriceNotFound, - }); - } - - // update the latest slot here - const newOpenbookConditionaPrice: PricesRecord = { - marketAcct: account.toString(), - updatedSlot: context.slot.toString(), - price: midPrice.toString(), - pricesType: PricesType.Conditional, - baseAmount: - market.marketInstance.account.baseDepositTotal.toString() - , - quoteAmount: - market.marketInstance.account.quoteDepositTotal.toString() - , - createdBy: "openbook-v2-account-indexer", - }; - - const pricesInsertResult = - (await usingDb((db) => - db - .insert(schema.prices) - .values(newOpenbookConditionaPrice) - .onConflictDoUpdate({ - target: [schema.prices.createdAt, schema.prices.marketAcct], - set: newOpenbookConditionaPrice, - }) - .returning({ marketAcct: schema.prices.marketAcct }) - )) ?? []; - - return Ok({ acct: pricesInsertResult[0].marketAcct }); - } catch (error) { - logger.error( - "Unexpected error in openbook v2 market info index function:", - error - ); - return Err({ - type: OpenbookV2MarketAccountIndexerError.GeneralError, - error, - }); - } - }, -}; diff --git a/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-indexer.ts b/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-indexer.ts deleted file mode 100644 index 7cb48703..00000000 --- a/packages/indexer/src/v3_indexer/indexers/openbook-v2/openbook-v2-indexer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { OPENBOOK_PROGRAM_ID } from "@metadaoproject/futarchy-sdk"; -import { Err, InstructionIndexer, Ok } from "../instruction-indexer"; - -export const OpenbookV2Indexer: InstructionIndexer = { - PROGRAM_NAME: "OpenBookV2", - PROGRAM_ID: OPENBOOK_PROGRAM_ID.toBase58(), - PROGRAM_IDL: null, - indexInstruction: async (dbTx, txIdx, txRes, ixIdx, ix) => { - return Ok; - }, -}; diff --git a/packages/indexer/src/v3_indexer/indexers/start-account-info-indexers.ts b/packages/indexer/src/v3_indexer/indexers/start-account-info-indexers.ts index 49748830..6b7d759e 100644 --- a/packages/indexer/src/v3_indexer/indexers/start-account-info-indexers.ts +++ b/packages/indexer/src/v3_indexer/indexers/start-account-info-indexers.ts @@ -1,10 +1,9 @@ import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema"; -import { PublicKey } from "@solana/web3.js"; -import { connection } from "../connection"; +import { PublicKey, RpcResponseAndContext, AccountInfo } from "@solana/web3.js"; +import { rpc } from "../../rpc-wrapper"; import { IndexerWithAccountDeps } from "../types"; -import { AccountInfoIndexer } from "./account-info-indexer"; -import { AmmMarketAccountUpdateIndexer } from "./amm-market/amm-market-account-indexer"; -import { OpenbookV2MarketAccountUpdateIndexer } from "./openbook-v2/openbook-v2-account-indexer"; +import { AccountInfoIndexer } from "../types/account-info-indexer"; +import { AmmMarketAccountUpdateIndexer } from "./amm/amm-market-account-indexer"; import { logger } from "../../logger"; export async function startAccountInfoIndexer( @@ -19,9 +18,11 @@ export async function startAccountInfoIndexer( if (implementation && dependentAccount && dependentAccount.acct) { const accountPubKey = new PublicKey(dependentAccount.acct); - const accountInfo = await connection.getAccountInfoAndContext( - accountPubKey - ); + const accountInfo = await rpc.call( + "getAccountInfoAndContext", + [accountPubKey], + "Get account info for account info indexer" + ) as RpcResponseAndContext | null>; //index refresh on startup if (accountInfo.value) { @@ -57,8 +58,6 @@ function getAccountInfoIndexerImplementation( switch (implementation) { case IndexerImplementation.AmmMarketIndexer: return AmmMarketAccountUpdateIndexer; - case IndexerImplementation.OpenbookV2MarketIndexer: - return OpenbookV2MarketAccountUpdateIndexer; } return null; } diff --git a/packages/indexer/src/v3_indexer/indexers/start-interval-fetch-indexers.ts b/packages/indexer/src/v3_indexer/indexers/start-interval-fetch-indexers.ts index 07037996..63b28a32 100644 --- a/packages/indexer/src/v3_indexer/indexers/start-interval-fetch-indexers.ts +++ b/packages/indexer/src/v3_indexer/indexers/start-interval-fetch-indexers.ts @@ -5,9 +5,9 @@ import { import { IndexerWithAccountDeps } from "../types"; import { BirdeyePricesIndexer } from "./birdeye/birdeye-prices-indexer"; -import { IntervalFetchIndexer } from "./interval-fetch-indexer"; +import { IntervalFetchIndexer } from "../types/interval-fetch-indexer"; import { JupiterQuotesIndexer } from "./jupiter/jupiter-quotes-indexer"; -import { AmmMarketAccountIntervalFetchIndexer } from "./amm-market/amm-market-account-interval-indexer"; +import { AmmMarketAccountIntervalFetchIndexer } from "./amm/amm-market-account-interval-indexer"; import { AutocratDaoIndexer } from "./autocrat/autocrat-dao-indexer"; import { AutocratProposalIndexer } from "./autocrat/autocrat-proposal-indexer"; import { TokenMintIndexer } from "./token/token-mint-indexer"; diff --git a/packages/indexer/src/v3_indexer/indexers/start-logs-subscribe-indexer.ts b/packages/indexer/src/v3_indexer/indexers/start-logs-subscribe-indexer.ts index 4c8f267a..c66615b3 100644 --- a/packages/indexer/src/v3_indexer/indexers/start-logs-subscribe-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/start-logs-subscribe-indexer.ts @@ -1,9 +1,9 @@ import { IndexerImplementation } from "@metadaoproject/indexer-db/lib/schema"; import { PublicKey } from "@solana/web3.js"; -import { connection } from "../connection"; +import { connection } from "../../connection"; import { IndexerWithAccountDeps } from "../types"; -import { AccountLogsIndexer } from "./account-logs-indexer"; -import { AmmMarketLogsSubscribeIndexer } from "./amm-market/amm-market-logs-subscribe-indexer"; +import { AccountLogsIndexer } from "../types/account-logs-indexer"; +import { AmmMarketLogsSubscribeIndexer } from "./amm/amm-market-logs-subscribe-indexer"; import { logger } from "../../logger"; export async function startLogsSubscribeIndexer( diff --git a/packages/indexer/src/v3_indexer/indexers/start-transaction-history-indexers.ts b/packages/indexer/src/v3_indexer/indexers/start-transaction-history-indexers.ts index 55a45df4..9a9fe949 100644 --- a/packages/indexer/src/v3_indexer/indexers/start-transaction-history-indexers.ts +++ b/packages/indexer/src/v3_indexer/indexers/start-transaction-history-indexers.ts @@ -6,8 +6,8 @@ import { } from "@metadaoproject/indexer-db/lib/schema"; import { IndexerWithAccountDeps } from "../types"; -import { AmmMarketInstructionsIndexer } from "./amm-market/amm-market-instruction-indexer"; -import { InstructionIndexer } from "./instruction-indexer"; +import { AmmMarketInstructionsIndexer } from "./amm/amm-market-instruction-indexer"; +import { InstructionIndexer } from "../types/instruction-indexer"; import { Idl } from "@coral-xyz/anchor"; import { schema, usingDb, eq, and, gte } from "@metadaoproject/indexer-db"; import * as fastq from "fastq"; diff --git a/packages/indexer/src/v3_indexer/indexers/token/token-mint-indexer.ts b/packages/indexer/src/v3_indexer/indexers/token/token-mint-indexer.ts index c1e7a2be..79c06699 100644 --- a/packages/indexer/src/v3_indexer/indexers/token/token-mint-indexer.ts +++ b/packages/indexer/src/v3_indexer/indexers/token/token-mint-indexer.ts @@ -1,7 +1,7 @@ -import { IntervalFetchIndexer } from "../interval-fetch-indexer"; -import { provider } from "../../connection"; +import { IntervalFetchIndexer } from "../../types/interval-fetch-indexer"; +import { provider } from "../../../connection"; import { usingDb, schema, eq } from "@metadaoproject/indexer-db"; -import { Err, Ok } from "../../match"; +import { Err, Ok } from "../../utils/match"; import { PublicKey } from "@solana/web3.js"; import { TokenRecord } from "@metadaoproject/indexer-db/lib/schema"; import { TokenAccountNotFoundError, getMint } from "@solana/spl-token"; diff --git a/packages/indexer/src/v3_indexer/instruction-dispatch.ts b/packages/indexer/src/v3_indexer/instruction-dispatch.ts deleted file mode 100644 index 1821f3c9..00000000 --- a/packages/indexer/src/v3_indexer/instruction-dispatch.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { connection } from "./connection"; -import { InstructionIndexer } from "./indexers/instruction-indexer"; -import { BorshCoder } from "@coral-xyz/anchor"; -import { OpenbookTwapIndexer } from "./indexers/openbook-twap/openbook-twap-instruction-indexer"; -import { OpenbookV2Indexer } from "./indexers/openbook-v2/openbook-v2-indexer"; -import { AutocratV0Indexer } from "./indexers/autocrat/autocrat-v0-indexer"; -import { AutocratV0_1Indexer } from "./indexers/autocrat/autocrat-v0_1-indexer"; -import { red, yellow } from "ansicolor"; -import { - AddressLookupTableAccount, - Message, - MessageAccountKeys, -} from "@solana/web3.js"; - -export type IndexTransactionResult = - | { indexed: true } - | { indexed: false; error: { type: E; details: ErrorDetails[E] } }; - -export enum IndexTransactionError { - NoTxReturned = "NoTxReturned", - NoKnownProgram = "NoKnownProgram", - MoreSignaturesThanExpected = "MoreSignaturesThanExpected", - WrongSignature = "WrongSignature", - FailedToIndexInstruction = "FailedToIndexInstruction", - InvalidVersion = "InvalidVersion", - VersionLookupTableMismatch = "VersionLookupTableMismatch", - InvalidLookupTable = "InvalidLookupTable", -} - -export type MessageAddressTableLookup = Message["addressTableLookups"][0]; - -export type ErrorDetails = { - [IndexTransactionError.NoTxReturned]: undefined; - [IndexTransactionError.NoKnownProgram]: { programs: string[] }; - [IndexTransactionError.MoreSignaturesThanExpected]: { signatures: string[] }; - [IndexTransactionError.WrongSignature]: { signature: string }; - [IndexTransactionError.FailedToIndexInstruction]: undefined; - [IndexTransactionError.InvalidVersion]: { version: any }; - [IndexTransactionError.VersionLookupTableMismatch]: { - version: any; - addressTableLookups: MessageAddressTableLookup[]; - }; - [IndexTransactionError.InvalidLookupTable]: { accountKey: string }; -}; - -const indexers: InstructionIndexer[] = [ - AutocratV0Indexer, - AutocratV0_1Indexer, - OpenbookTwapIndexer, - OpenbookV2Indexer, -]; - -enum TransactionVersionType { - Legacy = "Legacy", - V0 = "V0", -} - -const programToIndexer: Record< - string, - { indexer: InstructionIndexer; coder: BorshCoder } -> = Object.fromEntries( - indexers.map((indexer) => [ - indexer.PROGRAM_ID, - { indexer, coder: new BorshCoder(indexer.PROGRAM_IDL) }, - ]) -); - -function error( - e: E, - details: ErrorDetails[E] -): IndexTransactionResult { - return { indexed: false, error: { type: e, details } }; -} - -const ok = { indexed: true } as const; - -export async function indexTransaction( - txIdx: number, - signature: string -): Promise> { - const tx = await connection.getTransaction(signature, { - maxSupportedTransactionVersion: 0, - }); - - if (tx == null || tx === undefined) { - return error(IndexTransactionError.NoTxReturned, undefined); - } - - if (tx.meta?.err) { - // TODO: mark tx as processed - return ok; - } - - const { transaction } = tx; - const { signatures } = transaction; - - let txVersion: TransactionVersionType; - let accountKeys: MessageAccountKeys; - switch (tx.version) { - case "legacy": - txVersion = TransactionVersionType.Legacy; - if (transaction.message.addressTableLookups.length > 0) { - return error(IndexTransactionError.VersionLookupTableMismatch, { - version: txVersion, - addressTableLookups: transaction.message.addressTableLookups, - }); - } - accountKeys = transaction.message.getAccountKeys(); - break; - case 0: - txVersion = TransactionVersionType.V0; - if (transaction.message.addressTableLookups.length === 0) { - return error(IndexTransactionError.VersionLookupTableMismatch, { - version: txVersion, - addressTableLookups: transaction.message.addressTableLookups, - }); - } - // https://solana.stackexchange.com/questions/8652/how-do-i-parse-the-accounts-in-a-versioned-transaction - const lookupTables: AddressLookupTableAccount[] = []; - for (const { accountKey } of transaction.message.addressTableLookups) { - const lookupTable = await connection.getAddressLookupTable(accountKey); - if (lookupTable === null) { - console.log("no response from getAddressLookupTable"); - return error(IndexTransactionError.InvalidLookupTable, { - accountKey: accountKey.toBase58(), - }); - } - if (lookupTable.value === null) { - console.log("null lookup table value"); - return error(IndexTransactionError.InvalidLookupTable, { - accountKey: accountKey.toBase58(), - }); - } - lookupTables.push(lookupTable.value); - } - accountKeys = transaction.message.getAccountKeys({ - addressLookupTableAccounts: lookupTables, - }); - break; - default: - return error(IndexTransactionError.InvalidVersion, { - version: tx.version, - }); - } - - // console.log('indexing', signature, tx.version, tx.transaction.message.addressTableLookups.length); - - // TODO: maybe do something with inner instructions - // tx.meta?.innerInstructions - // console.log(JSON.stringify(tx)); - - /* - first tx has 5 signatures. Need to investigate whether all show up in the getSignaturesForAccount response. - We don't want to process the same tx multiple times. - if (signatures.length > 1) { - return error(IndexTransactionError.MoreSignaturesThanExpected, {signatures: tx.transaction.signatures}) - } - */ - - const txSig = signatures[0]; - if (txSig !== signature) { - return error(IndexTransactionError.WrongSignature, { signature: txSig }); - } - - //const accountKeys = transaction.message.getAccountKeys({a}); - const instructions = transaction.message.compiledInstructions; - const programs: string[] = []; - let matchingProgramFound = false; - const timeStr = new Date(tx.blockTime! * 1000) - .toISOString() - .replace("T", " ") - .replace(".000Z", ""); - for (let i = 0; i < instructions.length; ++i) { - const ix = instructions[i]; - const program = accountKeys.staticAccountKeys[ix.programIdIndex].toBase58(); - programs.push(program); - if (program in programToIndexer) { - matchingProgramFound = true; - const { indexer, coder } = programToIndexer[program]; - const ixIdx = i; - const prefix = `[${timeStr}][slot ${tx.slot}][${indexer.PROGRAM_NAME}] ${txIdx}.${ixIdx}.`; - const secondLogMessage = (tx.meta?.logMessages ?? [])[1]; - switch (secondLogMessage) { - case "Program log: Instruction: IdlCreateAccount": - console.log( - yellow(`${prefix} skipping IdlCreateAccount ${signature}`) - ); - continue; - case "Program log: Instruction: IdlWrite": - console.log(yellow(`${prefix} skipping IdlWrite ${signature}`)); - continue; - } - const decoded = coder.instruction.decode(Buffer.from(ix.data)); - if (decoded == null) { - console.log( - `${prefix} Cannot decode instruction ${i} of transaction ${signature}` - ); - console.log(tx.meta?.logMessages); - continue; - } - let ixLine = `${prefix} ${decoded.name} ${signature}`; - console.log(ixLine); - const result = await indexer.indexInstruction( - {} as any, // TODO: initialize db transaction and pass here - txIdx, - tx, - ixIdx, - decoded - ); - if (!result.indexed) { - return error(IndexTransactionError.FailedToIndexInstruction, undefined); - } - } - } - if (!matchingProgramFound) { - return error(IndexTransactionError.NoKnownProgram, { programs }); - } - - return ok; -} diff --git a/packages/indexer/src/v3_indexer/local-cache.ts b/packages/indexer/src/v3_indexer/local-cache.ts deleted file mode 100644 index c845545e..00000000 --- a/packages/indexer/src/v3_indexer/local-cache.ts +++ /dev/null @@ -1,40 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { logger } from "./logger"; - -const CACHE_DIR = path.resolve(__dirname, "cache"); -const CACHE_LOCALLY = false; // TODO: turn off when ready to actually start indexing - -if (!fs.existsSync(CACHE_DIR)) { - await fs.promises.mkdir(CACHE_DIR); -} - -function getFileAndDirPath(key: string[]): { file: string; dir: string } { - if (key.length === 0) { - const error = new Error("Invalid empty key"); - logger.error(error.message); - throw error; - } - const keyCopy = [...key]; - const filename = keyCopy.pop()!; - const dir = path.resolve(CACHE_DIR, keyCopy.join("/")); - const file = path.resolve(dir, filename); - return { file, dir }; -} - -export async function get(key: string[]): Promise { - if (!CACHE_LOCALLY) return undefined; - const { file } = getFileAndDirPath(key); - if (fs.existsSync(file)) { - logger.log(`Cache hit on ${key.join("/")}`); - return (await fs.promises.readFile(file)).toString().split("\n"); - } - return undefined; -} - -export async function set(key: string[], value: string[]): Promise { - if (!CACHE_LOCALLY) return; - const { file, dir } = getFileAndDirPath(key); - await fs.promises.mkdir(dir, { recursive: true }); - await fs.promises.writeFile(file, value.join("\n")); -} diff --git a/packages/indexer/src/v3_indexer/proposal-indexer.ts b/packages/indexer/src/v3_indexer/proposal-indexer.ts deleted file mode 100644 index ffb4a590..00000000 --- a/packages/indexer/src/v3_indexer/proposal-indexer.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AUTOCRAT_VERSIONS, OPENBOOK_PROGRAM_ID } from '@metadaoproject/futarchy-sdk/lib/constants'; -import { AutocratProgram, DaoState, ProgramVersion, Proposal } from '@metadaoproject/futarchy-sdk/lib/types'; -import { AccountMeta, PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'; -import { OpenbookV2, IDL as OPENBOOK_IDL } from '@openbook-dex/openbook-v2'; -import { Program, utils } from '@coral-xyz/anchor'; -import { connection, provider } from './connection'; -import { ProposalOutcome, MarketType } from '@metadaoproject/indexer-db/lib/schema'; - -const SUPPORTED_AUTOCRAT_VERSIONS = ['V0.1', 'V0']; - -// TODO: send this to monitoring -function emitErrorMetric(msg: String) { - console.log(msg); -} - -async function getDAOProposals({label, programId, idl}: ProgramVersion) { - //console.log(`fetching ${label} proposals`); - - if (!SUPPORTED_AUTOCRAT_VERSIONS.includes(label)) { - // TODO: Emit error metric - emitErrorMetric(`Unable to parse proposals with label ${label}`); - return []; - } - - // const dao = PublicKey.findProgramAddressSync( - // [utils.bytes.utf8.encode('WWCACOTMICMIBMHAFTTWYGHMB')], - // programId, - // )[0]; - - // const daoTreasury = PublicKey.findProgramAddressSync([dao.toBuffer()], programId)[0]; - - const autocratProgram = new Program(idl as AutocratProgram, programId, provider); - - // const openbook = new Program(OPENBOOK_IDL, OPENBOOK_PROGRAM_ID, provider); - - // const daoState = await autocratProgram.account.dao.fetch(dao); - - const proposals = (await autocratProgram.account.proposal.all()) - .sort((a, b) => b.account.number - a.account.number) // descending order - - return proposals; -} - -export async function getProposals() { - return (await Promise.all(AUTOCRAT_VERSIONS.map(getDAOProposals))).flat(); -} diff --git a/packages/indexer/src/v3_indexer/transaction/account-resolver.ts b/packages/indexer/src/v3_indexer/transaction/account-resolver.ts index 0c1871a2..59c36d00 100644 --- a/packages/indexer/src/v3_indexer/transaction/account-resolver.ts +++ b/packages/indexer/src/v3_indexer/transaction/account-resolver.ts @@ -1,6 +1,6 @@ -import { Result, Ok, Err } from '../match'; +import { Result, Ok, Err } from '../utils/match'; import { VersionedTransactionResponse, AddressLookupTableAccount, MessageAccountKeys, MessageAddressTableLookup, RpcResponseAndContext, PublicKey } from "@solana/web3.js"; -import { connection } from '../connection'; +import { connection } from '../../connection'; export enum ResolveAccountsErrorType { AddressTableLookupsInLegacy = 'AddressTableLookupsInLegacy', diff --git a/packages/indexer/src/v3_indexer/transaction/history.ts b/packages/indexer/src/v3_indexer/transaction/history.ts index cd41d9f1..472a65b9 100644 --- a/packages/indexer/src/v3_indexer/transaction/history.ts +++ b/packages/indexer/src/v3_indexer/transaction/history.ts @@ -1,5 +1,6 @@ -import { connection } from "../connection"; -import { PublicKey } from "@solana/web3.js"; +import { PublicKey, ConfirmedSignatureInfo } from "@solana/web3.js"; +import { rpc } from "../../rpc-wrapper"; +import { connection } from "../../connection"; import { logger } from "../../logger"; export type TransactionMeta = Awaited< @@ -33,11 +34,11 @@ export async function getTransactionHistory( let page = 1; while (true) { // The Solana RPC tx API has us do a backwards walk - const transactions = await connection.getSignaturesForAddress( - account, - { before: earliestSig }, - "confirmed" - ); + const transactions = await rpc.call( + "getSignaturesForAddress", + [account, { before: earliestSig }, "confirmed"], + "Get historical signatures" + ) as ConfirmedSignatureInfo[]; if (transactions.length === 0) { break; } diff --git a/packages/indexer/src/v3_indexer/transaction/serializer.ts b/packages/indexer/src/v3_indexer/transaction/serializer.ts index 460ce085..a5836fda 100644 --- a/packages/indexer/src/v3_indexer/transaction/serializer.ts +++ b/packages/indexer/src/v3_indexer/transaction/serializer.ts @@ -7,11 +7,11 @@ import { PublicKey, VersionedTransactionResponse, } from "@solana/web3.js"; -import { Ok, Err, Result } from "../match"; +import { Ok, Err, Result } from "../utils/match"; import { z } from "zod"; import { resolveAccounts, ResolveAccountsError } from "./account-resolver"; import * as base58 from "bs58"; -import { connection, provider } from "../connection"; +import { connection, provider } from "../../connection"; import { BorshInstructionCoder, Idl, Program } from "@coral-xyz/anchor"; import { PROGRAM_ID_TO_IDL_MAP } from "../constants"; import { diff --git a/packages/indexer/src/v3_indexer/transaction/watcher.ts b/packages/indexer/src/v3_indexer/transaction/watcher.ts index cda8183a..b7a1f6f5 100644 --- a/packages/indexer/src/v3_indexer/transaction/watcher.ts +++ b/packages/indexer/src/v3_indexer/transaction/watcher.ts @@ -7,9 +7,9 @@ import { serialize, } from "./serializer"; import { getTransactionHistory } from "./history"; -import { connection } from "../connection"; +import { connection } from "../../connection"; import { logger } from "../../logger"; -import { Err, Ok, Result, TaggedUnion } from "../match"; +import { Err, Ok, Result, TaggedUnion } from "../utils/match"; import { InstructionType, TransactionWatchStatus, diff --git a/packages/indexer/src/v3_indexer/indexers/account-info-indexer.ts b/packages/indexer/src/v3_indexer/types/account-info-indexer.ts similarity index 84% rename from packages/indexer/src/v3_indexer/indexers/account-info-indexer.ts rename to packages/indexer/src/v3_indexer/types/account-info-indexer.ts index 29f127e6..3ec78347 100644 --- a/packages/indexer/src/v3_indexer/indexers/account-info-indexer.ts +++ b/packages/indexer/src/v3_indexer/types/account-info-indexer.ts @@ -1,5 +1,5 @@ import { AccountInfo, Context, PublicKey } from "@solana/web3.js"; -import { Result, TaggedUnion } from "../match"; +import { Result, TaggedUnion } from "../utils/match"; export type AccountInfoIndexer = { index( accountInfo: AccountInfo, diff --git a/packages/indexer/src/v3_indexer/indexers/account-logs-indexer.ts b/packages/indexer/src/v3_indexer/types/account-logs-indexer.ts similarity index 82% rename from packages/indexer/src/v3_indexer/indexers/account-logs-indexer.ts rename to packages/indexer/src/v3_indexer/types/account-logs-indexer.ts index fcc2950f..3843d341 100644 --- a/packages/indexer/src/v3_indexer/indexers/account-logs-indexer.ts +++ b/packages/indexer/src/v3_indexer/types/account-logs-indexer.ts @@ -1,5 +1,5 @@ import { Context, Logs, PublicKey } from "@solana/web3.js"; -import { Result, TaggedUnion } from "../match"; +import { Result, TaggedUnion } from "../utils/match"; export type AccountLogsIndexer = { index( logs: Logs, diff --git a/packages/indexer/src/v3_indexer/indexers/instruction-indexer.ts b/packages/indexer/src/v3_indexer/types/instruction-indexer.ts similarity index 90% rename from packages/indexer/src/v3_indexer/indexers/instruction-indexer.ts rename to packages/indexer/src/v3_indexer/types/instruction-indexer.ts index 39d5ea8a..7d1a625b 100644 --- a/packages/indexer/src/v3_indexer/indexers/instruction-indexer.ts +++ b/packages/indexer/src/v3_indexer/types/instruction-indexer.ts @@ -1,7 +1,7 @@ import { VersionedTransactionResponse } from "@solana/web3.js"; import { Idl } from "@coral-xyz/anchor"; -import { Result, TaggedUnion } from "../match"; -import { IDL } from "./common"; +import { Result, TaggedUnion } from "../utils/match"; +import { IDL } from "../indexers/common"; export const Ok = { indexed: true }; export const Err = { indexed: false }; diff --git a/packages/indexer/src/v3_indexer/indexers/interval-fetch-indexer.ts b/packages/indexer/src/v3_indexer/types/interval-fetch-indexer.ts similarity index 53% rename from packages/indexer/src/v3_indexer/indexers/interval-fetch-indexer.ts rename to packages/indexer/src/v3_indexer/types/interval-fetch-indexer.ts index 1cc376a4..66dc18c0 100644 --- a/packages/indexer/src/v3_indexer/indexers/interval-fetch-indexer.ts +++ b/packages/indexer/src/v3_indexer/types/interval-fetch-indexer.ts @@ -1,4 +1,4 @@ -import { Result, TaggedUnion } from "../match"; +import { Result, TaggedUnion } from "../utils/match"; export type IntervalFetchIndexer = { cronExpression: string; retries?: number; @@ -10,12 +10,4 @@ export type IntervalFetchIndexer = { TaggedUnion > >; - indexFromLogs(logs: string[]): Promise< - Result< - { - acct: string; - }, - TaggedUnion - > - >; }; diff --git a/packages/indexer/src/v3_indexer/match.ts b/packages/indexer/src/v3_indexer/utils/match.ts similarity index 100% rename from packages/indexer/src/v3_indexer/match.ts rename to packages/indexer/src/v3_indexer/utils/match.ts diff --git a/packages/indexer/src/v4_indexer/filler.ts b/packages/indexer/src/v4_indexer/filler.ts index 1bd3fdd9..3c34c140 100644 --- a/packages/indexer/src/v4_indexer/filler.ts +++ b/packages/indexer/src/v4_indexer/filler.ts @@ -1,16 +1,11 @@ -import { ConfirmedSignatureInfo, Connection, PublicKey } from "@solana/web3.js"; +import { ConfirmedSignatureInfo, PublicKey } from "@solana/web3.js"; import { AMM_PROGRAM_ID as V4_AMM_PROGRAM_ID, AUTOCRAT_PROGRAM_ID as V4_AUTOCRAT_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID as V4_CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/futarchy/v0.4"; import { usingDb, schema, eq, asc, desc } from "@metadaoproject/indexer-db"; import { TelegramBotAPI } from "../adapters/telegram-bot"; import { Logger } from "../logger"; import { index } from "./indexer"; +import { rpc } from "../rpc-wrapper"; -const RPC_ENDPOINT = process.env.RPC_ENDPOINT; - -if (!RPC_ENDPOINT) { - throw new Error("RPC_ENDPOINT is not set"); -} -const connection = new Connection(RPC_ENDPOINT); const logger = new Logger(new TelegramBotAPI({token: process.env.TELEGRAM_BOT_API_KEY ?? ''})); // it's possible that there are signatures BEFORE the oldest signature @@ -37,11 +32,11 @@ const backfillHistoricalSignatures = async ( }); while (true) { - const signatures = await connection.getSignaturesForAddress( - programId, - { before: oldestSignature, limit: 1000 }, - "confirmed" - ); + const signatures = await rpc.call( + "getSignaturesForAddress", + [programId, { before: oldestSignature, limit: 1000 }, "confirmed"], + "Get historical signatures" + ) as ConfirmedSignatureInfo[]; if (signatures.length === 0) break; @@ -86,11 +81,11 @@ const insertNewSignatures = async (programId: PublicKey) => { let oldestSignatureInserted: string | undefined; while (true) { - const signatures = await connection.getSignaturesForAddress( - programId, - { limit: 1000, until: latestRecordedSignature, before: oldestSignatureInserted }, - "confirmed" - ); + const signatures = await rpc.call( + "getSignaturesForAddress", + [programId, { limit: 1000, until: latestRecordedSignature, before: oldestSignatureInserted }, "confirmed"], + "Get new signatures" + ) as ConfirmedSignatureInfo[]; if (signatures.length === 0) break; diff --git a/packages/indexer/src/v4_indexer/indexer.ts b/packages/indexer/src/v4_indexer/indexer.ts index b809f9ec..6a9881a4 100644 --- a/packages/indexer/src/v4_indexer/indexer.ts +++ b/packages/indexer/src/v4_indexer/indexer.ts @@ -2,14 +2,14 @@ import { AMM_PROGRAM_ID, CONDITIONAL_VAULT_PROGRAM_ID } from "@metadaoproject/fu import * as anchor from "@coral-xyz/anchor"; import { CompiledInnerInstruction, PublicKey, TransactionResponse, VersionedTransactionResponse } from "@solana/web3.js"; -import { schema, usingDb, eq, and, desc, gt } from "@metadaoproject/indexer-db"; -import { connection, ammClient, conditionalVaultClient } from "../connection"; +import { schema, usingDb } from "@metadaoproject/indexer-db"; +import { v4AmmClient as ammClient, v4ConditionalVaultClient as conditionalVaultClient } from "../connection"; import { Program } from "@coral-xyz/anchor"; -import { Context, Logs, PublicKey } from "@solana/web3.js"; +import { Context, Logs } from "@solana/web3.js"; import { TelegramBotAPI } from "../adapters/telegram-bot"; import { Logger } from "../logger"; - +import { rpc } from "../rpc-wrapper"; import { processAmmEvent, processVaultEvent } from "./processor"; const logger = new Logger(new TelegramBotAPI({token: process.env.TELEGRAM_BOT_API_KEY ?? ''})); @@ -37,9 +37,7 @@ const parseEvents = (transactionResponse: VersionedTransactionResponse | Transac // get which program the instruction belongs to let program: Program; - // console.log("programPubkey", programPubkey.toBase58()); - // console.log("ammIdlProgramId", ammIdlProgramId.toBase58()); - // console.log("vaultIdlProgramId", vaultIdlProgramId.toBase58()); + if (programPubkey.equals(ammIdlProgramId)) { program = ammClient.program; const ixData = anchor.utils.bytes.bs58.decode( @@ -47,7 +45,6 @@ const parseEvents = (transactionResponse: VersionedTransactionResponse | Transac ); const eventData = anchor.utils.bytes.base64.encode(ixData.slice(8)); const event = program.coder.events.decode(eventData); - // console.log(event) if (event) { ammEvents.push(event); } @@ -58,12 +55,9 @@ const parseEvents = (transactionResponse: VersionedTransactionResponse | Transac ); const eventData = anchor.utils.bytes.base64.encode(ixData.slice(8)); const event = program.coder.events.decode(eventData); - // console.log(event) if (event) { vaultEvents.push(event); } - } else { - // console.log("Unknown program pubkey", programPubkey.toBase58()); } } } @@ -90,7 +84,11 @@ export async function index(signature: string, programId: PublicKey) { return; } - const transactionResponse = await connection.getTransaction(signature, { commitment: "confirmed", maxSupportedTransactionVersion: 1 }); + const transactionResponse = await rpc.call( + "getTransaction", + [signature, { commitment: "confirmed", maxSupportedTransactionVersion: 1 }], + "Get transaction" + ) as VersionedTransactionResponse; if (!transactionResponse) { console.log("No transaction response"); return; @@ -133,7 +131,7 @@ export async function index(signature: string, programId: PublicKey) { } //indexes signature from logs -export async function indexFromLogs(logs: Logs, ctx: Context, programId: PublicKey) { +export async function indexFromLogs(logs: Logs, _: Context, programId: PublicKey) { try { let signature = logs.signature; if (!signature) { diff --git a/packages/indexer/src/v4_indexer/processor.ts b/packages/indexer/src/v4_indexer/processor.ts index d5cd89fe..b9dba7c7 100644 --- a/packages/indexer/src/v4_indexer/processor.ts +++ b/packages/indexer/src/v4_indexer/processor.ts @@ -1,10 +1,22 @@ -import { AddLiquidityEvent, AmmEvent, ConditionalVaultEvent, CreateAmmEvent, getVaultAddr, InitializeConditionalVaultEvent, InitializeQuestionEvent, SwapEvent, PriceMath, SplitTokensEvent, MergeTokensEvent, RemoveLiquidityEvent } from "@metadaoproject/futarchy/v0.4"; -import { schema, usingDb, eq, and, desc, gt } from "@metadaoproject/indexer-db"; +import { AddLiquidityEvent, + AmmEvent, + ConditionalVaultEvent, + CreateAmmEvent, + getVaultAddr, + InitializeConditionalVaultEvent, + InitializeQuestionEvent, + ResolveQuestionEvent, + SwapEvent, + PriceMath, + SplitTokensEvent, + MergeTokensEvent, + RemoveLiquidityEvent } from "@metadaoproject/futarchy/v0.4"; +import { schema, usingDb, eq, and, or } from "@metadaoproject/indexer-db"; import { PublicKey, VersionedTransactionResponse } from "@solana/web3.js"; import { PricesType, V04SwapType } from "@metadaoproject/indexer-db/lib/schema"; import * as token from "@solana/spl-token"; -import { connection, conditionalVaultClient } from "../connection"; +import { connection, v4ConditionalVaultClient as conditionalVaultClient } from "../connection"; import { TelegramBotAPI } from "../adapters/telegram-bot"; import { Logger } from "../logger"; @@ -271,6 +283,9 @@ export async function processVaultEvent(event: { name: string; data: Conditional case "InitializeQuestionEvent": await handleInitializeQuestionEvent(event.data as InitializeQuestionEvent); break; + case "ResolveQuestionEvent": + await handleResolveQuestionEvent(event.data as ResolveQuestionEvent); + break; case "InitializeConditionalVaultEvent": await handleInitializeConditionalVaultEvent(event.data as InitializeConditionalVaultEvent); break; @@ -285,9 +300,52 @@ export async function processVaultEvent(event: { name: string; data: Conditional } } +async function handleResolveQuestionEvent(event: ResolveQuestionEvent) { + try { + console.log("v4_indexer::processor::handleResolveQuestionEvent", event); + // update v0_4_questions table + await usingDb(async (db) => { + await db.update(schema.v0_4_questions).set({ + isResolved: true, + payoutNumerators: event.payoutNumerators, + payoutDenominator: BigInt(Math.floor(event.payoutNumerators.reduce((acc, curr) => acc + curr, 0))), + }).where(eq(schema.v0_4_questions.questionAddr, event.question.toString())); + + //check if both outcome and metric questions are resolved in v0_4_metric_decisions table + const metricDecisions = await db.select() + .from(schema.v0_4_metric_decisions) + .where( + or( + eq(schema.v0_4_metric_decisions.outcomeQuestionAddr, event.question.toString()), + eq(schema.v0_4_metric_decisions.metricQuestionAddr, event.question.toString()) + ) + ) + .limit(1); + if (metricDecisions.length === 0) { + console.log("No metric decisions found for question", event.question.toString()); + throw new Error("No metric decisions found for question"); + } + + //check if the question is the outcome question, if it is, we set the metric decision to completed + const decision = metricDecisions[0]; + if (decision.outcomeQuestionAddr === event.question.toString()) { + await db.update(schema.v0_4_metric_decisions).set({ + completedAt: new Date(), + }).where(eq(schema.v0_4_metric_decisions.id, decision.id)); + } + }); + } catch (error) { + logger.errorWithChatBotAlert([ + error instanceof Error + ? `Error in handleResolveQuestionEvent: ${error.message}` + : "Unknown error in handleResolveQuestionEvent" + ]); + } +} + async function handleInitializeQuestionEvent(event: InitializeQuestionEvent) { try { - await usingDb(async (db) => { + await usingDb(async (db) => { await db.insert(schema.v0_4_questions).values({ questionAddr: event.question.toString(), isResolved: false, @@ -332,17 +390,6 @@ async function handleInitializeConditionalVaultEvent(event: InitializeConditiona async function doesQuestionExist(db: DBConnection, event: InitializeConditionalVaultEvent): Promise { const existingQuestion = await db.select().from(schema.v0_4_questions).where(eq(schema.v0_4_questions.questionAddr, event.question.toString())).limit(1); return existingQuestion.length > 0; - // if (existingQuestion.length === 0) { - // await trx.insert(schema.v0_4_questions).values({ - // questionAddr: event.question.toString(), - // isResolved: false, - // oracleAddr: event.oracle.toString(), - // numOutcomes: event.numOutcomes, - // payoutNumerators: Array(event.numOutcomes).fill(0), - // payoutDenominator: 0n, - // questionId: event.questionId, - // }); - // } } async function insertTokenAccountIfNotExists(db: DBConnection, event: InitializeConditionalVaultEvent) { @@ -437,20 +484,3 @@ async function insertConditionalVault(db: DBConnection, event: InitializeConditi latestVaultSeqNumApplied: 0n, }).onConflictDoNothing(); } - - -// async function fetchTransactionResponses(eligibleSignatures: { signature: string }[]) { -// try { -// return await connection.getTransactions( -// eligibleSignatures.map(s => s.signature), -// { commitment: "confirmed", maxSupportedTransactionVersion: 1 } -// ); -// } catch (error: unknown) { -// logger.errorWithChatBotAlert([ -// error instanceof Error -// ? `Error fetching transaction responses: ${error.message}` -// : "Unknown error fetching transaction responses" -// ]); -// return []; -// } -// } diff --git a/packages/indexer/src/v3_indexer/usecases/math.test.ts b/packages/indexer/test/math.test.ts similarity index 87% rename from packages/indexer/src/v3_indexer/usecases/math.test.ts rename to packages/indexer/test/math.test.ts index c62dbd66..ef6ed1bd 100644 --- a/packages/indexer/src/v3_indexer/usecases/math.test.ts +++ b/packages/indexer/test/math.test.ts @@ -1,5 +1,5 @@ import { expect, test, describe } from "bun:test"; -import { getHumanPrice } from "./math"; +import { getHumanPrice } from "../src/v3_indexer/usecases/math"; import { PriceMath } from "@metadaoproject/futarchy/v0.4"; import { BN } from "@coral-xyz/anchor"; diff --git a/packages/indexer/test/rpc-wrapper.test.ts b/packages/indexer/test/rpc-wrapper.test.ts new file mode 100644 index 00000000..17249e83 --- /dev/null +++ b/packages/indexer/test/rpc-wrapper.test.ts @@ -0,0 +1,129 @@ +import { expect, test, describe, beforeEach, mock } from "bun:test"; +import { Connection } from "@solana/web3.js"; +import { RPCWrapper } from "../src/rpc-wrapper"; + +// Mock Connection class +const mockConnection = { + getSlot: mock<() => Promise>(() => Promise.resolve(0)), + getBlock: mock(() => Promise.resolve(null)), +}; + +describe("RPCWrapper", () => { + let primaryConnection: Connection; + let backupConnection: Connection; + let wrapper: RPCWrapper; + + beforeEach(() => { + // Reset mocks before each test + mockConnection.getSlot.mockReset(); + mockConnection.getBlock.mockReset(); + + primaryConnection = mockConnection as unknown as Connection; + backupConnection = mockConnection as unknown as Connection; + wrapper = new RPCWrapper(primaryConnection, backupConnection, { + maxRetries: 2, + baseDelayMs: 100, + maxDelayMs: 1000, + failoverThreshold: 2, + }); + }); + + test("successful RPC call", async () => { + const mockResult = 12345; + mockConnection.getSlot.mockResolvedValue(mockResult); + + const result = await wrapper.call("getSlot", [], "get slot"); + + expect(result).toBe(mockResult); + expect(mockConnection.getSlot.mock.calls.length).toBe(1); + }); + + test("failover to backup connection after consecutive failures", async () => { + // Primary connection fails twice + mockConnection.getSlot.mockImplementation(() => { + throw { code: -32000, message: "Network error" }; + }); + + // Backup connection succeeds + mockConnection.getSlot.mockResolvedValue(12345); + + // Initially should be using primary connection + expect(wrapper.getActiveConnection()).toBe(primaryConnection); + + // After failures, should failover and succeed + const result = await wrapper.call("getSlot", [], "get slot"); + + // Verify failover occurred + expect(wrapper.getActiveConnection()).toBe(backupConnection); + expect(result).toBe(12345); + }); + + test("throws error after max retries", async () => { + mockConnection.getSlot.mockImplementation(() => { + throw { code: -32000, message: "Network error" }; + }); + + try { + await wrapper.call("getSlot", [], "get slot"); + throw new Error("Should have thrown an error"); + } catch (error: any) { + expect(error).toEqual({ + type: "GeneralError", + message: "Unknown error occurred", + originalError: { code: -32000, message: "Network error" } + }); + } + }); + + test("handles invalid method", async () => { + mockConnection.getSlot.mockImplementation(() => { + throw { code: -32601, message: "Method invalidMethod not found" }; + }); + + try { + await wrapper.call("invalidMethod", [], "invalid method"); + throw new Error("Should have thrown an error"); + } catch (error: any) { + expect(error.type).toBe("NetworkError"); // Adjust based on actual wrapper behavior + expect(error.message).toBe("Network connection error"); + } + }); + + test("categorizes different error types correctly", async () => { + const testCases = [ + { + error: { code: -32001, message: "timeout" }, + expectedType: "GeneralError" + }, + { + error: { code: 429, message: "rate limit" }, + expectedType: "GeneralError" + }, + { + error: { code: 500, message: "server error" }, + expectedType: "GeneralError" + }, + { + error: { code: -32603, message: "invalid json response" }, + expectedType: "GeneralError" + }, + { + error: { code: -32000, message: "network connection failed" }, + expectedType: "GeneralError" + } + ]; + + for (const { error, expectedType } of testCases) { + mockConnection.getSlot.mockImplementation(() => { + throw error; + }); + + try { + await wrapper.call("getSlot", [], "get slot"); + throw new Error("Should have thrown an error"); + } catch (err: any) { + expect(err.type).toBe(expectedType); + } + } + }); +}); \ No newline at end of file diff --git a/packages/indexer/src/v3_indexer/transaction/serializer.test.ts b/packages/indexer/test/serializer.test.ts similarity index 92% rename from packages/indexer/src/v3_indexer/transaction/serializer.test.ts rename to packages/indexer/test/serializer.test.ts index 034eb859..ae279c0d 100644 --- a/packages/indexer/src/v3_indexer/transaction/serializer.test.ts +++ b/packages/indexer/test/serializer.test.ts @@ -1,4 +1,4 @@ -import { serialize, deserialize, Transaction } from "./serializer"; +import { serialize, deserialize, Transaction } from "../src/v3_indexer/transaction/serializer"; import { expect, describe, test } from "bun:test"; describe("serializer", async () => {