Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions libs/evm-protocols/src/event-registry/eventRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CommunityStakeAbi,
ContestGovernorAbi,
ContestGovernorSingleAbi,
FutarchyRouterAbi,
LPBondingCurveAbi,
LaunchpadAbi,
NamespaceFactoryAbi,
Expand Down Expand Up @@ -125,6 +126,14 @@ export const binaryVaultSource: ContractSource = {
eventSignatures: [EvmEventSignatures.PredictionMarket.TokensMinted],
};

// FutarchyRouter is deployed per prediction market; addresses are stored in
// EvmEventSources at deploy time. Exported for use by the EVM worker when
// building contract sources from the DB.
export const futarchyRouterSource: ContractSource = {
abi: FutarchyRouterAbi,
eventSignatures: [EvmEventSignatures.PredictionMarket.SwapExecuted],
};

const tokenBondingCurveSource: ContractSource = {
abi: TokenBondingCurveAbi,
eventSignatures: [
Expand Down
2 changes: 2 additions & 0 deletions libs/evm-protocols/src/event-registry/eventSignatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export const EvmEventSignatures = {
PredictionMarket: {
TokensMinted:
'0xef616469a0b35ce807813d17c53c505b9d4796a93287cd361318dbca99ac9250',
SwapExecuted:
'0x6c3029970cad07cf2c4bef13d30bdb7b6b77093a579d543839b5386cb0184b03',
},
TokenCommunityManager: {
CommunityNamespaceCreated:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const inputs = {
PredictionMarketProposalCreated: events.PredictionMarketProposalCreated,
PredictionMarketMarketCreated: events.PredictionMarketMarketCreated,
PredictionMarketTokensMinted: events.PredictionMarketTokensMinted,
PredictionMarketSwapExecuted: events.PredictionMarketSwapExecuted,
};

export function PredictionMarketProjection(): Projection<typeof inputs> {
Expand Down Expand Up @@ -120,6 +121,119 @@ export function PredictionMarketProjection(): Projection<typeof inputs> {
);
});
},
PredictionMarketSwapExecuted: async ({ payload }) => {
const {
market_id,
eth_chain_id,
transaction_hash,
trader_address,
buy_pass,
amount_in,
amount_out,
timestamp,
} = payload;

const market = await models.PredictionMarket.findOne({
where: { market_id },
});
if (!market) {
log.warn(
`PredictionMarketSwapExecuted: market not found for market_id=${market_id}`,
);
return;
}

await models.sequelize.transaction(async (transaction) => {
const action = buy_pass
? PredictionMarketTradeAction.SwapBuyPass
: PredictionMarketTradeAction.SwapBuyFail;

// For trade record: map swap amounts to token fields
// buyPass=true: spending f_tokens (amountIn), receiving p_tokens (amountOut)
// buyPass=false: spending p_tokens (amountIn), receiving f_tokens (amountOut)
const p_token_amount = buy_pass ? amount_out : amount_in;
const f_token_amount = buy_pass ? amount_in : amount_out;

// Insert trade (idempotent via composite PK)
const [, tradeCreated] =
await models.PredictionMarketTrade.findOrCreate({
where: { eth_chain_id, transaction_hash },
defaults: {
eth_chain_id,
transaction_hash,
prediction_market_id: market.id!,
trader_address,
action,
collateral_amount: 0n,
p_token_amount,
f_token_amount,
timestamp,
},
transaction,
});

// Skip position/market updates if trade already existed (idempotency)
if (!tradeCreated) return;

// Upsert position: swap exchanges one token for the other
// buyPass=true: p_token += amount_out, f_token -= amount_in
// buyPass=false: f_token += amount_out, p_token -= amount_in
const [position, positionCreated] =
await models.PredictionMarketPosition.findOrCreate({
where: {
prediction_market_id: market.id!,
user_address: trader_address,
},
defaults: {
prediction_market_id: market.id!,
user_address: trader_address,
p_token_balance: buy_pass ? amount_out : 0n,
f_token_balance: buy_pass ? 0n : amount_out,
total_collateral_in: 0n,
},
transaction,
});

if (!positionCreated) {
if (buy_pass) {
await models.PredictionMarketPosition.update(
{
p_token_balance: models.sequelize.literal(
`p_token_balance + ${amount_out}`,
) as unknown as bigint,
f_token_balance: models.sequelize.literal(
`f_token_balance - ${amount_in}`,
) as unknown as bigint,
},
{ where: { id: position.id }, transaction },
);
} else {
await models.PredictionMarketPosition.update(
{
f_token_balance: models.sequelize.literal(
`f_token_balance + ${amount_out}`,
) as unknown as bigint,
p_token_balance: models.sequelize.literal(
`p_token_balance - ${amount_in}`,
) as unknown as bigint,
},
{ where: { id: position.id }, transaction },
);
}
}

// Update market probability from swap ratio
const total = Number(amount_in) + Number(amount_out);
const probability = buy_pass
? Number(amount_out) / total
: Number(amount_in) / total;

await models.PredictionMarket.update(
{ current_probability: probability },
{ where: { id: market.id }, transaction },
);
});
},
},
};
}
27 changes: 27 additions & 0 deletions libs/model/src/services/evmChainEvents/chain-event-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CommunityStakeAbi,
ContestGovernorAbi,
ContestGovernorSingleAbi,
FutarchyRouterAbi,
LPBondingCurveAbi,
NamespaceFactoryAbi,
ReferralFeeManagerAbi,
Expand Down Expand Up @@ -371,6 +372,30 @@ const predictionMarketTokensMintedMapper: EvmMapper<
};
};

const predictionMarketSwapExecutedMapper: EvmMapper<
'PredictionMarketSwapExecuted'
> = (event: EvmEvent) => {
const decoded = decodeLog({
abi: FutarchyRouterAbi,
eventName: 'SwapExecuted',
data: event.rawLog.data,
topics: event.rawLog.topics,
});
return {
event_name: 'PredictionMarketSwapExecuted',
event_payload: {
market_id: decoded.args.marketId,
eth_chain_id: event.eventSource.ethChainId,
transaction_hash: event.rawLog.transactionHash as `0x${string}`,
trader_address: decoded.args.user,
buy_pass: decoded.args.buyPass,
amount_in: decoded.args.amountIn,
amount_out: decoded.args.amountOut,
timestamp: Number(event.block.timestamp),
},
};
};

const communityNamespaceCreatedMapper: EvmMapper<
'CommunityNamespaceCreated'
> = (event: EvmEvent) => {
Expand Down Expand Up @@ -519,6 +544,8 @@ export const chainEventMappers: Record<string, EvmMapper<OutboxEvents>> = {
// Prediction Markets
[EvmEventSignatures.PredictionMarket.TokensMinted]:
predictionMarketTokensMintedMapper,
[EvmEventSignatures.PredictionMarket.SwapExecuted]:
predictionMarketSwapExecutedMapper,

// TokenCommunityManager
[EvmEventSignatures.TokenCommunityManager.CommunityNamespaceCreated]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ describe('Prediction Market Mint', () => {
chainEventMappers[EvmEventSignatures.PredictionMarket.TokensMinted];
expect(mapper).toBeDefined();

const traderAddress = '0x1234567890123456789012345678901234567890';
const amount = 1000000000000000000n; // 1e18

const evmEvent: EvmEvent = {
Expand Down
Loading
Loading