@turtleclub/opportunities 0.1.0-beta.56 → 0.1.0-beta.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +179 -0
- package/package.json +5 -4
- package/src/components/index.ts +1 -1
- package/src/deposit/NativeDepositSection.tsx +199 -0
- package/src/deposit/TemporalWrapper.tsx +155 -0
- package/src/{components → deposit/components}/balances-data-table.tsx +6 -0
- package/src/deposit/components/index.ts +3 -0
- package/src/deposit/components/swap-input-v3.tsx +194 -0
- package/src/deposit/components/token-selector-v3.tsx +122 -0
- package/src/deposit/index.ts +4 -0
- package/src/index.ts +6 -0
- package/src/opportunity-actions/OpportunityActions.tsx +182 -0
- package/src/opportunity-actions/index.ts +1 -0
- package/src/opportunity-table/components/opportunities-table.tsx +6 -5
- package/src/route-details/index.ts +6 -0
- package/src/route-details/route-details-v2.tsx +137 -0
- package/src/route-details/route-details.tsx +5 -4
- package/src/route-details/types.ts +7 -0
- package/src/transaction-status/hooks/useTransactionQueue.ts +1 -1
- package/src/withdraw/NativeWithdrawSection.tsx +45 -0
- package/src/withdraw/index.ts +1 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { Card, CardContent } from "@turtleclub/ui";
|
|
2
|
+
import { formatBalance } from "@turtleclub/utils";
|
|
3
|
+
import { formatUnits } from "viem";
|
|
4
|
+
import { ArrowRight } from "lucide-react";
|
|
5
|
+
import type { RouteMetadata, RouteStep } from "./types";
|
|
6
|
+
|
|
7
|
+
export interface RouteDetailsV2Props {
|
|
8
|
+
metadata: RouteMetadata;
|
|
9
|
+
showApprove?: boolean;
|
|
10
|
+
approveAmount?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MIN_FORMAT_THRESHOLD = 0.0001;
|
|
15
|
+
const SMALL_NUMBER_DECIMALS = 4;
|
|
16
|
+
|
|
17
|
+
function formatSmallNumber(val: string | number): string {
|
|
18
|
+
const num = typeof val === "string" ? parseFloat(val) : val;
|
|
19
|
+
if (isNaN(num) || num === 0) return "0";
|
|
20
|
+
if (num < MIN_FORMAT_THRESHOLD) return `<${MIN_FORMAT_THRESHOLD}`;
|
|
21
|
+
return formatBalance(num, SMALL_NUMBER_DECIMALS);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TokenIcon({ token }: { token: RouteStep["from"] }) {
|
|
25
|
+
if (token.logoUrl) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex h-7 w-7 max-h-7 max-w-7 shrink-0 items-center justify-center overflow-hidden rounded-full">
|
|
28
|
+
<img
|
|
29
|
+
src={token.logoUrl}
|
|
30
|
+
alt={token.symbol}
|
|
31
|
+
className="h-5 w-5 max-h-5 max-w-5 rounded-full object-cover"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex h-7 w-7 max-h-7 max-w-7 shrink-0 items-center justify-center rounded-full">
|
|
39
|
+
<div className="bg-neutral-alpha-10 flex h-5 w-5 items-center justify-center rounded-full">
|
|
40
|
+
<span className="text-muted-foreground text-[10px] font-medium">
|
|
41
|
+
{token.symbol?.charAt(0).toUpperCase() || "?"}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function StepCard({
|
|
49
|
+
label,
|
|
50
|
+
fromToken,
|
|
51
|
+
toToken,
|
|
52
|
+
amount,
|
|
53
|
+
}: {
|
|
54
|
+
label: string;
|
|
55
|
+
fromToken: RouteStep["from"];
|
|
56
|
+
toToken?: RouteStep["to"];
|
|
57
|
+
amount?: string;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<Card variant="border" className="h-[72px] min-w-[120px] flex-1">
|
|
61
|
+
<CardContent className="flex h-full flex-col justify-between px-5 py-2.5">
|
|
62
|
+
<span className="text-muted-foreground text-xs">{label}</span>
|
|
63
|
+
<div className="flex items-center gap-1.5">
|
|
64
|
+
<TokenIcon token={fromToken} />
|
|
65
|
+
<span className="text-sm font-medium">
|
|
66
|
+
{amount ? `${formatSmallNumber(amount)} ${fromToken.symbol}` : fromToken.symbol || "?"}
|
|
67
|
+
</span>
|
|
68
|
+
{toToken && (
|
|
69
|
+
<>
|
|
70
|
+
<ArrowRight className="text-muted-foreground size-3 shrink-0" />
|
|
71
|
+
<TokenIcon token={toToken} />
|
|
72
|
+
<span className="text-sm font-medium">{toToken.symbol || "?"}</span>
|
|
73
|
+
</>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Displays route details for routed deposits with provider info and step cards */
|
|
82
|
+
export function RouteDetailsV2({ metadata, showApprove, approveAmount, className }: RouteDetailsV2Props) {
|
|
83
|
+
const { provider, providerImg, route, amountOut } = metadata;
|
|
84
|
+
|
|
85
|
+
if (!route || route.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const approveToken = route[0]?.from;
|
|
90
|
+
const outputToken = route[route.length - 1]?.to;
|
|
91
|
+
|
|
92
|
+
// Format amountOut using the output token's decimals
|
|
93
|
+
const formattedAmountOut =
|
|
94
|
+
amountOut && outputToken
|
|
95
|
+
? formatSmallNumber(formatUnits(BigInt(amountOut), outputToken.decimals))
|
|
96
|
+
: null;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className={className}>
|
|
100
|
+
<div className="mb-3 space-y-3 px-2 text-xs">
|
|
101
|
+
{formattedAmountOut && outputToken && (
|
|
102
|
+
<div className="flex items-center gap-1.5">
|
|
103
|
+
<span className="text-muted-foreground">You receive</span>
|
|
104
|
+
<span className="text-sm font-medium">
|
|
105
|
+
{formattedAmountOut} {outputToken.symbol}
|
|
106
|
+
</span>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
<div className="flex items-center ">
|
|
110
|
+
<span className="text-muted-foreground">Route powered by</span>
|
|
111
|
+
{providerImg ? (
|
|
112
|
+
<img src={providerImg} alt={provider} className="h-2 px-2" />
|
|
113
|
+
) : (
|
|
114
|
+
<span className="text-sm font-medium capitalize">{provider}</span>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:overflow-x-auto">
|
|
120
|
+
{showApprove && approveToken && (
|
|
121
|
+
<StepCard label="Approve" fromToken={approveToken} amount={approveAmount} />
|
|
122
|
+
)}
|
|
123
|
+
{route.map((step, index) => {
|
|
124
|
+
const label = step.action.charAt(0).toUpperCase() + step.action.slice(1);
|
|
125
|
+
return (
|
|
126
|
+
<StepCard
|
|
127
|
+
key={`${step.action}-${step.from.address}-${index}`}
|
|
128
|
+
label={label}
|
|
129
|
+
fromToken={step.from}
|
|
130
|
+
toToken={step.to}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { Card, CardContent, LabelWithIcon, ScrollArea } from "@turtleclub/ui";
|
|
3
3
|
import { ChevronsRight } from "lucide-react";
|
|
4
|
-
import ensoLogo from "../images/enso.png";
|
|
5
4
|
import { useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
const ENSO_LOGO_URL = "https://storage.googleapis.com/turtle-assets/partners/enso/enso.png";
|
|
6
7
|
import type { TokenStep } from "@turtleclub/hooks";
|
|
7
8
|
|
|
8
9
|
export type { TokenStep } from "@turtleclub/hooks";
|
|
@@ -38,7 +39,7 @@ export const RouteDetails = ({ value, className }: RouteDetailsProps) => {
|
|
|
38
39
|
<div className="text-sm font-medium">Swap Route</div>
|
|
39
40
|
<div className="mb-1 flex items-center gap-1 text-[10px]">
|
|
40
41
|
<span className="text-sm opacity-50">Powered by</span>
|
|
41
|
-
<img src={
|
|
42
|
+
<img src={ENSO_LOGO_URL} className="w-10" alt="Enso" />
|
|
42
43
|
</div>
|
|
43
44
|
{/* Route visualization */}
|
|
44
45
|
<ScrollArea className="mt-2 rounded-md whitespace-nowrap">
|
|
@@ -69,7 +70,7 @@ export const RouteDetails = ({ value, className }: RouteDetailsProps) => {
|
|
|
69
70
|
)}
|
|
70
71
|
</LabelWithIcon>
|
|
71
72
|
|
|
72
|
-
{step.type !== "approve" && (
|
|
73
|
+
{step.type !== "approve" && step.out && (
|
|
73
74
|
<>
|
|
74
75
|
{/* Arrow between tokens */}
|
|
75
76
|
<ChevronsRight className="text-primary size-4 shrink-0" />
|
|
@@ -82,7 +83,7 @@ export const RouteDetails = ({ value, className }: RouteDetailsProps) => {
|
|
|
82
83
|
>
|
|
83
84
|
{!isLongRoute && (
|
|
84
85
|
<span className="hidden gap-2 sm:block">
|
|
85
|
-
{step.
|
|
86
|
+
{step.out.symbol}
|
|
86
87
|
</span>
|
|
87
88
|
)}
|
|
88
89
|
</LabelWithIcon>
|
|
@@ -32,7 +32,7 @@ interface UseTransactionQueueProps {
|
|
|
32
32
|
earnRoute: EarnRouteResponse | null;
|
|
33
33
|
userAddress?: string;
|
|
34
34
|
chainId?: number;
|
|
35
|
-
sendTransaction: (transaction: StepTx) => Promise<`0x${string}` | undefined>;
|
|
35
|
+
sendTransaction: (transaction: StepTx & { chainId?: number }) => Promise<`0x${string}` | undefined>;
|
|
36
36
|
onError?: (error: Error) => void;
|
|
37
37
|
onSuccess?: () => void;
|
|
38
38
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Button } from "@turtleclub/ui";
|
|
5
|
+
import type { Opportunity, TokenBalance } from "@turtleclub/hooks";
|
|
6
|
+
import { GeoCheckBlocker, SwapInputV3 } from "../deposit/components";
|
|
7
|
+
|
|
8
|
+
export interface NativeWithdrawSectionProps {
|
|
9
|
+
opportunity: Opportunity;
|
|
10
|
+
address: string | undefined;
|
|
11
|
+
balances: TokenBalance[];
|
|
12
|
+
isBalancesLoading?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function NativeWithdrawSection({
|
|
16
|
+
opportunity,
|
|
17
|
+
address,
|
|
18
|
+
balances,
|
|
19
|
+
isBalancesLoading = false,
|
|
20
|
+
}: NativeWithdrawSectionProps) {
|
|
21
|
+
const [amount, setAmount] = useState<string | undefined>();
|
|
22
|
+
const [selectedTokenAddress, setSelectedTokenAddress] = useState<string | undefined>();
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="flex flex-col gap-3">
|
|
26
|
+
<SwapInputV3
|
|
27
|
+
value={amount ?? ""}
|
|
28
|
+
balances={balances}
|
|
29
|
+
selectedTokenAddress={selectedTokenAddress}
|
|
30
|
+
disabled={isBalancesLoading}
|
|
31
|
+
isWalletConnected={!!address}
|
|
32
|
+
onChange={setAmount}
|
|
33
|
+
onTokenChange={setSelectedTokenAddress}
|
|
34
|
+
showBalance={true}
|
|
35
|
+
className="border-border border p-5"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<GeoCheckBlocker>
|
|
39
|
+
<Button disabled className="w-full">
|
|
40
|
+
Withdraw (Coming Soon)
|
|
41
|
+
</Button>
|
|
42
|
+
</GeoCheckBlocker>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NativeWithdrawSection, type NativeWithdrawSectionProps } from "./NativeWithdrawSection";
|