Skip to content

Commit

Permalink
[ECO-2561] Production release v1.0.0 (#475)
Browse files Browse the repository at this point in the history
Co-authored-by: Bogdan Crisan <[email protected]>
Co-authored-by: alnoki <[email protected]>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent 76f9324 commit b5d17a3
Show file tree
Hide file tree
Showing 26 changed files with 579 additions and 218 deletions.
1 change: 1 addition & 0 deletions cfg/cspell-frontend-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ testid
ADBEEF
bytea
nominalize
dexscreener
27 changes: 27 additions & 0 deletions src/move/market_metadata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,33 @@ aptos move publish \
--profile $PROFILE
```

## Add admin

```sh
NEW_ADMIN=0xccc...
MARKET_METADATA=0xaaa...
PROFILE_NAME=my-profile

aptos move run \
--args address:$NEW_ADMIN \
--function-id $MARKET_METADATA::emojicoin_dot_fun_market_metadata::add_admin \
--profile $PROFILE_NAME
```

## Remove admin

```sh
ADMIN_TO_REMOVE=0xccc...
MARKET_METADATA=0xaaa...
PROFILE_NAME=my-profile

aptos move run \
--args address:$ADMIN_TO_REMOVE \
--function-id \
$MARKET_METADATA::emojicoin_dot_fun_market_metadata::remove_admin \
--profile $PROFILE_NAME
```

## Set property

```sh
Expand Down
21 changes: 16 additions & 5 deletions src/move/rewards/README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
# `emojicoin-dot-fun` rewards

This package contains an overloaded version of the `swap` function for
`emojicoin-dot-fun`, `swap_with_rewards`, which gives users an opportunity for a
reward based on the amount of integrator fees they pay for their swap.
## `emojicoin_dot_fun_rewards`

The rewards vault can be loaded up via the `fund_tiers` function.
This module contains an overloaded version of the `swap` function for
`emojicoin-dot-fun`, `emojicoin_dot_fun_rewards::swap_with_rewards`, which gives
users an opportunity for a reward based on the amount of integrator fees they
pay for their swap.

Rewards distributions are random, and based on a probabilistic threshold
determined by the number of nominal rewards distributions expected for a given
nominal volume amount. See `reward_tiers` for more.
nominal volume amount. See `emojicoin_dot_fun_rewards::reward_tiers` for more.

For ease of parameter modeling, named constants use values in `APT`, which are
converted to octas internally.

The rewards vault can be loaded up via the
`emojicoin_dot_fun_rewards::fund_tiers` function.

## `emojicoin_dot_fun_claim_link`

This module was designed to enable claim links, like for "magic link" sign on,
which would rely on private keys in QR codes or similar in order to claim
emojicoins. It was never used beyond the initial Move implementation, but is
kept here for reference.

## Publish commands

Set variables:
Expand Down
3 changes: 3 additions & 0 deletions src/typescript/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ NEXT_PUBLIC_LINKS='{
"tos": ""
}'

# Discord channel name for adding social links.
NEXT_PUBLIC_DISCORD_METADATA_REQUEST_CHANNEL=""

# The URL for the indexer's `postgrest` REST API.
EMOJICOIN_INDEXER_URL="http://localhost:3000"

Expand Down
2 changes: 1 addition & 1 deletion src/typescript/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,5 @@
"test:e2e": "playwright test --project=firefox",
"vercel-install": "./submodule.sh && pnpm i"
},
"version": "0.0.1-alpha"
"version": "1.0.0"
}
18 changes: 14 additions & 4 deletions src/typescript/frontend/src/app/dexscreener/asset/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { EMOJICOIN_SUPPLY } from "@sdk/const";
import { calculateCirculatingSupply } from "@sdk/markets";
import { symbolEmojiStringToArray } from "../util";
import { fetchMarketState } from "@/queries/market";
import { toNominal } from "lib/utils/decimals";

