Skip to content
Open
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
3 changes: 3 additions & 0 deletions libs/evm-protocols/src/common-protocol/chainConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export const factoryContracts: FactoryContractsType = {
ReferralFeeManager: '0x9d3BE262bed6F3A0AAb4E97c0232071EF730632f',
TokenLaunchpad: '0xACD353A1Bf569662E2e0e5Dd127FeCbee505986c',
TokenBondingCurve: '0xC815d94E05dCAa6D84a740291A03A1e73BF48C1f',
BinaryVault: '0x9F43B07FF2B63F895930017D727c3dfA58fB838c',
FutarchyRouter: '0xFE985509910e5C1f667C2E8AD86349c50f35f234',
FutarchyGovernor: '0xD454743008A7c127136bDc0DFA1A24524F3Daf66',
chainId: 8453,
},
[ValidChains.Linea]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,32 @@ function formatRevertReason(err: unknown): string | null {
}
}

function normalizeAddress(
web3Like: { utils: { toChecksumAddress: (value: string) => string } },
value: string,
fieldName: string,
): `0x${string}` {
try {
return web3Like.utils.toChecksumAddress(value) as `0x${string}`;
} catch {
throw new Error(`Invalid ${fieldName} address: ${value}`);
}
}

/** ERC-20 allowance cap for uint256-sized values passed to approve(). */
const MAX_UINT256 = 2n ** 256n - 1n;

/**
* Allowance to request before propose(): 2× initial liquidity (capped at MAX_UINT256).
* Gives headroom for protocols that pull collateral in more than one transferFrom,
* without granting unlimited spend.
*/
function allowanceForPropose(initialLiquidityWei: bigint): bigint {
if (initialLiquidityWei <= 0n) return 0n;
if (initialLiquidityWei > MAX_UINT256 / 2n) return MAX_UINT256;
return initialLiquidityWei * 2n;
}

class PredictionMarket extends ContractBase {
constructor(governorAddress: string, rpc: string) {
super(governorAddress, FutarchyGovernorAbi as unknown as AbiItem[], rpc);
Expand Down Expand Up @@ -151,25 +177,36 @@ class PredictionMarket extends ContractBase {
logs?: Array<{ address?: string; data?: string; topics?: string[] }>;
}> {
this.isInitialized();
const normalizedCollateralAddress = normalizeAddress(
this.web3,
collateralAddress,
'collateral',
);
const normalizedFromAddress = normalizeAddress(
this.web3,
fromAddress,
'wallet',
);

// Approve governor to spend collateral before propose (required when initialLiquidity > 0).
// Matches common-protocol prediction_market_helpers_frontend: approve then propose.
if (initialLiquidityWei > 0n) {
const collateralToken = new this.web3.eth.Contract(
erc20Abi as unknown as AbiItem[],
collateralAddress,
normalizedCollateralAddress,
);
const spender = this.contractAddress;
const currentAllowance = BigInt(
(await collateralToken.methods
.allowance(fromAddress, spender)
.allowance(normalizedFromAddress, spender)
.call()) as string,
);
if (currentAllowance < initialLiquidityWei) {
const targetAllowance = allowanceForPropose(initialLiquidityWei);
if (currentAllowance < targetAllowance) {
try {
await collateralToken.methods
.approve(spender, initialLiquidityWei)
.send({ from: fromAddress });
.approve(spender, targetAllowance.toString())
.send({ from: normalizedFromAddress });
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
if (/user rejected|denied|reject/i.test(msg)) {
Expand All @@ -182,10 +219,10 @@ class PredictionMarket extends ContractBase {
}
}
const currentBalance = BigInt(
(await collateralToken.methods.balanceOf(fromAddress).call()) as string,
(await collateralToken.methods
.balanceOf(normalizedFromAddress)
.call()) as string,
);
console.log('currentBalance', currentBalance);
console.log('initialLiquidityWei', initialLiquidityWei);
if (currentBalance < initialLiquidityWei) {
throw new Error(
'Insufficient collateral balance for initial liquidity.',
Expand All @@ -196,15 +233,15 @@ class PredictionMarket extends ContractBase {
const tx = this.contract.methods.propose(
proposalId,
marketId,
collateralAddress,
normalizedCollateralAddress,
durationSeconds,
resolutionThreshold,
initialLiquidityWei,
);
try {
const gas = await tx.estimateGas({ from: fromAddress });
const gas = await tx.estimateGas({ from: normalizedFromAddress });
return await tx.send({
from: fromAddress,
from: normalizedFromAddress,
gas: String(BigInt(gas.toString()) + 100000n),
});
} catch (err) {
Expand All @@ -227,8 +264,17 @@ class PredictionMarket extends ContractBase {
* Propose a new market, decode events, and return the payload for deployPredictionMarket mutation.
*/
async deploy(params: DeployParams): Promise<DeployPredictionMarketPayload> {
console.log('params => ', params);
this.isInitialized();
const normalizedCollateralAddress = normalizeAddress(
this.web3,
params.collateral_address,
'collateral',
);
const normalizedUserAddress = normalizeAddress(
this.web3,
params.user_address,
'wallet',
);
const proposalId = randomBytes32();
const marketId = randomBytes32();
const durationDays = Math.max(1, Math.floor(params.duration_days || 1));
Expand Down Expand Up @@ -258,7 +304,7 @@ class PredictionMarket extends ContractBase {
if (liquidityInput && liquidityInput !== '0') {
const collateralToken = new this.web3.eth.Contract(
erc20Abi as unknown as AbiItem[],
params.collateral_address,
normalizedCollateralAddress,
);
const decimals = Number(
(await collateralToken.methods.decimals().call()) as string | number,
Expand All @@ -279,11 +325,11 @@ class PredictionMarket extends ContractBase {
const rawReceipt = await this.propose(
proposalId,
marketId,
params.collateral_address,
normalizedCollateralAddress,
durationSeconds,
resolutionThresholdWei,
initialLiquidityWei,
params.user_address,
normalizedUserAddress,
);

const logs: Array<{ address: string; data: string; topics: string[] }> = (
Expand Down
Loading
Loading