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...