/**
* - In most cases, asset ids will correspond to contract addresses. Ids are case-sensitive.
Expand Down Expand Up @@ -63,26 +64,35 @@ async function getAsset(assetId: string): Promise<Asset> {

const circulatingSupply: { circulatingSupply?: number | string } = {};
if (marketState && marketState.state) {
circulatingSupply.circulatingSupply = calculateCirculatingSupply(marketState.state).toString();
circulatingSupply.circulatingSupply = toNominal(calculateCirculatingSupply(marketState.state));
}

return {
id: assetId,
name: marketEmojiData.symbolData.name,
symbol: marketEmojiData.symbolData.symbol,
totalSupply: Number(EMOJICOIN_SUPPLY),
totalSupply: toNominal(EMOJICOIN_SUPPLY),
...circulatingSupply,
// coinGeckoId: assetId,
// coinMarketCapId: assetId,
};
}

// NextJS JSON response handler
// Although this route would be ideal for caching, nextjs doesn't offer the ability to control
// caches for failed responses. In other words, if someone queries an asset that doesn't exist
// yet at this endpoint, it would permanently cache that asset as not existing and thus return
// the failed query JSON response. This is obviously problematic for not yet existing markets,
// so unless we have some way to not cache failed queries/empty responses, we can't cache this
// endpoint at all.
export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";

export async function GET(request: NextRequest): Promise<NextResponse<AssetResponse>> {
const searchParams = request.nextUrl.searchParams;
const assetId = searchParams.get("id");
if (!assetId) {
// This is a required field, and is an error otherwise
// This is a required field, and is an error otherwise.
return new NextResponse("id is a parameter", { status: 400 });
}
const asset = await getAsset(assetId);
Expand Down
130 changes: 69 additions & 61 deletions src/typescript/frontend/src/app/dexscreener/events/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,27 @@ import { calculateCurvePrice, calculateRealReserves } from "@sdk/markets";
import { toCoinDecimalString } from "../../../lib/utils/decimals";
import { DECIMALS } from "@sdk/const";
import { symbolEmojisToPairId } from "../util";
import { compareBigInt } from "@econia-labs/emojicoin-sdk";
import { compareBigInt, type Flatten } from "@econia-labs/emojicoin-sdk";
import { type XOR } from "@sdk/utils/utility-types";

export type Asset0In1Out = {
asset0In: number | string;
asset1Out: number | string;
};

export type Asset1In0Out = {
asset0Out: number | string;
asset1In: number | string;
};

export type AssetInOut = XOR<Asset0In1Out, Asset1In0Out>;

export type DexscreenerReserves = {
reserves: {
asset0: number | string;
asset1: number | string;
};
};

/**
* - `txnId` is a transaction identifier such as a transaction hash
Expand Down Expand Up @@ -128,24 +148,19 @@ import { compareBigInt } from "@econia-labs/emojicoin-sdk";
* - The Indexer automatically handles calculations for USD pricing (`priceUsd` as opposed to
* `priceNative`)
*/
export interface SwapEvent {
eventType: "swap";
txnId: string;
txnIndex: number;
eventIndex: number;
maker: string;
pairId: string;
asset0In?: number | string;
asset1In?: number | string;
asset0Out?: number | string;
asset1Out?: number | string;
priceNative: number | string;
reserves?: {
asset0: number | string;
asset1: number | string;
};
metadata?: Record<string, string>;
}
export type SwapEvent = Flatten<
{
eventType: "swap";
txnId: string;
txnIndex: number;
eventIndex: number;
maker: string;
pairId: string;
priceNative: number | string;
metadata?: Record<string, string>;
} & AssetInOut &
DexscreenerReserves
>;

/**
* - `txnId` is a transaction identifier such as a transaction hash
Expand All @@ -167,49 +182,38 @@ export interface SwapEvent {
* - `metadata` includes any optional auxiliary info not covered in the default schema and not
* required in most cases
*/
interface JoinExitEvent {
eventType: "join" | "exit";
txnId: string;
txnIndex: number;
eventIndex: number;
maker: string;
pairId: string;
amount0: number | string;
amount1: number | string;
reserves?: {
asset0: number | string;
asset1: number | string;
};
metadata?: Record<string, string>;
}
type JoinExitEvent = Flatten<
{
eventType: "join" | "exit";
txnId: string;
txnIndex: number;
eventIndex: number;
maker: string;
pairId: string;
amount0: number | string;
amount1: number | string;
metadata?: Record<string, string>;
} & DexscreenerReserves
>;

type BlockInfo = { block: Block };
type Event = (SwapEvent | JoinExitEvent) & BlockInfo;

interface EventsResponse {
events: Event[];
}

