| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- 'use client'
- import { useState } from 'react'
- import {
- Card,
- Input,
- Button,
- Form,
- InputNumber,
- message,
- Descriptions,
- Typography,
- Space,
- } from 'antd'
- import { CopyOutlined, LoadingOutlined } from '@ant-design/icons'
- const { Title, Text } = Typography
- export default function LpCopyPage() {
- const [loading, setLoading] = useState(false)
- const [positionInfo, setPositionInfo] = useState<{
- poolAddress: string
- tickLower: number
- tickUpper: number
- base: string
- baseAmount: string
- otherAmountMax: string
- estimatedValue: number
- priceLower: string
- priceUpper: string
- } | null>(null)
- const [form] = Form.useForm()
- const handleCopy = async (values: {
- positionAddress: string
- maxUsdValue: number
- }) => {
- setLoading(true)
- setPositionInfo(null)
- try {
- const response = await fetch('/api/lp-copy', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- positionAddress: values.positionAddress,
- maxUsdValue: values.maxUsdValue,
- }),
- })
- const data = await response.json()
- if (!response.ok) {
- throw new Error(data.error || 'Failed to copy position')
- }
- setPositionInfo(data.positionInfo)
- message.success('Position copied successfully!')
- message.info(`Transaction ID: ${data.txid}`)
- } catch (error: unknown) {
- const errorMessage =
- error instanceof Error ? error.message : 'Failed to copy position'
- message.error(errorMessage)
- } finally {
- setLoading(false)
- }
- }
- return (
- <div style={{ padding: '24px', maxWidth: '800px', margin: '0 auto' }}>
- <Title level={2}>LP Position Copy</Title>
- <Text type="secondary">
- Copy an existing LP position with a limited investment amount. The
- system will automatically calculate the optimal token amounts.
- </Text>
- <Card style={{ marginTop: '24px' }}>
- <Form
- form={form}
- layout="vertical"
- onFinish={handleCopy}
- initialValues={{
- maxUsdValue: 10,
- }}
- >
- <Form.Item
- label="Position Address"
- name="positionAddress"
- rules={[
- { required: true, message: 'Please enter position address' },
- {
- pattern: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/,
- message: 'Invalid Solana address format',
- },
- ]}
- >
- <Input
- placeholder="Enter the NFT mint address of the position to copy"
- size="large"
- />
- </Form.Item>
- <Form.Item
- label="Maximum Investment (USD)"
- name="maxUsdValue"
- rules={[
- { required: true, message: 'Please enter maximum USD value' },
- {
- type: 'number',
- min: 0.01,
- message: 'Must be greater than 0.01',
- },
- ]}
- >
- <InputNumber
- placeholder="e.g., 5, 10, 50"
- min={0.01}
- step={1}
- style={{ width: '100%' }}
- size="large"
- formatter={(value) =>
- `$ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
- }
- parser={(value) => {
- const parsed = value?.replace(/\$\s?|(,*)/g, '') || '0'
- return parseFloat(parsed) || 0
- }}
- />
- </Form.Item>
- <Form.Item>
- <Button
- type="primary"
- htmlType="submit"
- size="large"
- icon={loading ? <LoadingOutlined /> : <CopyOutlined />}
- loading={loading}
- block
- >
- {loading ? 'Copying Position...' : 'Copy Position'}
- </Button>
- </Form.Item>
- </Form>
- </Card>
- {positionInfo && (
- <Card style={{ marginTop: '24px' }} title="Position Details">
- <Descriptions column={1} bordered>
- <Descriptions.Item label="Pool Address">
- <Text code>{positionInfo.poolAddress}</Text>
- </Descriptions.Item>
- <Descriptions.Item label="Tick Range">
- {positionInfo.tickLower} - {positionInfo.tickUpper}
- </Descriptions.Item>
- <Descriptions.Item label="Price Range">
- {positionInfo.priceLower} - {positionInfo.priceUpper}
- </Descriptions.Item>
- <Descriptions.Item label="Base Token">
- {positionInfo.base}
- </Descriptions.Item>
- <Descriptions.Item label="Base Amount">
- {positionInfo.baseAmount}
- </Descriptions.Item>
- <Descriptions.Item label="Other Amount Max">
- {positionInfo.otherAmountMax}
- </Descriptions.Item>
- <Descriptions.Item label="Estimated Value">
- ${positionInfo.estimatedValue.toFixed(2)} USD
- </Descriptions.Item>
- </Descriptions>
- </Card>
- )}
- <Card style={{ marginTop: '24px' }}>
- <Title level={4}>How it works:</Title>
- <Space orientation="vertical" size="small">
- <Text>
- 1. Enter the NFT mint address of the position you want to copy
- </Text>
- <Text>2. Set your maximum investment amount in USD</Text>
- <Text>3. The system will automatically:</Text>
- <Text style={{ marginLeft: '16px' }}>
- • Fetch the position details (price range, pool info)
- </Text>
- <Text style={{ marginLeft: '16px' }}>
- • Calculate optimal token amounts within your budget
- </Text>
- <Text style={{ marginLeft: '16px' }}>
- • Create a new position with the same price range
- </Text>
- <Text style={{ marginLeft: '16px' }}>
- • Add a memo linking to the original position
- </Text>
- </Space>
- </Card>
- </div>
- )
- }
|