/** M3c: full live copy-order โ server authenticates + signs + submits a real
* Polymarket order on behalf of the user (sigType 2, maker=Safe). Resting 10ยข
* BUY limit so it won't fill. All server-side, no user present. */
import fs from 'node:fs';
import { hashTypedData } from 'viem';
import { PrivyClient } from '@privy-io/server-auth';
const env = {};
for (const l of fs.readFileSync(new URL('../.env.privy', import.meta.url), 'utf8').split('\n')) {
const t = l.trim(); if (!t || t.startsWith('#')) continue;
const i = t.indexOf('='); if (i > 0) env[t.slice(0, i).trim()] = t.slice(i + 1).trim();
}
const privy = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET, {
walletApi: { authorizationPrivateKey: env.PRIVY_AUTHORIZATION_KEY },
});
const SCREENER = 'http://localhost:3240';
const SAFE = '0x7570210eACa5F3783577997fF74e8FEbe9B9E1f4';
const post = async (p, b) => { const r = await fetch(SCREENER + p, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(b) }); return { status: r.status, body: await r.json().catch(() => ({})) }; };
const users = await privy.getUsers();
let eoa = null;
for (const u of users) for (const a of u.linkedAccounts || []) if (a.type === 'wallet' && a.walletClientType === 'privy') eoa = a.address;
console.log('EOA (signer):', eoa, '| Safe (maker):', SAFE);
// 1. auth โ server signs ClobAuth โ create creds
const ts = Math.floor(Date.now() / 1000).toString();
const authTD = { domain: { name: 'ClobAuthDomain', version: '1', chainId: 137 },
types: { ClobAuth: [{ name: 'address', type: 'address' }, { name: 'timestamp', type: 'string' }, { name: 'nonce', type: 'uint256' }, { name: 'message', type: 'string' }] },
primaryType: 'ClobAuth', message: { address: eoa, timestamp: ts, nonce: '0', message: 'This message attests that I control the given wallet' } };
const { signature: authSig } = await privy.walletApi.ethereum.signTypedData({ address: eoa, chainType: 'ethereum', typedData: authTD });
let cr = await post('/api/auth/create-key', { address: eoa, signature: authSig, timestamp: ts, nonce: 0 });
if (!cr.body?.success) cr = await post('/api/auth/derive-key', { address: eoa, signature: authSig, timestamp: ts, nonce: 0 });
console.log('[auth]', cr.status, cr.body?.success ? 'creds OK' : JSON.stringify(cr.body).slice(0, 120));
if (!cr.body?.success) process.exit(1);
// 2. build order (sigType 2), server signs
const CTF_EXCHANGE = '0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E'; // V1 exchange (has getSafeAddress)
const ZERO32 = '0x' + '00'.repeat(32);
const BUILDER = '0x3c829d5150b70f3ba347670d4b1eda96be3c255b3f64895a7eeef5caea7952d5';
const TOKEN = '88275040060084773376557187972215267513049848642895776801789297917961077894224';
const types = { Order: [
{ name: 'salt', type: 'uint256' }, { name: 'maker', type: 'address' }, { name: 'signer', type: 'address' },
{ name: 'tokenId', type: 'uint256' }, { name: 'makerAmount', type: 'uint256' }, { name: 'takerAmount', type: 'uint256' },
{ name: 'side', type: 'uint8' }, { name: 'signatureType', type: 'uint8' }, { name: 'timestamp', type: 'uint256' },
{ name: 'metadata', type: 'bytes32' }, { name: 'builder', type: 'bytes32' } ] };
const domain = { name: 'Polymarket CTF Exchange', version: '2', chainId: 137, verifyingContract: CTF_EXCHANGE };
const salt = BigInt(Math.floor(Math.random() * Date.now()));
const tnow = BigInt(Date.now());
const msg = { salt, maker: SAFE, signer: eoa, tokenId: BigInt(TOKEN),
makerAmount: 1000000n, takerAmount: 10000000n, side: 0, signatureType: 2, timestamp: tnow, metadata: ZERO32, builder: BUILDER };
void hashTypedData({ domain, types, primaryType: 'Order', message: msg });
const sMsg = { ...msg, salt: salt.toString(), tokenId: TOKEN, makerAmount: '1000000', takerAmount: '10000000', timestamp: tnow.toString() };
const { signature } = await privy.walletApi.ethereum.signTypedData({ address: eoa, chainType: 'ethereum', typedData: { domain, types, primaryType: 'Order', message: sMsg } });
const signed_order = { order: { salt: Number(salt), maker: SAFE, signer: eoa, tokenId: TOKEN,
makerAmount: '1000000', takerAmount: '10000000', side: 'BUY', expiration: '0', signatureType: 2,
timestamp: tnow.toString(), metadata: ZERO32, builder: BUILDER, signature } };
// 3. submit
const ord = await post('/api/trade/order', { signedOrder: signed_order, orderType: 'GTC', owner: eoa });
console.log('\n[ORDER]', ord.status, JSON.stringify(ord.body).slice(0, 400));
console.log('\n' + (ord.body?.success ? '๐ฏ๐ฏ M3c PASS โ LIVE COPY-ORDER PLACED on Polymarket via delegated server signing!' : 'โ ๏ธ CLOB response above is the verdict (likely allowance/wrap โ next step)'));