Skip to content

Commit

Permalink
Merge pull request #9867 from hicommonwealth/malik.8804.token-trading…
Browse files Browse the repository at this point in the history
…-from-explore

`[Launchpad Discovery]:` Token Trade From Explore Page - Buy Mode
  • Loading branch information
mzparacha authored Nov 14, 2024
2 parents 87e977a + a3674b7 commit 6d917f2
Show file tree
Hide file tree
Showing 41 changed files with 1,085 additions and 60 deletions.
3 changes: 3 additions & 0 deletions libs/model/src/chainEventSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ export const launchpadTradeEventSignature =

export const launchpadTokenRegisteredEventSignature =
'0xc2fe88a1a3c1957424571593960b97f158a519d0aa4cef9e13a247c64f1f4c35';

export const communityNamespaceCreatedEventSignature =
'0xa16d784cb6c784b621c7877ce80495765ed32ca0b3dba2ef467116a435f125fd';
3 changes: 2 additions & 1 deletion libs/model/src/community/UpdateCommunity.command.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { InvalidInput, type Command } from '@hicommonwealth/core';
import { commonProtocol } from '@hicommonwealth/model';
import * as schemas from '@hicommonwealth/schemas';
import { ChainBase } from '@hicommonwealth/shared';
import { models } from '../database';
import { authRoles } from '../middleware';
import { mustExist } from '../middleware/guards';
import { checkSnapshotObjectExists, commonProtocol } from '../services';
import { checkSnapshotObjectExists } from '../services';

export const UpdateCommunityErrors = {
SnapshotOnlyOnEthereum:
Expand Down
27 changes: 25 additions & 2 deletions libs/model/src/services/commonProtocol/newNamespaceValidator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { AppError, ServerError } from '@hicommonwealth/core';
import { models } from '@hicommonwealth/model';
import {
communityNamespaceCreatedEventSignature,
models,
} from '@hicommonwealth/model';
import { BalanceSourceType, commonProtocol } from '@hicommonwealth/shared';
import Web3 from 'web3';
import { CommunityAttributes } from '../../models';
Expand Down Expand Up @@ -74,7 +77,27 @@ export const validateNamespace = async (
factoryData.factory,
);

if (!equalEvmAddresses(activeNamespace, txReceipt.logs[0].address)) {
let namespaceAddress: string | undefined;

// only emitted in token launch flows (launchpad)
const communityNamespaceCreatedLog = txReceipt.logs.find((l) => {
if (l.topics && l.topics.length > 0) {
return l.topics[0].toString() === communityNamespaceCreatedEventSignature;
}
return false;
});
if (communityNamespaceCreatedLog) {
const { 0: _namespaceAddress } = web3.eth.abi.decodeParameters(
['address', 'address'],
communityNamespaceCreatedLog.data!.toString(),
);
namespaceAddress = _namespaceAddress as string;
} else {
// default namespace deployment tx
namespaceAddress = txReceipt.logs[0].address;
}

if (!equalEvmAddresses(activeNamespace, namespaceAddress)) {
throw new AppError('Invalid tx hash for namespace creation');
}

Expand Down
11 changes: 8 additions & 3 deletions libs/model/src/token/CreateLaunchpadTrade.command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command, InvalidState } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { commonProtocol } from '@hicommonwealth/shared';
import z from 'zod';
import { models } from '../database';
import { mustExist } from '../middleware/guards';
import { getLaunchpadTradeTransaction } from '../services/commonProtocol/launchpadHelpers';
Expand Down Expand Up @@ -35,7 +36,9 @@ export function CreateLaunchpadTrade(): Command<
},
});
if (existingTrade) {
return existingTrade?.get({ plain: true });
return existingTrade?.get({ plain: true }) as unknown as z.infer<
typeof schemas.LaunchpadTradeView
>;
}

const chainNode = await models.ChainNode.scope('withPrivateData').findOne(
Expand All @@ -58,7 +61,7 @@ export function CreateLaunchpadTrade(): Command<
const trade = await models.LaunchpadTrade.create({
eth_chain_id,
transaction_hash,
token_address: result.parsedArgs.tokenAddress,
token_address: result.parsedArgs.tokenAddress.toLowerCase(),
trader_address: result.parsedArgs.traderAddress,
is_buy: result.parsedArgs.isBuy,
community_token_amount: result.parsedArgs.communityTokenAmount,
Expand All @@ -68,7 +71,9 @@ export function CreateLaunchpadTrade(): Command<
timestamp: Number(result.block.timestamp),
});

return trade.get({ plain: true });
return trade.get({ plain: true }) as unknown as z.infer<
typeof schemas.LaunchpadTradeView
>;
},
};
}
6 changes: 4 additions & 2 deletions libs/model/src/token/CreateToken.command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { InvalidState, type Command } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { TokenView } from '@hicommonwealth/schemas';
import { commonProtocol } from '@hicommonwealth/shared';
import z from 'zod';
import { models } from '../database';
import { authRoles } from '../middleware';
import { mustExist } from '../middleware/guards';
Expand Down Expand Up @@ -46,7 +48,7 @@ export function CreateToken(): Command<typeof schemas.CreateToken> {
}

