← Back
// Multi-user: approve EVERY deposit wallet for ALL standard+neg-risk spenders (gasless).
// Idempotent — skips wallets already fully approved. Based on approve-dw.mjs.
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, createPublicClient, http, encodeFunctionData, erc20Abi, maxUint256, getAddress } from "viem";
import { toAccount } from "viem/accounts";
import { polygon } from "viem/chains";

const { RelayClient, RelayerTransactionState } = pkg;
const { BuilderConfig } = signingPkg;
const SERVICE_TOKEN = process.env.SERVICE_TOKEN || "";

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();

const PUSD = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB";
const CTF = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045";
const SPENDERS = ["0xE111180000d2663C0091e4f400237545B87B996B","0xe2222d279d744050d28e00520010520000310F59","0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296","0xC5d563A36AE78145C45a50134d48A1215220f80a"];
const ctfAbi = [
  { inputs: [{ name: "operator", type: "address" }, { name: "approved", type: "bool" }], name: "setApprovalForAll", outputs: [], stateMutability: "nonpayable", type: "function" },
  { inputs: [{ name: "account", type: "address" }, { name: "operator", type: "address" }], name: "isApprovedForAll", outputs: [{ type: "bool" }], stateMutability: "view", type: "function" },
];
const pub = createPublicClient({ chain: polygon, transport: http("https://polygon-bor-rpc.publicnode.com") });
const builderConfig = new BuilderConfig({ remoteBuilderConfig: { url: "http://127.0.0.1:3240/api/polymarket/sign" } });
const THRESH = 1000000000000n;

function makeAccount(EOA) {
  return 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);
      return (await privy.walletApi.ethereum.signTypedData({ address: EOA, chainType: "ethereum", typedData: { ...td, message: fix(td.message) } })).signature;
    },
    async signTransaction() { throw new Error("n/a"); },
  });
}
async function needsApproval(dw) {
  for (const sp of SPENDERS) {
    const a = await pub.readContract({ address: PUSD, abi: erc20Abi, functionName: "allowance", args: [getAddress(dw), getAddress(sp)] });
    if (a < THRESH) return true;
    const ok = await pub.readContract({ address: CTF, abi: ctfAbi, functionName: "isApprovedForAll", args: [getAddress(dw), getAddress(sp)] });
    if (!ok) return true;
  }
  return false;
}

const r = await fetch("http://127.0.0.1:3240/api/copy/wallets", { headers: { "x-service-token": SERVICE_TOKEN } });
const wallets = (await r.json()).data || [];
console.log("wallets:", wallets.length);
let done = 0, skipped = 0, failed = 0, noeoa = 0;
for (const w of wallets) {
  const dw = w.depositWallet, ownerEoa = (w.ownerEoa || "").toLowerCase();
  let EOA = null;
  for (const u of users) for (const a of u.linkedAccounts || []) if (a.type === "wallet" && a.walletClientType === "privy" && a.address.toLowerCase() === ownerEoa) EOA = a.address;
  if (!EOA) { console.log("  no EOA for", dw.slice(0,10)); noeoa++; continue; }
  try {
    if (!(await needsApproval(dw))) { console.log("  already approved", dw.slice(0,10)); skipped++; continue; }
    const account = makeAccount(EOA);
    const walletClient = createWalletClient({ account, chain: polygon, transport: http("https://polygon.drpc.org") });
    const relay = new RelayClient("https://relayer-v2.polymarket.com/", 137, walletClient, builderConfig);
    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).toString();
    const resp = await relay.executeDepositWalletBatch(calls, dw, deadline);
    const res = await relay.pollUntilState(resp.transactionID, [RelayerTransactionState.STATE_CONFIRMED, RelayerTransactionState.STATE_FAILED], "90", 3000);
    const ok = res?.state === RelayerTransactionState.STATE_CONFIRMED;
    console.log(ok ? "  CONFIRMED" : "  NOT-CONFIRMED", dw.slice(0,10), resp.transactionID);
    ok ? done++ : failed++;
  } catch (e) { console.log("  ERR", dw.slice(0,10), String(e.message).slice(0,90)); failed++; }
}
console.log(`DONE: approved=${done} skipped=${skipped} failed=${failed} noEOA=${noeoa}`);