Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wip #2

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions app/components/BuyConfigBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Button, Card, Link, Select, SelectItem } from "@nextui-org/react";
import { useState } from "react";
import ReactJson from "react-json-view";
import { generateBuyConfig } from "../utils/queries";
import { scrollToHeader } from "../utils/helpers";


export function BuyConfigBox() {
// json response containing BuyConfig and BuyOptions
const [buyConfig, setBuyConfig] = useState();

const buyConfigurationWrapper = async () => {
const response = await generateBuyConfig();
try {
setBuyConfig(response);
} catch (error) {
alert(error);
}
}

return (
<Card id="buyConfigHeader" className="flex flex-col p-10">
<h1 className="font-bold mb-1" onClick={() => scrollToHeader("buyConfigHeader")}> Generate Buy Config: </h1>
<h2>The <Link isExternal href="https://docs.cdp.coinbase.com/onramp/docs/api-configurations/#buy-options"> Buy Config API </Link> returns the list of countries supported by Coinbase Onramp, and the payment methods available in each country.</h2>
<div className="flex flex-row w-full justify-center gap-10 mt-5">
<Button className="w-full" onClick={buyConfigurationWrapper}> Generate Buy Config </Button>
<Card className="w-full p-5">
{buyConfig && <ReactJson collapsed={true} src={buyConfig} />}
</Card>
</div>
</Card>
);
}
336 changes: 336 additions & 0 deletions app/components/BuyQuoteBox.tsx

Large diffs are not rendered by default.

150 changes: 150 additions & 0 deletions app/components/SecureTokenBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Input } from "@nextui-org/input";
import { Code } from "@nextui-org/code";
import { Button } from "@nextui-org/button";
import { Textarea } from "@nextui-org/input";
import { useState, useCallback, useMemo, ChangeEvent } from "react";

import {Card, Link, Select, SelectItem} from "@nextui-org/react";
import { AggregatorInputParams} from "../utils/types";
import { generateSecureToken } from "../utils/queries";
import {BLOCKCHAIN_LIST} from "../utils/blockchains";

export default function SecureTokenBox({ aggregatorInputs, showBuyQuoteURLText, blockchains }: { aggregatorInputs?: AggregatorInputParams, showBuyQuoteURLText?: boolean, blockchains?: string[]}) {
const [secureToken, setSecureToken] = useState("");
const [ethAddress, setEthAddress] = useState("");

const [blockchainOption, setBlockchainOption] = useState("");

const setBlockchain = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
setBlockchainOption(event.target.value);
}, []);

const secureTokenWrapper = useCallback(async () => {
const response = await generateSecureToken({ethAddress, blockchains: showBuyQuoteURLText ? blockchains : [blockchainOption.toLowerCase()]})
console.log("generateSecureToken");
try {
if (response) {setSecureToken(response);} else {setSecureToken('')}
} catch (error) {
alert(error);
console.error(error);
}}, [ethAddress, showBuyQuoteURLText, blockchains, blockchainOption]);


// fetch("/api/secure-token", {
// method: "POST",
// body: JSON.stringify({ ethAddress, blockchains: blockchains || [blockchainOption]}),
// })
// .then(async (response) => {
// const json = await response.json();
// if(response.ok) {
// setSecureToken(json.token);
// } else {
// alert("Error generating token: "+json.error);
// console.log("Error generating token: "+json.error);
// }
// });
// }, [ethAddress, blockchains]);

const linkReady = useMemo(() => secureToken.length > 0, [secureToken]);

