Ctrl K
ComunidadGuías…

deploy/migrations/0095_star_gift_lifecycle.up.sql

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
-- Complete collectible Star Gift lifecycle: ownership state, transfer/resale, purchase
-- offers, crafting, auctions, notification preferences and the explicit TON boundary.

ALTER TABLE public.peer_star_gifts
    DROP CONSTRAINT IF EXISTS peer_star_gifts_terminal_state_check,
    ADD COLUMN lifecycle_status text DEFAULT 'active' NOT NULL,
    ADD COLUMN transfer_stars bigint DEFAULT 0 NOT NULL,
	ADD COLUMN prepaid_upgrade_hash text DEFAULT '' NOT NULL,
	ADD COLUMN gift_num integer DEFAULT 0 NOT NULL,
    ADD COLUMN can_export_at integer DEFAULT 0 NOT NULL,
    ADD COLUMN can_transfer_at integer DEFAULT 0 NOT NULL,
    ADD COLUMN can_resell_at integer DEFAULT 0 NOT NULL,
    ADD COLUMN drop_original_details_stars bigint DEFAULT 0 NOT NULL,
    ADD COLUMN can_craft_at integer DEFAULT 0 NOT NULL;

UPDATE public.peer_star_gifts SET lifecycle_status='converted' WHERE converted;

ALTER TABLE public.peer_star_gifts
    ADD CONSTRAINT peer_star_gifts_lifecycle_check CHECK (
        lifecycle_status IN ('active', 'converted', 'burned', 'exported') AND
        transfer_stars >= 0 AND gift_num >= 0 AND can_export_at >= 0 AND can_transfer_at >= 0 AND
        can_resell_at >= 0 AND drop_original_details_stars >= 0 AND can_craft_at >= 0 AND
        ((lifecycle_status='converted' AND converted AND unique_gift_id IS NULL) OR
         (lifecycle_status='active' AND NOT converted) OR
         (lifecycle_status IN ('burned','exported') AND NOT converted AND unique_gift_id IS NOT NULL))
    );

CREATE UNIQUE INDEX peer_star_gifts_prepaid_upgrade_hash_uniq
    ON public.peer_star_gifts(prepaid_upgrade_hash) WHERE prepaid_upgrade_hash<>'';

