|
| 1 | +"""Consolidate wrap schema into simplicity (extended_contracts, vaults, swap_positions, remaining_supply) |
| 2 | +
|
| 3 | +Revision ID: 20251030_01 |
| 4 | +Revises: f0e124fbfbf0 |
| 5 | +Create Date: 2025-10-30 |
| 6 | +
|
| 7 | +""" |
| 8 | +from typing import Sequence, Union |
| 9 | + |
| 10 | +from alembic import op |
| 11 | +import sqlalchemy as sa |
| 12 | +from sqlalchemy.sql import text |
| 13 | + |
| 14 | + |
| 15 | +# revision identifiers, used by Alembic. |
| 16 | +revision: str = "20251030_01" |
| 17 | +down_revision: Union[str, Sequence[str], None] = "f0e124fbfbf0" |
| 18 | +branch_labels: Union[str, Sequence[str], None] = None |
| 19 | +depends_on: Union[str, Sequence[str], None] = None |
| 20 | + |
| 21 | + |
| 22 | +def upgrade() -> None: |
| 23 | + # 1) deploys.remaining_supply (align with model) |
| 24 | + conn = op.get_bind() |
| 25 | + |
| 26 | + # Add column nullable first (if not exists) |
| 27 | + op.add_column( |
| 28 | + "deploys", |
| 29 | + sa.Column("remaining_supply", sa.Numeric(precision=38, scale=8), nullable=True), |
| 30 | + ) |
| 31 | + |
| 32 | + # Initialize values: set remaining_supply = max_supply |
| 33 | + conn.execute(text("UPDATE deploys SET remaining_supply = max_supply")) |
| 34 | + # Special case for W with max_supply = 0 → remaining_supply = 0 |
| 35 | + conn.execute(text("UPDATE deploys SET remaining_supply = 0 WHERE ticker = 'W' AND max_supply = 0")) |
| 36 | + # Set NOT NULL |
| 37 | + op.alter_column("deploys", "remaining_supply", nullable=False) |
| 38 | + |
| 39 | + # 2) extended_contracts |
| 40 | + op.create_table( |
| 41 | + "extended_contracts", |
| 42 | + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), |
| 43 | + sa.Column("script_address", sa.String(), nullable=False), |
| 44 | + sa.Column("initiator_address", sa.String(), nullable=False), |
| 45 | + sa.Column("status", sa.String(), nullable=False, server_default="active"), |
| 46 | + sa.Column("timelock_delay", sa.Integer(), nullable=False, comment="Timelock delay in blocks"), |
| 47 | + sa.Column("initial_amount", sa.Numeric(precision=38, scale=8), nullable=True, comment="Amount of tokens initially wrapped"), |
| 48 | + sa.Column("creation_txid", sa.String(), nullable=False), |
| 49 | + sa.Column("creation_timestamp", sa.DateTime(), nullable=False), |
| 50 | + sa.Column("creation_height", sa.Integer(), nullable=False), |
| 51 | + sa.Column("internal_pubkey", sa.String(), nullable=True), |
| 52 | + sa.Column("tapscript_hex", sa.Text(), nullable=True), |
| 53 | + sa.Column("merkle_root", sa.String(), nullable=True), |
| 54 | + sa.Column("closure_txid", sa.String(), nullable=True, comment="Transaction ID that closed the contract"), |
| 55 | + sa.Column("closure_timestamp", sa.DateTime(), nullable=True, comment="Block timestamp when contract was closed"), |
| 56 | + sa.Column("closure_height", sa.Integer(), nullable=True, comment="Block height when contract was closed"), |
| 57 | + sa.Column("extension_data", sa.Text(), nullable=True), |
| 58 | + sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 59 | + sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 60 | + sa.PrimaryKeyConstraint("id", name=op.f("extended_contracts_pkey")), |
| 61 | + ) |
| 62 | + op.create_index(op.f("ix_extended_contracts_script_address"), "extended_contracts", ["script_address"], unique=True) |
| 63 | + op.create_index(op.f("ix_extended_contracts_initiator_address"), "extended_contracts", ["initiator_address"], unique=False) |
| 64 | + op.create_index(op.f("ix_extended_contracts_creation_txid"), "extended_contracts", ["creation_txid"], unique=False) |
| 65 | + op.create_index(op.f("ix_extended_contracts_creation_height"), "extended_contracts", ["creation_height"], unique=False) |
| 66 | + |
| 67 | + # 3) vaults (Enum values in lowercase to align with models) |
| 68 | + vaultstatus = sa.Enum("active", "abandoned", "recycled", "sovereign_recovery", "closed", name="vaultstatus") |
| 69 | + vaultstatus.create(op.get_bind(), checkfirst=True) |
| 70 | + |
| 71 | + op.create_table( |
| 72 | + "vaults", |
| 73 | + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), |
| 74 | + sa.Column("vault_type", sa.String(), nullable=False, comment="Defines the type of contract, allowing for future protocol extensions."), |
| 75 | + sa.Column("status", vaultstatus, nullable=False, comment="The current state of the game for this vault."), |
| 76 | + sa.Column("p2tr_address", sa.String(), nullable=False, comment="The Taproot (P2TR) address encoding the contract's spend paths."), |
| 77 | + sa.Column("owner_address", sa.String(), nullable=False, comment="The address of the vault's sovereign owner."), |
| 78 | + sa.Column("collateral_amount_sats", sa.Numeric(precision=38, scale=0), nullable=False, comment="The amount of BTC collateral in satoshis."), |
| 79 | + sa.Column("remaining_blocks", sa.Integer(), nullable=True, comment="Countdown for the liquidation timelock."), |
| 80 | + sa.Column("w_proof_commitment", sa.String(), nullable=False, comment="Hash of the W_PROOF from the reveal transaction's witness."), |
| 81 | + sa.Column("reveal_operation_id", sa.Integer(), nullable=False), |
| 82 | + sa.Column("closing_operation_id", sa.Integer(), nullable=True), |
| 83 | + sa.Column("reveal_txid", sa.String(), nullable=False, comment="TXID of the transaction that locked the collateral."), |
| 84 | + sa.Column("reveal_block_height", sa.Integer(), nullable=False), |
| 85 | + sa.Column("reveal_timestamp", sa.DateTime(), nullable=False), |
| 86 | + sa.Column("closing_txid", sa.String(), nullable=True, comment="TXID of the transaction that unlocked the collateral."), |
| 87 | + sa.Column("closing_block_height", sa.Integer(), nullable=True), |
| 88 | + sa.Column("closing_timestamp", sa.DateTime(), nullable=True), |
| 89 | + sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 90 | + sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 91 | + sa.ForeignKeyConstraint(["reveal_operation_id"], ["brc20_operations.id"]), |
| 92 | + sa.ForeignKeyConstraint(["closing_operation_id"], ["brc20_operations.id"]), |
| 93 | + sa.PrimaryKeyConstraint("id"), |
| 94 | + ) |
| 95 | + op.create_index(op.f("ix_vaults_vault_type"), "vaults", ["vault_type"], unique=False) |
| 96 | + op.create_index(op.f("ix_vaults_status"), "vaults", ["status"], unique=False) |
| 97 | + op.create_index(op.f("ix_vaults_p2tr_address"), "vaults", ["p2tr_address"], unique=True) |
| 98 | + op.create_index(op.f("ix_vaults_owner_address"), "vaults", ["owner_address"], unique=False) |
| 99 | + op.create_index(op.f("ix_vaults_remaining_blocks"), "vaults", ["remaining_blocks"], unique=False) |
| 100 | + op.create_index(op.f("ix_vaults_reveal_block_height"), "vaults", ["reveal_block_height"], unique=False) |
| 101 | + op.create_index(op.f("ix_vaults_reveal_operation_id"), "vaults", ["reveal_operation_id"], unique=True) |
| 102 | + op.create_index(op.f("ix_vaults_reveal_txid"), "vaults", ["reveal_txid"], unique=True) |
| 103 | + op.create_index(op.f("ix_vaults_closing_operation_id"), "vaults", ["closing_operation_id"], unique=True) |
| 104 | + op.create_index(op.f("ix_vaults_closing_txid"), "vaults", ["closing_txid"], unique=True) |
| 105 | + op.create_index(op.f("ix_vaults_closing_block_height"), "vaults", ["closing_block_height"], unique=False) |
| 106 | + |
| 107 | + # 4) swap_positions (status as VARCHAR + CHECK to align with model native_enum=False) |
| 108 | + active_values = ("active", "expired", "closed") |
| 109 | + op.create_table( |
| 110 | + "swap_positions", |
| 111 | + sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True, nullable=False), |
| 112 | + sa.Column("owner_address", sa.String(), nullable=False), |
| 113 | + sa.Column("pool_id", sa.String(), nullable=False, comment="Canonical pair id (alphabetical)"), |
| 114 | + sa.Column("src_ticker", sa.String(), nullable=False), |
| 115 | + sa.Column("dst_ticker", sa.String(), nullable=False), |
| 116 | + sa.Column("amount_locked", sa.Numeric(precision=38, scale=8), nullable=False), |
| 117 | + sa.Column("lock_duration_blocks", sa.Integer(), nullable=False), |
| 118 | + sa.Column("lock_start_height", sa.Integer(), nullable=False), |
| 119 | + sa.Column("unlock_height", sa.Integer(), nullable=False), |
| 120 | + sa.Column("status", sa.String(), nullable=False, server_default="active"), |
| 121 | + sa.Column("init_operation_id", sa.Integer(), sa.ForeignKey("brc20_operations.id"), nullable=False, unique=True), |
| 122 | + sa.Column("closing_operation_id", sa.Integer(), sa.ForeignKey("brc20_operations.id"), nullable=True, unique=True), |
| 123 | + sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 124 | + sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.now()), |
| 125 | + sa.CheckConstraint(f"status in {active_values}", name="ck_swap_positions_status_values"), |
| 126 | + ) |
| 127 | + op.create_index("ix_swap_positions_owner_address", "swap_positions", ["owner_address"]) |
| 128 | + op.create_index("ix_swap_positions_pool_id", "swap_positions", ["pool_id"]) |
| 129 | + op.create_index("ix_swap_positions_src_ticker", "swap_positions", ["src_ticker"]) |
| 130 | + op.create_index("ix_swap_positions_dst_ticker", "swap_positions", ["dst_ticker"]) |
| 131 | + op.create_index("ix_swap_positions_lock_start_height", "swap_positions", ["lock_start_height"]) |
| 132 | + op.create_index("ix_swap_positions_unlock_height", "swap_positions", ["unlock_height"]) |
| 133 | + op.create_index("ix_swap_positions_status", "swap_positions", ["status"]) |
| 134 | + |
| 135 | + # Drop obsolete unique constraint if present (idempotent) |
| 136 | + try: |
| 137 | + op.drop_constraint("uq_swap_pos_owner_pool_unlock", "swap_positions", type_="unique") |
| 138 | + except Exception: |
| 139 | + pass |
| 140 | + |
| 141 | + |
| 142 | +def downgrade() -> None: |
| 143 | + # swap_positions |
| 144 | + op.drop_index("ix_swap_positions_status", table_name="swap_positions") |
| 145 | + op.drop_index("ix_swap_positions_unlock_height", table_name="swap_positions") |
| 146 | + op.drop_index("ix_swap_positions_lock_start_height", table_name="swap_positions") |
| 147 | + op.drop_index("ix_swap_positions_dst_ticker", table_name="swap_positions") |
| 148 | + op.drop_index("ix_swap_positions_src_ticker", table_name="swap_positions") |
| 149 | + op.drop_index("ix_swap_positions_pool_id", table_name="swap_positions") |
| 150 | + op.drop_index("ix_swap_positions_owner_address", table_name="swap_positions") |
| 151 | + op.drop_table("swap_positions") |
| 152 | + |
| 153 | + # vaults |
| 154 | + op.drop_index(op.f("ix_vaults_closing_block_height"), table_name="vaults") |
| 155 | + op.drop_index(op.f("ix_vaults_closing_txid"), table_name="vaults") |
| 156 | + op.drop_index(op.f("ix_vaults_closing_operation_id"), table_name="vaults") |
| 157 | + op.drop_index(op.f("ix_vaults_reveal_txid"), table_name="vaults") |
| 158 | + op.drop_index(op.f("ix_vaults_reveal_operation_id"), table_name="vaults") |
| 159 | + op.drop_index(op.f("ix_vaults_reveal_block_height"), table_name="vaults") |
| 160 | + op.drop_index(op.f("ix_vaults_remaining_blocks"), table_name="vaults") |
| 161 | + op.drop_index(op.f("ix_vaults_p2tr_address"), table_name="vaults") |
| 162 | + op.drop_index(op.f("ix_vaults_owner_address"), table_name="vaults") |
| 163 | + op.drop_index(op.f("ix_vaults_status"), table_name="vaults") |
| 164 | + op.drop_index(op.f("ix_vaults_vault_type"), table_name="vaults") |
| 165 | + op.drop_table("vaults") |
| 166 | + try: |
| 167 | + sa.Enum(name="vaultstatus").drop(op.get_bind(), checkfirst=True) |
| 168 | + except Exception: |
| 169 | + pass |
| 170 | + |
| 171 | + # extended_contracts |
| 172 | + op.drop_index(op.f("ix_extended_contracts_creation_height"), table_name="extended_contracts") |
| 173 | + op.drop_index(op.f("ix_extended_contracts_creation_txid"), table_name="extended_contracts") |
| 174 | + op.drop_index(op.f("ix_extended_contracts_initiator_address"), table_name="extended_contracts") |
| 175 | + op.drop_index(op.f("ix_extended_contracts_script_address"), table_name="extended_contracts") |
| 176 | + op.drop_table("extended_contracts") |
| 177 | + |
| 178 | + # deploys.remaining_supply |
| 179 | + op.drop_column("deploys", "remaining_supply") |
| 180 | + |
| 181 | + |
0 commit comments