const link = useMemo(() => {
if (!linkReady) return "Generate a secure token first to create your one time URL";
return (
"https://pay.coinbase.com/buy/select-asset?sessionToken=" + secureToken +
(aggregatorInputs?.quoteID ? "&quoteId=" + aggregatorInputs.quoteID : "") +
(aggregatorInputs?.defaultAsset ? "&defaultAsset=" + aggregatorInputs.defaultAsset : "") +
(aggregatorInputs?.defaultPaymentMethod ? "&defaultPaymentMethod=" + aggregatorInputs.defaultPaymentMethod : "") +
(aggregatorInputs?.defaultNetwork ? "&defaultNetwork=" + aggregatorInputs.defaultNetwork : "") +
(aggregatorInputs?.fiatCurrency ? "&fiatCurrency=" + aggregatorInputs.fiatCurrency : "") +
(aggregatorInputs?.presentFiatAmount ? "&presetFiatAmount=" + aggregatorInputs.presentFiatAmount : "")
);
}, [linkReady, secureToken, aggregatorInputs]);

const launch = useCallback(() => {
open(link, "_blank", "popup,width=540,height=700")
}, [link]);

const helperText = showBuyQuoteURLText ?
<h2> The generated link initializes the Coinbase Onramp URL with the appropriate parameters to execute that buy in just one click for the user. </h2>:
<h2>Generate a secure one time URL to launch an Onramp session.</h2>

const buyQuoteURLDirections = (
<div className="flex flex-col ml-10 gap-1 w-2/5">
<h2 > 1. Generate a Buy Quote in the section above to get the input parameters to create a secure Onramp URL. </h2>
<h2> 2. Enter a <b>destination wallet address</b> and then click <b>&lsquo;Generate secure token&rsquo;</b>. </h2>
<h2> 3. Click <b> Launch Onramp </b> to see the one-click buy experience for your users. </h2>
</div>
)

return (
<Card>
<div className="flex flex-col p-10 pb-5 gap-1">
<h1 className="font-bold underline"> <Link color="foreground" href="https://docs.cdp.coinbase.com/onramp/docs/api-initializing/" isExternal> Generate Secure Onramp Token & URL: </Link> </h1>
{helperText}
</div>
{showBuyQuoteURLText && buyQuoteURLDirections}

<section className="flex flex-row justify-between gap-10 p-10 pt-5">
<div className="flex flex-col space-y-5 w-full">
<Input
className="flex w-full"
type="text"
label="Destination Wallet Address"
placeholder="Enter your address"
value={ethAddress}
onValueChange={(value) => {
setEthAddress(value);
setSecureToken("");
}}
isRequired
/>

{!showBuyQuoteURLText &&
<Select
className="flex w-full"
name="blockchain-option"
label="Blockchain Network"
placeholder="Select a network"
value={blockchainOption}
onChange={setBlockchain}
items={BLOCKCHAIN_LIST}
isRequired
>
{(curr) => <SelectItem key={curr.id}>{curr.name}</SelectItem>}
</Select>}

<Button
onClick={secureTokenWrapper}
isDisabled={
(showBuyQuoteURLText && ethAddress.length === 0) ||
(!showBuyQuoteURLText && (ethAddress.length === 0 || !blockchainOption))
}
>
Generate secure token
</Button>

{secureToken.length > 0 && (
<>
<h4 className="text-medium">Onramp token:</h4>
<Code>{secureToken}</Code>
</>
)}
</div>


<div className="flex flex-col space-y-5 w-full">
<Textarea
className="flex-auto"
isReadOnly
label="Onramp URL"
variant="bordered"
value={link}
/>
<Button isDisabled={!linkReady} color="primary" onClick={launch}>
Launch Onramp
</Button>
</div>

</section>
</Card>
)}
154 changes: 47 additions & 107 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,114 +1,54 @@
"use client";
import { Input } from "@nextui-org/input";
import { Code } from "@nextui-org/code";
import { Button } from "@nextui-org/button";
import { Textarea } from "@nextui-org/input";
import { Divider } from "@nextui-org/divider";
import { useState, useCallback, useMemo } from "react";
import Image from "next/image";