ALTER TABLE public.unique_star_gifts
    ALTER COLUMN owner_peer_type DROP NOT NULL,
    ALTER COLUMN owner_peer_id DROP NOT NULL,
    DROP CONSTRAINT IF EXISTS unique_star_gift_owner_check,
    ADD COLUMN require_premium boolean DEFAULT false NOT NULL,
    ADD COLUMN resale_ton_only boolean DEFAULT false NOT NULL,
    ADD COLUMN theme_available boolean DEFAULT false NOT NULL,
    ADD COLUMN burned boolean DEFAULT false NOT NULL,
    ADD COLUMN crafted boolean DEFAULT false NOT NULL,
    ADD COLUMN original_owner_peer_type text,
    ADD COLUMN original_owner_peer_id bigint,
    ADD COLUMN owner_name text DEFAULT '' NOT NULL,
    ADD COLUMN owner_address text DEFAULT '' NOT NULL,
    ADD COLUMN gift_address text DEFAULT '' NOT NULL,
    ADD COLUMN released_by_peer_type text,
    ADD COLUMN released_by_peer_id bigint,
    ADD COLUMN value_amount bigint DEFAULT 0 NOT NULL,
    ADD COLUMN value_currency text DEFAULT '' NOT NULL,
    ADD COLUMN value_usd_amount bigint DEFAULT 0 NOT NULL,
    ADD COLUMN theme_peer_type text,
    ADD COLUMN theme_peer_id bigint,
    ADD COLUMN host_peer_type text,
    ADD COLUMN host_peer_id bigint,
    ADD COLUMN offer_min_stars integer DEFAULT 0 NOT NULL,
    ADD COLUMN craft_chance_permille integer DEFAULT 0 NOT NULL,
    ADD COLUMN last_sale_date integer DEFAULT 0 NOT NULL,
    ADD COLUMN last_sale_currency text DEFAULT '' NOT NULL,
    ADD COLUMN last_sale_amount bigint DEFAULT 0 NOT NULL,
    ADD CONSTRAINT unique_star_gift_owner_check CHECK (
        (owner_peer_type IN ('user','channel') AND owner_peer_id > 0 AND owner_address='') OR
        (owner_peer_type IS NULL AND owner_peer_id IS NULL AND owner_address<>'')
    ),
    ADD CONSTRAINT unique_star_gift_released_by_check CHECK (
        (released_by_peer_type IS NULL AND released_by_peer_id IS NULL) OR
        (released_by_peer_type IN ('user','channel') AND released_by_peer_id > 0)
    ),
    ADD CONSTRAINT unique_star_gift_theme_peer_check CHECK (
        (theme_peer_type IS NULL AND theme_peer_id IS NULL) OR
        (theme_peer_type IN ('user','channel') AND theme_peer_id > 0)
    ),
    ADD CONSTRAINT unique_star_gift_host_peer_check CHECK (
        (host_peer_type IS NULL AND host_peer_id IS NULL) OR
        (host_peer_type IN ('user','channel') AND host_peer_id > 0)
    ),
    ADD CONSTRAINT unique_star_gift_value_check CHECK (
        value_amount >= 0 AND value_usd_amount >= 0 AND offer_min_stars >= 0 AND
        craft_chance_permille BETWEEN 0 AND 1000 AND last_sale_date >= 0 AND last_sale_amount >= 0 AND
        ((value_currency='' AND value_amount=0) OR value_currency<>'') AND
        ((last_sale_currency='' AND last_sale_amount=0 AND last_sale_date=0) OR
         (last_sale_currency IN ('XTR','TON') AND last_sale_amount>0 AND last_sale_date>0)) AND
        (NOT burned OR owner_address='') AND
        ((owner_address='' AND gift_address='') OR (owner_address<>'' AND gift_address<>''))
    );

UPDATE public.unique_star_gifts u
SET original_owner_peer_type=p.owner_peer_type, original_owner_peer_id=p.owner_peer_id
FROM public.peer_star_gifts p WHERE p.id=u.source_saved_gift_id;

ALTER TABLE public.unique_star_gifts
    ALTER COLUMN original_owner_peer_type SET NOT NULL,
    ALTER COLUMN original_owner_peer_id SET NOT NULL,
    ADD CONSTRAINT unique_star_gift_original_owner_check CHECK (
        original_owner_peer_type IN ('user','channel') AND original_owner_peer_id>0
    );

CREATE TABLE public.star_gift_listings (
    unique_gift_id bigint PRIMARY KEY REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    seller_peer_type text NOT NULL,
    seller_peer_id bigint NOT NULL,
    currency text NOT NULL,
    amount bigint NOT NULL,
    listed_at integer NOT NULL,
    updated_at integer NOT NULL,
    version bigint DEFAULT 1 NOT NULL,
    CONSTRAINT star_gift_listing_seller_check CHECK (seller_peer_type IN ('user','channel') AND seller_peer_id>0),
    CONSTRAINT star_gift_listing_amount_check CHECK (currency IN ('XTR','TON') AND amount>0 AND listed_at>0 AND updated_at>=listed_at)
);
CREATE INDEX star_gift_listings_gift_price_idx ON public.star_gift_listings(currency, amount, unique_gift_id);
CREATE INDEX star_gift_listings_updated_idx ON public.star_gift_listings(updated_at DESC, unique_gift_id DESC);

