← Back
/** M3-final-a: deposit wallet approves the CTF Exchange to spend its pUSD, via the
 *  relayer batch (server-signed). Needed before BUY orders settle. */
import fs from 'node:fs';
import pkg from '@polymarket/builder-relayer-client';
import signingPkg from '@polymarket/builder-signing-sdk';
import { PrivyClient } from '@privy-io/server-auth';
import { createWalletClient, http, encodeFunctionData, erc20Abi, maxUint256 } from 'viem';
import { toAccount } from 'viem/accounts';
import { polygon } from 'viem/chains';

const { RelayClient, RelayerTransactionState } = pkg;
const { BuilderConfig } = signingPkg;
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 users = await privy.getUsers();
// DW 0x50A8… is owned by this EOA per the relayer wallet registry (NOT the last
// global privy wallet). Match by lowercase to recover Privy's stored checksum casing.
const OWNER = '0xE2D892E35a55811c4215B14EC5ca59B3f9633d3F';
let EOA = null;
for (const u of users) for (const a of u.linkedAccounts || []) if (a.type === 'wallet' && a.walletClientType === 'privy' && a.address.toLowerCase() === OWNER.toLowerCase()) EOA = a.address;
if (!EOA) throw new Error(`owner EOA ${OWNER} not among Privy embedded wallets`);

const account = toAccount({
  address: EOA,
  async signMessage({ message }) { const m = typeof message === 'string' ? message : (message.raw ?? message); return (await privy.walletApi.ethereum.signMessage({ address: EOA, chainType: 'ethereum', message: m })).signature; },
  async signTypedData(td) {
    const fix = (v) => typeof v === 'bigint' ? v.toString() : Array.isArray(v) ? v.map(fix) : (v && typeof v === 'object' ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, fix(x)])) : v);
    const typedData = { ...td, message: fix(td.message) };
    return (await privy.walletApi.ethereum.signTypedData({ address: EOA, chainType: 'ethereum', typedData })).signature;
  },
  async signTransaction() { throw new Error('not supported'); },
});
const walletClient = createWalletClient({ account, chain: polygon, transport: http('https://polygon.drpc.org') });
const builderConfig = new BuilderConfig({ remoteBuilderConfig: { url: 'http://127.0.0.1:3240/api/polymarket/sign' } });
const relay = new RelayClient('https://relayer-v2.polymarket.com/', 137, walletClient, builderConfig);

const DW = '0x50A8061e9448EB1e5d5e7aF07BE4E4F63C6F24Ff';
const PUSD = '0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB';
const EXCHANGE = '0xE111180000d2663C0091e4f400237545B87B996B';
// V2 neg-risk exchange — settles neg-risk markets (tournaments/sports "who wins").
// The CLOB API named this spender in the "allowance is not enough" reject. pUSD must
// be approved here for BUY; CTF operator approval here for SELL/close.
const NEG_RISK_V2 = '0xe2222d279d744050d28e00520010520000310F59';
const CTF = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045';
const ctfAbi = [{ inputs: [{ name: 'operator', type: 'address' }, { name: 'approved', type: 'bool' }], name: 'setApprovalForAll', outputs: [], stateMutability: 'nonpayable', type: 'function' }];
const NEG_RISK_ADAPTER = '0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296';
const NEG_RISK_CTF = '0xC5d563A36AE78145C45a50134d48A1215220f80a';
const SPENDERS = [EXCHANGE, NEG_RISK_V2, NEG_RISK_ADAPTER, NEG_RISK_CTF];
const calls = [
  ...SPENDERS.map((sp) => ({ target: PUSD, value: '0', data: encodeFunctionData({ abi: erc20Abi, functionName: 'approve', args: [sp, maxUint256] }) })),
  ...SPENDERS.map((op) => ({ target: CTF, value: '0', data: encodeFunctionData({ abi: ctfAbi, functionName: 'setApprovalForAll', args: [op, true] }) })),
];
const deadline = Math.floor(Date.now() / 1000) + 3600;
console.log('approving pUSD → exchange from deposit wallet', DW);
const resp = await relay.executeDepositWalletBatch(calls, DW, deadline.toString());
console.log('relayer tx:', resp.transactionID, '| state:', resp.state);
const result = await relay.pollUntilState(resp.transactionID, [RelayerTransactionState.STATE_CONFIRMED, RelayerTransactionState.STATE_FAILED], '90', 3000);
console.log('final:', JSON.stringify(result).slice(0, 160));
console.log(result?.state === RelayerTransactionState.STATE_CONFIRMED ? '🎯 pUSD approval CONFIRMED' : '⚠️ not confirmed');