create or replace function create_user( p_name varchar, -- p_discord_id numeric(20,0), p_wallet varchar(64), p_user_id uuid default null ) returns uuid as $$ declare new_uuid uuid; res_id integer; currency_start_amount float8; begin if p_user_id is null then insert into users(name, wallet) values (p_name, p_wallet) returning id into new_uuid; else new_uuid := p_user_id; insert into users(id, name, wallet) values (p_user_id, p_name, p_wallet); end if; select value into currency_start_amount from game_constants where key = 'MoonbucksStartAmount'; insert into bank_account(user_id, balance) values (new_uuid, currency_start_amount); insert into resource_account (resource_id, user_id, balance) select resource.id, new_uuid, 50 from resource left join lateral (values (id)) as subquery(value) on true; return new_uuid; end; $$ language plpgsql; create or replace function get_accounts(user_id uuid) returns table ( id uuid, "primaryBalance" float8, "resourceAccounts" json ) as $$ begin return query select bank_account.id, bank_account.balance, json_agg( json_build_object( 'id', resource_account.id, 'resourceType', resource.name, 'balance', resource_account.balance) order by resource.name) from bank_account join resource_account on bank_account.user_id = resource_account.user_id join resource on resource.id = resource_account.resource_id where bank_account.user_id = $1 group by bank_account.id; end; $$ language plpgsql; create or replace function sell_resources( p_user_id uuid, p_resources jsonb ) returns table ( "resourceName" text, "resourceAmount" float8, "returnAmount" float8, "saleResult" text ) as $$ declare resource jsonb; resource_name text; resource_amount float8; return_amount float8; resource_sell_factor real; sale_result text; begin for resource in select * from jsonb_array_elements(p_resources) loop resource_name := resource ->> 'resourceType'; resource_amount := (resource ->> 'resourceAmount')::float8; sale_result := null; begin update resource_account set balance = balance - resource_amount where user_id = p_user_id and resource_id = ( select id from resource where name = resource_name ); exception -- TODO: Test this out to see if it is properly returned when others then sale_result := 'Error: Insufficient amount'; end; if sale_result is null then select value into resource_sell_factor from game_constants where key = resource_name || 'ToMoonbucks'; update bank_account set balance = balance + resource_amount / resource_sell_factor where user_id = p_user_id returning resource_amount * resource_sell_factor into return_amount; sale_result := 'Success'; end if; return query select resource_name, resource_amount, return_amount, sale_result; end loop; end; $$ language plpgsql; create or replace function clear_user_data(p_user_id uuid) returns void as $$ begin delete from staking_source where user_id = p_user_id; delete from inventory_item where user_id = p_user_id; update bank_account set balance = 2000 where user_id = p_user_id; update resource_account set balance = 0 where user_id = p_user_id; end; $$ language plpgsql; create or replace function get_game_config() returns table ( resources json, "gameConstants" json, "stakingSources" json, "storeItems" json ) as $$ begin select json_agg(name) into resources from resource; select json_object_agg(key, value) into "gameConstants" from game_constants; select json_agg(staking_source_item) into "stakingSources" from staking_source_item; select json_agg( json_build_object( 'id', store_item.id, 'name', store_item.name, 'description', store_item.description, 'price', store_item.price, 'image', store_item.image_name, 'claimAmount', store_item.claim_amount, 'completionTimeInMins', store_item.completion_time_in_mins, 'upgrades', upgrades ) order by store_item.id ) into "storeItems" from store_item join ( select store_item_id, json_agg( json_build_object( 'tier', tier, 'price', upgrade_item.price, 'claimBoost', claim_boost ) order by upgrade_item.tier ) as upgrades from upgrade_item group by store_item_id ) u on u.store_item_id = store_item.id; return next; end; $$ language plpgsql; create or replace function get_staking_sources(user_id uuid) returns table ( id uuid, name varchar, description varchar, "imageName" text, "resourceWells" json, "activeStakes" json ) as $$ begin return query select staking_source.id as id, staking_source.name, staking_source.description, staking_source.image_name, json_agg( json_build_object( 'id', resource_well.id, 'resourceType', resource.name, 'supply', resource_well.supply ) ) as "resourceWells", COALESCE (json_agg( json_build_object( 'id', se.id, 'resourceType', resource.name, 'inventoryItemId', se.inventory_item_id, 'durationInMins', se.duration_in_mins, 'stakeAmount', se.stake_amount, 'startTime', se.created_at ) ) filter (where se.id is not null), '[]'::json) as "activeStakes" from staking_source inner join resource_well on resource_well.source_id = staking_source.id inner join resource on resource.id = resource_well.resource_id left join ( select staking_event.id, staking_event.well_id, staking_event.created_at, duration_in_mins, stake_amount, inventory_item_id from staking_event left join claim_event on claim_event.staking_event_id = staking_event.id where claim_event.staking_event_id is null ) se on se.well_id = resource_well.id where staking_source.user_id = $1 group by staking_source.id; end; $$ language plpgsql; create or replace function create_staking_source(p_user_id uuid) returns uuid as $$ declare source_item record; new_source_id uuid; res_id integer; address varchar; price float8; min_res float8; max_res float8; begin select value into price from game_constants where key = 'price'; begin update bank_account set balance = balance - price where user_id = p_user_id; exception when check_violation then raise exception 'Insufficient funds'; end; select * into source_item from staking_source_item order by random() limit 1; select substr(md5(random()::text), 1, 16) AS random_string into address; select value into min_res from game_constants where key = 'resourceMinStartAmount'; select value into max_res from game_constants where key = 'resourceMaxStartAmount'; insert into staking_source(user_id, name, description, image_name, address) values ( p_user_id, source_item.name, source_item.description, source_item.image_name, '0x' || address ) returning id into new_source_id; insert into resource_well (source_id, resource_id, supply) select new_source_id, resource.id, random_supply from resource, (select floor(random() * (max_res - min_res) + min_res) as random_supply) subquery left join lateral (values(id)) subquery2(value) on true; return new_source_id as result; end; $$ language plpgsql; create or replace function get_leaderboard(top_n integer) returns table ( id uuid, name varchar, wallet varchar, balance float8 ) as $$ begin return query select users.id, users.name, users.wallet, bank_account.balance from users join bank_account on bank_account.user_id = users.id order by balance desc limit top_n; end; $$ language plpgsql; create or replace function purchase_item( p_user_id uuid, p_store_item_id integer ) returns uuid as $$ declare new_item_id uuid; item_price float8; begin select price into item_price from store_item where store_item.id = p_store_item_id; begin update bank_account set balance = balance - item_price where user_id = p_user_id; exception when check_violation then raise exception 'Insufficient funds'; end; begin insert into inventory_item (user_id, store_item_id) values (p_user_id, p_store_item_id) returning id into new_item_id; exception when unique_violation then raise exception 'User already owns this weapons'; end; return new_item_id; end; $$ language plpgsql; create or replace function upgrade_item( p_user_id uuid, p_item_id uuid ) returns uuid as $$ declare upgrade_event_id uuid; item_insert_id uuid; upgrade_price float8; begin select upgrade_item.price into upgrade_price from inventory_item join store_item on inventory_item.store_item_id = store_item.id join upgrade_item on store_item.id = upgrade_item.store_item_id where inventory_item.id = p_item_id and upgrade_item.tier = inventory_item.tier + 1; if upgrade_price is null then raise exception 'Inventory item not found'; end if; begin update bank_account set balance = balance - upgrade_price where user_id = p_user_id; exception when check_violation then raise exception 'Insufficient funds'; end; update inventory_item set tier = tier + 1 where inventory_item.id = p_item_id and inventory_item.user_id = p_user_id returning id into item_insert_id; if item_insert_id is null then raise exception 'Inventory item does not belong to the user'; end if; insert into upgrade_event(inventory_item_id) values (p_item_id) returning id into upgrade_event_id; return upgrade_event_id; end; $$ language plpgsql; create or replace function stake( p_user_id uuid, p_well_id uuid, p_inventory_item_id uuid ) returns uuid as $$ declare staking_event_id uuid; item_stake_duration integer; stake_end_time timestamp; inv_id uuid; stake_id uuid; stake_created_at timestamp; total_stake_amount float8; found_resource_well_id uuid; begin select completion_time_in_mins into item_stake_duration from inventory_item join store_item on inventory_item.store_item_id = store_item.id where inventory_item.id = p_inventory_item_id; select inventory_item.id, staking_event.id, staking_event.created_at from inventory_item left join staking_event on staking_event.inventory_item_id = inventory_item.id where inventory_item.id = p_inventory_item_id and inventory_item.user_id = p_user_id order by stake_created_at desc limit 1 into inv_id, stake_id, stake_created_at; if inv_id is null then raise exception 'Unable to find an inventory item that belongs to the user'; end if; stake_end_time := stake_created_at + (item_stake_duration * interval '1 minute'); if stake_id is not null and now() at time zone 'utc' < stake_end_time then raise exception 'Unable to find an inventory item that is owned and not actively staked'; end if; select claim_amount into total_stake_amount from inventory_item join store_item on inventory_item.store_item_id = store_item.id left join upgrade_item on store_item.id = upgrade_item.store_item_id and upgrade_item.tier = inventory_item.tier where inventory_item.id = p_inventory_item_id; select resource_well.id into found_resource_well_id from resource_well join staking_source on source_id = staking_source.id where user_id = p_user_id and resource_well.id = p_well_id; if not found then raise exception 'Resource well does not belong to this user'; end if; insert into staking_event(user_id, well_id, inventory_item_id, duration_in_mins, stake_amount) values (p_user_id, p_well_id, p_inventory_item_id, item_stake_duration, total_stake_amount) returning id into staking_event_id; return staking_event_id; end; $$ language plpgsql; create or replace function get_all_stakes(p_user_id uuid) returns table ( id uuid, "sourceId" uuid, "wellId" uuid, "inventoryItemId" uuid, "resourceType" varchar, "stakeAmount" float8, "stakeTime" timestamp with time zone, "durationInMins" integer, unclaimed bool ) as $$ begin return query select staking_event.id, staking_source.id as sourceId, resource_well.id as wellId, inventory_item_id as inventoryItemId, resource.name as resourceType, stake_amount as stakeAmount, staking_event.created_at as stakeTime, duration_in_mins as durationInMins, case when claim_event.staking_event_id is null then true else false end as unclaimed from staking_event inner join resource_well on well_id = resource_well.id inner join resource on resource.id = resource_well.resource_id inner join staking_source on source_id = staking_source.id left join claim_event on staking_event.id = claim_event.staking_event_id where staking_event.user_id = p_user_id; end; $$ language plpgsql; create or replace function claim(p_user_id uuid, p_stake_id uuid) returns uuid as $$ declare stake record; claim_event_id uuid; stake_end_time timestamp; resource_supply float8; final_supply float8; begin select staking_event.id, resource.id as res_id, staking_event.user_id, well_id, inventory_item_id, duration_in_mins, stake_amount, created_at into stake from staking_event join resource_well on resource_well.id = well_id join resource on resource.id = resource_well.resource_id where staking_event.id = p_stake_id and staking_event.user_id = p_user_id; if stake is null then raise exception sqlstate '90001' using message = 'Staking event not found'; end if; if (select id from claim_event where staking_event_id = stake.id) is not null then raise exception sqlstate '90001' using message = 'Stake already claimed'; end if; stake_end_time := stake.created_at + (stake.duration_in_mins * interval '1 minute'); if now() at time zone 'utc' < stake_end_time then raise exception sqlstate '90001' using message = 'Stake has not finished, cannot claim'; end if; select supply from resource_well where id = stake.well_id into resource_supply; final_supply := case when resource_supply > stake.stake_amount then stake.stake_amount else resource_supply end; update resource_account set balance = balance + final_supply where user_id = p_user_id and resource_id = stake.res_id; update resource_well set supply = supply - final_supply where resource_id = stake.res_id; insert into claim_event(staking_event_id, claim_amount) values (stake.id, final_supply) returning id into claim_event_id; return claim_event_id; end; $$ language plpgsql;