Skip to content

Commit 46a0820

Browse files
committed
Direct Payment first pushh
1 parent 3a7b14f commit 46a0820

5 files changed

Lines changed: 158 additions & 103 deletions

File tree

.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
NEXT_PUBLIC_THIRDWEB_CLIENT_ID=6f4b9d28993ca599e4fc109a86ffae22
2+
NEXT_PUBLIC_WALLET_ADDRESS=0xE32Ac8F1eDB5DcF1F739D85278bA50501f5FAF47

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12+
"next": "14.2.8",
1213
"react": "^18",
1314
"react-dom": "^18",
14-
"next": "14.2.8"
15+
"thirdweb": "^5.51.0"
1516
},
1617
"devDependencies": {
17-
"typescript": "^5",
1818
"@types/node": "^20",
1919
"@types/react": "^18",
2020
"@types/react-dom": "^18",
21+
"eslint": "^8",
22+
"eslint-config-next": "14.2.8",
2123
"postcss": "^8",
2224
"tailwindcss": "^3.4.1",
23-
"eslint": "^8",
24-
"eslint-config-next": "14.2.8"
25+
"typescript": "^5"
2526
}
2627
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"use client";
2+
3+
import {
4+
BuyWithFiatQuote,
5+
getBuyWithFiatQuote,
6+
getBuyWithFiatStatus,
7+
} from "thirdweb/pay";
8+
import { NATIVE_TOKEN_ADDRESS, ThirdwebClient } from "thirdweb";
9+
import { base } from "thirdweb/chains";
10+
import { isSwapRequiredPostOnramp } from "thirdweb/pay";
11+
import { useEffect, useState } from "react";
12+
13+
export default function DirectPayment({
14+
client,
15+
fromAddress,
16+
}: {
17+
client: ThirdwebClient;
18+
fromAddress: string;
19+
}) {
20+
const [quote, setQuote] = useState<BuyWithFiatQuote | null>(null);
21+
const [polling, setPolling] = useState<NodeJS.Timeout | null>(null);
22+
const [isLoading, setIsLoading] = useState(false);
23+
const [isSuccess, setIsSuccess] = useState(false);
24+
25+
useEffect(() => {
26+
if (quote) {
27+
startPolling();
28+
}
29+
}, [quote]);
30+
31+
const startPolling = () => {
32+
stopPolling();
33+
const interval = setInterval(pollStatus, 10000);
34+
setPolling(interval);
35+
};
36+
37+
const stopPolling = () => {
38+
if (polling) {
39+
clearInterval(polling);
40+
setPolling(null);
41+
}
42+
};
43+
44+
const pollStatus = async () => {
45+
setIsLoading(true);
46+
47+
if (quote && quote.intentId) {
48+
try {
49+
const fiatStatus = await getBuyWithFiatStatus({
50+
client,
51+
intentId: quote.intentId,
52+
});
53+
const currentStatus = fiatStatus.status as string;
54+
const hasTwoSteps = isSwapRequiredPostOnramp(quote);
55+
56+
if (currentStatus === "NOT_FOUND") {
57+
alert("Error: Intent ID not found");
58+
}
59+
60+
if (
61+
currentStatus === "NONE" ||
62+
currentStatus === "PENDING_PAYMENT" ||
63+
currentStatus === "PENDING_ON_RAMP_TRANSFER" ||
64+
currentStatus === "ON_RAMP_TRANSFER_IN_PROGRESS"
65+
) {
66+
console.log("Current status: ", currentStatus);
67+
}
68+
69+
if (currentStatus === "ON_RAMP_TRANSFER_FAILED") {
70+
alert("Error: Onramp transfer failed");
71+
stopPolling();
72+
}
73+
74+
if (currentStatus === "ON_RAMP_TRANSFER_COMPLETED") {
75+
// if only on-ramp is required - process is done!
76+
if (!hasTwoSteps) {
77+
alert("Payment successful");
78+
setIsSuccess(true);
79+
stopPolling();
80+
} else {
81+
alert("Crypto Swap Required");
82+
}
83+
}
84+
85+
if (currentStatus === "CRYPTO_SWAP_REQUIRED") {
86+
alert("This is where you swap the crypto for fiat");
87+
}
88+
} catch (error) {
89+
console.log("Failed to get status", error);
90+
stopPolling();
91+
}
92+
}
93+
};
94+
95+
const sendFiatTransaction = async () => {
96+
// Get a quote for buying 0.01 Base ETH with USD
97+
try {
98+
const quote = await getBuyWithFiatQuote({
99+
client, // thirdweb client
100+
fromCurrencySymbol: "USD", // fiat currency symbol
101+
toChainId: base.id, // base chain id
102+
toAmount: "0.01", // amount of token to buy
103+
toTokenAddress: NATIVE_TOKEN_ADDRESS, // native token
104+
toAddress: `${process.env.NEXT_PUBLIC_WALLET_ADDRESS}`,
105+
isTestMode: true,
106+
fromAddress,
107+
purchaseData: { nftId: 1 },
108+
});
109+
110+
setQuote(quote);
111+
window.open(quote.onRampLink, "_blank");
112+
} catch (error) {
113+
console.error("Error sending transaction:", error);
114+
}
115+
};
116+
117+
return (
118+
<button
119+
className="flex w-full justify-center rounded-lg border border-gray-400 px-4 py-2 hover:bg-gray-300 hover:border-gray-300 hover:text-black active:opacity-80"
120+
onClick={() => sendFiatTransaction()}
121+
>
122+
{isSuccess ? "Success" : isLoading ? "Loading..." : "Initiate Payment"}
123+
</button>
124+
);
125+
}