const token = await models.Token.create({
token_address: tokenData.parsedArgs.tokenAddress,
token_address: tokenData.parsedArgs.tokenAddress.toLowerCase(),
namespace: tokenData.parsedArgs.namespace,
name: tokenInfo.name,
symbol: tokenInfo.symbol,
Expand All @@ -58,7 +60,7 @@ export function CreateToken(): Command<typeof schemas.CreateToken> {
icon_url: icon_url ?? null,
});

return token!.toJSON();
return token!.toJSON() as unknown as z.infer<typeof TokenView>;
},
};
}
7 changes: 5 additions & 2 deletions libs/model/src/token/GetTokens.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ export function GetTokens(): Query<typeof schemas.GetTokens> {
count(*) OVER() AS total
FROM "Tokens" as T
JOIN "Communities" as C ON T.namespace = C.namespace
${search ? 'WHERE LOWER(name) LIKE :search' : ''}
${search ? 'WHERE LOWER(T.name) LIKE :search' : ''}
ORDER BY ${order_col} :direction
LIMIT :limit
OFFSET :offset
`;

const tokens = await models.sequelize.query<
z.infer<typeof schemas.Token> & { total?: number; community_id: string }
z.infer<typeof schemas.TokenView> & {
total?: number;
community_id: string;
}
>(sql, {
replacements,
type: QueryTypes.SELECT,
Expand Down
39 changes: 20 additions & 19 deletions libs/model/test/launchpad/launchpad.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Actor, command, dispose } from '@hicommonwealth/core';
import { config } from '@hicommonwealth/model';
import { config, equalEvmAddresses } from '@hicommonwealth/model';
import { BalanceType, commonProtocol } from '@hicommonwealth/shared';
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
Expand All @@ -10,9 +10,11 @@ import { CreateLaunchpadTrade, CreateToken } from '../../src/token';

chai.use(chaiAsPromised);

const transaction_hash =
'0x754d65b374aa224c0f74b0951e88f97e223b1fdd7e0ec468e253c486ae7e8a68';
const token_address = '0x48651D8dE5F3c1634C77A46f77836FE2338fdc0C';
const CREATE_TOKEN_TXN_HASH =
'0x735a6ec2a5d1b71634e74183f2436f4b76855e613e97fc008f2df486d9eb73db';
const TRADE_TOKEN_TXN_HASH =
'0xf516b28f2baba449b2776c190580200320165f5436a94f5f2dc35500a3001aee';
const TOKEN_ADDRESS = '0x656a7C7429a7Ef95f55A1c1F4cc0D5D0B9E11b87';

describe('Launchpad Lifecycle', () => {
let actor: Actor;
Expand All @@ -34,7 +36,7 @@ describe('Launchpad Lifecycle', () => {
});

const [community] = await seed('Community', {
namespace: 'Tim Testing 3',
namespace: 'DogeMoonLanding',
chain_node_id: node?.id,
lifetime_thread_count: 0,
profile_count: 1,
Expand Down Expand Up @@ -72,7 +74,7 @@ describe('Launchpad Lifecycle', () => {
{ timeout: 10_000 },
async () => {
const payload = {
transaction_hash,
transaction_hash: CREATE_TOKEN_TXN_HASH,
chain_node_id: node!.id!,
description: 'test',
icon_url: 'test',
Expand All @@ -84,19 +86,18 @@ describe('Launchpad Lifecycle', () => {
payload,
});

expect(results?.token_address).to.equal(token_address);
expect(results?.symbol).to.equal('tim3');
expect(equalEvmAddresses(results?.token_address, TOKEN_ADDRESS)).to.be
.true;
expect(results?.symbol).to.equal('DMLND');
},
);

// TODO: complete test in #9867
test.skip(
test(
'Get a launchpad trade txn and project it',
{ timeout: 10_000 },
async () => {
const buyTxHash = '';
const payload = {
transaction_hash: buyTxHash,
transaction_hash: TRADE_TOKEN_TXN_HASH,
eth_chain_id: commonProtocol.ValidChains.SepoliaBase,
};
const results = await command(CreateLaunchpadTrade(), {
Expand All @@ -105,14 +106,14 @@ describe('Launchpad Lifecycle', () => {
});
expect(results).to.deep.equal({
eth_chain_id: commonProtocol.ValidChains.SepoliaBase,
transaction_hash: buyTxHash,
token_address,
trader_address: '',
transaction_hash: TRADE_TOKEN_TXN_HASH,
token_address: TOKEN_ADDRESS.toLowerCase(),
trader_address: '0x2cE1F5d4f84B583Ab320cAc0948AddE52a131FBE',
is_buy: true,
community_token_amount: 1n,
price: 1n,
floating_supply: 1n,
timestamp: 1,
community_token_amount: '534115082271506067334',
price: '2507151',
floating_supply: '535115082271506067334',
timestamp: 1731523956,
});
},
);
Expand Down
13 changes: 10 additions & 3 deletions libs/schemas/src/commands/token.schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod';
import { AuthContext } from '../context';
import { LaunchpadTrade, Token } from '../entities';
import { LaunchpadTrade } from '../entities';
import { TokenView } from '../queries';

export const CreateToken = {
input: z.object({
Expand All @@ -10,14 +11,20 @@ export const CreateToken = {
description: z.string().nullish(),
icon_url: z.string().nullish(),
}),
output: Token,
output: TokenView,
context: AuthContext,
};

export const LaunchpadTradeView = LaunchpadTrade.extend({
community_token_amount: z.string(),
price: z.string(),
floating_supply: z.string(),
});

export const CreateLaunchpadTrade = {
input: z.object({
eth_chain_id: z.number(),
transaction_hash: z.string().length(66),
}),
output: LaunchpadTrade,
output: LaunchpadTradeView,
};
7 changes: 6 additions & 1 deletion libs/schemas/src/queries/token.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { z } from 'zod';
import { Token } from '../entities';
import { PaginatedResultSchema, PaginationParamsSchema } from './pagination';

export const TokenView = Token.extend({
initial_supply: z.string(),
launchpad_liquidity: z.string(),
});

export const GetTokens = {
input: PaginationParamsSchema.extend({
search: z.string().optional(),
order_by: z.enum(['name']).optional(),
}),
output: PaginatedResultSchema.extend({
results: Token.extend({ community_id: z.string() }).array(),
results: TokenView.extend({ community_id: z.string() }).array(),
}),
};
16 changes: 12 additions & 4 deletions libs/shared/src/commonProtocol/contractHelpers/Launchpad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const launchToken = async (
shares,
holders,
totalSupply,
0,
1,
0,
'0x0000000000000000000000000000000000000000',
tokenCommunityManager,
Expand All @@ -32,9 +32,17 @@ export const buyToken = async (
walletAddress: string,
value: number,
) => {
const txReceipt = await contract.methods.buyToken(tokenAddress, 0).send({
const contractCall = contract.methods.buyToken(tokenAddress, 0);
const gasResult = await contractCall.estimateGas({
from: walletAddress,
value: value.toFixed(0),
});

const txReceipt = await contractCall.send({
from: walletAddress,
value,
value: value.toFixed(0),
gas: gasResult.toString(),
type: '0x2',
});
return txReceipt;
};
Expand All @@ -47,7 +55,7 @@ export const sellToken = async (
walletAddress: string,
) => {
const txReceipt = await contract.methods
.sellToken(tokenAddress, amount, 0)
.sellToken(tokenAddress, amount.toFixed(0), 0)
.send({ from: walletAddress });
return txReceipt;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ class LaunchpadBondingCurve extends ContractBase {
return txReceipt;
}

async buyToken(amountEth: number, walletAddress: string) {
async buyToken(amountEth: number, walletAddress: string, chainId: string) {
if (!this.initialized || !this.walletEnabled) {
await this.initialize(true);
await this.initialize(true, chainId);
}

const txReceipt = await buyToken(
Expand Down
35 changes: 35 additions & 0 deletions packages/commonwealth/client/scripts/helpers/currency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export enum SupportedCurrencies {
USD = 'USD',
}

export enum CurrenciesSymbols {
USD = '$',
}

export const currencyNameToSymbolMap: Record<
SupportedCurrencies,
CurrenciesSymbols
> = {
[SupportedCurrencies.USD]: CurrenciesSymbols.USD,
// add more when supported
};

export const currencySymbolPlacements = {
onLeft: [SupportedCurrencies.USD],
onRight: [] as string[], // some currencies use right side placements
};

export const getAmountWithCurrencySymbol = (
amount: number,
currencyName: SupportedCurrencies,
) => {
const symbol = currencyNameToSymbolMap[currencyName];
const leftSymbol = currencySymbolPlacements.onLeft.includes(currencyName)
? symbol
: '';
const rightymbol = currencySymbolPlacements.onRight.includes(currencyName)
? symbol
: '';

return `${leftSymbol} ${amount} ${rightymbol}`;
};
Loading

0 comments on commit 6d917f2

Please sign in to comment.