Merge pull request #27 from exstake/add-stake-order-creation

Add stake order creation
This commit is contained in:
João Geonizeli
2021-08-17 20:29:45 -03:00
committed by GitHub
28 changed files with 1003 additions and 185 deletions

View File

@@ -0,0 +1,8 @@
# frozen_string_literal: true
module Inputs
class CreateStakeOrderAttributesInput < Types::BaseInputObject
argument :currency_id, ID, required: true
argument :pool_name, String, required: true
argument :amount, String, "Amount to be paid", required: true
end
end

View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
module Mutations
class CreateStakeOrder < BaseMutation
field :order, Types::StakeOrderType, null: true
argument :order, Inputs::CreateStakeOrderAttributesInput, required: true
def resolve(order:)
currency_id = decode_id(order[:currency_id])
amount = BigDecimal(order[:amount])
ActiveRecord::Base.transaction do
current_user
.balances
.find_by!(currency_id: currency_id)
.withdrawal!(amount)
record = StakeOrder.create!(
amount: amount,
currency_id: currency_id,
pool_name: order[:pool_name],
user_id: current_user.id,
)
{ order: record }
rescue ActiveRecord::RecordInvalid => e
{ errors: Resolvers::ModelErrors.from_active_record_model(e.record) }
end
end
end
end

View File

@@ -1,6 +1,7 @@
# frozen_string_literal: true
module Types
class MutationType < Types::BaseObject
field :create_stake_order, mutation: Mutations::CreateStakeOrder
field :create_sell_crypto_order, mutation: Mutations::CreateSellCryptoOrder
field :create_buy_crypto_order, mutation: Mutations::CreateBuyCryptoOrder
end

View File

