| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- import 'dotenv/config';
- import axios from "axios";
- import { HttpsProxyAgent } from "https-proxy-agent";
- import { BuilderConfig } from "@polymarket/builder-signing-sdk";
- import {
- deriveProxyWallet,
- RelayerTxType,
- RelayClient,
- } from "@polymarket/builder-relayer-client";
- import {
- createPublicClient,
- createWalletClient,
- encodeFunctionData,
- erc20Abi,
- formatUnits,
- http,
- maxUint256,
- parseUnits,
- } from "viem";
- import { privateKeyToAccount } from "viem/accounts";
- import { polygon } from "viem/chains";
- const CHAIN_ID = 137;
- const PUSD_ADDRESS = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB";
- const PUSD_DECIMALS = 6;
- const PROXY_FACTORY_ADDRESS = "0xaB45c5A4B0c941a2F231C04C3f49182e1A254052";
- const DEFAULT_CLOB_SPENDERS = [
- "0xE111180000d2663C0091e4f400237545B87B996B",
- "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296",
- "0xe2222d279d744050d28e00520010520000310F59",
- ];
- const NODE_HTTP_PROXY = process.env.NODE_HTTP_PROXY;
- const proxyAgent = NODE_HTTP_PROXY ? new HttpsProxyAgent(NODE_HTTP_PROXY) : undefined;
- if (NODE_HTTP_PROXY) {
- axios.defaults.proxy = false;
- axios.defaults.httpAgent = proxyAgent;
- axios.defaults.httpsAgent = proxyAgent;
- }
- const getRequiredEnv = (key) => {
- const value = process.env[key];
- if (!value) {
- throw new Error(`${key} is required`);
- }
- return value;
- }
- const getOptionalEnv = (key) => {
- const value = process.env[key];
- return value && value.trim() ? value.trim() : undefined;
- }
- const normalizePrivateKey = (privateKey) => {
- return privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
- }
- const getArgValue = (name) => {
- const prefix = `${name}=`;
- const arg = process.argv.slice(2).find(item => item.startsWith(prefix));
- return arg ? arg.slice(prefix.length) : undefined;
- }
- const normalizeDirection = () => {
- const from = (getArgValue("--from") || process.env.TRANSFER_FROM || "proxy").toLowerCase();
- const to = (getArgValue("--to") || process.env.TRANSFER_TO || "deposit").toLowerCase();
- if (!["proxy", "deposit"].includes(from) || !["proxy", "deposit"].includes(to) || from === to) {
- throw new Error("Transfer direction must be proxy -> deposit or deposit -> proxy");
- }
- return { from, to };
- }
- const normalizeAction = () => {
- const action = (getArgValue("--action") || process.env.TRANSFER_ACTION || "transfer").toLowerCase();
- if (!["transfer", "approve"].includes(action)) {
- throw new Error("action must be transfer or approve");
- }
- return action;
- }
- const normalizeWalletMode = () => {
- const wallet = (getArgValue("--wallet") || process.env.APPROVE_WALLET || "deposit").toLowerCase();
- if (!["proxy", "deposit", "both"].includes(wallet)) {
- throw new Error("wallet must be proxy, deposit, or both");
- }
- return wallet;
- }
- const getClobSpenders = () => {
- const configured = getArgValue("--spenders") || process.env.CLOB_SPENDERS;
- if (!configured) {
- return DEFAULT_CLOB_SPENDERS;
- }
- const spenders = configured.split(",").map(item => item.trim()).filter(Boolean);
- if (!spenders.length) {
- throw new Error("CLOB spenders cannot be empty");
- }
- return spenders;
- }
- const action = normalizeAction();
- const amount = getArgValue("--amount") || process.env.TRANSFER_AMOUNT;
- if (action === "transfer" && (!amount || Number(amount) <= 0)) {
- throw new Error("Transfer amount is required. Example: npm run transfer -- --amount=10");
- }
- const direction = action === "transfer" ? normalizeDirection() : undefined;
- const walletMode = action === "approve" ? normalizeWalletMode() : undefined;
- const clobSpenders = getClobSpenders();
- const execute = process.argv.includes("--execute");
- const relayerUrl = getOptionalEnv("POLYMARKET_RELAYER_URL") || "https://relayer-v2.polymarket.com";
- const rpcUrl = getOptionalEnv("POLYGON_RPC_URL");
- const account = privateKeyToAccount(normalizePrivateKey(getRequiredEnv("POLYMARKET_PRIVATE_KEY")));
- const walletTransport = rpcUrl ? http(rpcUrl) : http();
- const signer = createWalletClient({ account, chain: polygon, transport: walletTransport });
- const publicClient = createPublicClient({ chain: polygon, transport: walletTransport });
- const builderConfig = new BuilderConfig({
- localBuilderCreds: {
- key: getRequiredEnv("POLYMARKET_BUILDER_API_KEY"),
- secret: getRequiredEnv("POLYMARKET_BUILDER_SECRET"),
- passphrase: getRequiredEnv("POLYMARKET_BUILDER_PASS_PHRASE"),
- },
- });
- const createRelayer = (relayTxType = RelayerTxType.PROXY) => {
- const relayer = new RelayClient(relayerUrl, CHAIN_ID, signer, builderConfig, relayTxType);
- if (proxyAgent) {
- relayer.httpClient.instance.defaults.proxy = false;
- relayer.httpClient.instance.defaults.httpAgent = proxyAgent;
- relayer.httpClient.instance.defaults.httpsAgent = proxyAgent;
- }
- return relayer;
- }
- const proxyRelayer = createRelayer(RelayerTxType.PROXY);
- const proxyWalletAddress = getOptionalEnv("POLYMARKET_PROXY_WALLET_ADDRESS")
- || deriveProxyWallet(account.address, PROXY_FACTORY_ADDRESS);
- const depositWalletAddress = getOptionalEnv("POLYMARKET_DEPOSIT_WALLET_ADDRESS")
- || await proxyRelayer.deriveDepositWalletAddress();
- const [proxyBalance, depositBalance] = await Promise.all([
- publicClient.readContract({
- address: PUSD_ADDRESS,
- abi: erc20Abi,
- functionName: "balanceOf",
- args: [proxyWalletAddress],
- }),
- publicClient.readContract({
- address: PUSD_ADDRESS,
- abi: erc20Abi,
- functionName: "balanceOf",
- args: [depositWalletAddress],
- }),
- ]);
- const baseResult = {
- owner: account.address,
- proxyWalletAddress,
- depositWalletAddress,
- pUSD: PUSD_ADDRESS,
- proxyBalance: formatUnits(proxyBalance, PUSD_DECIMALS),
- depositBalance: formatUnits(depositBalance, PUSD_DECIMALS),
- execute,
- };
- const createApproveData = (spender) => {
- return encodeFunctionData({
- abi: erc20Abi,
- functionName: "approve",
- args: [spender, maxUint256],
- });
- }
- const runApprove = async () => {
- const wallets = [];
- if (walletMode === "proxy" || walletMode === "both") {
- wallets.push({
- type: "proxy",
- address: proxyWalletAddress,
- calls: clobSpenders.map(spender => ({
- to: PUSD_ADDRESS,
- data: createApproveData(spender),
- value: "0",
- })),
- });
- }
- if (walletMode === "deposit" || walletMode === "both") {
- wallets.push({
- type: "deposit",
- address: depositWalletAddress,
- calls: clobSpenders.map(spender => ({
- target: PUSD_ADDRESS,
- data: createApproveData(spender),
- value: "0",
- })),
- });
- }
- console.log(JSON.stringify({
- ...baseResult,
- action,
- wallet: walletMode,
- spenders: clobSpenders,
- approvals: wallets.map(item => ({
- type: item.type,
- address: item.address,
- spenderCount: item.calls.length,
- })),
- }, null, 2));
- if (!execute) {
- console.log(`Dry run only. Add --execute to submit CLOB approvals for ${walletMode} wallet.`);
- return;
- }
- const results = [];
- for (const wallet of wallets) {
- const response = wallet.type === "proxy"
- ? await proxyRelayer.execute(wallet.calls, "approve pUSD for CLOB")
- : await createRelayer().executeDepositWalletBatch(
- wallet.calls,
- depositWalletAddress,
- String(Math.floor(Date.now() / 1000) + 600),
- );
- const confirmed = await response.wait();
- results.push({
- type: wallet.type,
- address: wallet.address,
- transactionID: response.transactionID,
- transactionHash: response.transactionHash || response.hash,
- confirmed,
- });
- }
- console.log(JSON.stringify({ results }, null, 2));
- }
- const runTransfer = async () => {
- const transferAmount = parseUnits(amount, PUSD_DECIMALS);
- const result = {
- ...baseResult,
- action,
- from: direction.from,
- to: direction.to,
- sourceAddress: direction.from === "proxy" ? proxyWalletAddress : depositWalletAddress,
- destinationAddress: direction.to === "deposit" ? depositWalletAddress : proxyWalletAddress,
- amount,
- };
- console.log(JSON.stringify(result, null, 2));
- const sourceBalance = direction.from === "proxy" ? proxyBalance : depositBalance;
- if (sourceBalance < transferAmount) {
- throw new Error(`Insufficient ${direction.from} wallet pUSD balance: ${formatUnits(sourceBalance, PUSD_DECIMALS)}`);
- }
- if (!execute) {
- console.log(`Dry run only. Add --execute to submit the ${direction.from} -> ${direction.to} transfer.`);
- return;
- }
- const data = encodeFunctionData({
- abi: erc20Abi,
- functionName: "transfer",
- args: [result.destinationAddress, transferAmount],
- });
- const deadline = String(Math.floor(Date.now() / 1000) + 600);
- const depositRelayer = createRelayer();
- const response = direction.from === "proxy"
- ? await proxyRelayer.execute(
- [{
- to: PUSD_ADDRESS,
- data,
- value: "0",
- }],
- `transfer ${amount} pUSD from proxy to deposit wallet`,
- )
- : await depositRelayer.executeDepositWalletBatch(
- [{
- target: PUSD_ADDRESS,
- data,
- value: "0",
- }],
- depositWalletAddress,
- deadline,
- );
- const confirmed = await response.wait();
- console.log(JSON.stringify({
- transactionID: response.transactionID,
- transactionHash: response.transactionHash || response.hash,
- confirmed,
- }, null, 2));
- }
- if (action === "approve") {
- await runApprove();
- }
- else {
- await runTransfer();
- }
|