function toDexscreenerSwapEvent(event: ReturnType<typeof toSwapEventModel>): SwapEvent & BlockInfo {
let assetInOut;

if (event.swap.isSell) {
// We are selling to APT
assetInOut = {
asset0In: toCoinDecimalString(event.swap.inputAmount, DECIMALS),
asset0Out: 0,
asset1In: 0,
asset1Out: toCoinDecimalString(event.swap.baseVolume, DECIMALS),
};
} else {
// We are buying with APT
assetInOut = {
asset0In: 0,
asset0Out: toCoinDecimalString(event.swap.quoteVolume, DECIMALS),
asset1In: toCoinDecimalString(event.swap.inputAmount, DECIMALS),
asset1Out: 0,
};
}
// Base / quote is emojicoin / APT.
// Thus asset0 / asset1 is always base volume / quote volume.
const assetInOut = event.swap.isSell
? {
asset0In: toCoinDecimalString(event.swap.baseVolume, DECIMALS),
asset1Out: toCoinDecimalString(event.swap.quoteVolume, DECIMALS),
}
: {
asset0Out: toCoinDecimalString(event.swap.baseVolume, DECIMALS),
asset1In: toCoinDecimalString(event.swap.quoteVolume, DECIMALS),
};

const { base, quote } = calculateRealReserves(event.state);
const reserves = {
Expand All @@ -224,7 +228,7 @@ function toDexscreenerSwapEvent(event: ReturnType<typeof toSwapEventModel>): Swa
return {
block: {
blockNumber: Number(event.blockAndEvent.blockNumber),
blockTimestamp: event.transaction.timestamp.getTime() / 1000,
blockTimestamp: Math.floor(event.transaction.timestamp.getTime() / 1000),
},
eventType: "swap",
txnId: event.transaction.version.toString(),
Expand All @@ -236,11 +240,8 @@ function toDexscreenerSwapEvent(event: ReturnType<typeof toSwapEventModel>): Swa
pairId: symbolEmojisToPairId(event.market.symbolEmojis),

...assetInOut,

asset0In: event.swap.inputAmount.toString(),
asset1Out: event.swap.quoteVolume.toString(),
priceNative,
...reserves,
reserves,
};
}

Expand All @@ -258,7 +259,7 @@ function toDexscreenerJoinExitEvent(
return {
block: {
blockNumber: Number(event.blockAndEvent.blockNumber),
blockTimestamp: event.transaction.timestamp.getTime() / 1000,
blockTimestamp: Math.floor(event.transaction.timestamp.getTime() / 1000),
},
eventType: event.liquidity.liquidityProvided ? "join" : "exit",

Expand Down Expand Up @@ -290,7 +291,14 @@ async function getEventsByVersion(fromBlock: number, toBlock: number): Promise<E
);
}

// NextJS JSON response handler
// Don't cache this request, because we could inadvertently cache data that is immediately invalid
// in the case where the `toBlock` is larger than the present block. Although this shouldn't happen,
// it's not worth caching these queries anyway, because they should only be called once or twice
// with the same query parameters.
export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";

/**
* We treat our versions as "blocks" because it's faster to implement given our current architecture
* This requires dexscreener to have relatively large `fromBlock - toBlock` ranges to keep up
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ import { getAptosClient } from "@sdk/utils/aptos-client";
*/
export interface Block {
blockNumber: number;
blockTimestamp: number;
blockTimestamp: number; // Whole number representing a UNIX timestamp in seconds.
metadata?: Record<string, string>;
}

interface LatestBlockResponse {
block: Block;
}

// NextJS JSON response handler
export const revalidate = 1;

export async function GET(_request: NextRequest): Promise<NextResponse<LatestBlockResponse>> {
const status = await getProcessorStatus();
const aptos = getAptosClient();
Expand All @@ -47,8 +48,7 @@ export async function GET(_request: NextRequest): Promise<NextResponse<LatestBlo
return NextResponse.json({
block: {
blockNumber,
// Convert to seconds
blockTimestamp: status.lastTransactionTimestamp.getTime() / 1000,
blockTimestamp: Math.floor(status.lastTransactionTimestamp.getTime() / 1000),
},
});
}
17 changes: 14 additions & 3 deletions src/typescript/frontend/src/app/dexscreener/pair/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ interface Pair {
asset0Id: string;
asset1Id: string;
createdAtBlockNumber?: number;
createdAtBlockTimestamp?: number;
createdAtBlockTimestamp?: number; // Whole number representing a UNIX timestamp in seconds.
createdAtTxnId?: string;
creator?: string;
feeBps?: number;
Expand Down Expand Up @@ -104,14 +104,25 @@ async function getPair(
asset0Id: symbolEmojisToString(symbolEmojis),
asset1Id: "APT",
createdAtBlockNumber: parseInt(block.block_height),
createdAtBlockTimestamp: marketRegistration.transaction.timestamp.getTime() / 1000,
createdAtBlockTimestamp: Math.floor(
marketRegistration.transaction.timestamp.getTime() / 1000
),
createdAtTxnId: String(marketRegistration.transaction.version),
feeBps: INTEGRATOR_FEE_RATE_BPS,
},
};
}

// NextJS JSON response handler
// Although this route would be ideal for caching, nextjs doesn't offer the ability to control
// caches for failed responses. In other words, if someone queries an asset that doesn't exist
// yet at this endpoint, it would permanently cache that asset as not existing and thus return
// the failed query JSON response. This is obviously problematic for not yet existing markets,
// so unless we have some way to not cache failed queries/empty responses, we can't cache this
// endpoint at all.
export const revalidate = 0;
export const dynamic = "force-dynamic";
export const fetchCache = "force-no-store";

export async function GET(request: NextRequest): Promise<NextResponse<PairResponse>> {
const searchParams = request.nextUrl.searchParams;
const pairId = searchParams.get("id");
Expand Down
Loading

0 comments on commit b5d17a3

Please sign in to comment.