@@ -144,6 +144,42 @@ type CreateSellCryptoOrderPayload {
order: SellCryptoOrder
}
input CreateStakeOrderAttributesInput {
"""
Amount to be paid
"""
amount: String!
currencyId: ID!
poolName: String!
}
"""
Autogenerated input type of CreateStakeOrder
"""
input CreateStakeOrderInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
order: CreateStakeOrderAttributesInput!
}
"""
Autogenerated return type of CreateStakeOrder
"""
type CreateStakeOrderPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [RecordInvalid!]
order: StakeOrder
}
type Currency implements Node {
id: ID!
name: String!
@@ -203,6 +239,12 @@ type Mutation {
"""
input: CreateSellCryptoOrderInput!
): CreateSellCryptoOrderPayload
createStakeOrder(
"""
Parameters for CreateStakeOrder
"""
input: CreateStakeOrderInput!
): CreateStakeOrderPayload
}
"""

View File

@@ -0,0 +1,73 @@
import type { FC } from "react";
import React, { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
type Props = {
isOpen: boolean;
setIsOpen: (state: boolean) => void;
title: string;
className?: string;
};
export const Modal: FC<Props> = ({
isOpen,
setIsOpen,
children,
title,
className = "",
}) => {
const closeModal = () => {
setIsOpen(false);
};
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog
open={isOpen}
as="div"
className={`fixed inset-0 z-10 overflow-y-auto ${className}`}
onClose={closeModal}
>
<div className="min-h-screen px-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-900 bg-opacity-50" />
</Transition.Child>
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900 mb-4"
>
{title}
</Dialog.Title>
<div className="mt-2">{children}</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
);
};

View File

@@ -0,0 +1 @@
export * from "./Modal";

View File

@@ -1,17 +0,0 @@
import React from "react";
import { pools } from "../constants/Pools";
import { Pool } from "./Pool";
export const PoolListing = () => {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 place-items-center w-full gap-8 py-4 -mt-16 overflow-x-hidden">
{pools
.filter((pool) => !pool.isFinished)
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
.map((pool) => (
<Pool key={pool.sousId} pool={pool} />
))}
</div>
);
};

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { Link } from "react-router-dom";
import { Transition } from "@headlessui/react";
import { useApp } from "../contexts/AppProvider";
import { useApp } from "../../contexts/AppProvider";
type MenuItem = {
label: string;

View File

@@ -0,0 +1 @@
export * from "./SideNav";

View File

@@ -1,2 +1,3 @@
export * from "./Navbar";
export * from "./SideNav";
export * from "./Modal";

View File

@@ -1,9 +1,9 @@
import type { FC } from "react";
import React from "react";
import { Container } from "../../components/Container";
import { Header } from "../../components/Header";
import { PoolListing } from "../../components/PoolListing";
import { Container } from "./Container";
import { Header } from "./Header";
import { PoolListing } from "./PoolListing";
export const Home: FC = () => {
return (

View File

@@ -1,16 +1,20 @@
import type { FC } from "react";
import React from "react";
import type { PoolConfig } from "../types";
import { useBsc } from "../contexts/BscProvider";
import { getPriceInBusd } from "../utils/getPrice";
import { getApr } from "../utils/apr";
import { getTotalStaked } from "../utils/getTotalStaked";
import type { PoolConfig } from "../../types";
import { useBsc } from "../../contexts/BscProvider";
import { getApr } from "../../utils/apr";
import { getPriceInBusd } from "../../utils/getPrice";
import { getTotalStaked } from "../../utils/getTotalStaked";
import { StakeOrderModal } from "./StakeOrderModal";
type PoolProps = {
pool: PoolConfig;
balance: string;
currencyId: string;
};
export const Pool = ({ pool }: PoolProps) => {
export const Pool: FC<PoolProps> = ({ pool, balance, currencyId }) => {
const {
provider,
pancake: { router },
@@ -77,12 +81,7 @@ export const Pool = ({ pool }: PoolProps) => {
/>
<div className="mt-4 p-2">
<p>
<span className="font-medium">Investir:</span>{" "}
{pool.stakingToken.symbol}
</p>
<p>
<span className="font-medium">Receber:</span>{" "}
{pool.earningToken.symbol}
<span className="font-medium">Pool:</span> {pool.earningToken.symbol}
</p>
<div className="flex items-center">
<span className="font-medium mr-1">Rendimento:</span>
@@ -92,6 +91,11 @@ export const Pool = ({ pool }: PoolProps) => {
`${apr.value}%`
)}
</div>
<StakeOrderModal
poolName={pool.earningToken.symbol}
balance={balance}
currencyId={currencyId}
/>
</div>
</div>
);

View File

@@ -0,0 +1,51 @@
import { graphql } from "babel-plugin-relay/macro";
import React from "react";
import { useLazyLoadQuery } from "react-relay";
import { pools } from "../../constants/Pools";
import { Pool } from "./Pool";
import type { PoolListingQuery } from "./__generated__/PoolListingQuery.graphql";
export const PoolListing = () => {
const { balances } = useLazyLoadQuery<PoolListingQuery>(
graphql`
query PoolListingQuery {
balances {
edges {
node {
currency {
id
name
}
amount
}
}
}
}
`,
{}
);
const cakeBalance = balances.edges.find(
(edge) => edge.node.currency.name === "CAKE"
)?.node;
const balance = cakeBalance?.amount ?? "0";
const currencyId = cakeBalance?.currency.id ?? "????";
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 place-items-center w-full gap-8 py-4 -mt-16 overflow-x-hidden">
{pools
.filter((pool) => !pool.isFinished)
.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
.map((pool) => (
<Pool
key={pool.sousId}
pool={pool}
balance={balance}
currencyId={currencyId}
/>
))}
</div>
);
};

View File

@@ -0,0 +1,110 @@
import type { ChangeEvent, FC } from "react";
import React, { useState } from "react";
import cx from "classnames";
import { BigNumber } from "bignumber.js";
import { useRelayEnvironment } from "react-relay";
import { Modal } from "../../../components";
import { commitCreateStakeOrderMutation } from "./createStakeOrder";
type Props = {
poolName: string;
balance: string;
currencyId: string;
};
const inputBaseStyles =
"rounded-lg border-transparent flex-1 appearance-none border border-gray-300 w-full py-2 px-4 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-blue-600 focus:border-transparent mb-3";
export const StakeOrderModal: FC<Props> = ({
poolName,
balance,
currencyId,
}) => {
const environment = useRelayEnvironment();
const [isOpen, setIsOpen] = useState(false);
const [investAmountInput, setInvestAmountInput] = useState("0");
const avaliableCake = new BigNumber(balance);
const investAmount = new BigNumber(investAmountInput);
const handleButtonClick = () => {
setIsOpen((prevState) => !prevState);
};
const onSubmit = () => {
commitCreateStakeOrderMutation(environment, {
currencyId,
amount: investAmountInput,
poolName,
});
};
const handleInvestInput = ({
currentTarget: { value },
}: ChangeEvent<HTMLInputElement>) => {
const newInvestAmount = new BigNumber(value);
if (
newInvestAmount.isLessThanOrEqualTo(avaliableCake) &&
newInvestAmount.isGreaterThanOrEqualTo(0)
) {
setInvestAmountInput(value);
}
};
const handleMaxButton = () => {
setInvestAmountInput(balance);
};
const stakeAvaliable =
avaliableCake.isGreaterThan(0) &&
avaliableCake.isLessThanOrEqualTo(investAmount);
return (
<div className="mt-4">
<button
onClick={handleButtonClick}
type="button"
className="py-2 px-4 text-blue-600 border-2 border-blue-600 hover:bg-blue-100 w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md rounded-lg "
>
Stake
</button>
<Modal
isOpen={isOpen}
setIsOpen={setIsOpen}
title={`Invista em ${poolName}`}
>
<span className="mb-2">CAKE disponível: {balance}</span>
<form onSubmit={onSubmit} className="bg-white py-2">
<div className="flex flex-row">
<input
className={cx(inputBaseStyles)}
type="number"
value={investAmountInput}
onChange={handleInvestInput}
/>
<button
type="button"
disabled={investAmountInput === balance}
className="flex items-center mb-3 ml-3 font-bold rounded-full text-red-500"
onClick={handleMaxButton}
>
Max
</button>
</div>
{avaliableCake.isEqualTo(0) && (
<span className="text-red-500 mb-1">Você não possuí saldo.</span>
)}
<button
className="cursor-pointer py-2 px-4 disabled:opacity-50 disabled:cursor-default bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 focus:ring-offset-blue-200 text-white w-full transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg"
disabled={!stakeAvaliable}
type="submit"
>
Fazer Stake
</button>
</form>
</Modal>
</div>
);
};

View File

@@ -0,0 +1,150 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from "relay-runtime";
export type createStakeOrderMutationVariables = {
currencyId: string;
poolName: string;
amount: string;
};
export type createStakeOrderMutationResponse = {
readonly createStakeOrder: {
readonly order: {
readonly id: string;
} | null;
} | null;
};
export type createStakeOrderMutation = {
readonly response: createStakeOrderMutationResponse;
readonly variables: createStakeOrderMutationVariables;
};
/*
mutation createStakeOrderMutation(
$currencyId: ID!
$poolName: String!
$amount: String!
) {
createStakeOrder(input: {order: {currencyId: $currencyId, poolName: $poolName, amount: $amount}}) {
order {
id
}
}
}
*/
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "amount"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "currencyId"
},
v2 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "poolName"
},
v3 = [
{
"alias": null,
"args": [
{
"fields": [
{
"fields": [
{
"kind": "Variable",
"name": "amount",
"variableName": "amount"
},
{
"kind": "Variable",
"name": "currencyId",
"variableName": "currencyId"
},
{
"kind": "Variable",
"name": "poolName",
"variableName": "poolName"
}
],
"kind": "ObjectValue",
"name": "order"
}
],
"kind": "ObjectValue",
"name": "input"
}
],
"concreteType": "CreateStakeOrderPayload",
"kind": "LinkedField",
"name": "createStakeOrder",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "StakeOrder",
"kind": "LinkedField",
"name": "order",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "createStakeOrderMutation",
"selections": (v3/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v2/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "createStakeOrderMutation",
"selections": (v3/*: any*/)
},
"params": {
"cacheID": "bfe4935c593947810fbb8d7a52421483",
"id": null,
"metadata": {},
"name": "createStakeOrderMutation",
"operationKind": "mutation",
"text": "mutation createStakeOrderMutation(\n $currencyId: ID!\n $poolName: String!\n $amount: String!\n) {\n createStakeOrder(input: {order: {currencyId: $currencyId, poolName: $poolName, amount: $amount}}) {\n order {\n id\n }\n }\n}\n"
}
};
})();
(node as any).hash = '036f321e28fcb4bd3e274498cd3f116a';
export default node;

View File

@@ -0,0 +1,39 @@
import { graphql } from "babel-plugin-relay/macro";
import type { Environment } from "react-relay";
import { commitMutation } from "react-relay";
import type { createStakeOrderMutationVariables } from "./__generated__/createStakeOrderMutation.graphql";
export const commitCreateStakeOrderMutation = (
environment: Environment,
variables: createStakeOrderMutationVariables
) => {
return commitMutation(environment, {
mutation: graphql`
mutation createStakeOrderMutation(
$currencyId: ID!
$poolName: String!
$amount: String!
) {
createStakeOrder(
input: {
order: {
currencyId: $currencyId
poolName: $poolName
amount: $amount
}
}
) {
order {
id
}
}
}
`,
variables: { ...variables },
onCompleted: (_response) => {
window.location.reload();
},
onError: (_error) => {},
});
};

View File

@@ -0,0 +1 @@
export * from "./StakeOrderModal";

View File

@@ -0,0 +1,179 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from "relay-runtime";
export type PoolListingQueryVariables = {};
export type PoolListingQueryResponse = {
readonly balances: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly currency: {
readonly id: string;
readonly name: string;
};
readonly amount: string;
};
}>;
};
};
export type PoolListingQuery = {
readonly response: PoolListingQueryResponse;
readonly variables: PoolListingQueryVariables;
};
/*
query PoolListingQuery {
balances {
edges {
node {
currency {
id
name
}
amount
id
}
}
}
}
*/
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"concreteType": "Currency",
"kind": "LinkedField",
"name": "currency",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
}
],
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "amount",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "PoolListingQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BalanceConnection",
"kind": "LinkedField",
"name": "balances",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BalanceEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Balance",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "PoolListingQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BalanceConnection",
"kind": "LinkedField",
"name": "balances",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "BalanceEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Balance",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
(v0/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "06c9467183eb0e89329ec630a8cc4880",
"id": null,
"metadata": {},
"name": "PoolListingQuery",
"operationKind": "query",
"text": "query PoolListingQuery {\n balances {\n edges {\n node {\n currency {\n id\n name\n }\n amount\n id\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = '570efc1d3b5dac09303b8692d6830bb2';
export default node;

View File

@@ -4,26 +4,30 @@
#
# Table name: stake_orders
#
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# currency_id :bigint
# user_id :bigint not null
#
# Indexes
#
# index_stake_orders_on_user_id (user_id)
# index_stake_orders_on_currency_id (currency_id)
# index_stake_orders_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (currency_id => currencies.id)
# fk_rails_... (user_id => users.id)
#
class StakeOrder < ApplicationRecord
include Processable
belongs_to :user
belongs_to :currency
validates :pool_name, presence: true
validates :amount, presence: true

View File

@@ -0,0 +1,6 @@
# frozen_string_literal: true
class AddCurrencyToStakeOrder < ActiveRecord::Migration[6.1]
def change
add_reference(:stake_orders, :currency, foreign_key: true)
end
end

5
db/schema.rb generated
View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_08_16_032056) do
ActiveRecord::Schema.define(version: 2021_08_16_174637) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -111,6 +111,8 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do
t.decimal "amount", precision: 20, scale: 10, default: "0.0", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.bigint "currency_id"
t.index ["currency_id"], name: "index_stake_orders_on_currency_id"
t.index ["user_id"], name: "index_stake_orders_on_user_id"
end
@@ -145,6 +147,7 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do
add_foreign_key "fiat_balances", "users"
add_foreign_key "sell_crypto_orders", "currencies"
add_foreign_key "sell_crypto_orders", "users"
add_foreign_key "stake_orders", "currencies"
add_foreign_key "stake_orders", "users"
add_foreign_key "user_documents", "users"
end