CREATE TABLE public.star_gift_sales (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    seller_peer_type text NOT NULL,
    seller_peer_id bigint NOT NULL,
    buyer_peer_type text NOT NULL,
    buyer_peer_id bigint NOT NULL,
    currency text NOT NULL,
    amount bigint NOT NULL,
    commission_amount bigint DEFAULT 0 NOT NULL,
    sold_at integer NOT NULL,
    command_key text NOT NULL,
    CONSTRAINT star_gift_sales_command_uniq UNIQUE(command_key),
    CONSTRAINT star_gift_sales_peer_check CHECK (
        seller_peer_type IN ('user','channel') AND seller_peer_id>0 AND
        buyer_peer_type IN ('user','channel') AND buyer_peer_id>0),
    CONSTRAINT star_gift_sales_amount_check CHECK (
        currency IN ('XTR','TON') AND amount>0 AND commission_amount>=0 AND commission_amount<=amount AND sold_at>0)
);
CREATE INDEX star_gift_sales_unique_date_idx ON public.star_gift_sales(unique_gift_id, sold_at DESC);

CREATE TABLE public.star_gift_offers (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    buyer_user_id bigint NOT NULL,
    owner_peer_type text NOT NULL,
    owner_peer_id bigint NOT NULL,
    unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    currency text NOT NULL,
    amount bigint NOT NULL,
    random_id bigint NOT NULL,
    offer_msg_id integer DEFAULT 0 NOT NULL,
    buyer_msg_id integer DEFAULT 0 NOT NULL,
    status text DEFAULT 'pending' NOT NULL,
    created_at integer NOT NULL,
    expires_at integer NOT NULL,
    resolved_at integer DEFAULT 0 NOT NULL,
    balance_after bigint DEFAULT 0 NOT NULL,
	expiry_notified boolean DEFAULT false NOT NULL,
    CONSTRAINT star_gift_offer_random_uniq UNIQUE(buyer_user_id, random_id),
    CONSTRAINT star_gift_offer_owner_msg_uniq UNIQUE(owner_peer_type, owner_peer_id, offer_msg_id),
    CONSTRAINT star_gift_offer_peer_check CHECK (buyer_user_id>0 AND owner_peer_type IN ('user','channel') AND owner_peer_id>0),
    CONSTRAINT star_gift_offer_amount_check CHECK (currency IN ('XTR','TON') AND amount>0),
    CONSTRAINT star_gift_offer_status_check CHECK (status IN ('pending','accepted','declined','expired','cancelled')),
    CONSTRAINT star_gift_offer_time_check CHECK (created_at>0 AND expires_at>created_at AND resolved_at>=0 AND
        ((status='pending' AND resolved_at=0) OR (status<>'pending' AND resolved_at>=created_at)))
);
CREATE INDEX star_gift_offers_pending_expiry_idx ON public.star_gift_offers(expires_at, id) WHERE status='pending';
CREATE INDEX star_gift_offers_unique_pending_idx ON public.star_gift_offers(unique_gift_id, id) WHERE status='pending';

CREATE TABLE public.star_gift_transfer_commands (
    actor_user_id bigint NOT NULL,
    command_key text NOT NULL,
    unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    from_peer_type text NOT NULL,
    from_peer_id bigint NOT NULL,
    to_peer_type text NOT NULL,
    to_peer_id bigint NOT NULL,
    charge_stars bigint DEFAULT 0 NOT NULL,
    balance_after bigint DEFAULT 0 NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_transfer_commands_pkey PRIMARY KEY(actor_user_id, command_key),
    CONSTRAINT star_gift_transfer_command_peer_check CHECK (
        actor_user_id>0 AND from_peer_type IN ('user','channel') AND from_peer_id>0 AND
        to_peer_type IN ('user','channel') AND to_peer_id>0 AND charge_stars>=0 AND created_at>0)
);

