← Back
import { useCallback } from 'react';
import { createPublicClient, http } from 'viem';
import { polygon } from 'viem/chains';
import { RelayClient, RelayerTransactionState } from '@polymarket/builder-relayer-client';
import { createDwApprovalCalls, createWithdrawCall, type DwCall } from '../utils/dwApprovals';
import { buildRedeemCalls, buildWrapCalls } from '../utils/dwRedeem';

const pub = createPublicClient({ chain: polygon, transport: http('https://polygon.drpc.org') });

// The shipped .d.ts omits the deposit-wallet methods (present in the .js).
// Cast through a narrow interface — same pattern as useSafeDeployment.getDeployed.
type DwRelay = {
  deriveDepositWalletAddress: () => Promise<string>;
  deployDepositWallet: () => Promise<{ transactionID: string }>;
  executeDepositWalletBatch: (calls: DwCall[], walletAddress: string, deadline: string) => Promise<{ transactionID: string }>;
};
const asDw = (r: RelayClient) => r as unknown as DwRelay;

/**
 * Derive + deploy the user's Polymarket DEPOSIT-WALLET (per-user, deterministic
 * from the embedded EOA), gasless via the RelayClient. This is the wallet the
 * copy-trading demon trades from (sigType3 / POLY_1271) — NOT a Gnosis Safe.
 * Mirrors server-side node-executor/deploy-dw.mjs so the client derives the
 * SAME address the server already provisioned for existing users.
 */
export default function useDepositWallet() {
  // Derive via the relay (handles beacon vs UUPS) so it matches the server.
  const deriveDepositWallet = useCallback(
    async (relay: RelayClient): Promise<string> => asDw(relay).deriveDepositWalletAddress(),
    [],
  );

  const isDepositWalletDeployed = useCallback(async (addr: string): Promise<boolean> => {
    const code = await pub.getCode({ address: addr as `0x${string}` });
    return !!code && code !== '0x';
  }, []);

  // Deploy is a gasless WALLET-CREATE (no signature needed); poll to confirmation.
  const deployDepositWallet = useCallback(async (relay: RelayClient): Promise<string> => {
    const resp = await asDw(relay).deployDepositWallet();
    const result = await relay.pollUntilState(
      resp.transactionID,
      [
        RelayerTransactionState.STATE_MINED,
        RelayerTransactionState.STATE_CONFIRMED,
        RelayerTransactionState.STATE_FAILED,
      ],
      '60',
      3000,
    );
    if (!result) throw new Error('Deposit wallet deployment failed');
    // Return the deterministic address (poll result has no proxyAddress for WALLET-CREATE).
    return asDw(relay).deriveDepositWalletAddress();
  }, []);

  // Set the 3 trading approvals on the DW (pUSD→ExchangeV2, CTF→ExchangeV2+NegRisk),
  // gasless via the relayer batch. The owner EOA signs the batch (onboarding, user-present)
  // — done BEFORE the orders-only Privy policy is locked onto the server delegate.
  const approveDepositWallet = useCallback(async (relay: RelayClient, dw: string): Promise<boolean> => {
    const deadline = String(Math.floor(Date.now() / 1000) + 3600);
    const resp = await asDw(relay).executeDepositWalletBatch(createDwApprovalCalls(), dw, deadline);
    const result = await relay.pollUntilState(
      resp.transactionID,
      [RelayerTransactionState.STATE_CONFIRMED, RelayerTransactionState.STATE_FAILED],
      '90',
      3000,
    );
    return result?.state === RelayerTransactionState.STATE_CONFIRMED;
  }, []);

  // Withdraw pUSD from the DW to `to`, signed by the owner EOA (the user, present).
  // The orders-only Privy policy is on the SERVER delegate, not the user's own auth,
  // so the user can always move their own funds out.
  const withdrawDepositWallet = useCallback(async (relay: RelayClient, dw: string, to: string, amountRaw: bigint): Promise<boolean> => {
    const deadline = String(Math.floor(Date.now() / 1000) + 3600);
    const resp = await asDw(relay).executeDepositWalletBatch([createWithdrawCall(to, amountRaw)], dw, deadline);
    const result = await relay.pollUntilState(
      resp.transactionID,
      [RelayerTransactionState.STATE_CONFIRMED, RelayerTransactionState.STATE_FAILED],
      '90',
      3000,
    );
    return result?.state === RelayerTransactionState.STATE_CONFIRMED;
  }, []);

  // USER-PRESENT redeem of resolved positions → USDC.e → pUSD, signed by the user's own embedded
  // wallet (owner) via the relayer batch. The orders-only Privy policy restricts only the SERVER
  // delegate, so owner-signed redeem/wrap is NOT blocked. Returns the # of conditions redeemed.
  const redeemResolved = useCallback(async (relay: RelayClient, dw: string): Promise<number> => {
    const runBatch = async (calls: DwCall[]) => {
      const deadline = String(Math.floor(Date.now() / 1000) + 3600);
      const resp = await asDw(relay).executeDepositWalletBatch(calls, dw, deadline);
      await relay.pollUntilState(
        resp.transactionID,
        [RelayerTransactionState.STATE_CONFIRMED, RelayerTransactionState.STATE_FAILED],
        '90',
        3000,
      );
    };
    const redeem = await buildRedeemCalls(dw);
    if (redeem.length) await runBatch(redeem);
    const wrap = await buildWrapCalls(dw);   // re-check after redeem so freshly-freed USDC.e wraps
    if (wrap.length) await runBatch(wrap);
    return redeem.length;
  }, []);

  return { deriveDepositWallet, isDepositWalletDeployed, deployDepositWallet, approveDepositWallet, withdrawDepositWallet, redeemResolved };
}

📜 Git History

6c47fa4chore: local Polikopi project home + Phase 1 redesign artifacts12 days ago
Show last diff
Loading...