Merge remote-tracking branch 'upstream/main' into automated-market-resolution

# Conflicts:
#	common/contract.ts
#	package.json
#	web/components/contract/quick-bet.tsx
#	web/components/outcome-label.tsx
#	web/components/resolution-panel.tsx
This commit is contained in:
Milli 2022-06-01 22:38:45 +02:00
commit 6cf574f33c
101 changed files with 8575 additions and 663 deletions

View File

@ -31,7 +31,7 @@ Operations with complicated contracts (e.g. buying shares) are provided in a sep
- `og-image/`: The OpenGraph image generator; creates the preview images shown on Twitter/social media.
Also: Our docs are currently in [a separate repo](https://github.com/manifoldmarkets/docs). TODO: move them into this monorepo.
- `docs/`: Manifold's public documentation that lives at https://docs.manifold.markets.
## Contributing

View File

@ -1,12 +1,12 @@
import { addCpmmLiquidity, getCpmmLiquidity } from './calculate-cpmm'
import { Binary, CPMM, FullContract } from './contract'
import { CPMMContract } from './contract'
import { LiquidityProvision } from './liquidity-provision'
import { User } from './user'
export const getNewLiquidityProvision = (
user: User,
amount: number,
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
newLiquidityProvisionId: string
) => {
const { pool, p, totalLiquidity } = contract

View File

@ -2,12 +2,10 @@ import { range } from 'lodash'
import { Bet, NumericBet } from './bet'
import { getDpmProbability, getValueFromBucket } from './calculate-dpm'
import {
Binary,
CPMM,
DPM,
FreeResponse,
FullContract,
Numeric,
CPMMBinaryContract,
DPMBinaryContract,
FreeResponseContract,
NumericContract,
} from './contract'
import { User } from './user'
import { LiquidityProvision } from './liquidity-provision'
@ -23,7 +21,7 @@ export const HOUSE_LIQUIDITY_PROVIDER_ID = 'IPTOzEqrpkWmEzh6hwvAyY9PqFb2' // @Ma
export function getCpmmInitialLiquidity(
providerId: string,
contract: FullContract<CPMM, Binary>,
contract: CPMMBinaryContract,
anteId: string,
amount: number
) {
@ -47,7 +45,7 @@ export function getCpmmInitialLiquidity(
export function getAnteBets(
creator: User,
contract: FullContract<DPM, Binary>,
contract: DPMBinaryContract,
yesAnteId: string,
noAnteId: string
) {
@ -89,7 +87,7 @@ export function getAnteBets(
export function getFreeAnswerAnte(
anteBettorId: string,
contract: FullContract<DPM, FreeResponse>,
contract: FreeResponseContract,
anteBetId: string
) {
const { totalBets, totalShares } = contract
@ -117,7 +115,7 @@ export function getFreeAnswerAnte(
export function getNumericAnte(
anteBettorId: string,
contract: FullContract<DPM, Numeric>,
contract: NumericContract,
ante: number,
newBetId: string
) {

View File

@ -1,6 +1,6 @@
import { sum, groupBy, mapValues, sumBy } from 'lodash'
import { Binary, CPMM, FullContract } from './contract'
import { CPMMContract } from './contract'
import { CREATOR_FEE, Fees, LIQUIDITY_FEE, noFees, PLATFORM_FEE } from './fees'
import { LiquidityProvision } from './liquidity-provision'
import { addObjects } from './util/object'
@ -14,7 +14,7 @@ export function getCpmmProbability(
}
export function getCpmmProbabilityAfterBetBeforeFees(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
outcome: string,
bet: number
) {
@ -31,7 +31,7 @@ export function getCpmmProbabilityAfterBetBeforeFees(
}
export function getCpmmOutcomeProbabilityAfterBet(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
outcome: string,
bet: number
) {
@ -59,7 +59,7 @@ function calculateCpmmShares(
}
export function getCpmmLiquidityFee(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
bet: number,
outcome: string
) {
@ -78,7 +78,7 @@ export function getCpmmLiquidityFee(
}
export function calculateCpmmSharesAfterFee(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
bet: number,
outcome: string
) {
@ -89,7 +89,7 @@ export function calculateCpmmSharesAfterFee(
}
export function calculateCpmmPurchase(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
bet: number,
outcome: string
) {
@ -133,7 +133,7 @@ function sellSharesK(
}
function calculateCpmmShareValue(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
shares: number,
outcome: 'YES' | 'NO'
) {
@ -168,7 +168,7 @@ function calculateCpmmShareValue(
}
export function calculateCpmmSale(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
shares: number,
outcome: string
) {
@ -222,7 +222,7 @@ export function calculateCpmmSale(
}
export function getCpmmProbabilityAfterSale(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
shares: number,
outcome: 'YES' | 'NO'
) {
@ -261,7 +261,7 @@ export function addCpmmLiquidity(
}
export function getCpmmLiquidityPoolWeights(
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
liquidities: LiquidityProvision[]
) {
const { p } = contract
@ -291,7 +291,7 @@ export function getCpmmLiquidityPoolWeights(
}
// export function removeCpmmLiquidity(
// contract: FullContract<CPMM, Binary>,
// contract: CPMMContract,
// liquidity: number
// ) {
// const { YES, NO } = contract.pool

View File

@ -1,13 +1,6 @@
import { cloneDeep, range, sum, sumBy, sortBy, mapValues } from 'lodash'
import { Bet, NumericBet } from './bet'
import {
Binary,
DPM,
FreeResponse,
FullContract,
Numeric,
NumericContract,
} from './contract'
import { DPMContract, DPMBinaryContract, NumericContract } from './contract'
import { DPM_FEES } from './fees'
import { normpdf } from '../common/util/math'
import { addObjects } from './util/object'
@ -202,7 +195,7 @@ export function calculateDpmRawShareValue(
}
export function calculateDpmMoneyRatio(
contract: FullContract<DPM, any>,
contract: DPMContract,
bet: Bet,
shareValue: number
) {
@ -228,10 +221,7 @@ export function calculateDpmMoneyRatio(
return actual / expected
}
export function calculateDpmShareValue(
contract: FullContract<DPM, any>,
bet: Bet
) {
export function calculateDpmShareValue(contract: DPMContract, bet: Bet) {
const { pool, totalShares } = contract
const { shares, outcome } = bet
@ -243,17 +233,14 @@ export function calculateDpmShareValue(
return adjShareValue
}
export function calculateDpmSaleAmount(
contract: FullContract<DPM, any>,
bet: Bet
) {
export function calculateDpmSaleAmount(contract: DPMContract, bet: Bet) {
const { amount } = bet
const winnings = calculateDpmShareValue(contract, bet)
return deductDpmFees(amount, winnings)
}
export function calculateDpmPayout(
contract: FullContract<DPM, any>,
contract: DPMContract,
bet: Bet,
outcome: string
) {
@ -263,10 +250,7 @@ export function calculateDpmPayout(
return calculateStandardDpmPayout(contract, bet, outcome)
}
export function calculateDpmCancelPayout(
contract: FullContract<DPM, any>,
bet: Bet
) {
export function calculateDpmCancelPayout(contract: DPMContract, bet: Bet) {
const { totalBets, pool } = contract
const betTotal = sum(Object.values(totalBets))
const poolTotal = sum(Object.values(pool))
@ -275,7 +259,7 @@ export function calculateDpmCancelPayout(
}
export function calculateStandardDpmPayout(
contract: FullContract<DPM, any>,
contract: DPMContract,
bet: Bet,
outcome: string
) {
@ -308,7 +292,7 @@ export function calculateStandardDpmPayout(
}
export function calculateDpmPayoutAfterCorrectBet(
contract: FullContract<DPM, any>,
contract: DPMContract,
bet: Bet
) {
const { totalShares, pool, totalBets, outcomeType } = contract
@ -338,13 +322,10 @@ export function calculateDpmPayoutAfterCorrectBet(
: outcomeType,
}
return calculateStandardDpmPayout(newContract, bet, outcome)
return calculateStandardDpmPayout(newContract as any, bet, outcome)
}
function calculateMktDpmPayout(
contract: FullContract<DPM, Binary | FreeResponse | Numeric>,
bet: Bet
) {
function calculateMktDpmPayout(contract: DPMContract, bet: Bet) {
if (contract.outcomeType === 'BINARY')
return calculateBinaryMktDpmPayout(contract, bet)
@ -389,10 +370,7 @@ function calculateMktDpmPayout(
return deductDpmFees(amount, winnings)
}
function calculateBinaryMktDpmPayout(
contract: FullContract<DPM, Binary>,
bet: Bet
) {
function calculateBinaryMktDpmPayout(contract: DPMBinaryContract, bet: Bet) {
const { resolutionProbability, totalShares, phantomShares } = contract
const p =
resolutionProbability !== undefined
@ -413,7 +391,7 @@ function calculateBinaryMktDpmPayout(
return deductDpmFees(amount, winnings)
}
export function resolvedDpmPayout(contract: FullContract<DPM, any>, bet: Bet) {
export function resolvedDpmPayout(contract: DPMContract, bet: Bet) {
if (contract.resolution)
return calculateDpmPayout(contract, bet, contract.resolution)
throw new Error('Contract was not resolved')

View File

@ -1,9 +1,9 @@
import { Bet } from './bet'
import { getProbability } from './calculate'
import { Binary, FixedPayouts, FullContract } from './contract'
import { CPMMContract } from './contract'
export function calculateFixedPayout(
contract: FullContract<FixedPayouts, Binary>,
contract: CPMMContract,
bet: Bet,
outcome: string
) {
@ -23,10 +23,7 @@ export function calculateStandardFixedPayout(bet: Bet, outcome: string) {
return shares
}
function calculateFixedMktPayout(
contract: FullContract<FixedPayouts, Binary>,
bet: Bet
) {
function calculateFixedMktPayout(contract: CPMMContract, bet: Bet) {
const { resolutionProbability } = contract
const p =
resolutionProbability !== undefined

View File

@ -18,24 +18,15 @@ import {
getDpmProbabilityAfterSale,
} from './calculate-dpm'
import { calculateFixedPayout } from './calculate-fixed-payouts'
import {
Binary,
Contract,
CPMM,
DPM,
FreeResponseContract,
FullContract,
} from './contract'
import { Contract, BinaryContract, FreeResponseContract } from './contract'
export function getProbability(contract: FullContract<DPM | CPMM, Binary>) {
export function getProbability(contract: BinaryContract) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbability(contract.pool, contract.p)
: getDpmProbability(contract.totalShares)
}
export function getInitialProbability(
contract: FullContract<DPM | CPMM, Binary>
) {
export function getInitialProbability(contract: BinaryContract) {
if (contract.initialProbability) return contract.initialProbability
if (contract.mechanism === 'dpm-2' || (contract as any).totalShares)
@ -59,11 +50,7 @@ export function getOutcomeProbabilityAfterBet(
bet: number
) {
return contract.mechanism === 'cpmm-1'
? getCpmmOutcomeProbabilityAfterBet(
contract as FullContract<CPMM, Binary>,
outcome,
bet
)
? getCpmmOutcomeProbabilityAfterBet(contract, outcome, bet)
: getDpmOutcomeProbabilityAfterBet(contract.totalShares, outcome, bet)
}
@ -73,11 +60,7 @@ export function calculateShares(
betChoice: string
) {
return contract.mechanism === 'cpmm-1'
? calculateCpmmSharesAfterFee(
contract as FullContract<CPMM, Binary>,
bet,
betChoice
)
? calculateCpmmSharesAfterFee(contract, bet, betChoice)
: calculateDpmShares(contract.totalShares, bet, betChoice)
}
@ -99,11 +82,7 @@ export function getProbabilityAfterSale(
shares: number
) {
return contract.mechanism === 'cpmm-1'
? getCpmmProbabilityAfterSale(
contract as FullContract<CPMM, Binary>,
shares,
outcome as 'YES' | 'NO'
)
? getCpmmProbabilityAfterSale(contract, shares, outcome as 'YES' | 'NO')
: getDpmProbabilityAfterSale(contract.totalShares, outcome, shares)
}

View File

@ -47,8 +47,10 @@ export const charities: Charity[] = [
website: 'https://funds.effectivealtruism.org/funds/far-future',
photo: 'https://i.imgur.com/C2qka9g.png',
preview:
'Positively influence the long-term trajectory of civilization by making grants that address global catastrophic risks.',
description: `The Fund has a broad remit to make grants that promote, implement and advocate for longtermist ideas. Many of our grants aim to address potential risks from advanced artificial intelligence and to build infrastructure and advocate for longtermist projects. However, we welcome applications related to long-term institutional reform or other global catastrophic risks (e.g., pandemics or nuclear conflict).
'The Long-Term Future Fund aims to improve the long-term trajectory of civilization by making grants that address global catastrophic risks.',
description: `The Long-Term Future Fund aims to positively influence the long-term trajectory of civilization by making grants that address global catastrophic risks, especially potential risks from advanced artificial intelligence and pandemics. In addition, we seek to promote, implement, and advocate for longtermist ideas, and to otherwise increase the likelihood that future generations will flourish.
The Fund has a broad remit to make grants that promote, implement and advocate for longtermist ideas. Many of our grants aim to address potential risks from advanced artificial intelligence and to build infrastructure and advocate for longtermist projects. However, we welcome applications related to long-term institutional reform or other global catastrophic risks (e.g., pandemics or nuclear conflict).
We intend to support:
- Projects that directly contribute to reducing existential risks through technical research, policy analysis, advocacy, and/or demonstration projects
@ -56,6 +58,53 @@ export const charities: Charity[] = [
- Promoting long-term thinking`,
tags: ['Featured'] as CharityTag[],
},
{
name: 'Global Health and Development Fund',
website: 'https://funds.effectivealtruism.org/funds/global-development',
photo: 'https://i.imgur.com/C2qka9g.png',
preview:
"The Global Health and Development Fund aims to improve people's lives, typically in the poorest regions of the world where the need for healthcare and economic empowerment is greatest.",
description: `The Global Health and Development Fund recommends grants with the aim of improving people's lives, typically in the poorest regions of the world where the need for healthcare and economic empowerment is greatest. This will be achieved primarily by supporting projects that:
- Directly provide healthcare, or preventive measures that will improve health, well-being, or life expectancy
- Directly provide services that raise incomes or otherwise improve economic conditions
- Provide assistance to governments in the design and implementation of effective policies
In addition, the Global Health and Development Fund has a broad remit, and may fund other activities whose ultimate purpose is to serve people living in the poorest regions of the world, for example by raising additional funds (e.g. One for the World) or by exploring novel financing arrangements (e.g. Instiglio).
The Fund manager recommends grants to GiveWell top charities as a baseline, but will recommend higher-risk grants they believe to be more effective (in expectation) than GiveWell top charities. As such, the fund makes grants with a variety of different risk profiles.`,
},
{
name: 'Animal Welfare Fund',
website: 'https://funds.effectivealtruism.org/funds/animal-welfare',
photo: 'https://i.imgur.com/C2qka9g.png',
preview:
'The Animal Welfare Fund aims to effectively improve the well-being of nonhuman animals.',
description: `The Animal Welfare Fund aims to effectively improve the well-being of nonhuman animals, by making grants that focus on one or more of the following:
- Relatively neglected geographic regions or groups of animals
- Promising research into animal advocacy or animal well-being
- Activities that could make it easier to help animals in the future
- Otherwise best-in-class opportunities
The Fund focuses on projects that primarily address farmed animals, as well as projects that could affect other large populations of nonhuman animals. Some examples of projects that the Fund could support:
- Supporting farmed animal advocacy in Asia
- Researching ways to improve the welfare of farmed fish
- Promoting alternative proteins in order to reduce demand for animal products
- Advocating against the use of some cruel practice within the industrial agriculture system
- Growing the field of welfare biology in order to improve our understanding of different ways to address wild animal suffering`,
},
{
name: 'Effective Altruism Infrastructure Fund',
website: 'https://funds.effectivealtruism.org/funds/ea-community',
photo: 'https://i.imgur.com/C2qka9g.png',
preview:
'The Effective Altruism Infrastructure Fund aims to increase the impact of projects that use the principles of effective altruism.',
description: `The Effective Altruism Infrastructure Fund (EA Infrastructure Fund) recommends grants that aim to improve the work of projects using principles of effective altruism, by increasing their access to talent, capital, and knowledge.
The EA Infrastructure Fund has historically attempted to make strategic grants to incubate and grow projects that attempt to use reason and evidence to do as much good as possible. These include meta-charities that fundraise for highly effective charities doing direct work on important problems, research organizations that improve our understanding of how to do good more effectively, and projects that promote principles of effective altruism in contexts like academia.`,
},
{
name: 'Nonlinear',
website: 'https://www.nonlinear.org/',
@ -104,6 +153,32 @@ export const charities: Charity[] = [
- Each charitys plans for additional funding
- The cost-effectiveness of each funding gap`,
},
{
name: "Founder's Pledge Climate Change Fund",
website: 'https://founderspledge.com/funds/climate-change-fund',
photo: 'https://i.imgur.com/ZAhzHu4.png',
preview:
'The Climate Change Fund aims to sustainably reach net-zero emissions globally, while still allowing growth to free millions from energy poverty.',
description: `The Climate Change Fund aims to sustainably reach net-zero emissions globally.
Current levels of emissions are contributing to millions of deaths annually from air pollution and causing irrevocable damage to our planet. In addition, millions worldwide do not have access to modern energy technology, severely hampering development goals.
This Fund is committed to finding and funding sustainable solutions to the emissions crisis that still allow growth, freeing millions from the prison of energy poverty.
The Fund is a philanthropic co-funding vehicle that does not provide investment returns.`,
},
{
name: "Founder's Pledge Patient Philanthropy Fund",
website: 'https://founderspledge.com/funds/patient-philanthropy-fund',
photo: 'https://i.imgur.com/ZAhzHu4.png',
preview:
'The Patient Philanthropy Project aims to safeguard and benefit the long-term future of humanity',
description: `The Patient Philanthropy Project focuses on how we can collectively grow our resources to support the long-term flourishing of humanity. It addresses a crucial gap: as a society, we spend much too little on safeguarding and benefiting future generations. In fact, we spend more money on ice cream each year than we do on preventing our own extinction. However, people in the future - who do not have a voice in their future survival or environment - matter. Lots of them may yet come into existence and we have the ability to positively affect their lives now, if only by making sure we avoid major catastrophes that could destroy our common future.
Housed within the Project is the Patient Philanthropy Fund, a philanthropic co-funding vehicle which invests to give and ensures capital is at the ready when extraordinary opportunities to safeguard and improve the long-term future arise.
The Funds patient approach means that we aim to identify the point in time when the highest-impact opportunities are available, which may be years, decades, or even centuries ahead.`,
},
{
name: 'ARC',
website: 'https://alignment.org/',

View File

@ -1,10 +1,15 @@
import { Answer } from './answer'
import { Fees } from './fees'
export type FullContract<
M extends DPM | CPMM,
T extends Binary | Multi | FreeResponse | Numeric
> = {
export type AnyMechanism = DPM | CPMM
export type AnyOutcomeType = Binary | FreeResponse | Numeric
export type AnyContractType =
| (CPMM & Binary)
| (DPM & Binary)
| (DPM & FreeResponse)
| (DPM & Numeric)
export type Contract<T extends AnyContractType = AnyContractType> = {
id: string
slug: string // auto-generated; must be unique
@ -37,16 +42,15 @@ export type FullContract<
volume7Days: number
collectedFees: Fees
} & M &
T
} & T
export type Contract = FullContract<
DPM | CPMM,
Binary | Multi | FreeResponse | Numeric
>
export type BinaryContract = FullContract<DPM | CPMM, Binary>
export type FreeResponseContract = FullContract<DPM | CPMM, FreeResponse>
export type NumericContract = FullContract<DPM, Numeric>
export type BinaryContract = Contract & Binary
export type NumericContract = Contract & Numeric
export type FreeResponseContract = Contract & FreeResponse
export type DPMContract = Contract & DPM
export type CPMMContract = Contract & CPMM
export type DPMBinaryContract = BinaryContract & DPM
export type CPMMBinaryContract = BinaryContract & CPMM
export type DPM = {
mechanism: 'dpm-2'
@ -64,8 +68,6 @@ export type CPMM = {
totalLiquidity: number // in M$
}
export type FixedPayouts = CPMM
export type Binary = {
outcomeType: 'BINARY'
initialProbability: number
@ -73,12 +75,6 @@ export type Binary = {
resolution?: resolution
}
export type Multi = {
outcomeType: 'MULTI'
multiOutcomes: string[] // Used for outcomeType 'MULTI'.
resolutions?: { [outcome: string]: number } // Used for MKT resolution.
}
export type FreeResponse = {
outcomeType: 'FREE_RESPONSE'
answers: Answer[] // Used for outcomeType 'FREE_RESPONSE'.
@ -96,13 +92,12 @@ export type Numeric = {
}
export type contractField = keyof Contract
export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE' | 'NUMERIC'
export type outcomeType = AnyOutcomeType['outcomeType']
export type resolution = 'YES' | 'NO' | 'MKT' | 'CANCEL'
export const RESOLUTIONS = [ 'YES', 'NO', 'MKT', 'CANCEL'] as const
export const OUTCOME_TYPES = [ 'BINARY', 'MULTI', 'FREE_RESPONSE', 'NUMERIC'] as const
export const RESOLUTION_TYPES = ['MANUAL', 'COMBINED'] as const
export const OUTCOME_TYPES = ['BINARY', 'FREE_RESPONSE', 'NUMERIC'] as const
export const MAX_QUESTION_LENGTH = 480
export const MAX_DESCRIPTION_LENGTH = 10000
export const MAX_TAG_LENGTH = 60

View File

@ -10,12 +10,9 @@ import {
} from './calculate-dpm'
import { calculateCpmmPurchase, getCpmmProbability } from './calculate-cpmm'
import {
Binary,
CPMM,
DPM,
FreeResponse,
FullContract,
Multi,
CPMMBinaryContract,
DPMBinaryContract,
FreeResponseContract,
NumericContract,
} from './contract'
import { noFees } from './fees'
@ -35,7 +32,7 @@ export type BetInfo = {
export const getNewBinaryCpmmBetInfo = (
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<CPMM, Binary>,
contract: CPMMBinaryContract,
loanAmount: number
) => {
const { shares, newPool, newP, fees } = calculateCpmmPurchase(
@ -69,7 +66,7 @@ export const getNewBinaryCpmmBetInfo = (
export const getNewBinaryDpmBetInfo = (
outcome: 'YES' | 'NO',
amount: number,
contract: FullContract<DPM, Binary>,
contract: DPMBinaryContract,
loanAmount: number
) => {
const { YES: yesPool, NO: noPool } = contract.pool
@ -116,7 +113,7 @@ export const getNewBinaryDpmBetInfo = (
export const getNewMultiBetInfo = (
outcome: string,
amount: number,
contract: FullContract<DPM, Multi | FreeResponse>,
contract: FreeResponseContract,
loanAmount: number
) => {
const { pool, totalShares, totalBets } = contract

29
common/notification.ts Normal file
View File

@ -0,0 +1,29 @@
export type Notification = {
id: string
userId: string
reasonText?: string
reason?: notification_reason_types
createdTime: number
viewTime?: number
isSeen: boolean
sourceId?: string
sourceType?: notification_source_types
sourceContractId?: string
sourceUserName?: string
sourceUserUsername?: string
sourceUserAvatarUrl?: string
}
export type notification_source_types =
| 'contract'
| 'comment'
| 'bet'
| 'answer'
| 'liquidity'
export type notification_reason_types =
| 'created'
| 'updated'
| 'resolved'
| 'tagged'
| 'replied'

View File

@ -2,14 +2,11 @@ import { sum, groupBy, sumBy, mapValues } from 'lodash'
import { Bet, NumericBet } from './bet'
import { deductDpmFees, getDpmProbability } from './calculate-dpm'
import { DPM, FreeResponse, FullContract, Multi } from './contract'
import { DPMContract, FreeResponseContract } from './contract'
import { DPM_CREATOR_FEE, DPM_FEES, DPM_PLATFORM_FEE } from './fees'
import { addObjects } from './util/object'
export const getDpmCancelPayouts = (
contract: FullContract<DPM, any>,
bets: Bet[]
) => {
export const getDpmCancelPayouts = (contract: DPMContract, bets: Bet[]) => {
const { pool } = contract
const poolTotal = sum(Object.values(pool))
console.log('resolved N/A, pool M$', poolTotal)
@ -31,7 +28,7 @@ export const getDpmCancelPayouts = (
export const getDpmStandardPayouts = (
outcome: string,
contract: FullContract<DPM, any>,
contract: DPMContract,
bets: Bet[]
) => {
const winningBets = bets.filter((bet) => bet.outcome === outcome)
@ -78,7 +75,7 @@ export const getDpmStandardPayouts = (
export const getNumericDpmPayouts = (
outcome: string,
contract: FullContract<DPM, any>,
contract: DPMContract,
bets: NumericBet[]
) => {
const totalShares = sumBy(bets, (bet) => bet.allOutcomeShares[outcome] ?? 0)
@ -129,7 +126,7 @@ export const getNumericDpmPayouts = (
}
export const getDpmMktPayouts = (
contract: FullContract<DPM, any>,
contract: DPMContract,
bets: Bet[],
resolutionProbability?: number
) => {
@ -183,7 +180,7 @@ export const getDpmMktPayouts = (
export const getPayoutsMultiOutcome = (
resolutions: { [outcome: string]: number },
contract: FullContract<DPM, Multi | FreeResponse>,
contract: FreeResponseContract,
bets: Bet[]
) => {
const poolTotal = sum(Object.values(contract.pool))

View File

@ -3,7 +3,7 @@ import { sum } from 'lodash'
import { Bet } from './bet'
import { getProbability } from './calculate'
import { getCpmmLiquidityPoolWeights } from './calculate-cpmm'
import { Binary, CPMM, FixedPayouts, FullContract } from './contract'
import { CPMMContract } from './contract'
import { noFees } from './fees'
import { LiquidityProvision } from './liquidity-provision'
@ -30,7 +30,7 @@ export const getFixedCancelPayouts = (
export const getStandardFixedPayouts = (
outcome: string,
contract: FullContract<FixedPayouts, Binary>,
contract: CPMMContract,
bets: Bet[],
liquidities: LiquidityProvision[]
) => {
@ -65,7 +65,7 @@ export const getStandardFixedPayouts = (
}
export const getLiquidityPoolPayouts = (
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
outcome: string,
liquidities: LiquidityProvision[]
) => {
@ -81,7 +81,7 @@ export const getLiquidityPoolPayouts = (
}
export const getMktFixedPayouts = (
contract: FullContract<FixedPayouts, Binary>,
contract: CPMMContract,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
@ -116,7 +116,7 @@ export const getMktFixedPayouts = (
}
export const getLiquidityPoolProbPayouts = (
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
p: number,
liquidities: LiquidityProvision[]
) => {

View File

@ -1,15 +1,7 @@
import { sumBy, groupBy, mapValues } from 'lodash'
import { Bet, NumericBet } from './bet'
import {
Binary,
Contract,
DPM,
FixedPayouts,
FreeResponse,
FullContract,
Multi,
} from './contract'
import { Contract, CPMMBinaryContract, DPMContract } from './contract'
import { Fees } from './fees'
import { LiquidityProvision } from './liquidity-provision'
import {
@ -55,7 +47,7 @@ export type PayoutInfo = {
}
export const getPayouts = (
outcome: string,
outcome: string | undefined,
resolutions: {
[outcome: string]: number
},
@ -73,7 +65,6 @@ export const getPayouts = (
resolutionProbability
)
}
return getDpmPayouts(
outcome,
resolutions,
@ -84,8 +75,8 @@ export const getPayouts = (
}
export const getFixedPayouts = (
outcome: string,
contract: FullContract<FixedPayouts, Binary>,
outcome: string | undefined,
contract: CPMMBinaryContract,
bets: Bet[],
liquidities: LiquidityProvision[],
resolutionProbability?: number
@ -108,11 +99,11 @@ export const getFixedPayouts = (
}
export const getDpmPayouts = (
outcome: string,
outcome: string | undefined,
resolutions: {
[outcome: string]: number
},
contract: Contract,
contract: DPMContract,
bets: Bet[],
resolutionProbability?: number
): PayoutInfo => {
@ -125,13 +116,10 @@ export const getDpmPayouts = (
case 'MKT':
return contract.outcomeType === 'FREE_RESPONSE'
? getPayoutsMultiOutcome(
resolutions,
contract as FullContract<DPM, Multi | FreeResponse>,
openBets
)
? getPayoutsMultiOutcome(resolutions, contract, openBets)
: getDpmMktPayouts(contract, openBets, resolutionProbability)
case 'CANCEL':
case undefined:
return getDpmCancelPayouts(contract, openBets)
default:

View File

@ -1,7 +1,7 @@
import { groupBy, sumBy, mapValues, partition } from 'lodash'
import { Bet } from './bet'
import { Binary, Contract, FullContract } from './contract'
import { Contract } from './contract'
import { getPayouts } from './payouts'
export function scoreCreators(contracts: Contract[]) {
@ -24,23 +24,24 @@ export function scoreTraders(contracts: Contract[], bets: Bet[][]) {
return userScores
}
export function scoreUsersByContract(
contract: FullContract<any, Binary>,
bets: Bet[]
) {
const { resolution, resolutionProbability } = contract
export function scoreUsersByContract(contract: Contract, bets: Bet[]) {
const { resolution } = contract
const resolutionProb =
contract.outcomeType == 'BINARY'
? contract.resolutionProbability
: undefined
const [closedBets, openBets] = partition(
bets,
(bet) => bet.isSold || bet.sale
)
const { payouts: resolvePayouts } = getPayouts(
resolution,
resolution as string,
{},
contract,
openBets,
[],
resolutionProbability
resolutionProb
)
const salePayouts = closedBets.map((bet) => {

View File

@ -5,14 +5,14 @@ import {
deductDpmFees,
} from './calculate-dpm'
import { calculateCpmmSale, getCpmmProbability } from './calculate-cpmm'
import { Binary, DPM, CPMM, FullContract } from './contract'
import { CPMMContract, DPMContract } from './contract'
import { DPM_CREATOR_FEE, DPM_PLATFORM_FEE, Fees } from './fees'
import { User } from './user'
export const getSellBetInfo = (
user: User,
bet: Bet,
contract: FullContract<DPM, any>,
contract: DPMContract,
newBetId: string
) => {
const { pool, totalShares, totalBets } = contract
@ -87,7 +87,7 @@ export const getCpmmSellBetInfo = (
user: User,
shares: number,
outcome: 'YES' | 'NO',
contract: FullContract<CPMM, Binary>,
contract: CPMMContract,
prevLoanAmount: number,
newBetId: string
) => {

2
docs/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

20
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

2
docs/.prettierignore Normal file
View File

@ -0,0 +1,2 @@
.docusaurus/
build/

7
docs/.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": false,
"trailingComma": "es5",
"singleQuote": true
}

3
docs/README.md Normal file
View File

@ -0,0 +1,3 @@
# docs
Manifold Markets Docs

3
docs/babel.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
}

85
docs/docs/about.md Normal file
View File

@ -0,0 +1,85 @@
---
id: about
slug: /
---
# About Manifold Markets
Manifold Markets lets anyone create a prediction market on any topic. Win virtual money betting on what you know, from **[chess tournaments](https://manifold.markets/SG/will-magnus-carlsen-lose-any-regula)** to **[lunar collisions](https://manifold.markets/Duncan/will-the-wayward-falcon-9-booster-h)** to **[newsletter subscriber rates](https://manifold.markets/Nu%C3%B1oSempere/how-many-additional-subscribers-wil)** - or learn about the future by creating your own market!
### **What are prediction markets?**
**Prediction markets are a place where you can bet on the outcome of future events.**
Consider a question like: "Will Democrats win the 2024 US presidential election?"
If I think the Democrats are very likely to win, and you disagree, I might offer $70 to your $30 (with the winner taking home $100 total). This set of bets imply a 70% probability of the Democrats winning.
Now, you or I could be mistaken and overshooting the true probability one way or another. If so, there's an incentive for someone else to bet and correct it! Over time, the implied probability will converge to the **[market's best estimate](https://en.wikipedia.org/wiki/Efficient-market_hypothesis)**. Since these probabilities are public, anyone can use them to make better decisions!
### **How does Manifold Markets work?**
1. **Anyone can create a market for any yes-or-no question.**
You can ask questions about the future like "Will Taiwan remove its 14-day COVID quarantine by Jun 01, 2022?" If the market thinks this is very likely, you can plan more activities for your trip.
You can also ask subjective, personal questions like "Will I enjoy my 2022 Taiwan trip?". Then share the market with your family and friends and get their takes!
2. **Anyone can bet on a market using Manifold Dollars (M$), our platform currency.**
You get M$ 1,000 just for signing up, so you can start betting immediately! When a market creator decides an outcome in your favor, you'll win Manifold Dollars from people who bet against you.
More questions? Check out **[this community-driven FAQ](https://outsidetheasylum.blog/manifold-markets-faq/)**!
### **Can prediction markets work without real money?**
Yes! There is substantial evidence that play-money prediction markets provide real predictive power. Examples include **[sports betting](http://www.electronicmarkets.org/fileadmin/user_upload/doc/Issues/Volume_16/Issue_01/V16I1_Statistical_Tests_of_Real-Money_versus_Play-Money_Prediction_Markets.pdf)** and internal prediction markets at firms like **[Google](https://www.networkworld.com/article/2284098/google-bets-on-value-of-prediction-markets.html)**.
Our overall design also ensures that good forecasting will come out on top in the long term. In the competitive environment of the marketplace, bettors that are correct more often will gain influence, leading to better-calibrated forecasts over time.
Since our launch, we've seen hundreds of users trade each day, on over a thousand different markets! You can track the popularity of our platform at **[http://manifold.markets/analytics](http://manifold.markets/analytics)**.
### **Why is this important?**
Prediction markets aggregate and reveal crucial information that would not otherwise be known. They are a bottom-up mechanism that can influence everything from politics, economics, and business, to scientific research and education.
Prediction markets can predict **[which research papers will replicate](https://www.pnas.org/content/112/50/15343)**; which drug is the most effective; which policy would generate the most tax revenue; which charity will be underfunded; or which startup idea is the most promising. By surfacing and quantifying our collective knowledge, we as a society become wiser.
### **How are markets resolved?**
The creator of the prediction market decides the outcome and earns a commission based on the trade volume.
This simple resolution mechanism has surprising benefits in allowing a diversity of views to flourish. Competition between market creators will lead to traders flocking to the creators with good judgment on market resolution.
What's more, when the creator is free to use their judgment, many new kinds of prediction markets can be created that are less objective or even personal. (E.g. "Will I enjoy participating in the Metaverse in 2023?")
<!-- ### **Can I create private markets?**
Soon! We're running a pilot version of Manifold for Teams - private Manifold instances where you can discuss internal topics and predict on outcomes for your organization.
If this sounds like something youd want, **[join the waitlist here](https://docs.google.com/forms/d/e/1FAIpQLSfM_rxRHemCjKE6KPiYXGyP2nBSInZNKn_wc7yS1-rvlLAVnA/viewform?usp=sf_link)**! -->
### **Who are we?**
Manifold Markets is currently a team of three:
- James Grugett
- Stephen Grugett
- Austin Chen
We've previously launched consumer-facing startups (**[Throne](https://throne.live/)**, **[One Word](http://oneword.games/platform)**), and worked at top tech and trading firms (Google, Susquehanna).
## **Talk to us!**
Questions? Comments? Want to create a market? Talk to us!
- Email: **[info@manifold.markets](mailto:info@manifold.markets)**
- Office hours:
- **[Calendly — Austin](https://calendly.com/austinchen/manifold)**
- **[Calendly — James](https://calendly.com/jamesgrugett/manifold)**
- Chat: **[Manifold Markets Discord server](https://discord.gg/eHQBNBqXuh)**
## **Further Reading**
- **[Above the fold](https://manifoldmarkets.substack.com/)**, our newsletter
- **[Scott Alexander on play-money prediction markets](https://astralcodexten.substack.com/p/play-money-and-reputation-systems)**

369
docs/docs/api.md Normal file
View File

@ -0,0 +1,369 @@
# API
:::caution
Our API is still in alpha — things may change or break at any time!
:::
Manifold currently supports a basic, read-only API for getting information about our markets.
If you have questions, come chat with us on [Discord](https://discord.com/invite/eHQBNBqXuh). Wed love to hear about what you build!
## List out all markets
### `/v0/markets`
- Example request
```
http://manifold.markets/api/v0/markets
```
- Example response
```json
[
{
"id":"FKtYX3t8ZfIp5gytJWAI",
"creatorUsername":"JamesGrugett",
"creatorName":"James Grugett",
"createdTime":1645139406452,
"closeTime":1647406740000,
"question":"What will be the best assessment of the Free response feature on March 15th?",
"description":"Hey guys, let's try this out!\nWe will see how people use the new Free response market type over the next month. Then I will pick the answer that I think best describes the consensus view of this feature on March 15th. Cheers.",
"tags":[
"ManifoldMarkets"
],
"url":"https://manifold.markets/JamesGrugett/what-will-be-the-best-assessment-of",
"pool":null,
"probability":0,
"volume7Days":100,
"volume24Hours":100,
"isResolved":false,
...
}
```
- Response type: Array of `LiteMarket`
```tsx
// Information about a market, but without bets or comments
type LiteMarket = {
// Unique identifer for this market
id: string
// Attributes about the creator
creatorUsername: string
creatorName: string
createdTime: number
creatorAvatarUrl?: string
// Market attributes. All times are in milliseconds since epoch
closeTime?: number // Min of creator's chosen date, and resolutionTime
question: string
description: string
tags: string[]
url: string
pool: number
probability: number
volume7Days: number
volume24Hours: number
isResolved: boolean
resolutionTime?: number
resolution?: string
}
```
## Get information about one market
### `/v0/market/[marketId]`
- Example request
```
https://manifold.markets/api/v0/market/3zspH9sSzMlbFQLn9GKR
```
- <details><summary>Example response</summary><p>
```json
{
"id": "3zspH9sSzMlbFQLn9GKR",
"creatorUsername": "Austin",
"creatorName": "Austin Chen",
"createdTime": 1644103005345,
"closeTime": 1667894340000,
"question": "Will Carrick Flynn win the general election for Oregon's 6th District?",
"description": "The Effective Altruism movement usually stays out of politics, but here is a recent, highly-upvoted endorsement of donating to Carrick Flynn as a high-impact area: https://forum.effectivealtruism.org/posts/Qi9nnrmjwNbBqWbNT/the-best-usd5-800-i-ve-ever-donated-to-pandemic-prevention\nFurther reading: https://ballotpedia.org/Oregon%27s_6th_Congressional_District_election,_2022\n\n#EffectiveAltruism #Politics",
"tags": ["EffectiveAltruism", "Politics"],
"url": "https://manifold.markets/Austin/will-carrick-flynn-win-the-general",
"pool": 400.0916328426886,
"probability": 0.34455568984059187,
"volume7Days": 326.9083671573114,
"volume24Hours": 0,
"isResolved": false,
"bets": [
{
"createdTime": 1644103005345,
"isAnte": true,
"shares": 83.66600265340756,
"userId": "igi2zGXsfxYPgB0DJTXVJVmwCOr2",
"amount": 70,
"probAfter": 0.3,
"probBefore": 0.3,
"id": "E1MjiVYBM0GkqRXhv5cR",
"outcome": "NO",
"contractId": "3zspH9sSzMlbFQLn9GKR"
},
{
"contractId": "3zspH9sSzMlbFQLn9GKR",
"probAfter": 0.3,
"shares": 54.77225575051661,
"userId": "igi2zGXsfxYPgB0DJTXVJVmwCOr2",
"isAnte": true,
"createdTime": 1644103005345,
"id": "jn3iIGwD5f0vxOHxo62o",
"amount": 30,
"probBefore": 0.3,
"outcome": "YES"
},
{
"shares": 11.832723364874056,
"probAfter": 0.272108843537415,
"userId": "PkBnU8cAZiOLa0fjxiUzMKsFMYZ2",
"contractId": "3zspH9sSzMlbFQLn9GKR",
"outcome": "NO",
"amount": 10,
"id": "f6sHBab6lbGw9PsnVXdc",
"probBefore": 0.3,
"createdTime": 1644203305863
},
{
"userId": "BTksWMdCeHfDitWVaAZdjLSdu3o1",
"amount": 10,
"id": "Vfui2KOQwy7gkRPP7xc6",
"shares": 18.12694184700382,
"outcome": "YES",
"createdTime": 1644212358699,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"probBefore": 0.272108843537415,
"probAfter": 0.3367768595041322
},
{
"contractId": "3zspH9sSzMlbFQLn9GKR",
"probAfter": 0.3659259259259259,
"userId": "BTksWMdCeHfDitWVaAZdjLSdu3o1",
"probBefore": 0.3367768595041322,
"amount": 5,
"outcome": "YES",
"createdTime": 1644433184238,
"id": "eGI1VwAWF822LkcmOUot",
"shares": 8.435122540124937
},
{
"userId": "NHA7Gv9nNpb7b60GpLD3oFkBvPa2",
"shares": 59.79133423528123,
"amount": 50,
"probAfter": 0.24495867768595042,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"createdTime": 1644693685223,
"probBefore": 0.3659259259259259,
"id": "fbU0DbmDWMnubggpQotw",
"outcome": "NO"
},
{
"amount": 25,
"userId": "iXw2OSyhs0c4QW2fAfK3yqmaYDv1",
"probAfter": 0.20583333333333328,
"outcome": "NO",
"shares": 28.3920247989266,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"createdTime": 1644695698202,
"id": "k9hyljJD3MMXK2OYxTsR",
"probBefore": 0.24495867768595042
},
{
"createdTime": 1644716782308,
"shares": 11.17480183821209,
"probBefore": 0.20583333333333328,
"userId": "clvYFhVDzccYu20OUc5NBKJyDxj2",
"probAfter": 0.1927679500520291,
"id": "yYkZ4JpLgZHrRQUugpCD",
"outcome": "NO",
"contractId": "3zspH9sSzMlbFQLn9GKR",
"amount": 10
},
{
"contractId": "3zspH9sSzMlbFQLn9GKR",
"outcome": "YES",
"amount": 30,
"id": "IU2Hb1DesgKIN140BkhE",
"shares": 58.893424111838016,
"createdTime": 1644736846538,
"probBefore": 0.1927679500520291,
"userId": "BTksWMdCeHfDitWVaAZdjLSdu3o1",
"probAfter": 0.3289359861591695
},
{
"isSold": true,
"userId": "5zeWhzi9nlNNf5C9TVjshAN7QOd2",
"createdTime": 1644751343436,
"outcome": "NO",
"contractId": "3zspH9sSzMlbFQLn9GKR",
"amount": 25,
"probBefore": 0.3289359861591695,
"id": "fkCxVH7THaDbEhyJjXVk",
"probAfter": 0.2854194032651529,
"shares": 30.022082866721178
},
{
"probAfter": 0.2838618650900295,
"id": "Ao05LRRMXVWw8d7LtwhL",
"outcome": "NO",
"probBefore": 0.2854194032651529,
"shares": 1.1823269994736165,
"userId": "pUF3dMs9oLNpgU2LYtFmodaoDow1",
"amount": 1,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"createdTime": 1644768321860
},
{
"id": "LJ8H8DTuK7CH9vN3u0Sd",
"createdTime": 1644771352663,
"shares": 113.5114039238785,
"probAfter": 0.17510453314667793,
"outcome": "NO",
"amount": 100,
"probBefore": 0.2838618650900295,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"userId": "ebX5nzwrs8V0M5UynWvbtcj7KAI2"
},
{
"outcome": "YES",
"amount": 20,
"probBefore": 0.17510453314667793,
"id": "TECEF9I5FqTqt6uTIsJX",
"contractId": "3zspH9sSzMlbFQLn9GKR",
"createdTime": 1644805061501,
"shares": 43.88281646028875,
"userId": "lHxg3179e4amWm5LJhJoJrcWK482",
"probAfter": 0.24160019644701852
},
{
"amount": -25.908367157311375,
"id": "G3u2EzETWOyrGo15wtiQ",
"outcome": "NO",
"createdTime": 1644847494264,
"sale": {
"betId": "fkCxVH7THaDbEhyJjXVk",
"amount": 25.862948799445807
},
"probAfter": 0.26957595409437557,
"shares": -30.022082866721178,
"probBefore": 0.24160019644701852,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"userId": "5zeWhzi9nlNNf5C9TVjshAN7QOd2"
},
{
"createdTime": 1644853733891,
"userId": "lbTXACtCnIacKDloKfXxYkDn0zM2",
"amount": 10,
"id": "z443uCkbYRLZW9QdXu1u",
"probAfter": 0.25822886066938844,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"outcome": "NO",
"shares": 11.655141043149968,
"probBefore": 0.26957595409437557
},
{
"userId": "BTksWMdCeHfDitWVaAZdjLSdu3o1",
"amount": 15,
"shares": 28.311399392675895,
"id": "axoryV664uzHZ0jzWSXR",
"outcome": "YES",
"probBefore": 0.25822886066938844,
"contractId": "3zspH9sSzMlbFQLn9GKR",
"createdTime": 1644863335939,
"probAfter": 0.3033936853512369
},
{
"createdTime": 1644987330420,
"id": "jHAYDdZRkDw3lFoDXdmm",
"shares": 26.353902809992064,
"userId": "BTksWMdCeHfDitWVaAZdjLSdu3o1",
"contractId": "3zspH9sSzMlbFQLn9GKR",
"probAfter": 0.34455568984059187,
"probBefore": 0.3033936853512369,
"amount": 15,
"outcome": "YES"
}
],
"comments": [
{
"contractId": "3zspH9sSzMlbFQLn9GKR",
"userUsername": "Celer",
"userAvatarUrl": "https://lh3.googleusercontent.com/a/AATXAJwp0vAolZgOmT7GbzFq7mOf8lr0BFEB_LqWWfZk=s96-c",
"userId": "NHA7Gv9nNpb7b60GpLD3oFkBvPa2",
"text": "It's a D+3 district, and the person we're pushing is functionally an outsider. I maxed my donation, but 25%, what I bought down to, implying even odds on both the general and the primary, seems if anything optimistic.",
"createdTime": 1644693740967,
"id": "fbU0DbmDWMnubggpQotw",
"betId": "fbU0DbmDWMnubggpQotw",
"userName": "Celer"
}
]
}
```
</p>
</details>
- Response type: A `FullMarket`
```tsx
// A complete market, along with bets and comments
type FullMarket = LiteMarket & {
bets: Bet[]
comments: Comment[]
}
type Bet = {
id: string
contractId: string
amount: number // bet size; negative if SELL bet
outcome: string
shares: number // dynamic parimutuel pool weight; negative if SELL bet
probBefore: number
probAfter: number
sale?: {
amount: number // amount user makes from sale
betId: string // id of bet being sold
}
isSold?: boolean // true if this BUY bet has been sold
isAnte?: boolean
createdTime: number
}
```
### `/v0/slug/[marketSlug]`
This is a convenience endpoint for getting info about a market from it slug (everything after the last slash in a markets URL).
- Example request
```
https://manifold.markets/api/v0/slug/will-carrick-flynn-win-the-general
```
- Response type: A `FullMarket` ; same as above.
## Deprecated
- Our old Markets API was available at [https://us-central1-mantic-markets.cloudfunctions.net/markets](https://us-central1-mantic-markets.cloudfunctions.net/markets)
- We dont plan on continuing to change this, but well support this endpoint until 2022-03-30
## Changelog
- 2022-02-28: Add `resolutionTime` to markets, change `closeTime` definition
- 2022-02-19: Removed user IDs from bets
- 2022-02-17: Released our v0 API, with `/markets`, `/market/[marketId]`, and `/slug/[slugId]`

View File

@ -0,0 +1,52 @@
# Guide to YES/NO markets
# Overview
Historically, Manifold used a special type of automated market marker based on a dynamic pari-mutuel (DPM) betting
system. Free response and numeric markets still use this system. Binary markets created prior to March 15, 2022 used
this system.
Binary markets created after March 15 use a constant-function market maker which holds constant the weighted geometric
mean, with weights equal to the probabilities chosen by the market creator at creation. This design was inspired by
Uniswap's CPMM and a suggestion from Manifold user Pepe.
# Basic facts
- Markets are structured around a question with a binary outcome.
- Traders can place a bet on either YES or NO and receive shares in the outcome in return.
- 1 YES share = M$1 if the event happens. 1 NO share = M$1 if the event does not happen.
- Notice that 1 YES share + 1 NO share = M$1. If you ever get multiple YES and NO shares, they will cancel out and you will be left with cash.
- When the market is resolved, you will be paid out according to your shares. If you own 100 YES shares, if the event resolves YES, you will earn M$100. (If the event resolves NO, you will earn M$0).
- The creator of each market is responsible for resolving each market YES or NO.
- Creators can also resolve N/A to cancel all transactions and return the money, or resolve to a particular probability (say 50%).
# Betting
- Betting on YES will increase the markets implied probability; betting on NO will decrease the probability.
- Manifold's automated market automatically adjusts the market probability after each trade and determines how many shares a user will get for their bet.
- You can sell back your shares for cash. If you sell YES shares, the market probability will go down. If you sell NO shares, the probability will go up.
- Manifold charges fees on each trade. They are baked into the number of shares you receive.
- If you place a M$100 bet on YES when the probability is 50%, you may end up with 150 YES shares. These shares already include our fees. Notice also that when you buy, the probability goes up, so you are not getting in exactly at 200 shares or 50%.
- Our fee schedule is currently: 13% _ (1 - post-bet probability) _ bet amount
- The post-trade probability is what the market probability would be after your bet if there were no fees.
- Example:
- If you bet M$100 on NO and the resulting probability without fees would be 10%, then you pay M$100 _ 13% _ 10% = M$1.3.
- If you bet M$100 on YES and the resulting probability without fees would be 90%, then you pay `M$100 * 13% * 10% = M$1.3`.
- The fees are used to provide a commission to the market creator and to subsidize trading within the market.
- The market creators commission is paid out only after the market is resolved.
- No fees are levied on sales.
# Market creation
- Users can create a market on any question they want.
- When you create a market, you must choose an initial probability and a close date (after which trading will halt).
- You must also pay a M$ 50 market creation fee, which is used to subsidize trading on your market.
- You will earn a commission on all bets placed in your market.
- You are responsible for resolving your market in a timely manner. All the fees you earned as a commission will be paid out after resolution.
# Liquidity
- The liquidity in a market is the amount of capital available for traders to trade against.
- The more liquidity, the greater incentive there is for traders to bet, the more accurate the market will be.
- You can add liquidity to a market you are interested in to increase the incentives for traders to participate. You can think of added liquidity as a subsidy for getting your question answered.
- You can add liquidity to any market by opening up the market info popup window located in the (...) section of the header on the market page.

67
docs/docs/bounties.md Normal file
View File

@ -0,0 +1,67 @@
# Bounties
## What are Manifold bounties?
From time to time, a member of our community goes above and beyond in helping Manifold make prediction markets accessible & ubiquitous. Wed like to recognize such contributions publicly, and include a token of our appreciation in the form of M$!
Examples of community work that may be eligible for a bounty:
- Blog posts, markets, or comments which lead us to significantly change our views
- A track record of creating markets that help people make better decisions
- Promoting Manifold & forecasting to a wider audience
- Identifying serious exploits with our financial infrastructure
Our community is the beating heart of Manifold; your individual contributions are what make this platform valuable at all. Thanks to everyone listed here (as well as countless unnamed others) for your help & support!
## Awarded bounties
🥧 *Awarded 2022-03-14*
**[Kevin Zielnicki](https://manifold.markets/kjz): M$ 10,000**
- For identifying issues with our Dynamic Parimutuel Market Maker in an [excellent blog post](https://kevin.zielnicki.com/2022/02/17/manifold/) (and [associated market](https://manifold.markets/kjz/will-manifolds-developers-agree-wit)), leading us to change to a different mechanism.
**[Pepe](https://manifold.markets/Pepe): M$ 10,000**
- For developing the function used in our Constant Function Market Maker and working with us to polish it on Discord, making it easier for us to provision liquidity compared to a CPMM.
**[Gurkenglas](https://manifold.markets/Gurkenglas): M$ 5,000**
- For concrete suggestions on Discord around improving our market maker algorithms, and creating useful graphs to make our different market makers more legible.
**[Scott Alexander](https://manifold.markets/ScottAlexander): M$ 5,000**
- For [developing and publicizing the idea of providing interest-free loans on each market](https://astralcodexten.substack.com/p/play-money-and-reputation-systems), helping make long-term markets more accurate.
**[David Glidden](https://manifold.markets/dglid): M$ 5,000**
- For taking on the mantle of [@MetaculusBot](https://manifold.markets/MetaculusBot), which allows traders access to a wider spread of topics, and permits head-to-head comparisons between our prediction markets and other forecasting platforms.
**[Isaac King](https://manifold.markets/IsaacKing): M$ 5,000**
- For [compiling a comprehensive FAQ](https://outsidetheasylum.blog/manifold-markets-faq/) that answers a variety of questions that new users commonly face, and also inspiring us to move to [this open-source docs platform](http://docs.manifold.markets/).
**[Blazer](https://manifold.markets/BlazingDarkness): M$ 2,500**
- For [calling out our mistake](https://manifold.markets/BlazingDarkness/was-it-an-unpleasant-surprise-when) in retroactively publicizing all market creators trades, leading us to revert this feature entirely.
⛑️ _Awarded 2022-01-09_
**[Duncan](https://manifold.markets/Duncan): USD $50**
- For identifying and confidentially reporting an exploit where entering negative numbers into the trade box would allow the trade to go through.
- _Note: this was denominated in USD, as it predated the creation of our bounty program._
## Final note
If a particular contribution isn't listed here, that doesn't mean we didn't really appreciate it. Theres so much great work by our community; we aren't always able to catch them all!
If you feel that someone's exceptional contribution has fallen through the cracks (including your own!), please consider creating a market for “Will <X\> be recognized for a Manifold bounty?” and posting it on our Discord. Thanks!
## See also
- [Will Manifold implement retroactive public goods funding by June 1?](https://manifold.markets/Austin/will-manifold-implement-retroactive)
- [Bounties as described on LessWrong](https://www.lesswrong.com/tag/bounties-active)
- Mistakes pages we admire: [Scott Alexander](https://astralcodexten.substack.com/p/mistakes), [Nintil](https://nintil.com/mistakes), [80K Hours](https://80000hours.org/about/credibility/evaluations/mistakes/)
- [Donald Knuths reward checks](https://en.wikipedia.org/wiki/Knuth_reward_check)
![https://imgs.xkcd.com/comics/applied_math.png](https://imgs.xkcd.com/comics/applied_math.png)

124
docs/docs/faq.md Normal file
View File

@ -0,0 +1,124 @@
# Community FAQ
## General
### Do I have to pay real money in order to participate?
Nope! Each account starts with a free M$ 1000. If you invest it wisely, you can increase your total without ever needing to put any real money into the site.
### What is the name for the currency Manifold uses, represented by M$?
Manifold Dollars, or mana for short.
### Can M$ be sold for real money?
No. Gambling laws put many restrictions on real-money prediction markets, so Manifold uses play money instead.
### How do the free response markets work?
Any user can enter a response and bet on it, or they can bet on on other people's responses. The response probabilities are weighted proportionally to how many people have bet on them. The market creator's ante goes into a "none of the above" pseudo-option that can't be bet on and can't be chosen as a correct answer when the market is resolved. (This means that free response markets tend to lose their creator almost their entire ante, whereas normal markets only lose them a small fraction that's proportional to how well they chose their starting odds. It also means that if there are only a finite number of options that could win, traders can make guaranteed money by investing in them all equally.) See [here](https://manifoldmarkets.substack.com/p/above-the-fold-milestones-and-new) for more information.
### How accurate are the market probabilities?
In general, prediction markets are very accurate. They do have some known issues, most of which can be found on the [Wikipedia page.](https://en.wikipedia.org/wiki/Prediction_market#Accuracy). There are also a few factors that are specific to Manifold Markets:
- Manifold uses play money for their markets, so there's less of an incentive for people to invest safely. People often goof around with silly markets and investments that they don't expect to win M$ from.
- Anyone can create a market on Manifold, and there's nothing preventing the creator of a market from trying to manipulate it to make a profit.
- Manifold Markets is a new project and has a large number of individual markets, which means that many of their markets don't have many participants, sometimes less than 5 people.
- Manifold's betting system isn't perfect and has some sources of error, discussed in detail [here](https://kevin.zielnicki.com/2022/02/17/manifold/).
As a general heuristic, check the total pool for the market in question. The more M$ there is in the market, the more likely it is to be accurate.
### Can I participate without having a Google account?
No. See [here](https://manifold.markets/hamnox/will-manifold-markets-add-nongoogle) for the probability that this changes.
## Placing and winning bets
### The payout probabilities I'm shown sometimes aren't right. For example if a market is at 15% and I bet M$ 1 on "no", it tells me that I'll make a 42% profit if I win, but the listed payout is just M$ 1. What's going on?
Payout amounts are visually rounded to the nearest M$ 1, and only integer amounts can be put into markets. Behind the scenes however, your balance does track fractional amounts, so you're making a M$ 0.42 profit on that bet. Once you win another M$ 0.08, that fractional M$ 0.5 will display as an extra M$ 1 in your account. (There's no way to view your exact balance, you can only see the rounded value.)
### What are the rules about insider trading? (Using private information about a market to make a profit.)
It's not only allowed, but encouraged. The whole point of a prediction market is to uncover and amplify this sort of hidden information. For example, if there's a market like "will [company] make [decision]?" and you work for that company and know what decision they're going to make, you can use that information to win M$ and make the market more accurate at the same time. (Subject to your company's policies on disclosing internal information of course.) However, if the reason you have private information is because you're colluding with the market creator, this will likely earn both of you a bad reputation and people will be less interested in participating in your markets in the future.
### Can I see who is buying/selling in a market?
Trading is anonymous by default. You'll only see their username if they leave a comment. As an exception, trading from the market's creator has their name attached.
## Creating and resolving markets
### Is there any benefit to creating markets?
You get your question answered! Plus, you earn a commission on trades in your markets.
### What can I create a market about?
Anything you want to! People ask about politics, science, gaming, and even [their personal lives](https://www.smbc-comics.com/?id=2418). Take a look at the [current list of markets](https://manifold.markets/markets) to see what sorts of things people ask about.
### What's the difference between a market being "closed" and being "resolved"?
A market being "closed" means that people can no longer place or sell bets, "locking in" the current probability. Markets close when the close date of the market is met. A market being "resolved" means that the market creator has indicated a given resolution to the market's question, such as "yes", "no", "N/A", or a certain probability. This is the point at which people are cashed out of the market. Resolving a market automatically closes it, but a market can close days, weeks, or even years before it gets resolved.
### What does "PROB" mean?
Resolving a market as "PROB" means that it's resolved at a certain probability, chosen by the market creator. PROB 100% is the same as "yes", and PROB 0% is the same as "no". For example, if a market is resolved at PROB 75%, anyone who bought "yes" at less than 75% will (usually) make a profit, and anyone who bought "yes" at greater than 75% will (usually) take a loss. Vice versa for "no".
### What happens if a market creator resolves a market incorrectly, or doesn't resolve it at all?
Nothing. The idea is for Manifold Markets to function with similar freedom and versatility to a Twitter poll, but with more accurate results due to the dynamics of prediction markets. Individual market resolution is not enforced by the site, so if you don't trust a certain user to judge their markets fairly, you probably shouldn't participate in their markets.
### How do I tell if a certain market creator is trustworthy?
Look at their market resolution history on their profile page. If their past markets have all been resolved correctly, their future ones probably will be too. You can also look at the comments on those markets to see if any traders noticed anything suspicious. You can also ask about that person in the [Manifold Markets Discord](https://discord.gg/eHQBNBqXuh). And if their profile links to their website or social media pages, you can take that into account too.
### Are there any content filters? What happens if someone creates an inappropriate, offensive, or [dangerous](https://en.wikipedia.org/wiki/Assassination_market) market?
Right now, there are no restrictions on what markets can be created. If this becomes a problem, they may change their policies.
### Can a market creator change the close date of their market?
Yes. As long as the market hasn't been resolved yet, the creator can freely change its close date. They can even reopen a market that has already closed.
### Is there a way to see my closed markets that I need to resolve?
You'll get an automated email when they close. You can also go to your profile page and select "closed" in the dropdown menu. (This will display only markets that you haven't resolved yet.)
### When do market creators get their commission fees?
When the creator resolves their market, they get the commission from all the trades that were exectuted in the market.
### How do I see markets that are currently open?
You can see the top 99 markets in various categories [here](https://manifold.markets/markets).
### Can I bet in a market I created?
Yes. However if you're doing things that the community would perceive as "shady", such as put all your money on the correct resolution immediately before closing the market, people may be more reluctant to participate in your markets in the future. Betting "normally" in your own market is fine though.
## Miscellaneous
### How do I report bugs or ask for new features?
Contact them via [email](mailto:info@manifold.markets), post in their [Discord](https://discord.gg/eHQBNBqXuh), or create a market about that bug/feature in order to draw more attention to it and get community input.
### How can I get notified of new developments?
Being a very recent project, Manifold is adding new features and tweaking existing ones quite frequently. You can keep up with changes by subscribing to their [Substack](https://manifoldmarkets.substack.com/), or joining their [Discord server](https://discord.gg/eHQBNBqXuh).
### Is there an app?
No, but the website is designed responsively and looks great on mobile.
### Does Manifold have an API for programmers?
Yep. Documentation is [here](https://www.notion.so/Manifold-Markets-API-5e7d0aef4dcf452bb04b319e178fabc5).
### If I have a question that isn't answered here, where can I ask it?
You can contact Manifold Markets via [email](mailto:info@manifold.markets) or post in their [Discord](https://discord.gg/eHQBNBqXuh). Once you have an answer, please consider updating this FAQ via "Edit this page" on Github!
## Credits
This FAQ was originally compiled by [Isaac King](https://outsidetheasylum.blog/manifold-markets-faq/).

133
docs/docusaurus.config.js Normal file
View File

@ -0,0 +1,133 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github')
const darkCodeTheme = require('prism-react-renderer/themes/dracula')
const math = require('remark-math')
const katex = require('rehype-katex')
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Manifold Docs',
tagline: 'Learn more about the BESTEST prediction market platform~',
url: 'https://docs.manifold.markets',
baseUrl: '/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: 'https://manifold.markets/favicon.ico',
organizationName: 'manifoldmarkets', // Usually your GitHub org/user name.
projectName: 'docs', // Usually your repo name.
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
routeBasePath: '/',
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
editUrl: 'https://github.com/manifoldmarkets/docs/tree/main/',
remarkPlugins: [math],
rehypePlugins: [katex],
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
stylesheets: [
{
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css',
type: 'text/css',
integrity:
'sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM',
crossorigin: 'anonymous',
},
],
scripts: [
{
src: 'https://cdn.jsdelivr.net/npm/link-summoner@1.0.2/dist/browser.min.js',
async: 'true',
},
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: 'Manifold Docs',
logo: {
alt: 'Manifold Markets Logo',
src: 'https://manifold.markets/logo.svg',
},
items: [
{
type: 'doc',
docId: 'about',
position: 'left',
label: 'Docs',
},
{
href: 'https://github.com/manifoldmarkets/docs',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Manifold',
items: [
{
label: 'Manifold Markets',
to: 'https://manifold.markets',
},
{
label: 'Docs',
to: '/',
},
],
},
{
title: 'Community',
items: [
{
label: 'Discord',
href: 'https://discord.gg/eHQBNBqXuh',
},
{
label: 'Twitter',
href: 'https://twitter.com/manifoldmarkets',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: 'https://manifoldmarkets.substack.com',
},
{
label: 'GitHub',
href: 'https://github.com/manifoldmarkets/docs',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Manifold Markets, Inc. Built with Docusaurus.`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
}
module.exports = config

47
docs/package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"dev": "yarn start",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc",
"format": "prettier --write ."
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"hast-util-is-element": "1.1.0",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rehype-katex": "5",
"remark-math": "3"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.0.0-beta.17",
"@tsconfig/docusaurus": "^1.0.4"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

31
docs/sidebars.js Normal file
View File

@ -0,0 +1,31 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
{
type: 'category',
label: 'Tutorial',
items: ['hello'],
},
],
*/
}
module.exports = sidebars

View File

@ -0,0 +1,70 @@
import React from 'react'
import clsx from 'clsx'
import styles from './styles.module.css'
type FeatureItem = {
title: string
Svg: React.ComponentType<React.ComponentProps<'svg'>>
description: JSX.Element
}
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
]
function Feature({ title, Svg, description }: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
)
}
export default function HomepageFeatures(): JSX.Element {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

44
docs/src/css/custom.css Normal file
View File

@ -0,0 +1,44 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
article {
max-width: 720px;
margin: 0 auto;
}
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
}
.docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.1);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}
[data-theme='dark'] .docusaurus-highlight-code-line {
background-color: rgba(0, 0, 0, 0.3);
}

View File

@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

0
docs/static/.nojekyll vendored Normal file
View File

BIN
docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

1
docs/static/img/logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

7
docs/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"baseUrl": "."
}
}

View File

@ -1,12 +1,7 @@
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import {
Contract,
DPM,
FreeResponse,
FullContract,
} from '../../common/contract'
import { Contract } from '../../common/contract'
import { User } from '../../common/user'
import { getNewMultiBetInfo } from '../../common/new-bet'
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
@ -96,12 +91,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
const loanAmount = 0
const { newBet, newPool, newTotalShares, newTotalBets } =
getNewMultiBetInfo(
answerId,
amount,
contract as FullContract<DPM, FreeResponse>,
loanAmount
)
getNewMultiBetInfo(answerId, amount, contract, loanAmount)
const newBalance = user.balance - amount
const betDoc = firestore.collection(`contracts/${contractId}/bets`).doc()

View File

@ -2,16 +2,13 @@ import * as admin from 'firebase-admin'
import { z } from 'zod'
import {
Binary,
CPMMBinaryContract,
Contract,
CPMM,
DPM,
FreeResponse,
FullContract,
FreeResponseContract,
MAX_DESCRIPTION_LENGTH,
MAX_QUESTION_LENGTH,
MAX_TAG_LENGTH,
Numeric,
NumericContract,
OUTCOME_TYPES,
} from '../../common/contract'
import { slugify } from '../../common/util/slugify'
@ -22,7 +19,6 @@ import { APIError, newEndpoint, validate, zTimestamp } from './api'
import {
FIXED_ANTE,
getAnteBets,
getCpmmInitialLiquidity,
getFreeAnswerAnte,
getNumericAnte,
@ -72,7 +68,7 @@ export const createContract = newEndpoint(['POST'], async (req, [user, _]) => {
// Uses utc time on server:
const today = new Date()
let freeMarketResetTime = today.setUTCHours(16, 0, 0, 0)
let freeMarketResetTime = new Date().setUTCHours(16, 0, 0, 0)
if (today.getTime() < freeMarketResetTime) {
freeMarketResetTime = freeMarketResetTime - 24 * 60 * 60 * 1000
}
@ -122,30 +118,14 @@ export const createContract = newEndpoint(['POST'], async (req, [user, _]) => {
const providerId = isFree ? HOUSE_LIQUIDITY_PROVIDER_ID : user.id
if (outcomeType === 'BINARY' && contract.mechanism === 'dpm-2') {
const yesBetDoc = firestore
.collection(`contracts/${contract.id}/bets`)
.doc()
const noBetDoc = firestore.collection(`contracts/${contract.id}/bets`).doc()
const { yesBet, noBet } = getAnteBets(
user,
contract as FullContract<DPM, Binary>,
yesBetDoc.id,
noBetDoc.id
)
await yesBetDoc.set(yesBet)
await noBetDoc.set(noBet)
} else if (outcomeType === 'BINARY') {
if (outcomeType === 'BINARY') {
const liquidityDoc = firestore
.collection(`contracts/${contract.id}/liquidity`)
.doc()
const lp = getCpmmInitialLiquidity(
providerId,
contract as FullContract<CPMM, Binary>,
contract as CPMMBinaryContract,
liquidityDoc.id,
ante
)
@ -165,7 +145,7 @@ export const createContract = newEndpoint(['POST'], async (req, [user, _]) => {
const anteBet = getFreeAnswerAnte(
providerId,
contract as FullContract<DPM, FreeResponse>,
contract as FreeResponseContract,
anteBetDoc.id
)
await anteBetDoc.set(anteBet)
@ -176,7 +156,7 @@ export const createContract = newEndpoint(['POST'], async (req, [user, _]) => {
const anteBet = getNumericAnte(
providerId,
contract as FullContract<DPM, Numeric>,
contract as NumericContract,
ante,
anteBetDoc.id
)

View File

@ -0,0 +1,172 @@
import * as admin from 'firebase-admin'
import {
Notification,
notification_reason_types,
notification_source_types,
} from '../../common/notification'
import { User } from '../../common/user'
import { Contract } from '../../common/contract'
import { getValues } from './utils'
import { Comment } from '../../common/comment'
import { uniq } from 'lodash'
import { Bet } from '../../common/bet'
import { Answer } from '../../common/answer'
const firestore = admin.firestore()
type user_to_reason_texts = {
[userId: string]: { text: string; reason: notification_reason_types }
}
export const createNotification = async (
sourceId: string,
sourceType: notification_source_types,
reason: notification_reason_types,
sourceContract: Contract,
sourceUser: User,
idempotencyKey: string
) => {
const shouldGetNotification = (
userId: string,
userToReasonTexts: user_to_reason_texts
) => {
return (
sourceUser.id != userId &&
!Object.keys(userToReasonTexts).includes(userId)
)
}
const createUsersNotifications = async (
userToReasonTexts: user_to_reason_texts
) => {
await Promise.all(
Object.keys(userToReasonTexts).map(async (userId) => {
const notificationRef = firestore
.collection(`/users/${userId}/notifications`)
.doc(idempotencyKey)
const notification: Notification = {
id: idempotencyKey,
userId,
reasonText: userToReasonTexts[userId].text,
reason: userToReasonTexts[userId].reason,
createdTime: Date.now(),
isSeen: false,
sourceId,
sourceType,
sourceContractId: sourceContract.id,
sourceUserName: sourceUser.name,
sourceUserUsername: sourceUser.username,
sourceUserAvatarUrl: sourceUser.avatarUrl,
}
await notificationRef.set(notification)
})
)
}
// TODO: Update for liquidity.
// TODO: Find tagged users.
// TODO: Find replies to comments.
// TODO: Filter bets for only open bets
if (
sourceType === 'comment' ||
sourceType === 'answer' ||
sourceType === 'contract'
) {
let reasonTextPretext = getReasonTextFromReason(sourceType, reason)
const notifyContractCreator = async (
userToReasonTexts: user_to_reason_texts
) => {
if (shouldGetNotification(sourceContract.creatorId, userToReasonTexts))
userToReasonTexts[sourceContract.creatorId] = {
text: `${reasonTextPretext} your question`,
reason,
}
}
const notifyOtherAnswerersOnContract = async (
userToReasonTexts: user_to_reason_texts
) => {
const answers = await getValues<Answer>(
firestore
.collection('contracts')
.doc(sourceContract.id)
.collection('answers')
)
const recipientUserIds = uniq(answers.map((answer) => answer.userId))
recipientUserIds.forEach((userId) => {
if (shouldGetNotification(userId, userToReasonTexts))
userToReasonTexts[userId] = {
text: `${reasonTextPretext} a question you submitted an answer to`,
reason,
}
})
}
const notifyOtherCommentersOnContract = async (
userToReasonTexts: user_to_reason_texts
) => {
const comments = await getValues<Comment>(
firestore
.collection('contracts')
.doc(sourceContract.id)
.collection('comments')
)
const recipientUserIds = uniq(comments.map((comment) => comment.userId))
recipientUserIds.forEach((userId) => {
if (shouldGetNotification(userId, userToReasonTexts))
userToReasonTexts[userId] = {
text: `${reasonTextPretext} a question you commented on`,
reason,
}
})
}
const notifyOtherBettorsOnContract = async (
userToReasonTexts: user_to_reason_texts
) => {
const betsSnap = await firestore
.collection(`contracts/${sourceContract.id}/bets`)
.get()
const bets = betsSnap.docs.map((doc) => doc.data() as Bet)
const recipientUserIds = uniq(bets.map((bet) => bet.userId))
recipientUserIds.forEach((userId) => {
if (shouldGetNotification(userId, userToReasonTexts))
userToReasonTexts[userId] = {
text: `${reasonTextPretext} a question you bet on`,
reason,
}
})
}
const getUsersToNotify = async () => {
const userToReasonTexts: user_to_reason_texts = {}
// The following functions modify the userToReasonTexts object in place.
await notifyContractCreator(userToReasonTexts)
await notifyOtherAnswerersOnContract(userToReasonTexts)
await notifyOtherCommentersOnContract(userToReasonTexts)
await notifyOtherBettorsOnContract(userToReasonTexts)
return userToReasonTexts
}
const userToReasonTexts = await getUsersToNotify()
await createUsersNotifications(userToReasonTexts)
}
}
function getReasonTextFromReason(
source: notification_source_types,
reason: notification_reason_types
) {
// TODO: Find tagged users.
// TODO: Find replies to comments.
switch (source) {
case 'comment':
return 'commented on'
case 'contract':
return reason
case 'answer':
return 'answered'
default:
throw new Error('Invalid notification reason')
}
}

View File

@ -110,9 +110,7 @@ const toDisplayResolution = (
getValueFromBucket(resolution, contract).toString()
)
const answer = (contract as FreeResponseContract).answers?.find(
(a) => a.id === resolution
)
const answer = contract.answers.find((a) => a.id === resolution)
if (answer) return answer.text
return `#${resolution}`
}

View File

@ -27,3 +27,5 @@ export * from './backup-db'
export * from './change-user-info'
export * from './market-close-emails'
export * from './add-liquidity'
export * from './on-create-answer'
export * from './on-update-contract'

View File

@ -0,0 +1,32 @@
import * as functions from 'firebase-functions'
import { getContract, getUser } from './utils'
import { createNotification } from './create-notification'
import { Answer } from '../../common/answer'
export const onCreateAnswer = functions.firestore
.document('contracts/{contractId}/answers/{answerNumber}')
.onCreate(async (change, context) => {
const { contractId } = context.params as {
contractId: string
}
const { eventId } = context
const contract = await getContract(contractId)
if (!contract)
throw new Error('Could not find contract corresponding with answer')
const answer = change.data() as Answer
// Ignore ante answer.
if (answer.number === 0) return
const answerCreator = await getUser(answer.userId)
if (!answerCreator) throw new Error('Could not find answer creator')
await createNotification(
answer.id,
'answer',
'created',
contract,
answerCreator,
eventId
)
})

View File

@ -7,6 +7,7 @@ import { Comment } from '../../common/comment'
import { sendNewCommentEmail } from './emails'
import { Bet } from '../../common/bet'
import { Answer } from '../../common/answer'
import { createNotification } from './create-notification'
const firestore = admin.firestore()
@ -16,6 +17,7 @@ export const onCreateComment = functions.firestore
const { contractId } = context.params as {
contractId: string
}
const { eventId } = context
const contract = await getContract(contractId)
if (!contract)
@ -25,7 +27,16 @@ export const onCreateComment = functions.firestore
const lastCommentTime = comment.createdTime
const commentCreator = await getUser(comment.userId)
if (!commentCreator) throw new Error('Could not find contract creator')
if (!commentCreator) throw new Error('Could not find comment creator')
await createNotification(
comment.id,
'comment',
'created',
contract,
commentCreator,
eventId
)
await firestore
.collection('contracts')

View File

@ -0,0 +1,38 @@
import * as functions from 'firebase-functions'
import { getUser } from './utils'
import { createNotification } from './create-notification'
import { Contract } from '../../common/contract'
export const onUpdateContract = functions.firestore
.document('contracts/{contractId}')
.onUpdate(async (change, context) => {
const contract = change.after.data() as Contract
const { eventId } = context
const contractUpdater = await getUser(contract.creatorId)
if (!contractUpdater) throw new Error('Could not find contract updater')
const previousValue = change.before.data() as Contract
if (previousValue.isResolved !== contract.isResolved) {
await createNotification(
contract.id,
'contract',
'resolved',
contract,
contractUpdater,
eventId
)
} else if (
previousValue.closeTime !== contract.closeTime ||
previousValue.description !== contract.description
) {
await createNotification(
contract.id,
'contract',
'updated',
contract,
contractUpdater,
eventId
)
}
})

View File

@ -4,7 +4,7 @@ import { partition, sumBy } from 'lodash'
import { Bet } from '../../common/bet'
import { getProbability } from '../../common/calculate'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { Contract } from '../../common/contract'
import { noFees } from '../../common/fees'
import { User } from '../../common/user'
@ -15,7 +15,7 @@ export const redeemShares = async (userId: string, contractId: string) => {
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as FullContract<CPMM, Binary>
const contract = contractSnap.data() as Contract
if (contract.outcomeType !== 'BINARY' || contract.mechanism !== 'cpmm-1')
return { status: 'success' }

View File

@ -6,14 +6,14 @@ initAdmin()
import { Bet } from '../../../common/bet'
import { getDpmProbability } from '../../../common/calculate-dpm'
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
import { DPMBinaryContract } from '../../../common/contract'
type DocRef = admin.firestore.DocumentReference
const firestore = admin.firestore()
async function migrateContract(
contractRef: DocRef,
contract: FullContract<DPM, Binary>
contract: DPMBinaryContract
) {
const bets = await contractRef
.collection('bets')
@ -34,9 +34,7 @@ async function migrateContract(
async function migrateContracts() {
const snapshot = await firestore.collection('contracts').get()
const contracts = snapshot.docs.map(
(doc) => doc.data() as FullContract<DPM, Binary>
)
const contracts = snapshot.docs.map((doc) => doc.data() as DPMBinaryContract)
console.log('Loaded contracts', contracts.length)

View File

@ -5,18 +5,15 @@ import { initAdmin } from './script-init'
initAdmin()
import {
Binary,
Contract,
CPMM,
DPM,
FullContract,
DPMBinaryContract,
CPMMBinaryContract,
} from '../../../common/contract'
import { Bet } from '../../../common/bet'
import {
calculateDpmPayout,
getDpmProbability,
} from '../../../common/calculate-dpm'
import { User } from '../../../common/user'
import { getCpmmInitialLiquidity } from '../../../common/antes'
import { noFees } from '../../../common/fees'
import { addObjects } from '../../../common/util/object'
@ -28,7 +25,7 @@ const firestore = admin.firestore()
async function recalculateContract(contractRef: DocRef, isCommit = false) {
await firestore.runTransaction(async (transaction) => {
const contractDoc = await transaction.get(contractRef)
const contract = contractDoc.data() as FullContract<DPM, Binary>
const contract = contractDoc.data() as DPMBinaryContract
if (!contract?.slug) {
console.log('missing slug; id=', contractRef.id)
@ -110,7 +107,7 @@ async function recalculateContract(contractRef: DocRef, isCommit = false) {
{
...contract,
...contractUpdate,
} as FullContract<CPMM, Binary>,
} as CPMMBinaryContract,
liquidityDocRef.id,
ante
)

View File

@ -4,7 +4,7 @@ import { sortBy, sumBy } from 'lodash'
import { initAdmin } from './script-init'
initAdmin()
import { Binary, Contract, DPM, FullContract } from '../../../common/contract'
import { Contract, DPMBinaryContract } from '../../../common/contract'
import { Bet } from '../../../common/bet'
import {
calculateDpmShares,
@ -32,7 +32,7 @@ async function recalculateContract(
await firestore.runTransaction(async (transaction) => {
const contractDoc = await transaction.get(contractRef)
const contract = contractDoc.data() as FullContract<DPM, Binary>
const contract = contractDoc.data() as DPMBinaryContract
const betDocs = await transaction.get(contractRef.collection('bets'))
const bets = sortBy(

View File

@ -2,7 +2,7 @@ import { partition, sumBy } from 'lodash'
import * as admin from 'firebase-admin'
import * as functions from 'firebase-functions'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { BinaryContract } from '../../common/contract'
import { User } from '../../common/user'
import { getCpmmSellBetInfo } from '../../common/sell-bet'
import { addObjects, removeUndefinedProps } from '../../common/util/object'
@ -35,7 +35,7 @@ export const sellShares = functions.runWith({ minInstances: 1 }).https.onCall(
const contractSnap = await transaction.get(contractDoc)
if (!contractSnap.exists)
return { status: 'error', message: 'Invalid contract' }
const contract = contractSnap.data() as FullContract<CPMM, Binary>
const contract = contractSnap.data() as BinaryContract
const { closeTime, mechanism, collectedFees, volume } = contract
if (mechanism !== 'cpmm-1')

View File

@ -3,6 +3,7 @@
"private": true,
"workspaces": [
"common",
"docs",
"functions",
"web"
],
@ -16,5 +17,7 @@
"prettier": "2.5.0",
"typescript": "4.6.4"
},
"version": "1.0.0"
"resolutions": {
"@types/react": "17.0.43"
}
}

View File

@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'
import { XIcon } from '@heroicons/react/solid'
import { Answer } from 'common/answer'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { FreeResponseContract } from 'common/contract'
import { BuyAmountInput } from '../amount-input'
import { Col } from '../layout/col'
import { APIError, placeBet } from 'web/lib/firebase/api-call'
@ -27,7 +27,7 @@ import { Bet } from 'common/bet'
export function AnswerBetPanel(props: {
answer: Answer
contract: FullContract<DPM, FreeResponse>
contract: FreeResponseContract
closePanel: () => void
className?: string
isModal?: boolean

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'
import { Answer } from 'common/answer'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { FreeResponseContract } from 'common/contract'
import { Col } from '../layout/col'
import { Row } from '../layout/row'
import { Avatar } from '../avatar'
@ -13,7 +13,7 @@ import { Linkify } from '../linkify'
export function AnswerItem(props: {
answer: Answer
contract: FullContract<DPM, FreeResponse>
contract: FreeResponseContract
showChoice: 'radio' | 'checkbox' | undefined
chosenProb: number | undefined
totalChosenProb?: number

View File

@ -2,7 +2,7 @@ import clsx from 'clsx'
import { sum, mapValues } from 'lodash'
import { useState } from 'react'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { Contract, FreeResponse } from 'common/contract'
import { Col } from '../layout/col'
import { resolveMarket } from 'web/lib/firebase/fn-call'
import { Row } from '../layout/row'
@ -11,7 +11,7 @@ import { ResolveConfirmationButton } from '../confirmation-button'
import { removeUndefinedProps } from 'common/util/object'
export function AnswerResolvePanel(props: {
contract: FullContract<DPM, FreeResponse>
contract: Contract & FreeResponse
resolveOption: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined
setResolveOption: (
option: 'CHOOSE' | 'CHOOSE_MULTIPLE' | 'CANCEL' | undefined

View File

@ -5,7 +5,7 @@ import { groupBy, sortBy, sumBy } from 'lodash'
import { memo } from 'react'
import { Bet } from 'common/bet'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { FreeResponseContract } from 'common/contract'
import { getOutcomeProbability } from 'common/calculate'
import { useBets } from 'web/hooks/use-bets'
import { useWindowSize } from 'web/hooks/use-window-size'
@ -13,7 +13,7 @@ import { useWindowSize } from 'web/hooks/use-window-size'
const NUM_LINES = 6
export const AnswersGraph = memo(function AnswersGraph(props: {
contract: FullContract<DPM, FreeResponse>
contract: FreeResponseContract
bets: Bet[]
height?: number
}) {
@ -161,10 +161,7 @@ function formatTime(time: number, includeTime: boolean) {
return dayjs(time).format('MMM D')
}
const computeProbsByOutcome = (
bets: Bet[],
contract: FullContract<DPM, FreeResponse>
) => {
const computeProbsByOutcome = (bets: Bet[], contract: FreeResponseContract) => {
const { totalBets } = contract
const betsByOutcome = groupBy(bets, (bet) => bet.outcome)

View File

@ -1,7 +1,7 @@
import { sortBy, partition, sum, uniq } from 'lodash'
import { useLayoutEffect, useState } from 'react'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { FreeResponseContract } from 'common/contract'
import { Col } from '../layout/col'
import { useUser } from 'web/hooks/use-user'
import { getDpmOutcomeProbability } from 'common/calculate-dpm'
@ -25,9 +25,7 @@ import { UserLink } from 'web/components/user-page'
import { Linkify } from 'web/components/linkify'
import { BuyButton } from 'web/components/yes-no-selector'
export function AnswersPanel(props: {
contract: FullContract<DPM, FreeResponse>
}) {
export function AnswersPanel(props: { contract: FreeResponseContract }) {
const { contract } = props
const { creatorId, resolution, resolutions, totalBets } = contract
@ -154,7 +152,7 @@ export function AnswersPanel(props: {
}
function getAnswerItems(
contract: FullContract<DPM, FreeResponse>,
contract: FreeResponseContract,
answers: Answer[],
user: User | undefined | null
) {
@ -182,7 +180,7 @@ function getAnswerItems(
}
function OpenAnswer(props: {
contract: FullContract<any, FreeResponse>
contract: FreeResponseContract
answer: Answer
items: ActivityItem[]
type: string

View File

@ -2,7 +2,7 @@ import clsx from 'clsx'
import { useState } from 'react'
import Textarea from 'react-expanding-textarea'
import { DPM, FreeResponse, FullContract } from 'common/contract'
import { FreeResponseContract } from 'common/contract'
import { BuyAmountInput } from '../amount-input'
import { Col } from '../layout/col'
import { createAnswer } from 'web/lib/firebase/fn-call'
@ -23,9 +23,7 @@ import { firebaseLogin } from 'web/lib/firebase/users'
import { Bet } from 'common/bet'
import { MAX_ANSWER_LENGTH } from 'common/answer'
export function CreateAnswerPanel(props: {
contract: FullContract<DPM, FreeResponse>
}) {
export function CreateAnswerPanel(props: { contract: FreeResponseContract }) {
const { contract } = props
const user = useUser()
const [text, setText] = useState('')

View File

@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'
import { partition, sumBy } from 'lodash'
import { useUser } from 'web/hooks/use-user'
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract, CPMMBinaryContract } from 'common/contract'
import { Col } from './layout/col'
import { Row } from './layout/row'
import { Spacer } from './layout/spacer'
@ -39,7 +39,7 @@ import { useSaveShares } from './use-save-shares'
import { SignUpPrompt } from './sign-up-prompt'
export function BetPanel(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
className?: string
}) {
const { contract, className } = props
@ -78,7 +78,7 @@ export function BetPanel(props: {
}
export function BetPanelSwitcher(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
className?: string
title?: string // Set if BetPanel is on a feed modal
selected?: 'YES' | 'NO'
@ -157,16 +157,19 @@ export function BetPanelSwitcher(props: {
text={tradeType === 'BUY' ? title ?? 'Place a trade' : 'Sell shares'}
/>
{tradeType === 'SELL' && user && sharesOutcome && (
<SellPanel
contract={contract as FullContract<CPMM, Binary>}
shares={yesShares || noShares}
sharesOutcome={sharesOutcome}
user={user}
userBets={userBets ?? []}
onSellSuccess={onBetSuccess}
/>
)}
{tradeType === 'SELL' &&
mechanism == 'cpmm-1' &&
user &&
sharesOutcome && (
<SellPanel
contract={contract}
shares={yesShares || noShares}
sharesOutcome={sharesOutcome}
user={user}
userBets={userBets ?? []}
onSellSuccess={onBetSuccess}
/>
)}
{tradeType === 'BUY' && (
<BuyPanel
@ -184,7 +187,7 @@ export function BetPanelSwitcher(props: {
}
function BuyPanel(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
user: User | null | undefined
selected?: 'YES' | 'NO'
onBuySuccess?: () => void
@ -374,7 +377,7 @@ function BuyPanel(props: {
}
export function SellPanel(props: {
contract: FullContract<CPMM, Binary>
contract: CPMMBinaryContract
userBets: Bet[]
shares: number
sharesOutcome: 'YES' | 'NO'

View File

@ -3,7 +3,7 @@ import clsx from 'clsx'
import { BetPanelSwitcher } from './bet-panel'
import { YesNoSelector } from './yes-no-selector'
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract } from 'common/contract'
import { Modal } from './layout/modal'
import { SellButton } from './sell-button'
import { useUser } from 'web/hooks/use-user'
@ -12,7 +12,7 @@ import { useSaveShares } from './use-save-shares'
// Inline version of a bet panel. Opens BetPanel in a new modal.
export default function BetRow(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
className?: string
btnClassName?: string
betPanelClassName?: string

View File

@ -48,13 +48,24 @@ import {
import { useTimeSinceFirstRender } from 'web/hooks/use-time-since-first-render'
import { trackLatency } from 'web/lib/firebase/tracking'
import { NumericContract } from 'common/contract'
import { useUser } from 'web/hooks/use-user'
import { SellSharesModal } from './sell-modal'
type BetSort = 'newest' | 'profit' | 'closeTime' | 'value'
type BetFilter = 'open' | 'sold' | 'closed' | 'resolved' | 'all'
export function BetsList(props: { user: User }) {
const { user } = props
const bets = useUserBets(user.id, { includeRedemptions: true })
export function BetsList(props: { user: User; hideBetsBefore?: number }) {
const { user, hideBetsBefore } = props
const signedInUser = useUser()
const isYourBets = user.id === signedInUser?.id
const allBets = useUserBets(user.id, { includeRedemptions: true })
// Hide bets before 06-01-2022 if this isn't your own profile
// NOTE: This means public profits also begin on 06-01-2022 as well.
const bets = allBets?.filter(
(bet) => bet.createdTime >= (hideBetsBefore ?? 0)
)
const [contracts, setContracts] = useState<Contract[] | undefined>()
const [sort, setSort] = useState<BetSort>('newest')
@ -75,7 +86,8 @@ export function BetsList(props: { user: User }) {
disposed = true
}
}
}, [bets])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [allBets, hideBetsBefore])
const getTime = useTimeSinceFirstRender()
useEffect(() => {
@ -210,11 +222,12 @@ export function BetsList(props: { user: User }) {
<NoBets />
) : (
displayedContracts.map((contract) => (
<MyContractBets
<ContractBets
key={contract.id}
contract={contract}
bets={contractBets[contract.id] ?? []}
metric={sort === 'profit' ? 'profit' : 'value'}
isYourBets={isYourBets}
/>
))
)}
@ -234,12 +247,13 @@ const NoBets = () => {
)
}
function MyContractBets(props: {
function ContractBets(props: {
contract: Contract
bets: Bet[]
metric: 'profit' | 'value'
isYourBets: boolean
}) {
const { bets, contract, metric } = props
const { bets, contract, metric, isYourBets } = props
const { resolution, outcomeType } = contract
const resolutionValue = (contract as NumericContract).resolutionValue
@ -247,7 +261,6 @@ function MyContractBets(props: {
const [collapsed, setCollapsed] = useState(true)
const isBinary = outcomeType === 'BINARY'
const probPercent = getBinaryProbPercent(contract)
const { payout, profit, profitPercent, invested } = getContractBetMetrics(
contract,
@ -257,12 +270,14 @@ function MyContractBets(props: {
<div
tabIndex={0}
className={clsx(
'collapse collapse-arrow relative cursor-pointer bg-white p-4 pr-6',
'collapse collapse-arrow relative bg-white p-4 pr-6',
collapsed ? 'collapse-close' : 'collapse-open pb-2'
)}
onClick={() => setCollapsed((collapsed) => !collapsed)}
>
<Row className="flex-wrap gap-2">
<Row
className="cursor-pointer flex-wrap gap-2"
onClick={() => setCollapsed((collapsed) => !collapsed)}
>
<Col className="flex-[2] gap-1">
<Row className="mr-2 max-w-lg">
<Link href={contractPath(contract)}>
@ -282,24 +297,27 @@ function MyContractBets(props: {
</Row>
<Row className="flex-1 items-center gap-2 text-sm text-gray-500">
{(isBinary || resolution) && (
{resolution ? (
<>
{resolution ? (
<div>
Resolved{' '}
<OutcomeLabel
outcome={resolution}
value={resolutionValue}
contract={contract}
truncate="short"
/>
</div>
) : (
<div className="text-primary text-lg">{probPercent}</div>
)}
<div>
Resolved{' '}
<OutcomeLabel
outcome={resolution}
value={resolutionValue}
contract={contract}
truncate="short"
/>
</div>
<div></div>
</>
)}
) : isBinary ? (
<>
<div className="text-primary text-lg">
{getBinaryProbPercent(contract)}
</div>
<div></div>
</>
) : null}
<UserLink
name={contract.creatorName}
username={contract.creatorUsername}
@ -325,26 +343,32 @@ function MyContractBets(props: {
>
<Spacer h={8} />
<MyBetsSummary
<BetsSummary
className="mr-5 flex-1 sm:mr-8"
contract={contract}
bets={bets}
isYourBets={isYourBets}
/>
<Spacer h={8} />
<ContractBetsTable contract={contract} bets={bets} />
<ContractBetsTable
contract={contract}
bets={bets}
isYourBets={isYourBets}
/>
</div>
</div>
)
}
export function MyBetsSummary(props: {
export function BetsSummary(props: {
contract: Contract
bets: Bet[]
isYourBets: boolean
className?: string
}) {
const { contract, className } = props
const { contract, isYourBets, className } = props
const { resolution, outcomeType, mechanism } = contract
const isBinary = outcomeType === 'BINARY'
const isCpmm = mechanism === 'cpmm-1'
@ -360,10 +384,11 @@ export function MyBetsSummary(props: {
const noWinnings = sumBy(excludeSalesAndAntes, (bet) =>
calculatePayout(contract, bet, 'NO')
)
const { invested, profitPercent, payout, profit } = getContractBetMetrics(
contract,
bets
)
const { invested, profitPercent, payout, profit, totalShares } =
getContractBetMetrics(contract, bets)
const [showSellModal, setShowSellModal] = useState(false)
const user = useUser()
return (
<Row className={clsx('flex-wrap gap-4 sm:flex-nowrap sm:gap-6', className)}>
@ -419,6 +444,31 @@ export function MyBetsSummary(props: {
<div className="whitespace-nowrap text-sm text-gray-500">Profit</div>
<div className="whitespace-nowrap">
{formatMoney(profit)} <ProfitBadge profitPercent={profitPercent} />
{isYourBets &&
isCpmm &&
isBinary &&
!resolution &&
invested > 0 &&
user && (
<>
<button
className="btn btn-sm ml-2"
onClick={() => setShowSellModal(true)}
>
Sell
</button>
{showSellModal && (
<SellSharesModal
contract={contract}
user={user}
userBets={bets}
shares={totalShares.YES || totalShares.NO}
sharesOutcome={totalShares.YES ? 'YES' : 'NO'}
setOpen={setShowSellModal}
/>
)}
</>
)}
</div>
</Col>
</Row>
@ -429,9 +479,10 @@ export function MyBetsSummary(props: {
export function ContractBetsTable(props: {
contract: Contract
bets: Bet[]
isYourBets: boolean
className?: string
}) {
const { contract, className } = props
const { contract, className, isYourBets } = props
const bets = props.bets.filter((b) => !b.isAnte)
@ -500,6 +551,7 @@ export function ContractBetsTable(props: {
bet={bet}
saleBet={salesDict[bet.id]}
contract={contract}
isYourBet={isYourBets}
/>
))}
</tbody>
@ -508,8 +560,13 @@ export function ContractBetsTable(props: {
)
}
function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
const { bet, saleBet, contract } = props
function BetRow(props: {
bet: Bet
contract: Contract
saleBet?: Bet
isYourBet: boolean
}) {
const { bet, saleBet, contract, isYourBet } = props
const {
amount,
outcome,
@ -550,7 +607,8 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
return (
<tr>
<td className="text-neutral">
{!isCPMM &&
{isYourBet &&
!isCPMM &&
!isResolved &&
!isClosed &&
!isSold &&

View File

@ -16,7 +16,7 @@ export function Donation(props: { txn: Txn }) {
return (
<div className="mb-2 flow-root pr-2 md:pr-0">
<div className="relative flex items-center space-x-3">
<Avatar username={user.name} avatarUrl={user.avatarUrl} size="sm" />
<Avatar username={user.username} avatarUrl={user.avatarUrl} size="sm" />
<div className="min-w-0 flex-1">
<p className="mt-0.5 text-sm text-gray-500">
<UserLink

View File

@ -115,7 +115,6 @@ export function ContractSearch(props: {
{showCategorySelector && (
<CategorySelector
className="mb-2"
user={user}
category={category}
setCategory={setCategory}
/>

View File

@ -2,19 +2,12 @@ import clsx from 'clsx'
import Link from 'next/link'
import { Row } from '../layout/row'
import { formatLargeNumber, formatPercent } from 'common/util/format'
import {
Contract,
contractPath,
getBinaryProbPercent,
} from 'web/lib/firebase/contracts'
import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts'
import { Col } from '../layout/col'
import {
Binary,
CPMM,
DPM,
FreeResponse,
Contract,
BinaryContract,
FreeResponseContract,
FullContract,
NumericContract,
} from 'common/contract'
import {
@ -65,9 +58,7 @@ export function ContractCard(props: {
<Col className="relative flex-1 gap-3 pr-1">
<div
className={clsx(
'peer absolute -left-6 -top-4 -bottom-4 z-10',
// Hack: Extend the clickable area for closed markets
showQuickBet ? 'right-0' : 'right-[-6.5rem]'
'peer absolute -left-6 -top-4 -bottom-4 right-0 z-10'
)}
>
<Link href={contractPath(contract)}>
@ -85,15 +76,12 @@ export function ContractCard(props: {
{outcomeType === 'FREE_RESPONSE' &&
(resolution ? (
<FreeResponseOutcomeLabel
contract={contract as FreeResponseContract}
contract={contract}
resolution={resolution}
truncate={'long'}
/>
) : (
<FreeResponseTopAnswer
contract={contract as FullContract<DPM, FreeResponse>}
truncate="long"
/>
<FreeResponseTopAnswer contract={contract} truncate="long" />
))}
<MiscDetails
@ -116,14 +104,14 @@ export function ContractCard(props: {
{outcomeType === 'NUMERIC' && (
<NumericResolutionOrExpectation
className="items-center"
contract={contract as NumericContract}
contract={contract}
/>
)}
{outcomeType === 'FREE_RESPONSE' && (
<FreeResponseResolutionOrChance
className="self-end text-gray-600"
contract={contract as FullContract<DPM, FreeResponse>}
contract={contract}
truncate="long"
/>
)}
@ -137,7 +125,7 @@ export function ContractCard(props: {
}
export function BinaryResolutionOrChance(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
large?: boolean
className?: string
}) {

View File

@ -1,4 +1,4 @@
import { Contract, tradingAllowed } from 'web/lib/firebase/contracts'
import { tradingAllowed } from 'web/lib/firebase/contracts'
import { Col } from '../layout/col'
import { Spacer } from '../layout/spacer'
import { ContractProbGraph } from './contract-prob-graph'
@ -16,12 +16,7 @@ import { Bet } from 'common/bet'
import { Comment } from 'common/comment'
import BetRow from '../bet-row'
import { AnswersGraph } from '../answers/answers-graph'
import {
DPM,
FreeResponse,
FullContract,
NumericContract,
} from 'common/contract'
import { Contract } from 'common/contract'
import { ContractDescription } from './contract-description'
import { ContractDetails } from './contract-details'
import { ShareMarket } from '../share-market'
@ -58,7 +53,7 @@ export const ContractOverview = (props: {
{outcomeType === 'NUMERIC' && (
<NumericResolutionOrExpectation
contract={contract as NumericContract}
contract={contract}
className="hidden items-end xl:flex"
/>
)}
@ -82,9 +77,7 @@ export const ContractOverview = (props: {
{outcomeType === 'NUMERIC' && (
<Row className="items-center justify-between gap-4 xl:hidden">
<NumericResolutionOrExpectation
contract={contract as NumericContract}
/>
<NumericResolutionOrExpectation contract={contract} />
</Row>
)}
@ -97,14 +90,9 @@ export const ContractOverview = (props: {
<Spacer h={4} />
{isBinary && <ContractProbGraph contract={contract} bets={bets} />}{' '}
{outcomeType === 'FREE_RESPONSE' && (
<AnswersGraph
contract={contract as FullContract<DPM, FreeResponse>}
bets={bets}
/>
)}
{outcomeType === 'NUMERIC' && (
<NumericGraph contract={contract as NumericContract} />
<AnswersGraph contract={contract} bets={bets} />
)}
{outcomeType === 'NUMERIC' && <NumericGraph contract={contract} />}
{(contract.description || isCreator) && <Spacer h={6} />}
{isCreator && <ShareMarket className="px-2" contract={contract} />}
<ContractDescription

View File

@ -4,12 +4,12 @@ import dayjs from 'dayjs'
import { memo } from 'react'
import { Bet } from 'common/bet'
import { getInitialProbability } from 'common/calculate'
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract } from 'common/contract'
import { useBetsWithoutAntes } from 'web/hooks/use-bets'
import { useWindowSize } from 'web/hooks/use-window-size'
export const ContractProbGraph = memo(function ContractProbGraph(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
bets: Bet[]
height?: number
}) {

View File

@ -4,7 +4,7 @@ import { Comment } from 'web/lib/firebase/comments'
import { User } from 'common/user'
import { useBets } from 'web/hooks/use-bets'
import { ContractActivity } from '../feed/contract-activity'
import { ContractBetsTable, MyBetsSummary } from '../bets-list'
import { ContractBetsTable, BetsSummary } from '../bets-list'
import { Spacer } from '../layout/spacer'
import { Tabs } from '../layout/tabs'
import { Col } from '../layout/col'
@ -67,13 +67,14 @@ export function ContractTabs(props: {
const yourTrades = (
<div>
<MyBetsSummary
<BetsSummary
className="px-2"
contract={contract}
bets={userBets ?? []}
isYourBets
/>
<Spacer h={6} />
<ContractBetsTable contract={contract} bets={userBets ?? []} />
<ContractBetsTable contract={contract} bets={userBets ?? []} isYourBets />
<Spacer h={12} />
</div>
)

View File

@ -5,17 +5,8 @@ import {
getTopAnswer,
} from 'common/calculate'
import { getExpectedValue } from 'common/calculate-dpm'
import {
Contract,
FullContract,
CPMM,
DPM,
Binary,
NumericContract,
FreeResponseContract,
resolution,
} from 'common/contract'
import { User } from 'common/user'
import { Contract, NumericContract, resolution } from 'common/contract'
import {
formatLargeNumber,
formatMoney,
@ -40,12 +31,12 @@ export function QuickBet(props: { contract: Contract; user: User }) {
const userBets = useUserContractBets(user.id, contract.id)
const topAnswer =
contract.outcomeType === 'FREE_RESPONSE'
? getTopAnswer(contract as FreeResponseContract)
? getTopAnswer(contract)
: undefined
// TODO: yes/no from useSaveShares doesn't work on numeric contracts
const { yesFloorShares, noFloorShares } = useSaveShares(
contract as FullContract<DPM | CPMM, Binary | FreeResponseContract>,
contract,
userBets,
topAnswer?.number.toString() || undefined
)
@ -227,10 +218,10 @@ function QuickOutcomeView(props: {
display = getBinaryProbPercent(contract)
break
case 'NUMERIC':
display = formatLargeNumber(getExpectedValue(contract as NumericContract))
display = formatLargeNumber(getExpectedValue(contract))
break
case 'FREE_RESPONSE': {
const topAnswer = getTopAnswer(contract as FreeResponseContract)
const topAnswer = getTopAnswer(contract)
display =
topAnswer &&
formatPercent(getOutcomeProbability(contract, topAnswer.id))
@ -258,7 +249,7 @@ function getProb(contract: Contract) {
: outcomeType === 'FREE_RESPONSE'
? getOutcomeProbability(contract, getTopAnswer(contract)?.id || '')
: outcomeType === 'NUMERIC'
? getNumericScale(contract as NumericContract)
? getNumericScale(contract)
: 1 // Should not happen
}

View File

@ -4,7 +4,7 @@ import { Answer } from 'common/answer'
import { Bet } from 'common/bet'
import { getOutcomeProbability } from 'common/calculate'
import { Comment } from 'common/comment'
import { Contract, DPM, FreeResponse, FullContract } from 'common/contract'
import { Contract, FreeResponseContract } from 'common/contract'
import { User } from 'common/user'
import { mapCommentsByBetId } from 'web/lib/firebase/comments'
@ -188,7 +188,7 @@ function groupBets(
}
function getAnswerGroups(
contract: FullContract<DPM, FreeResponse>,
contract: FreeResponseContract,
bets: Bet[],
comments: Comment[],
user: User | undefined | null,
@ -269,7 +269,7 @@ function getAnswerGroups(
}
function getAnswerAndCommentInputGroups(
contract: FullContract<DPM, FreeResponse>,
contract: FreeResponseContract,
bets: Bet[],
comments: Comment[],
user: User | undefined | null
@ -493,17 +493,11 @@ export function getRecentContractActivityItems(
const items = []
if (contract.outcomeType === 'FREE_RESPONSE') {
items.push(
...getAnswerGroups(
contract as FullContract<DPM, FreeResponse>,
bets,
comments,
user,
{
sortByProb: false,
abbreviated: true,
reversed: true,
}
)
...getAnswerGroups(contract, bets, comments, user, {
sortByProb: false,
abbreviated: true,
reversed: true,
})
)
} else {
items.push(
@ -587,7 +581,7 @@ export function getSpecificContractActivityItems(
case 'free-response-comment-answer-groups':
items.push(
...getAnswerAndCommentInputGroups(
contract as FullContract<DPM, FreeResponse>,
contract as FreeResponseContract,
bets,
comments,
user

View File

@ -1,16 +1,14 @@
import clsx from 'clsx'
import { User } from '../../../common/user'
import { Row } from '../layout/row'
import { CATEGORIES, CATEGORY_LIST } from '../../../common/categories'
export function CategorySelector(props: {
user: User | null | undefined
category: string
setCategory: (category: string) => void
className?: string
}) {
const { className, user, category, setCategory } = props
const { className, category, setCategory } = props
return (
<Row
@ -24,8 +22,7 @@ export function CategorySelector(props: {
key="all"
category="All"
isFollowed={category === 'all'}
toggle={async () => {
if (!user?.id) return
toggle={() => {
setCategory('all')
}}
/>
@ -35,8 +32,7 @@ export function CategorySelector(props: {
key={cat}
category={CATEGORIES[cat].split(' ')[0]}
isFollowed={cat === category}
toggle={async () => {
if (!user?.id) return
toggle={() => {
setCategory(cat)
}}
/>

View File

@ -1,4 +1,3 @@
import { FreeResponse, FullContract } from 'common/contract'
import { Answer } from 'common/answer'
import { ActivityItem } from 'web/components/feed/activity-items'
import { Bet } from 'common/bet'
@ -26,7 +25,7 @@ import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-ti
import { useRouter } from 'next/router'
export function FeedAnswerCommentGroup(props: {
contract: FullContract<any, FreeResponse>
contract: any
answer: Answer
items: ActivityItem[]
type: string

View File

@ -1,16 +1,10 @@
import clsx from 'clsx'
import { CSSProperties, Ref, ReactNode } from 'react'
export function Col(props: {
children?: ReactNode
className?: string
style?: CSSProperties
ref?: Ref<HTMLDivElement>
}) {
const { children, className, style, ref } = props
export function Col(props: JSX.IntrinsicElements['div']) {
const { children, className, ...rest } = props
return (
<div className={clsx(className, 'flex flex-col')} style={style} ref={ref}>
<div className={clsx(className, 'flex flex-col')} {...rest}>
{children}
</div>
)

View File

@ -1,15 +1,10 @@
import clsx from 'clsx'
import { ReactNode } from 'react'
export function Row(props: {
children?: ReactNode
className?: string
id?: string
}) {
const { children, className, id } = props
export function Row(props: JSX.IntrinsicElements['div']) {
const { children, className, ...rest } = props
return (
<div className={clsx(className, 'flex flex-row')} id={id}>
<div className={clsx(className, 'flex flex-row')} {...rest}>
{children}
</div>
)

View File

@ -5,7 +5,6 @@ import {
MenuAlt3Icon,
PresentationChartLineIcon,
SearchIcon,
ChatAltIcon,
XIcon,
} from '@heroicons/react/outline'
import { Transition, Dialog } from '@headlessui/react'
@ -16,17 +15,22 @@ import { formatMoney } from 'common/util/format'
import { Avatar } from '../avatar'
import clsx from 'clsx'
import { useRouter } from 'next/router'
import NotificationsIcon from 'web/components/notifications-icon'
import { useIsIframe } from 'web/hooks/use-is-iframe'
function getNavigation(username: string) {
return [
{ name: 'Home', href: '/home', icon: HomeIcon },
{ name: 'Activity', href: '/activity', icon: ChatAltIcon },
{
name: 'Portfolio',
href: `/${username}/bets`,
icon: PresentationChartLineIcon,
},
{
name: 'Notifications',
href: `/notifications`,
icon: NotificationsIcon,
},
]
}

View File

@ -4,29 +4,6 @@ import { formatMoney } from 'common/util/format'
import { Avatar } from '../avatar'
import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
export function getNavigationOptions(user?: User | null) {
if (IS_PRIVATE_MANIFOLD) {
return [{ name: 'Leaderboards', href: '/leaderboards' }]
}
if (!user) {
return [
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
]
}
return [
{ name: 'Add funds', href: '/add-funds' },
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
{ name: 'About', href: 'https://docs.manifold.markets' },
{ name: 'Sign out', href: '#', onClick: () => firebaseLogout() },
]
}
export function ProfileSummary(props: { user: User }) {
const { user } = props
return (

View File

@ -9,6 +9,7 @@ import {
PresentationChartLineIcon,
ChatAltIcon,
SparklesIcon,
NewspaperIcon,
} from '@heroicons/react/outline'
import clsx from 'clsx'
import { sortBy } from 'lodash'
@ -16,16 +17,18 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useFollowedFolds } from 'web/hooks/use-fold'
import { useUser } from 'web/hooks/use-user'
import { firebaseLogin, firebaseLogout } from 'web/lib/firebase/users'
import { firebaseLogin, firebaseLogout, User } from 'web/lib/firebase/users'
import { ManifoldLogo } from './manifold-logo'
import { MenuButton } from './menu'
import { getNavigationOptions, ProfileSummary } from './profile-menu'
import { ProfileSummary } from './profile-menu'
import {
getUtcFreeMarketResetTime,
useHasCreatedContractToday,
} from 'web/hooks/use-has-created-contract-today'
import { Row } from '../layout/row'
import NotificationsIcon from 'web/components/notifications-icon'
import React, { useEffect, useState } from 'react'
import { IS_PRIVATE_MANIFOLD } from 'common/envs/constants'
// Create an icon from the url of an image
function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
@ -37,16 +40,45 @@ function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
function getNavigation(username: string) {
return [
{ name: 'Home', href: '/home', icon: HomeIcon },
{ name: 'Activity', href: '/activity', icon: ChatAltIcon },
{
name: 'Portfolio',
href: `/${username}/bets`,
icon: PresentationChartLineIcon,
},
{
name: 'Notifications',
href: `/notifications`,
icon: NotificationsIcon,
},
{ name: 'Charity', href: '/charity', icon: HeartIcon },
]
}
function getMoreNavigation(user?: User | null) {
if (IS_PRIVATE_MANIFOLD) {
return [{ name: 'Leaderboards', href: '/leaderboards' }]
}
if (!user) {
return [
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
]
}
return [
{ name: 'Add funds', href: '/add-funds' },
{ name: 'Leaderboards', href: '/leaderboards' },
{ name: 'Blog', href: 'https://news.manifold.markets' },
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
{ name: 'Twitter', href: 'https://twitter.com/ManifoldMarkets' },
{ name: 'About', href: 'https://docs.manifold.markets' },
{ name: 'Sign out', href: '#', onClick: () => firebaseLogout() },
]
}
const signedOutNavigation = [
{ name: 'Home', href: '/home', icon: HomeIcon },
{ name: 'Explore', href: '/markets', icon: SearchIcon },
@ -57,6 +89,7 @@ const signedOutNavigation = [
const signedOutMobileNavigation = [
{ name: 'Charity', href: '/charity', icon: HeartIcon },
{ name: 'Leaderboards', href: '/leaderboards', icon: CakeIcon },
{ name: 'Blog', href: 'https://news.manifold.markets', icon: NewspaperIcon },
{
name: 'Discord',
href: 'https://discord.gg/eHQBNBqXuh',
@ -127,7 +160,7 @@ export default function Sidebar(props: { className?: string }) {
const currentPage = router.pathname
const [countdown, setCountdown] = useState('...')
useEffect(() => {
const nextUtcResetTime = getUtcFreeMarketResetTime(false)
const nextUtcResetTime = getUtcFreeMarketResetTime({ previousTime: false })
const interval = setInterval(() => {
const now = new Date().getTime()
const timeUntil = nextUtcResetTime - now
@ -193,7 +226,7 @@ export default function Sidebar(props: { className?: string }) {
))}
<MenuButton
menuItems={getNavigationOptions(user)}
menuItems={getMoreNavigation(user)}
buttonContent={<MoreButton />}
/>
</div>

View File

@ -0,0 +1,36 @@
import { BellIcon } from '@heroicons/react/outline'
import clsx from 'clsx'
import { Row } from 'web/components/layout/row'
import { useEffect, useState } from 'react'
import { Notification } from 'common/notification'
import { listenForNotifications } from 'web/lib/firebase/notifications'
import { useUser } from 'web/hooks/use-user'
import { useRouter } from 'next/router'
export default function NotificationsIcon(props: { className?: string }) {
const user = useUser()
const [notifications, setNotifications] = useState<
Notification[] | undefined
>()
const router = useRouter()
useEffect(() => {
if (router.pathname.endsWith('notifications')) return setNotifications([])
}, [router.pathname])
useEffect(() => {
if (user) return listenForNotifications(user.id, setNotifications, true)
}, [user])
return (
<Row className={clsx('justify-center')}>
<div className={'relative'}>
{notifications && notifications.length > 0 && (
<div className="-mt-0.75 absolute ml-3.5 min-w-[15px] rounded-full bg-indigo-500 p-[2px] text-center text-[10px] leading-3 text-white lg:-mt-1 lg:ml-2">
{notifications.length}
</div>
)}
<BellIcon className={clsx(props.className)} />
</div>
</Row>
)
}

View File

@ -3,17 +3,7 @@ import { ReactNode } from 'react'
import { Answer } from 'common/answer'
import { getProbability } from 'common/calculate'
import { getValueFromBucket } from 'common/calculate-dpm'
import {
Binary,
Contract,
CPMM,
DPM,
FreeResponse,
FreeResponseContract,
FullContract,
NumericContract,
resolution,
} from 'common/contract'
import { BinaryContract, Contract, FreeResponseContract, resolution } from 'common/contract'
import { formatPercent } from 'common/util/format'
import { ClientRender } from './client-render'
@ -31,13 +21,13 @@ export function OutcomeLabel(props: {
if (contract.outcomeType === 'NUMERIC')
return (
<span className="text-blue-500">
{value ?? getValueFromBucket(outcome, contract as NumericContract)}
{value ?? getValueFromBucket(outcome, contract)}
</span>
)
return (
<FreeResponseOutcomeLabel
contract={contract as FullContract<DPM, FreeResponse>}
contract={contract}
resolution={outcome}
truncate={truncate}
answerClassName={'font-bold text-base-400'}
@ -57,7 +47,7 @@ export function BinaryOutcomeLabel(props: {
}
export function BinaryContractOutcomeLabel(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
resolution: resolution
}) {
const { contract, resolution } = props
@ -81,8 +71,7 @@ export function FreeResponseOutcomeLabel(props: {
if (resolution === 'CANCEL') return <CancelLabel />
if (resolution === 'MKT') return <MultiLabel />
const { answers } = contract
const chosen = answers?.find((answer) => answer.id === resolution)
const chosen = contract.answers.find((answer) => answer.id === resolution)
if (!chosen) return <AnswerNumberLabel number={resolution} />
return (
<FreeResponseAnswerToolTip text={chosen.text}>

View File

@ -10,12 +10,12 @@ import { resolveMarket } from 'web/lib/firebase/fn-call'
import { ProbabilitySelector } from './probability-selector'
import { DPM_CREATOR_FEE } from 'common/fees'
import { getProbability } from 'common/calculate'
import { Binary, CPMM, DPM, FullContract, resolution } from 'common/contract'
import { BinaryContract, resolution } from 'common/contract'
import { formatMoney } from 'common/util/format'
export function ResolutionPanel(props: {
creator: User
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
className?: string
}) {
useEffect(() => {

View File

@ -1,4 +1,4 @@
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract } from 'common/contract'
import { User } from 'common/user'
import { useUserContractBets } from 'web/hooks/use-user-bets'
import { useState } from 'react'
@ -7,7 +7,7 @@ import clsx from 'clsx'
import { SellSharesModal } from './sell-modal'
export function SellButton(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
user: User | null | undefined
sharesOutcome: 'YES' | 'NO' | undefined
shares: number
@ -40,7 +40,7 @@ export function SellButton(props: {
{showSellModal && (
<SellSharesModal
className={panelClassName}
contract={contract as FullContract<CPMM, Binary>}
contract={contract}
user={user}
userBets={userBets ?? []}
shares={shares}

View File

@ -1,4 +1,4 @@
import { Binary, CPMM, FullContract } from 'common/contract'
import { CPMMBinaryContract } from 'common/contract'
import { Bet } from 'common/bet'
import { User } from 'common/user'
import { Modal } from './layout/modal'
@ -11,7 +11,7 @@ import clsx from 'clsx'
export function SellSharesModal(props: {
className?: string
contract: FullContract<CPMM, Binary>
contract: CPMMBinaryContract
userBets: Bet[]
shares: number
sharesOutcome: 'YES' | 'NO'

View File

@ -1,4 +1,4 @@
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract } from 'common/contract'
import { User } from 'common/user'
import { useState } from 'react'
import { Col } from './layout/col'
@ -10,7 +10,7 @@ import { useSaveShares } from './use-save-shares'
import { SellSharesModal } from './sell-modal'
export function SellRow(props: {
contract: FullContract<DPM | CPMM, Binary>
contract: BinaryContract
user: User | null | undefined
className?: string
}) {
@ -61,7 +61,7 @@ export function SellRow(props: {
</Col>
{showSellModal && (
<SellSharesModal
contract={contract as FullContract<CPMM, Binary>}
contract={contract}
user={user}
userBets={userBets ?? []}
shares={yesShares || noShares}

View File

@ -1,17 +1,11 @@
import {
Binary,
CPMM,
DPM,
FreeResponseContract,
FullContract,
} from 'common/contract'
import { Contract } from 'common/contract'
import { Bet } from 'common/bet'
import { useEffect, useState } from 'react'
import { partition, sumBy } from 'lodash'
import { safeLocalStorage } from 'web/lib/util/local'
export const useSaveShares = (
contract: FullContract<CPMM | DPM, Binary | FreeResponseContract>,
contract: Contract,
userBets: Bet[] | undefined,
freeResponseAnswerOutcome?: string
) => {

View File

@ -44,6 +44,7 @@ export function UserLink(props: {
}
export const TAB_IDS = ['markets', 'comments', 'bets']
const JUNE_1_2022 = new Date('2022-06-01T00:00:00.000Z').valueOf()
export function UserPage(props: {
user: User
@ -229,14 +230,27 @@ export function UserPage(props: {
title: 'Bets',
content: (
<div>
<AlertBox
title="Bets are becoming publicly visible on 2022-06-01"
text="Bettor identities have always been traceable through the Manifold API.
However, our interface implied that they were private.
As we develop new features such as leaderboards and bet history, it won't be technically feasible to keep this info private.
For more context, or if you'd like to wipe your bet history, see: https://manifold.markets/Austin/will-all-bets-on-manifold-be-public"
{isCurrentUser && (
<AlertBox
title="Bets after 2022-06-01 are publicly visible by default."
text="Note that all historical bets are also publicly accessible through the API.
See: https://manifold.markets/Austin/will-all-bets-on-manifold-be-public"
/>
)}
<BetsList
user={user}
hideBetsBefore={isCurrentUser ? 0 : JUNE_1_2022}
/>
{isCurrentUser && <BetsList user={user} />}
{!isCurrentUser && (
<>
<Spacer h={4} />
<AlertBox
title="Bets before 2022-06-01 are hidden by default."
text="Note that all historical bets are also publicly accessible through the API.
See: https://manifold.markets/Austin/will-all-bets-on-manifold-be-public"
/>
</>
)}
</div>
),
tabIcon: (

View File

@ -5,9 +5,8 @@ import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
dayjs.extend(utc)
let sessionCreatedContractToday = true
export function getUtcFreeMarketResetTime(previous: boolean) {
export function getUtcFreeMarketResetTime(options: { previousTime: boolean }) {
const { previousTime } = options
const localTimeNow = new Date()
const utc4pmToday = dayjs()
.utc()
@ -18,7 +17,7 @@ export function getUtcFreeMarketResetTime(previous: boolean) {
// if it's after 4pm UTC today
if (localTimeNow.getTime() > utc4pmToday.valueOf()) {
return previous
return previousTime
? // Return it as it is
utc4pmToday.valueOf()
: // Or add 24 hours to get the next 4pm UTC time:
@ -26,7 +25,7 @@ export function getUtcFreeMarketResetTime(previous: boolean) {
}
// 4pm UTC today is coming up
return previous
return previousTime
? // Subtract 24 hours to get the previous 4pm UTC time:
utc4pmToday.valueOf() - 24 * 60 * 60 * 1000
: // Return it as it is
@ -39,9 +38,12 @@ export const useHasCreatedContractToday = (user: User | null | undefined) => {
>('loading')
useEffect(() => {
const nextUtcResetTime = getUtcFreeMarketResetTime({ previousTime: false })
setHasCreatedContractToday('loading')
const previousResetTime = getUtcFreeMarketResetTime(true)
async function listUserContractsForToday() {
const previousResetTime = getUtcFreeMarketResetTime({
previousTime: true,
})
if (!user) return
const contracts = await listContracts(user.id)
@ -49,11 +51,15 @@ export const useHasCreatedContractToday = (user: User | null | undefined) => {
(contract) => contract.createdTime > previousResetTime
)
sessionCreatedContractToday = todayContracts.length > 0
setHasCreatedContractToday(sessionCreatedContractToday)
setHasCreatedContractToday(todayContracts.length > 0)
}
const timeoutUntilNextFreeMarket = setTimeout(() => {
setHasCreatedContractToday(false)
}, nextUtcResetTime - Date.now())
listUserContractsForToday()
return () => clearTimeout(timeoutUntilNextFreeMarket)
}, [user])
return hasCreatedContractToday

View File

@ -17,7 +17,7 @@ import { range, sortBy, sum } from 'lodash'
import { app } from './init'
import { getValues, listenForValue, listenForValues } from './utils'
import { Binary, Contract, FullContract } from 'common/contract'
import { BinaryContract, Contract } from 'common/contract'
import { getDpmProbability } from 'common/calculate-dpm'
import { createRNG, shuffle } from 'common/util/random'
import { getCpmmProbability } from 'common/calculate-cpmm'
@ -64,18 +64,18 @@ export function contractPool(contract: Contract) {
: 'Empty pool'
}
export function getBinaryProb(contract: FullContract<any, Binary>) {
const { totalShares, pool, p, resolutionProbability, mechanism } = contract
export function getBinaryProb(contract: BinaryContract) {
const { pool, resolutionProbability, mechanism } = contract
return (
resolutionProbability ??
(mechanism === 'cpmm-1'
? getCpmmProbability(pool, p)
: getDpmProbability(totalShares))
? getCpmmProbability(pool, contract.p)
: getDpmProbability(contract.totalShares))
)
}
export function getBinaryProbPercent(contract: FullContract<any, Binary>) {
export function getBinaryProbPercent(contract: BinaryContract) {
return formatPercent(getBinaryProb(contract))
}

View File

@ -0,0 +1,24 @@
import { collection, query, where } from 'firebase/firestore'
import { Notification } from 'common/notification'
import { db } from 'web/lib/firebase/init'
import { getValues, listenForValues } from 'web/lib/firebase/utils'
function getNotificationsQuery(userId: string, unseenOnly?: boolean) {
const notifsCollection = collection(db, `/users/${userId}/notifications`)
if (unseenOnly) return query(notifsCollection, where('isSeen', '==', false))
return query(notifsCollection)
}
export function listenForNotifications(
userId: string,
setNotifications: (notifs: Notification[]) => void,
unseenOnly?: boolean
) {
return listenForValues<Notification>(
getNotificationsQuery(userId, unseenOnly),
(notifs) => {
notifs.sort((n1, n2) => n2.createdTime - n1.createdTime)
setNotifications(notifs)
}
)
}

View File

@ -30,13 +30,6 @@ import { formatMoney } from 'common/util/format'
import { useUserById } from 'web/hooks/use-users'
import { ContractTabs } from 'web/components/contract/contract-tabs'
import { FirstArgument } from 'common/util/types'
import {
BinaryContract,
DPM,
FreeResponse,
FullContract,
NumericContract,
} from 'common/contract'
import { contractTextDetails } from 'web/components/contract/contract-details'
import { useWindowSize } from 'web/hooks/use-window-size'
import Confetti from 'react-confetti'
@ -143,24 +136,15 @@ export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
<Col className="gap-4">
{allowTrade &&
(isNumeric ? (
<NumericBetPanel
className="hidden xl:flex"
contract={contract as NumericContract}
/>
<NumericBetPanel className="hidden xl:flex" contract={contract} />
) : (
<BetPanel className="hidden xl:flex" contract={contract} />
))}
{allowResolve &&
(isNumeric ? (
<NumericResolutionPanel
creator={user}
contract={contract as NumericContract}
/>
<NumericResolutionPanel creator={user} contract={contract} />
) : (
<ResolutionPanel
creator={user}
contract={contract as BinaryContract}
/>
<ResolutionPanel creator={user} contract={contract} />
))}
</Col>
) : null
@ -205,18 +189,13 @@ export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
{outcomeType === 'FREE_RESPONSE' && (
<>
<Spacer h={4} />
<AnswersPanel
contract={contract as FullContract<DPM, FreeResponse>}
/>
<AnswersPanel contract={contract} />
<Spacer h={4} />
</>
)}
{isNumeric && (
<NumericBetPanel
className="xl:hidden"
contract={contract as NumericContract}
/>
<NumericBetPanel className="xl:hidden" contract={contract} />
)}
{isResolved && (

View File

@ -39,11 +39,7 @@ export default function Activity() {
<>
<Page assertUser="signed-in" suspend={!!contract}>
<Col className="mx-auto w-full max-w-[700px]">
<CategorySelector
user={user}
category={category}
setCategory={setCategory}
/>
<CategorySelector category={category} setCategory={setCategory} />
<Spacer h={1} />
{feed ? (
<ActivityFeed

View File

@ -15,6 +15,8 @@ import { getDailyBets } from 'web/lib/firebase/bets'
import { getDailyComments } from 'web/lib/firebase/comments'
import { getDailyContracts } from 'web/lib/firebase/contracts'
import { getDailyNewUsers } from 'web/lib/firebase/users'
import { SiteLink } from 'web/components/site-link'
import { Linkify } from 'web/components/linkify'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz() {
@ -192,9 +194,22 @@ export default function Analytics(props: {
}
return (
<Page>
<CustomAnalytics {...props} />
<Spacer h={8} />
{!IS_PRIVATE_MANIFOLD && <FirebaseAnalytics />}
<Tabs
tabs={[
{
title: 'Activity',
content: <CustomAnalytics {...props} />,
},
{
title: 'Market Stats',
content: <WasabiCharts />,
},
{
title: 'Google Analytics',
content: <FirebaseAnalytics />,
},
]}
/>
</Page>
)
}
@ -431,7 +446,6 @@ export function FirebaseAnalytics() {
return (
<>
<Title text="Google Analytics" />
<p className="text-gray-500">
Less accurate; includes all viewers (not just signed-in users).
</p>
@ -447,3 +461,28 @@ export function FirebaseAnalytics() {
</>
)
}
export function WasabiCharts() {
return (
<>
<p className="text-gray-500">
Courtesy of <Linkify text="@wasabipesto" />; originally found{' '}
<SiteLink
className="font-bold"
href="https://wasabipesto.com/jupyter/manifold/"
>
here.
</SiteLink>
</p>
<Spacer h={4} />
<iframe
className="w-full"
height={12000}
src="https://wasabipesto.com/jupyter/manifold/"
frameBorder="0"
style={{ border: 0 }}
allowFullScreen
/>
</>
)
}

View File

@ -57,9 +57,8 @@ export default function Charity(props: {
<Col className="max-w-xl gap-2">
<Title className="!mt-0" text="Manifold for Good" />
<div className="mb-6 text-gray-500">
Donate your winnings to charity! Through the month of May, every{' '}
{formatMoney(100)} you contribute turns into $1 USD sent to your
chosen charity.
Donate your winnings to charity! Every {formatMoney(100)} you give
turns into $1 USD we send to your chosen charity.
<Spacer h={5} />
Together we've donated over ${Math.floor(totalRaised / 100)} USD so
far!

View File

@ -1,12 +1,5 @@
import { Bet } from 'common/bet'
import {
BinaryContract,
Contract,
DPM,
FreeResponse,
FullContract,
NumericContract,
} from 'common/contract'
import { Contract } from 'common/contract'
import { DOMAIN } from 'common/envs/constants'
import { AnswersGraph } from 'web/components/answers/answers-graph'
import BetRow from 'web/components/bet-row'
@ -117,10 +110,7 @@ function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
{isBinary && (
<Row className="items-center gap-4">
<BetRow
contract={contract as BinaryContract}
betPanelClassName="scale-75"
/>
<BetRow contract={contract as any} betPanelClassName="scale-75" />
<BinaryResolutionOrChance contract={contract} />
</Row>
)}
@ -133,9 +123,7 @@ function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
)}
{outcomeType === 'NUMERIC' && (
<NumericResolutionOrExpectation
contract={contract as NumericContract}
/>
<NumericResolutionOrExpectation contract={contract} />
)}
</Row>
@ -152,18 +140,11 @@ function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
)}
{outcomeType === 'FREE_RESPONSE' && (
<AnswersGraph
contract={contract as FullContract<DPM, FreeResponse>}
bets={bets}
height={graphHeight}
/>
<AnswersGraph contract={contract} bets={bets} height={graphHeight} />
)}
{outcomeType === 'NUMERIC' && (
<NumericGraph
contract={contract as NumericContract}
height={graphHeight}
/>
<NumericGraph contract={contract} height={graphHeight} />
)}
</div>
</Col>

View File

@ -5,7 +5,7 @@ import { useState } from 'react'
import Textarea from 'react-expanding-textarea'
import { getProbability } from 'common/calculate'
import { Binary, CPMM, DPM, FullContract } from 'common/contract'
import { BinaryContract } from 'common/contract'
import { parseWordsAsTags } from 'common/util/parse'
import { BuyAmountInput } from 'web/components/amount-input'
import { InfoTooltip } from 'web/components/info-tooltip'
@ -26,7 +26,7 @@ type Prediction = {
createdUrl?: string
}
function toPrediction(contract: FullContract<DPM | CPMM, Binary>): Prediction {
function toPrediction(contract: BinaryContract): Prediction {
const startProb = getProbability(contract)
return {
question: contract.question,
@ -102,9 +102,7 @@ export default function MakePredictions() {
const [description, setDescription] = useState('')
const [tags, setTags] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [createdContracts, setCreatedContracts] = useState<
FullContract<DPM | CPMM, Binary>[]
>([])
const [createdContracts, setCreatedContracts] = useState<BinaryContract[]>([])
const [ante, setAnte] = useState<number | undefined>(100)
const [anteError, setAnteError] = useState<string | undefined>()
@ -155,7 +153,7 @@ ${TEST_VALUE}
ante,
closeTime,
tags: parseWordsAsTags(tags),
})) as FullContract<DPM | CPMM, Binary>
})) as BinaryContract
setCreatedContracts((prev) => [...prev, contract])
}

187
web/pages/notifications.tsx Normal file
View File

@ -0,0 +1,187 @@
import { Tabs } from 'web/components/layout/tabs'
import { useUser } from 'web/hooks/use-user'
import React, { useEffect, useState } from 'react'
import { Notification } from 'common/notification'
import { listenForNotifications } from 'web/lib/firebase/notifications'
import { Avatar } from 'web/components/avatar'
import { Row } from 'web/components/layout/row'
import { Page } from 'web/components/page'
import { Title } from 'web/components/title'
import { doc, updateDoc } from 'firebase/firestore'
import { db } from 'web/lib/firebase/init'
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
import { Answer } from 'common/answer'
import { Comment } from 'web/lib/firebase/comments'
import { getValue } from 'web/lib/firebase/utils'
import Custom404 from 'web/pages/404'
import { UserLink } from 'web/components/user-page'
import { Linkify } from 'web/components/linkify'
import { User } from 'common/user'
import { useContract } from 'web/hooks/use-contract'
export default function Notifications() {
const user = useUser()
const [notifications, setNotifications] = useState<
Notification[] | undefined
>()
useEffect(() => {
if (user) return listenForNotifications(user.id, setNotifications)
}, [user])
if (!user) {
// TODO: return sign in page
return <Custom404 />
}
// TODO: use infinite scroll
return (
<Page>
<div className={'p-4'}>
<Title text={'Notifications'} />
<Tabs
className={'pb-2 pt-1 '}
defaultIndex={0}
tabs={[
{
title: 'All Notifications',
content: (
<div className={''}>
{notifications &&
notifications.map((notification) => (
<Notification
currentUser={user}
notification={notification}
key={notification.id}
/>
))}
</div>
),
},
]}
/>
</div>
</Page>
)
}
function Notification(props: {
currentUser: User
notification: Notification
}) {
const { notification, currentUser } = props
const {
sourceType,
sourceContractId,
sourceId,
userId,
id,
sourceUserName,
sourceUserAvatarUrl,
reasonText,
sourceUserUsername,
createdTime,
} = notification
const [subText, setSubText] = useState<string>('')
const contract = useContract(sourceContractId ?? '')
useEffect(() => {
if (!contract) return
if (sourceType === 'contract') {
setSubText(contract.question)
}
}, [contract, sourceType])
useEffect(() => {
if (!sourceContractId || !sourceId) return
if (sourceType === 'answer') {
getValue<Answer>(
doc(db, `contracts/${sourceContractId}/answers/`, sourceId)
).then((answer) => {
setSubText(answer?.text || '')
})
} else if (sourceType === 'comment') {
getValue<Comment>(
doc(db, `contracts/${sourceContractId}/comments/`, sourceId)
).then((comment) => {
setSubText(comment?.text || '')
})
}
}, [sourceContractId, sourceId, sourceType])
useEffect(() => {
if (!contract || !notification || notification.isSeen) return
updateDoc(doc(db, `users/${currentUser.id}/notifications/`, id), {
...notification,
isSeen: true,
viewTime: new Date(),
})
}, [notification, contract, currentUser, id, userId])
function getSourceUrl(sourceId?: string) {
if (!contract) return ''
return `/${contract.creatorUsername}/${
contract.slug
}#${getSourceIdForLinkComponent(sourceId ?? '')}`
}
function getSourceIdForLinkComponent(sourceId: string) {
switch (sourceType) {
case 'answer':
return `answer-${sourceId}`
case 'comment':
return sourceId
case 'contract':
return ''
default:
return sourceId
}
}
return (
<div className={' bg-white px-4 pt-6'}>
<Row className={'items-center text-gray-500 sm:justify-start'}>
<Avatar
avatarUrl={sourceUserAvatarUrl}
size={'sm'}
className={'mr-2'}
username={sourceUserName}
/>
<div className={'flex-1'}>
<UserLink
name={sourceUserName || ''}
username={sourceUserUsername || ''}
className={'mr-0 flex-shrink-0'}
/>
<a href={getSourceUrl(sourceId)} className={'flex-1 pl-1'}>
{reasonText}
{contract && sourceId && (
<div className={'inline'}>
<CopyLinkDateTimeComponent
contract={contract}
createdTime={createdTime}
elementId={getSourceIdForLinkComponent(sourceId)}
/>
</div>
)}
</a>
</div>
</Row>
<a href={getSourceUrl(sourceId)}>
<div className={'ml-4 mt-1'}>
{' '}
{contract && subText === contract.question ? (
<div className={'text-md text-indigo-700 hover:underline'}>
{subText}
</div>
) : (
<Linkify text={subText} />
)}
</div>
<div className={'mt-6 border-b border-gray-300'} />
</a>
</div>
)
}

Some files were not shown because too many files have changed in this diff Show More