src/app/layout.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from "next";
22
import localFont from "next/font/local";
3+
import { ThirdwebProvider } from "thirdweb/react";
34
import "./globals.css";
45

56
const geistSans = localFont({
@@ -24,12 +25,14 @@ export default function RootLayout({
2425
children: React.ReactNode;
2526
}>) {
2627
return (
27-
<html lang="en">
28-
<body
29-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30-
>
31-
{children}
32-
</body>
33-
</html>
28+
<ThirdwebProvider>
29+
<html lang="en">
30+
<body
31+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
32+
>
33+
{children}
34+
</body>
35+
</html>
36+
</ThirdwebProvider>
3437
);
3538
}

src/app/page.tsx

Lines changed: 16 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,25 @@
1-
import Image from "next/image";
1+
"use client";
2+
3+
import { createThirdwebClient } from "thirdweb";
4+
import { ConnectButton } from "thirdweb/react";
5+
import { useActiveAccount } from "thirdweb/react";
6+
import DirectPayment from "./components/DirectPayment";
7+
8+
// create a thirdweb client
9+
const client = createThirdwebClient({
10+
clientId: `${process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID}`,
11+
});
212

313
export default function Home() {
14+
const account = useActiveAccount();
415
return (
516
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
617
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
7-
<Image
8-
className="dark:invert"
9-
src="https://nextjs.org/icons/next.svg"
10-
alt="Next.js logo"
11-
width={180}
12-
height={38}
13-
priority
14-
/>
15-
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
16-
<li className="mb-2">
17-
Get started by editing{" "}
18-
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
19-
src/app/page.tsx
20-
</code>
21-
.
22-
</li>
23-
<li>Save and see your changes instantly.</li>
24-
</ol>
25-
26-
<div className="flex gap-4 items-center flex-col sm:flex-row">
27-
<a
28-
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
29-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30-
target="_blank"
31-
rel="noopener noreferrer"
32-
>
33-
<Image
34-
className="dark:invert"
35-
src="https://nextjs.org/icons/vercel.svg"
36-
alt="Vercel logomark"
37-
width={20}
38-
height={20}
39-
/>
40-
Deploy now
41-
</a>
42-
<a
43-
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
44-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
45-
target="_blank"
46-
rel="noopener noreferrer"
47-
>
48-
Read our docs
49-
</a>
50-
</div>
18+
<ConnectButton client={client} />
19+
{account && (
20+
<DirectPayment client={client} fromAddress={account.address} />
21+
)}
5122
</main>
52-
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
53-
<a
54-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
55-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56-
target="_blank"
57-
rel="noopener noreferrer"
58-
>
59-
<Image
60-
aria-hidden
61-
src="https://nextjs.org/icons/file.svg"
62-
alt="File icon"
63-
width={16}
64-
height={16}
65-
/>
66-
Learn
67-
</a>
68-
<a
69-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
70-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
71-
target="_blank"
72-
rel="noopener noreferrer"
73-
>
74-
<Image
75-
aria-hidden
76-
src="https://nextjs.org/icons/window.svg"
77-
alt="Window icon"
78-
width={16}
79-
height={16}
80-
/>
81-
Examples
82-
</a>
83-
<a
84-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
85-
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
86-
target="_blank"
87-
rel="noopener noreferrer"
88-
>
89-
<Image
90-
aria-hidden
91-
src="https://nextjs.org/icons/globe.svg"
92-
alt="Globe icon"
93-
width={16}
94-
height={16}
95-
/>
96-
Go to nextjs.org →
97-
</a>
98-
</footer>
9923
</div>
10024
);
10125
}

0 commit comments

Comments
 (0)