View File

@@ -2,19 +2,11 @@
AdminUser.create!(email: "admin@example.com", password: "password")
user = User.create!(
Currency.create!(name: "CAKE")
User.create!(
first_name: "Test",
last_name: "User",
email: "user@example.com",
password: "password"
)
currency = Currency.create!(name: "CAKE")
Balance.create!(
user_id: user.id,
currency_id: currency.id,
amount: rand * 10000
)
FiatBalance.create!(user_id: user.id, amount_cents: 15000)

240
erd.svg
View File

@@ -4,180 +4,188 @@
<!-- Generated by graphviz version 2.48.0 (0)
-->
<!-- Title: XStake Pages: 1 -->
<svg width="426pt" height="712pt"
viewBox="0.00 0.00 425.60 712.10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 683.3)">
<svg width="426pt" height="724pt"
viewBox="0.00 0.00 425.60 724.10" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(28.8 695.3)">
<title>XStake</title>
<polygon fill="white" stroke="transparent" points="-28.8,28.8 -28.8,-683.3 396.8,-683.3 396.8,28.8 -28.8,28.8"/>
<text text-anchor="middle" x="184" y="-640.1" font-family="Arial Bold" font-size="13.00">XStake domain model</text>
<polygon fill="white" stroke="transparent" points="-28.8,28.8 -28.8,-695.3 396.8,-695.3 396.8,28.8 -28.8,28.8"/>
<text text-anchor="middle" x="184" y="-652.1" font-family="Arial Bold" font-size="13.00">XStake domain model</text>
<!-- m_AdminUser -->
<g id="node1" class="node">
<title>m_AdminUser</title>
<path fill="none" stroke="black" d="M12,-48C12,-48 150,-48 150,-48 156,-48 162,-54 162,-60 162,-60 162,-131 162,-131 162,-137 156,-143 150,-143 150,-143 12,-143 12,-143 6,-143 0,-137 0,-131 0,-131 0,-60 0,-60 0,-54 6,-48 12,-48"/>
<text text-anchor="start" x="49" y="-130.2" font-family="Arial Bold" font-size="11.00">AdminUser</text>
<polyline fill="none" stroke="black" points="0,-123 162,-123 "/>
<text text-anchor="start" x="7" y="-109.5" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-109.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-96.5" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-96.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-83.5" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-83.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-70.5" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-70.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-57.5" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-57.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<path fill="none" stroke="black" d="M12,-47C12,-47 150,-47 150,-47 156,-47 162,-53 162,-59 162,-59 162,-130 162,-130 162,-136 156,-142 150,-142 150,-142 12,-142 12,-142 6,-142 0,-136 0,-130 0,-130 0,-59 0,-59 0,-53 6,-47 12,-47"/>
<text text-anchor="start" x="49" y="-129.2" font-family="Arial Bold" font-size="11.00">AdminUser</text>
<polyline fill="none" stroke="black" points="0,-122 162,-122 "/>
<text text-anchor="start" x="7" y="-108.5" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-108.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-95.5" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-95.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-82.5" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-82.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-69.5" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-69.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-56.5" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-56.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_Balance -->
<g id="node2" class="node">
<title>m_Balance</title>
<path fill="none" stroke="black" d="M223,-548C223,-548 343,-548 343,-548 349,-548 355,-554 355,-560 355,-560 355,-605 355,-605 355,-611 349,-617 343,-617 343,-617 223,-617 223,-617 217,-617 211,-611 211,-605 211,-605 211,-560 211,-560 211,-554 217,-548 223,-548"/>
<text text-anchor="start" x="259.5" y="-604.2" font-family="Arial Bold" font-size="11.00">Balance</text>
<polyline fill="none" stroke="black" points="211,-597 355,-597 "/>
<text text-anchor="start" x="218" y="-583.5" font-family="Arial" font-size="10.00">amount </text>
<text text-anchor="start" x="254" y="-583.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="218" y="-570.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="272" y="-570.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="218" y="-557.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-557.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<path fill="none" stroke="black" d="M223,-560C223,-560 343,-560 343,-560 349,-560 355,-566 355,-572 355,-572 355,-617 355,-617 355,-623 349,-629 343,-629 343,-629 223,-629 223,-629 217,-629 211,-623 211,-617 211,-617 211,-572 211,-572 211,-566 217,-560 223,-560"/>
<text text-anchor="start" x="259.5" y="-616.2" font-family="Arial Bold" font-size="11.00">Balance</text>
<polyline fill="none" stroke="black" points="211,-609 355,-609 "/>
<text text-anchor="start" x="218" y="-595.5" font-family="Arial" font-size="10.00">amount </text>
<text text-anchor="start" x="254" y="-595.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="218" y="-582.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="272" y="-582.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="218" y="-569.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-569.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_BuyCryptoOrder -->
<g id="node3" class="node">
<title>m_BuyCryptoOrder</title>
<path fill="none" stroke="black" d="M210,-423C210,-423 356,-423 356,-423 362,-423 368,-429 368,-435 368,-435 368,-506 368,-506 368,-512 362,-518 356,-518 356,-518 210,-518 210,-518 204,-518 198,-512 198,-506 198,-506 198,-435 198,-435 198,-429 204,-423 210,-423"/>
<text text-anchor="start" x="237" y="-505.2" font-family="Arial Bold" font-size="11.00">BuyCryptoOrder</text>
<polyline fill="none" stroke="black" points="198,-498 368,-498 "/>
<text text-anchor="start" x="205" y="-484.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="259" y="-484.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="205" y="-471.5" font-family="Arial" font-size="10.00">paid_amount_cents </text>
<text text-anchor="start" x="293" y="-471.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="205" y="-458.5" font-family="Arial" font-size="10.00">received_amount </text>
<text text-anchor="start" x="283" y="-458.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="205" y="-445.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="236" y="-445.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="205" y="-432.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="240" y="-432.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<path fill="none" stroke="black" d="M210,-435C210,-435 356,-435 356,-435 362,-435 368,-441 368,-447 368,-447 368,-518 368,-518 368,-524 362,-530 356,-530 356,-530 210,-530 210,-530 204,-530 198,-524 198,-518 198,-518 198,-447 198,-447 198,-441 204,-435 210,-435"/>
<text text-anchor="start" x="237" y="-517.2" font-family="Arial Bold" font-size="11.00">BuyCryptoOrder</text>
<polyline fill="none" stroke="black" points="198,-510 368,-510 "/>
<text text-anchor="start" x="205" y="-496.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="259" y="-496.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="205" y="-483.5" font-family="Arial" font-size="10.00">paid_amount_cents </text>
<text text-anchor="start" x="293" y="-483.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="205" y="-470.5" font-family="Arial" font-size="10.00">received_amount </text>
<text text-anchor="start" x="283" y="-470.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="205" y="-457.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="236" y="-457.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="205" y="-444.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="240" y="-444.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_Currency -->
<g id="node4" class="node">
<title>m_Currency</title>
<path fill="none" stroke="black" d="M21,-449C21,-449 141,-449 141,-449 147,-449 153,-455 153,-461 153,-461 153,-480 153,-480 153,-486 147,-492 141,-492 141,-492 21,-492 21,-492 15,-492 9,-486 9,-480 9,-480 9,-461 9,-461 9,-455 15,-449 21,-449"/>
<text text-anchor="start" x="54.5" y="-479.2" font-family="Arial Bold" font-size="11.00">Currency</text>
<polyline fill="none" stroke="black" points="9,-472 153,-472 "/>
<text text-anchor="start" x="16" y="-458.5" font-family="Arial" font-size="10.00">name </text>
<text text-anchor="start" x="44" y="-458.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<path fill="none" stroke="black" d="M21,-398C21,-398 141,-398 141,-398 147,-398 153,-404 153,-410 153,-410 153,-429 153,-429 153,-435 147,-441 141,-441 141,-441 21,-441 21,-441 15,-441 9,-435 9,-429 9,-429 9,-410 9,-410 9,-404 15,-398 21,-398"/>
<text text-anchor="start" x="54.5" y="-428.2" font-family="Arial Bold" font-size="11.00">Currency</text>
<polyline fill="none" stroke="black" points="9,-421 153,-421 "/>
<text text-anchor="start" x="16" y="-407.5" font-family="Arial" font-size="10.00">name </text>
<text text-anchor="start" x="44" y="-407.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_Currency&#45;&gt;m_Balance -->
<g id="edge9" class="edge">
<title>m_Currency&#45;&gt;m_Balance</title>
<path fill="none" stroke="black" d="M120.56,-492.09C146.51,-506.62 181.46,-526.2 212.21,-543.42"/>
<polygon fill="black" stroke="black" points="210.95,-546.32 220.34,-547.97 214.03,-540.83 210.95,-546.32"/>
<path fill="none" stroke="black" d="M98.77,-441.41C119.93,-468.12 158.38,-513.63 198,-545.5 201.86,-548.61 205.95,-551.65 210.15,-554.6"/>
<polygon fill="black" stroke="black" points="208.6,-557.35 217.82,-559.79 212.14,-552.14 208.6,-557.35"/>
</g>
<!-- m_Currency&#45;&gt;m_BuyCryptoOrder -->
<g id="edge6" class="edge">
<title>m_Currency&#45;&gt;m_BuyCryptoOrder</title>
<path fill="none" stroke="black" d="M153.08,-470.5C164.62,-470.5 176.72,-470.5 188.7,-470.5"/>
<polygon fill="black" stroke="black" points="188.82,-473.65 197.82,-470.5 188.82,-467.35 188.82,-473.65"/>
<path fill="none" stroke="black" d="M150.27,-441C162.68,-444.91 175.82,-449.05 188.81,-453.14"/>
<polygon fill="black" stroke="black" points="188.23,-456.27 197.76,-455.97 190.13,-450.26 188.23,-456.27"/>
</g>
<!-- m_SellCryptoOrder -->
<g id="node6" class="node">
<title>m_SellCryptoOrder</title>
<path fill="none" stroke="black" d="M214.5,-298C214.5,-298 351.5,-298 351.5,-298 357.5,-298 363.5,-304 363.5,-310 363.5,-310 363.5,-381 363.5,-381 363.5,-387 357.5,-393 351.5,-393 351.5,-393 214.5,-393 214.5,-393 208.5,-393 202.5,-387 202.5,-381 202.5,-381 202.5,-310 202.5,-310 202.5,-304 208.5,-298 214.5,-298"/>
<text text-anchor="start" x="238" y="-380.2" font-family="Arial Bold" font-size="11.00">SellCryptoOrder</text>
<polyline fill="none" stroke="black" points="202.5,-373 363.5,-373 "/>
<text text-anchor="start" x="210" y="-359.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="264" y="-359.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="210" y="-346.5" font-family="Arial" font-size="10.00">paid_amount </text>
<text text-anchor="start" x="269" y="-346.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="210" y="-333.5" font-family="Arial" font-size="10.00">received_amount_cents </text>
<text text-anchor="start" x="317" y="-333.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="210" y="-320.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="241" y="-320.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="210" y="-307.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="245" y="-307.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<path fill="none" stroke="black" d="M214.5,-310C214.5,-310 351.5,-310 351.5,-310 357.5,-310 363.5,-316 363.5,-322 363.5,-322 363.5,-393 363.5,-393 363.5,-399 357.5,-405 351.5,-405 351.5,-405 214.5,-405 214.5,-405 208.5,-405 202.5,-399 202.5,-393 202.5,-393 202.5,-322 202.5,-322 202.5,-316 208.5,-310 214.5,-310"/>
<text text-anchor="start" x="238" y="-392.2" font-family="Arial Bold" font-size="11.00">SellCryptoOrder</text>
<polyline fill="none" stroke="black" points="202.5,-385 363.5,-385 "/>
<text text-anchor="start" x="210" y="-371.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="264" y="-371.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="210" y="-358.5" font-family="Arial" font-size="10.00">paid_amount </text>
<text text-anchor="start" x="269" y="-358.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="210" y="-345.5" font-family="Arial" font-size="10.00">received_amount_cents </text>
<text text-anchor="start" x="317" y="-345.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="210" y="-332.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="241" y="-332.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="210" y="-319.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="245" y="-319.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_Currency&#45;&gt;m_SellCryptoOrder -->
<g id="edge8" class="edge">
<title>m_Currency&#45;&gt;m_SellCryptoOrder</title>
<path fill="none" stroke="black" d="M116.84,-448.73C139.24,-434.72 169.37,-415.89 197.77,-398.15"/>
<polygon fill="black" stroke="black" points="199.76,-400.62 205.72,-393.18 196.42,-395.27 199.76,-400.62"/>
</g>
<!-- m_FiatBalance -->
<g id="node5" class="node">
<title>m_FiatBalance</title>
<path fill="none" stroke="black" d="M223,-199C223,-199 343,-199 343,-199 349,-199 355,-205 355,-211 355,-211 355,-256 355,-256 355,-262 349,-268 343,-268 343,-268 223,-268 223,-268 217,-268 211,-262 211,-256 211,-256 211,-211 211,-211 211,-205 217,-199 223,-199"/>
<text text-anchor="start" x="250" y="-255.2" font-family="Arial Bold" font-size="11.00">FiatBalance</text>
<polyline fill="none" stroke="black" points="211,-248 355,-248 "/>
<text text-anchor="start" x="218" y="-234.5" font-family="Arial" font-size="10.00">amount_cents </text>
<text text-anchor="start" x="283" y="-234.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="218" y="-221.5" font-family="Arial" font-size="10.00">amount_currency </text>
<text text-anchor="start" x="297" y="-221.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-208.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-208.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<path fill="none" stroke="black" d="M151.39,-397.99C165.03,-393.76 179.51,-389.27 193.68,-384.88"/>
<polygon fill="black" stroke="black" points="194.78,-387.84 202.45,-382.16 192.92,-381.82 194.78,-387.84"/>
</g>
<!-- m_StakeOrder -->
<g id="node7" class="node">
<title>m_StakeOrder</title>
<path fill="none" stroke="black" d="M223,-86.5C223,-86.5 343,-86.5 343,-86.5 349,-86.5 355,-92.5 355,-98.5 355,-98.5 355,-156.5 355,-156.5 355,-162.5 349,-168.5 343,-168.5 343,-168.5 223,-168.5 223,-168.5 217,-168.5 211,-162.5 211,-156.5 211,-156.5 211,-98.5 211,-98.5 211,-92.5 217,-86.5 223,-86.5"/>
<text text-anchor="start" x="251" y="-155.7" font-family="Arial Bold" font-size="11.00">StakeOrder</text>
<polyline fill="none" stroke="black" points="211,-148.5 355,-148.5 "/>
<text text-anchor="start" x="218" y="-135.5" font-family="Arial" font-size="10.00">amount </text>
<text text-anchor="start" x="254" y="-135.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="218" y="-122.5" font-family="Arial" font-size="10.00">pool_name </text>
<text text-anchor="start" x="269" y="-122.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-109.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="249" y="-109.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-96.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-96.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<path fill="none" stroke="black" d="M223,-185C223,-185 343,-185 343,-185 349,-185 355,-191 355,-197 355,-197 355,-268 355,-268 355,-274 349,-280 343,-280 343,-280 223,-280 223,-280 217,-280 211,-274 211,-268 211,-268 211,-197 211,-197 211,-191 217,-185 223,-185"/>
<text text-anchor="start" x="251" y="-267.2" font-family="Arial Bold" font-size="11.00">StakeOrder</text>
<polyline fill="none" stroke="black" points="211,-260 355,-260 "/>
<text text-anchor="start" x="218" y="-246.5" font-family="Arial" font-size="10.00">amount </text>
<text text-anchor="start" x="254" y="-246.5" font-family="Arial Italic" font-size="10.00" fill="#999999">decimal (20,10)</text>
<text text-anchor="start" x="218" y="-233.5" font-family="Arial" font-size="10.00">currency_id </text>
<text text-anchor="start" x="272" y="-233.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
<text text-anchor="start" x="218" y="-220.5" font-family="Arial" font-size="10.00">pool_name </text>
<text text-anchor="start" x="269" y="-220.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-207.5" font-family="Arial" font-size="10.00">status </text>
<text text-anchor="start" x="249" y="-207.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-194.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-194.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_Currency&#45;&gt;m_StakeOrder -->
<g id="edge10" class="edge">
<title>m_Currency&#45;&gt;m_StakeOrder</title>
<path fill="none" stroke="black" d="M100.24,-397.8C122.24,-372.15 160.91,-328.76 198,-295.5 201.47,-292.38 205.09,-289.26 208.79,-286.17"/>
<polygon fill="black" stroke="black" points="211.04,-288.4 215.99,-280.25 207.04,-283.53 211.04,-288.4"/>
</g>
<!-- m_FiatBalance -->
<g id="node5" class="node">
<title>m_FiatBalance</title>
<path fill="none" stroke="black" d="M223,-86C223,-86 343,-86 343,-86 349,-86 355,-92 355,-98 355,-98 355,-143 355,-143 355,-149 349,-155 343,-155 343,-155 223,-155 223,-155 217,-155 211,-149 211,-143 211,-143 211,-98 211,-98 211,-92 217,-86 223,-86"/>
<text text-anchor="start" x="250" y="-142.2" font-family="Arial Bold" font-size="11.00">FiatBalance</text>
<polyline fill="none" stroke="black" points="211,-135 355,-135 "/>
<text text-anchor="start" x="218" y="-121.5" font-family="Arial" font-size="10.00">amount_cents </text>
<text text-anchor="start" x="283" y="-121.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer</text>
<text text-anchor="start" x="218" y="-108.5" font-family="Arial" font-size="10.00">amount_currency </text>
<text text-anchor="start" x="297" y="-108.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="218" y="-95.5" font-family="Arial" font-size="10.00">user_id </text>
<text text-anchor="start" x="253" y="-95.5" font-family="Arial Italic" font-size="10.00" fill="#999999">integer (8) FK</text>
</g>
<!-- m_User -->
<g id="node8" class="node">
<title>m_User</title>
<path fill="none" stroke="black" d="M12,-173C12,-173 150,-173 150,-173 156,-173 162,-179 162,-185 162,-185 162,-282 162,-282 162,-288 156,-294 150,-294 150,-294 12,-294 12,-294 6,-294 0,-288 0,-282 0,-282 0,-185 0,-185 0,-179 6,-173 12,-173"/>
<text text-anchor="start" x="66.5" y="-281.2" font-family="Arial Bold" font-size="11.00">User</text>
<polyline fill="none" stroke="black" points="0,-274 162,-274 "/>
<text text-anchor="start" x="7" y="-260.5" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-260.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-247.5" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-247.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-234.5" font-family="Arial" font-size="10.00">first_name </text>
<text text-anchor="start" x="56" y="-234.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-221.5" font-family="Arial" font-size="10.00">last_name </text>
<text text-anchor="start" x="56" y="-221.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-208.5" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-208.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-195.5" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-195.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-182.5" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-182.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<path fill="none" stroke="black" d="M12,-172C12,-172 150,-172 150,-172 156,-172 162,-178 162,-184 162,-184 162,-281 162,-281 162,-287 156,-293 150,-293 150,-293 12,-293 12,-293 6,-293 0,-287 0,-281 0,-281 0,-184 0,-184 0,-178 6,-172 12,-172"/>
<text text-anchor="start" x="66.5" y="-280.2" font-family="Arial Bold" font-size="11.00">User</text>
<polyline fill="none" stroke="black" points="0,-273 162,-273 "/>
<text text-anchor="start" x="7" y="-259.5" font-family="Arial" font-size="10.00">email </text>
<text text-anchor="start" x="34" y="-259.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string U</text>
<text text-anchor="start" x="7" y="-246.5" font-family="Arial" font-size="10.00">encrypted_password </text>
<text text-anchor="start" x="101" y="-246.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-233.5" font-family="Arial" font-size="10.00">first_name </text>
<text text-anchor="start" x="56" y="-233.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-220.5" font-family="Arial" font-size="10.00">last_name </text>
<text text-anchor="start" x="56" y="-220.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
<text text-anchor="start" x="7" y="-207.5" font-family="Arial" font-size="10.00">remember_created_at </text>
<text text-anchor="start" x="105" y="-207.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-194.5" font-family="Arial" font-size="10.00">reset_password_sent_at </text>
<text text-anchor="start" x="117" y="-194.5" font-family="Arial Italic" font-size="10.00" fill="#999999">datetime</text>
<text text-anchor="start" x="7" y="-181.5" font-family="Arial" font-size="10.00">reset_password_token </text>
<text text-anchor="start" x="109" y="-181.5" font-family="Arial Italic" font-size="10.00" fill="#999999">string</text>
</g>
<!-- m_User&#45;&gt;m_Balance -->
<g id="edge3" class="edge">
<title>m_User&#45;&gt;m_Balance</title>
<path fill="none" stroke="black" d="M106.63,-294.01C122.7,-333.81 143.96,-387.11 162,-434.5 178.66,-478.25 167.99,-497.56 198,-533.5 200.43,-536.42 203.09,-539.2 205.91,-541.85"/>
<polygon fill="black" stroke="black" points="203.89,-544.27 212.76,-547.79 208.02,-539.51 203.89,-544.27"/>
<path fill="none" stroke="black" d="M118.97,-293.21C134.07,-320.11 150.62,-352.6 162,-383.5 187.49,-452.71 154.51,-485.93 198,-545.5 200.01,-548.25 202.23,-550.88 204.6,-553.37"/>
<polygon fill="black" stroke="black" points="202.54,-555.76 211.23,-559.69 206.89,-551.2 202.54,-555.76"/>
</g>
<!-- m_User&#45;&gt;m_BuyCryptoOrder -->
<g id="edge5" class="edge">
<title>m_User&#45;&gt;m_BuyCryptoOrder</title>
<path fill="none" stroke="black" d="M115.16,-294.4C136.17,-329.91 165.48,-374.29 198,-408.5 200.6,-411.23 203.33,-413.93 206.16,-416.59"/>
<polygon fill="black" stroke="black" points="204.11,-418.98 212.9,-422.68 208.34,-414.31 204.11,-418.98"/>
<path fill="none" stroke="black" d="M111.75,-293.26C132.6,-332.2 162.94,-382.4 198,-420.5 200.55,-423.28 203.25,-426.01 206.04,-428.69"/>
<polygon fill="black" stroke="black" points="203.97,-431.07 212.73,-434.85 208.24,-426.43 203.97,-431.07"/>
</g>
<!-- m_User&#45;&gt;m_FiatBalance -->
<g id="edge4" class="edge">
<title>m_User&#45;&gt;m_FiatBalance</title>
<path fill="none" stroke="black" d="M162.2,-233.5C178.28,-233.5 195.08,-233.5 210.83,-233.5"/>
<path fill="none" stroke="black" d="M162.2,-187.59C181.54,-176.76 201.91,-165.35 220.26,-155.07"/>
</g>
<!-- m_User&#45;&gt;m_SellCryptoOrder -->
<g id="edge7" class="edge">
<title>m_User&#45;&gt;m_SellCryptoOrder</title>
<path fill="none" stroke="black" d="M162.2,-278.41C172.76,-284.33 183.63,-290.41 194.32,-296.4"/>
<polygon fill="black" stroke="black" points="193.05,-299.3 202.44,-300.95 196.12,-293.8 193.05,-299.3"/>
<path fill="none" stroke="black" d="M162.2,-282.62C173.96,-289.98 186.11,-297.57 197.95,-304.97"/>
<polygon fill="black" stroke="black" points="196.38,-307.7 205.68,-309.8 199.72,-302.36 196.38,-307.7"/>
</g>
<!-- m_User&#45;&gt;m_StakeOrder -->
<g id="edge2" class="edge">
<title>m_User&#45;&gt;m_StakeOrder</title>
<path fill="none" stroke="black" d="M162.2,-191C175.51,-183.94 189.32,-176.62 202.62,-169.57"/>
<polygon fill="black" stroke="black" points="204.35,-172.22 210.83,-165.22 201.4,-166.65 204.35,-172.22"/>
<path fill="none" stroke="black" d="M162.2,-232.5C175.14,-232.5 188.54,-232.5 201.49,-232.5"/>
<polygon fill="black" stroke="black" points="201.83,-235.65 210.83,-232.5 201.83,-229.35 201.83,-235.65"/>
</g>
<!-- m_UserDocument -->
<g id="node9" class="node">
@@ -193,8 +201,8 @@
<!-- m_User&#45;&gt;m_UserDocument -->
<g id="edge1" class="edge">
<title>m_User&#45;&gt;m_UserDocument</title>
<path fill="none" stroke="black" d="M150.99,-172.78C154.93,-168.13 158.64,-163.35 162,-158.5 185.8,-124.08 169.63,-102.26 198,-71.5 201.11,-68.12 204.55,-64.97 208.21,-62.04"/>
<polygon fill="black" stroke="black" points="210.12,-64.54 215.48,-56.65 206.37,-59.48 210.12,-64.54"/>
<path fill="none" stroke="black" d="M150.96,-171.77C154.91,-167.12 158.63,-162.35 162,-157.5 185.64,-123.47 169.85,-101.9 198,-71.5 201.12,-68.13 204.56,-64.98 208.22,-62.05"/>
<polygon fill="black" stroke="black" points="210.14,-64.56 215.5,-56.67 206.39,-59.49 210.14,-64.56"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -4,20 +4,23 @@
#
# Table name: stake_orders
#
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# currency_id :bigint
# user_id :bigint not null
#
# Indexes
#
# index_stake_orders_on_user_id (user_id)
# index_stake_orders_on_currency_id (currency_id)
# index_stake_orders_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (currency_id => currencies.id)
# fk_rails_... (user_id => users.id)
#
FactoryBot.define do

View File

@@ -0,0 +1,123 @@
# frozen_string_literal: true
require "rails_helper"
RSpec.describe(Mutations::CreateStakeOrder, type: :mutation) do
let(:query_string) do
<<~GQL
mutation(
$currencyId: ID!,
$amount: String!,
$poolName: String!,
) {
createStakeOrder(input: {
order: {
currencyId: $currencyId,
amount: $amount,
poolName: $poolName,
}
}) {
errors {
fullMessages
fieldName
messages
path
}
order {
poolName
status
amount
}
}
}
GQL
end
context "when the user has enough balance" do
it "withdraws from his account and creates a buy order" do
currency = create(:currency)
user = create(
:user,
balances: [
build(:balance, currency: currency, amount: 1.0034),
]
)
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
variables = {
"currencyId": currency_global_id,
"amount": "0.80",
"poolName": "CAKE/BNB",
"status": "PROCESSING",
}
context = { current_user: user }
result = XStakeSchema.execute(
query_string,
variables: variables,
context: context
).to_h.with_indifferent_access
expect(result).to(eq({
"data" => {
"createStakeOrder" => {
"errors" => nil,
"order" => {
"status" => "PROCESSING",
"amount" => "0.8",
"poolName" => "CAKE/BNB",
},
},
},
}))
expect(user.balances.first.reload.amount.to_s).to(eq("0.2034"))
end
end
context "when the user does not have enough balance" do
it "returns withdrawl error" do
currency = create(:currency)
user = create(
:user,
balances: [
build(:balance, currency: currency, amount: 0.0034),
]
)
currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id)
variables = {
"currencyId": currency_global_id,
"amount": "0.80",
"poolName": "CAKE/BNB",
}
context = { current_user: user }
result = XStakeSchema.execute(
query_string,
variables: variables,
context: context
).to_h.with_indifferent_access
expect(result).to(eq({
"data" => {
"createStakeOrder" => {
"errors" => [{
"fullMessages" => ["Quantia saldo insuficiente"],
"fieldName" => "amount",
"messages" => ["saldo insuficiente"],
"path" => ["attributes", "amount"],
}],
"order" => nil,
},
},
}))
expect(user.balances.first.reload.amount.to_s).to(eq("0.0034"))
end
end
end

View File

@@ -4,20 +4,23 @@
#
# Table name: stake_orders
#
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# id :bigint not null, primary key
# amount :decimal(20, 10) default(0.0), not null
# pool_name :string not null
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# currency_id :bigint
# user_id :bigint not null
#
# Indexes
#
# index_stake_orders_on_user_id (user_id)
# index_stake_orders_on_currency_id (currency_id)
# index_stake_orders_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (currency_id => currencies.id)
# fk_rails_... (user_id => users.id)
#
require "rails_helper"
@@ -30,5 +33,6 @@ RSpec.describe(StakeOrder, type: :model) do
describe "associations" do
it { is_expected.to(belong_to(:user)) }
it { is_expected.to(belong_to(:currency)) }
end
end