CREATE TABLE public.star_gift_purchase_commands (
    buyer_user_id bigint NOT NULL,
    command_key text NOT NULL,
    gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
    recipient_peer_type text NOT NULL,
    recipient_peer_id bigint NOT NULL,
    saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
    form_id bigint NOT NULL,
    charge_stars bigint NOT NULL,
    balance_after bigint NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_purchase_commands_pkey PRIMARY KEY(buyer_user_id,command_key),
    CONSTRAINT star_gift_purchase_commands_form_uniq UNIQUE(buyer_user_id,form_id),
    CONSTRAINT star_gift_purchase_command_shape_check CHECK (
        buyer_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
        form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);

CREATE TABLE public.star_gift_prepaid_upgrade_commands (
    payer_user_id bigint NOT NULL,
    command_key text NOT NULL,
    saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
    form_id bigint NOT NULL,
    charge_stars bigint NOT NULL,
    balance_after bigint NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_prepaid_upgrade_commands_pkey PRIMARY KEY(payer_user_id, command_key),
    CONSTRAINT star_gift_prepaid_upgrade_commands_form_uniq UNIQUE(payer_user_id, form_id),
    CONSTRAINT star_gift_prepaid_upgrade_command_shape_check CHECK (
        payer_user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);

CREATE TABLE public.star_gift_drop_details_commands (
    user_id bigint NOT NULL,
    command_key text NOT NULL,
    saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
    unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    form_id bigint NOT NULL,
    charge_stars bigint NOT NULL,
    balance_after bigint NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_drop_details_commands_pkey PRIMARY KEY(user_id, command_key),
    CONSTRAINT star_gift_drop_details_commands_form_uniq UNIQUE(user_id, form_id),
    CONSTRAINT star_gift_drop_details_command_shape_check CHECK (
        user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
);

CREATE TABLE public.star_gift_craft_commands (
    user_id bigint NOT NULL,
    command_key text NOT NULL,
    input_unique_gift_ids bigint[] NOT NULL,
    gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
    success boolean NOT NULL,
    result_unique_gift_id bigint REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    chance_permille integer NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_craft_commands_pkey PRIMARY KEY(user_id, command_key),
    CONSTRAINT star_gift_craft_shape_check CHECK (
        user_id>0 AND cardinality(input_unique_gift_ids) BETWEEN 1 AND 4 AND
        chance_permille BETWEEN 0 AND 1000 AND created_at>0 AND
        ((success AND result_unique_gift_id IS NOT NULL) OR (NOT success AND result_unique_gift_id IS NULL)))
);

CREATE TABLE public.star_gift_notification_settings (
    user_id bigint NOT NULL,
    channel_id bigint NOT NULL,
    enabled boolean DEFAULT true NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT star_gift_notification_settings_pkey PRIMARY KEY(user_id, channel_id),
    CONSTRAINT star_gift_notification_settings_peer_check CHECK (user_id>0 AND channel_id>0)
);

CREATE TABLE public.star_gift_withdrawal_requests (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
    owner_user_id bigint NOT NULL,
    provider text NOT NULL,
    provider_request_id text NOT NULL,
    url text NOT NULL,
    status text DEFAULT 'pending' NOT NULL,
    created_at integer NOT NULL,
    expires_at integer NOT NULL,
    completed_at integer DEFAULT 0 NOT NULL,
    CONSTRAINT star_gift_withdrawal_request_unique UNIQUE(unique_gift_id),
    CONSTRAINT star_gift_withdrawal_provider_request_uniq UNIQUE(provider, provider_request_id),
    CONSTRAINT star_gift_withdrawal_shape_check CHECK (
        owner_user_id>0 AND provider<>'' AND provider_request_id<>'' AND url<>'' AND
        status IN ('pending','completed','failed') AND created_at>0 AND expires_at>created_at AND completed_at>=0)
);

CREATE TABLE public.star_gift_auctions (
    gift_id bigint PRIMARY KEY REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
    slug text NOT NULL UNIQUE,
    version integer DEFAULT 1 NOT NULL,
    start_date integer NOT NULL,
    end_date integer NOT NULL,
    round_duration integer NOT NULL,
    gifts_per_round integer NOT NULL,
    total_rounds integer NOT NULL,
    current_round integer DEFAULT 0 NOT NULL,
    next_round_at integer NOT NULL,
    last_gift_num integer DEFAULT 0 NOT NULL,
    gifts_left integer NOT NULL,
    min_bid_amount bigint NOT NULL,
    status text DEFAULT 'pending' NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT star_gift_auction_shape_check CHECK (
        version>0 AND start_date>0 AND end_date>start_date AND round_duration>0 AND
        gifts_per_round>0 AND total_rounds>0 AND current_round BETWEEN 0 AND total_rounds AND
        next_round_at>=start_date AND last_gift_num>=0 AND gifts_left>=0 AND min_bid_amount>0 AND
        status IN ('pending','active','completed','cancelled'))
);

CREATE TABLE public.star_gift_auction_bids (
    gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
    bidder_user_id bigint NOT NULL,
    recipient_peer_type text NOT NULL,
    recipient_peer_id bigint NOT NULL,
    amount bigint NOT NULL,
    bid_date integer NOT NULL,
    hide_name boolean DEFAULT false NOT NULL,
    message text DEFAULT '' NOT NULL,
    returned boolean DEFAULT false NOT NULL,
    acquired_count integer DEFAULT 0 NOT NULL,
	active boolean DEFAULT true NOT NULL,
    version bigint DEFAULT 1 NOT NULL,
    CONSTRAINT star_gift_auction_bids_pkey PRIMARY KEY(gift_id, bidder_user_id),
    CONSTRAINT star_gift_auction_bid_peer_check CHECK (
        bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0),
    CONSTRAINT star_gift_auction_bid_amount_check CHECK (amount>0 AND bid_date>0 AND acquired_count>=0 AND version>0)
);
CREATE INDEX star_gift_auction_bids_rank_idx ON public.star_gift_auction_bids(gift_id, amount DESC, bid_date, bidder_user_id) WHERE active;

CREATE TABLE public.star_gift_auction_bid_payments (
    user_id bigint NOT NULL,
    form_id bigint NOT NULL,
    gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
    bid_amount bigint NOT NULL,
    balance_after bigint NOT NULL,
    created_at integer NOT NULL,
    CONSTRAINT star_gift_auction_bid_payments_pkey PRIMARY KEY(user_id, form_id),
    CONSTRAINT star_gift_auction_bid_payment_shape_check CHECK (
        user_id>0 AND form_id>0 AND bid_amount>0 AND balance_after>=0 AND created_at>0)
);

CREATE TABLE public.star_gift_auction_acquired (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
    bidder_user_id bigint NOT NULL,
    recipient_peer_type text NOT NULL,
    recipient_peer_id bigint NOT NULL,
    saved_gift_id bigint REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
    bid_amount bigint NOT NULL,
    round integer NOT NULL,
    pos integer NOT NULL,
    gift_num integer,
    acquired_at integer NOT NULL,
    hide_name boolean DEFAULT false NOT NULL,
    message text DEFAULT '' NOT NULL,
    CONSTRAINT star_gift_auction_acquired_round_pos_uniq UNIQUE(gift_id, round, pos),
    CONSTRAINT star_gift_auction_acquired_shape_check CHECK (
        bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
        bid_amount>0 AND round>0 AND pos>0 AND acquired_at>0 AND (gift_num IS NULL OR gift_num>0))
);
CREATE INDEX star_gift_auction_acquired_user_idx ON public.star_gift_auction_acquired(bidder_user_id, gift_id, id);

CREATE TABLE public.ton_balances (
    user_id bigint PRIMARY KEY,
    balance_nanoton bigint DEFAULT 0 NOT NULL,
    granted boolean DEFAULT false NOT NULL,
    updated_at timestamp with time zone DEFAULT now() NOT NULL,
    CONSTRAINT ton_balances_check CHECK (user_id>0 AND balance_nanoton>=0)
);

CREATE TABLE public.ton_transactions (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    user_id bigint NOT NULL,
    amount_nanoton bigint NOT NULL,
    reason text NOT NULL,
    peer_type text,
    peer_id bigint,
    gift_id bigint,
    date integer NOT NULL,
    CONSTRAINT ton_transaction_amount_check CHECK (user_id>0 AND amount_nanoton<>0 AND date>0),
    CONSTRAINT ton_transaction_peer_check CHECK (
        (peer_type IS NULL AND peer_id IS NULL) OR (peer_type IN ('user','channel') AND peer_id>0))
);
CREATE INDEX ton_transactions_user_idx ON public.ton_transactions(user_id, id DESC);

CREATE FUNCTION public.telesrv_guard_star_gift_listing() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
    gift_owner_type text;
    gift_owner_id bigint;
    gift_burned boolean;
BEGIN
    SELECT owner_peer_type, owner_peer_id, burned
      INTO gift_owner_type, gift_owner_id, gift_burned
      FROM public.unique_star_gifts WHERE id=NEW.unique_gift_id FOR SHARE;
    IF gift_burned OR gift_owner_type IS DISTINCT FROM NEW.seller_peer_type OR gift_owner_id IS DISTINCT FROM NEW.seller_peer_id THEN
        RAISE EXCEPTION 'star gift listing owner/state mismatch';
    END IF;
    RETURN NEW;
END;
$$;
CREATE TRIGGER star_gift_listing_guard BEFORE INSERT OR UPDATE ON public.star_gift_listings
    FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_star_gift_listing();

CREATE FUNCTION public.telesrv_check_unique_star_gift_owner() RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
    unique_id bigint;
    gift_owner_type text;
    gift_owner_id bigint;
    gift_owner_address text;
    gift_burned boolean;
    saved_status text;
    saved_owner_type text;
    saved_owner_id bigint;
BEGIN
    IF TG_TABLE_NAME = 'unique_star_gifts' THEN
        unique_id := COALESCE(NEW.id, OLD.id);
    ELSE
        unique_id := COALESCE(NEW.unique_gift_id, OLD.unique_gift_id);
    END IF;
    IF unique_id IS NULL THEN RETURN NULL; END IF;
    SELECT owner_peer_type, owner_peer_id, owner_address, burned
      INTO gift_owner_type, gift_owner_id, gift_owner_address, gift_burned
      FROM public.unique_star_gifts WHERE id=unique_id;
    IF NOT FOUND THEN RETURN NULL; END IF;
    SELECT lifecycle_status, owner_peer_type, owner_peer_id
      INTO saved_status, saved_owner_type, saved_owner_id
      FROM public.peer_star_gifts WHERE unique_gift_id=unique_id;
    IF NOT FOUND THEN RAISE EXCEPTION 'unique star gift missing saved aggregate'; END IF;
    IF gift_burned THEN
        IF saved_status <> 'burned' THEN RAISE EXCEPTION 'burned unique star gift has live saved aggregate'; END IF;
    ELSIF gift_owner_address <> '' THEN
        IF saved_status <> 'exported' THEN RAISE EXCEPTION 'exported unique star gift has non-exported saved aggregate'; END IF;
    ELSIF saved_status <> 'active' OR gift_owner_type IS DISTINCT FROM saved_owner_type OR gift_owner_id IS DISTINCT FROM saved_owner_id THEN
        RAISE EXCEPTION 'unique star gift owner mismatch';
    END IF;
    RETURN NULL;
END;
$$;
CREATE CONSTRAINT TRIGGER unique_star_gift_owner_guard
    AFTER INSERT OR UPDATE ON public.unique_star_gifts DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();
CREATE CONSTRAINT TRIGGER peer_unique_star_gift_owner_guard
    AFTER INSERT OR UPDATE ON public.peer_star_gifts DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW WHEN (NEW.unique_gift_id IS NOT NULL)
    EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();