export default function Home() {
const [secureToken, setSecureToken] = useState("");
const [ethAddress, setEthAddress] = useState("");

const generateSecureToken = useCallback(async () => {
console.log("generateSecureToken");
fetch("/api/secure-token", {
method: "POST",
body: JSON.stringify({ ethAddress }),
})
.then(async (response) => {
const json = await response.json();
if(response.ok) {
setSecureToken(json.token);
} else {
console.log("Error generating token: "+json.error);
}
});
}, [ethAddress]);

const linkReady = useMemo(() => secureToken.length > 0, [secureToken]);

const link = useMemo(() => {
if (!linkReady) return "Create a secure token to generate a URL";

return (
"https://pay.coinbase.com/buy/select-asset?sessionToken=" + secureToken
);
}, [linkReady, secureToken]);

const launch = useCallback(() => {
open(link, "_blank", "popup,width=540,height=700")
}, [link]);
import {Tabs, Tab, Card, Link} from "@nextui-org/react";
import SecureTokenBox from "./components/SecureTokenBox";
import BuyQuoteBox from "./components/BuyQuoteBox";

export default function Home() {
return (
<section className="flex flex-col items-center justify-center gap-4">
<Image
src="/cdp.svg"
alt="Next.js Logo"
width={180}
height={37}
priority
className="align-center"
/>
<div className="inline-block max-w-lg text-center justify-center">
<h1 className="text-4xl">Coinbase Onramp demo app</h1>
</div>
<div className="inline-block max-w-lg text-left justify-center">
<p className="text-lg">Instructions:</p>
<p className="text-lg">
1. Go to the CDP portal and create a project.{" "}
</p>
<p className="text-lg">
2. Click on the Onramp tab and configure your integration.{" "}
</p>
<p className="text-lg">
3. Navigate to the API keys page and download a private key. Copy
the private key file to api_keys/cdp_api_key.json.
</p>
</div>

<div style={{ width: "600px" }} className="flex">
<Input
type="text"
label="ETH Address"
placeholder="Enter your address"
value={ethAddress}
onValueChange={(value) => {
setEthAddress(value);
setSecureToken("");
}}
/>
</div>

<Button
onClick={generateSecureToken}
isDisabled={ethAddress.length === 0}
>
Generate secure token
</Button>

{secureToken.length > 0 && (
<>
<h4 className="text-medium">Onramp token:</h4>
<Code>{secureToken}</Code>
</>
)}

<Divider className="my-4" />

<Textarea
isReadOnly
label="Onramp URL"
variant="bordered"
labelPlacement="outside"
value={link}
className="max-w-xs"
/>

<Button isDisabled={!linkReady} color="primary" onClick={launch}>
Launch CB Onramp
</Button>
</section>
<div className="space-y-10 size-full">
<Card className="flex flex-row justify-between p-5 w-full">

<div className="flex flex-col space-y-5">
<div className="inline-block max-w-lg text-center">
<h1 className="text-4xl font-bold">Coinbase Onramp Demo App</h1>
</div>
<div className="inline-block max-w-lg text-left">
<p className="text-lg">Instructions:</p>
<p className="text-lg">
1. Go to <Link href="https://portal.cdp.coinbase.com/products/onramp" isExternal> Onramp </Link> in your Coinbase Developer Platform and configure your integration{" "}
</p>
<p className="text-lg">
2. Navigate to the <Link href="https://portal.cdp.coinbase.com/access/api" isExternal> API Keys </Link> tab and download a private key.{" "}
</p>
<p className="text-lg">
3. Copy the private key file to api_keys/cdp_api_key.json inside your onramp-demo-app repo.
</p>
</div>
</div>
<div>
<Image
src="/cdp.svg"
alt="Next.js Logo"
width={200}
height={37}
priority
className="flex flex-col"
/>
</div>
</Card>

<div className="flex w-full flex-col gap-5">
<Tabs aria-label="Options">
<Tab key="genToken" title="Generate Onramp URL">
<SecureTokenBox />
</Tab>
<Tab key="buyQuote" title="Generate Onramp Aggregator URL">
<BuyQuoteBox />
</Tab>
</Tabs>
</div>
</div>
);
}
Loading