Skip to content

Commit

Permalink
progress on using POST /compact in the UI
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Dec 8, 2024
1 parent 1bc08e9 commit 426740a
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 103 deletions.
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ function App() {
{/* Balance Display */}
{sessionToken && (
<div className="mx-auto p-6 bg-[#0a0a0a] rounded-lg shadow-xl border border-gray-800">
<BalanceDisplay />
<BalanceDisplay sessionToken={sessionToken} />
</div>
)}

Expand Down
7 changes: 6 additions & 1 deletion frontend/src/components/BalanceDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { ForcedWithdrawalDialog } from './ForcedWithdrawalDialog';
import { useCompact } from '../hooks/useCompact';
import { useNotification } from '../hooks/useNotification';

interface BalanceDisplayProps {
sessionToken: string | null;
}

// Interface for the selected lock data needed by ForcedWithdrawalDialog
interface SelectedLockData {
chainId: string;
Expand Down Expand Up @@ -45,7 +49,7 @@ const formatResetPeriod = (seconds: number): string => {
return `${Math.floor(seconds / 86400)} days`;
};

export function BalanceDisplay(): JSX.Element | null {
export function BalanceDisplay({ sessionToken }: BalanceDisplayProps): JSX.Element | null {
const { isConnected } = useAccount();
const { balances, error, isLoading } = useBalances();
const { data: resourceLocksData, isLoading: resourceLocksLoading } =
Expand Down Expand Up @@ -297,6 +301,7 @@ export function BalanceDisplay(): JSX.Element | null {
}}
tokenSymbol={balance.token?.symbol || ''}
withdrawalStatus={balance.withdrawalStatus}
sessionToken={sessionToken}
onForceWithdraw={() => {
setSelectedLockId(balance.lockId);
setIsWithdrawalDialogOpen(true);
Expand Down
247 changes: 146 additions & 101 deletions frontend/src/components/Transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseUnits, formatUnits } from 'viem';
import { useNotification } from '../hooks/useNotification';
import { useAllocatedTransfer } from '../hooks/useAllocatedTransfer';
import { useAllocatedWithdrawal } from '../hooks/useAllocatedWithdrawal';
import { useRequestAllocation } from '../hooks/useRequestAllocation';
import { COMPACT_ADDRESS, COMPACT_ABI } from '../constants/contracts';

interface TransferProps {
Expand All @@ -18,16 +19,17 @@ interface TransferProps {
};
tokenSymbol: string;
withdrawalStatus: number;
sessionToken: string | null;
onForceWithdraw: () => void;
onDisableForceWithdraw: () => void;
}

interface FormData {
nonce: string;
expires: string;
recipient: string;
amount: string;
allocatorSignature: string;
allocatorSignature?: string;
nonce?: string;
}

interface WalletError extends Error {
Expand All @@ -46,6 +48,7 @@ export function Transfer({
tokenName,
tokenSymbol,
withdrawalStatus,
sessionToken,
onForceWithdraw,
onDisableForceWithdraw,
}: TransferProps) {
Expand All @@ -54,16 +57,17 @@ export function Transfer({
const [isOpen, setIsOpen] = useState(false);
const [isWithdrawal, setIsWithdrawal] = useState(false);
const [isWithdrawalLoading, setIsWithdrawalLoading] = useState(false);
const [isRequestingAllocation, setIsRequestingAllocation] = useState(false);
const [hasAllocation, setHasAllocation] = useState(false);
const [formData, setFormData] = useState<FormData>({
nonce: '',
expires: '',
recipient: '',
amount: '',
allocatorSignature: '',
});

const { allocatedTransfer, isPending: isTransferLoading } = useAllocatedTransfer();
const { allocatedWithdrawal, isPending: isWithdrawalPending } = useAllocatedWithdrawal();
const { requestAllocation } = useRequestAllocation();
const { showNotification } = useNotification();
const [fieldErrors, setFieldErrors] = useState<{ [key: string]: string | undefined }>({});

Expand Down Expand Up @@ -151,18 +155,7 @@ export function Transfer({

const isFormValid = useMemo(() => {
// Basic form validation
if (
!formData.nonce ||
!formData.expires ||
!formData.recipient ||
!formData.amount ||
!formData.allocatorSignature
) {
return false;
}

// Check if nonce has been consumed
if (nonceError) {
if (!formData.expires || !formData.recipient || !formData.amount) {
return false;
}

Expand All @@ -178,7 +171,7 @@ export function Transfer({
}

return true;
}, [formData, nonceError, fieldErrors]);
}, [formData, fieldErrors]);

const handleAction = async (action: 'transfer' | 'withdraw' | 'force' | 'disable') => {
// Check if we need to switch networks
Expand Down Expand Up @@ -248,30 +241,79 @@ export function Transfer({
}
};

const handleRequestAllocation = async () => {
if (!isFormValid || !sessionToken || !address) {
if (!sessionToken) {
showNotification({
type: 'error',
title: 'Session Required',
message: 'Please sign in to request allocation',
});
}
if (!address) {
showNotification({
type: 'error',
title: 'Wallet Required',
message: 'Please connect your wallet first',
});
}
return;
}

try {
setIsRequestingAllocation(true);

const params = {
chainId: targetChainId,
compact: {
// Set arbiter equal to sponsor (user's address)
arbiter: address,
sponsor: address,
nonce: null,
expires: formData.expires,
id: lockId.toString(),
amount: parseUnits(formData.amount, decimals).toString(),
witnessTypeString: null,
witnessHash: null,
},
};

const response = await requestAllocation(params, sessionToken);

setFormData(prev => ({
...prev,
allocatorSignature: response.signature,
nonce: response.nonce,
}));

setHasAllocation(true);
showNotification({
type: 'success',
title: 'Allocation Requested',
message: 'Successfully received allocation. You can now submit the transfer.',
});
} catch (error) {
console.error('Error requesting allocation:', error);
showNotification({
type: 'error',
title: 'Allocation Request Failed',
message: error instanceof Error ? error.message : 'Failed to request allocation',
});
} finally {
setIsRequestingAllocation(false);
}
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isFormValid) return;
if (!isFormValid || !formData.allocatorSignature || !formData.nonce) return;

try {
// Validate inputs before creating transfer struct
if (!formData.allocatorSignature?.startsWith('0x')) {
throw new Error('Allocator signature must start with 0x');
}
// Validate recipient
if (!formData.recipient?.startsWith('0x')) {
throw new Error('Recipient must be a valid address starting with 0x');
}

// Validate numeric fields
if (!formData.nonce || !/^\d+$/.test(formData.nonce)) {
throw new Error('Nonce must be a valid number');
}
if (!formData.expires || !/^\d+$/.test(formData.expires)) {
throw new Error('Expires must be a valid timestamp');
}
if (!formData.amount || isNaN(Number(formData.amount)) || Number(formData.amount) <= 0) {
throw new Error('Amount must be a valid positive number');
}

try {
// Convert values and prepare transfer struct
const transfer = {
Expand Down Expand Up @@ -300,12 +342,11 @@ export function Transfer({

// Reset form and close
setFormData({
nonce: '',
expires: '',
recipient: '',
amount: '',
allocatorSignature: '',
});
setHasAllocation(false);
setIsOpen(false);
} catch (conversionError) {
console.error('Error converting values:', conversionError);
Expand Down Expand Up @@ -419,39 +460,6 @@ export function Transfer({
</div>

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Allocator Signature
</label>
<input
type="text"
value={formData.allocatorSignature}
onChange={(e) =>
setFormData((prev) => ({ ...prev, allocatorSignature: e.target.value }))
}
placeholder="0x..."
className="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-gray-300 focus:outline-none focus:border-[#00ff00] transition-colors"
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Nonce
</label>
<input
type="text"
value={formData.nonce}
onChange={(e) => setFormData((prev) => ({ ...prev, nonce: e.target.value }))}
placeholder="Enter nonce"
className={`w-full px-3 py-2 bg-gray-800 border ${
fieldErrors.nonce ? 'border-red-500' : 'border-gray-700'
} rounded-lg text-gray-300 focus:outline-none focus:border-[#00ff00] transition-colors`}
/>
{fieldErrors.nonce && (
<p className="mt-1 text-sm text-red-500">{fieldErrors.nonce}</p>
)}
</div>

<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Expires
Expand Down Expand Up @@ -536,39 +544,76 @@ export function Transfer({
)}
</div>

<button
type="submit"
disabled={!isFormValid || isTransferLoading || isWithdrawalPending}
className="w-full py-2 px-4 bg-[#00ff00] text-gray-900 rounded-lg font-medium hover:bg-[#00dd00] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isTransferLoading || isWithdrawalPending ? (
<span className="flex items-center justify-center">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-900"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
{isWithdrawal ? 'Submitting Withdrawal...' : 'Submitting Transfer...'}
</span>
) : (
<>{isWithdrawal ? 'Submit Withdrawal' : 'Submit Transfer'}</>
)}
</button>
{!hasAllocation ? (
<button
type="button"
onClick={handleRequestAllocation}
disabled={!isFormValid || isRequestingAllocation}
className="w-full py-2 px-4 bg-[#00ff00] text-gray-900 rounded-lg font-medium hover:bg-[#00dd00] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isRequestingAllocation ? (
<span className="flex items-center justify-center">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-900"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Requesting Allocation...
</span>
) : (
'Request Allocation'
)}
</button>
) : (
<button
type="submit"
disabled={!isFormValid || isTransferLoading || isWithdrawalPending}
className="w-full py-2 px-4 bg-[#00ff00] text-gray-900 rounded-lg font-medium hover:bg-[#00dd00] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isTransferLoading || isWithdrawalPending ? (
<span className="flex items-center justify-center">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-gray-900"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
{isWithdrawal ? 'Submitting Withdrawal...' : 'Submitting Transfer...'}
</span>
) : (
<>{isWithdrawal ? 'Submit Withdrawal' : 'Submit Transfer'}</>
)}
</button>
)}
</form>
</div>
</div>
Expand Down
Loading

0 comments on commit 426740a

Please sign in to comment.