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

chore: add buy to playground #1755

Open
wants to merge 79 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
3b1985f
initial commit
alissacrane-cb Dec 13, 2024
38b978d
fix typo
alissacrane-cb Dec 13, 2024
4d2accf
lint
alissacrane-cb Dec 13, 2024
b3474a8
fix lint
alissacrane-cb Dec 13, 2024
75a16d4
fix imports
alissacrane-cb Dec 13, 2024
ee0f7bc
adjust swap quote functionality
alissacrane-cb Dec 13, 2024
de3805d
fix swap test
alissacrane-cb Dec 13, 2024
9e98425
adjust svgs
alissacrane-cb Dec 13, 2024
fd8241f
adjust imports
alissacrane-cb Dec 13, 2024
1672671
adjust hook behavior
alissacrane-cb Dec 13, 2024
7f8cdc4
add popup monitor
alissacrane-cb Dec 16, 2024
9570480
fix optional params - revisit
alissacrane-cb Dec 16, 2024
2303671
remove unused file
alissacrane-cb Dec 16, 2024
1f5d208
add leading 0
alissacrane-cb Dec 16, 2024
ddf3654
fix swap error
alissacrane-cb Dec 16, 2024
6b34326
remove SwapLite components
alissacrane-cb Dec 16, 2024
62f9586
add buy components
alissacrane-cb Dec 16, 2024
63f4717
add test coverage
alissacrane-cb Dec 16, 2024
eb0d0ac
remove unused code
alissacrane-cb Dec 16, 2024
64decc8
remove exit handler
alissacrane-cb Dec 16, 2024
66545a6
adjust imports
alissacrane-cb Dec 16, 2024
1e65878
add test coverage
alissacrane-cb Dec 16, 2024
8194392
add test coverage
alissacrane-cb Dec 16, 2024
e792579
fix imports and lint
alissacrane-cb Dec 16, 2024
0cee0d3
fix import
alissacrane-cb Dec 16, 2024
40e8074
update import
alissacrane-cb Dec 16, 2024
24c5d01
throw error if no project id
alissacrane-cb Dec 16, 2024
12c5ae7
fix import
alissacrane-cb Dec 16, 2024
676fed9
fix imports
alissacrane-cb Dec 16, 2024
a30f64e
remove console log
alissacrane-cb Dec 16, 2024
e2837e7
add comments
alissacrane-cb Dec 16, 2024
d60b789
add test coverage
alissacrane-cb Dec 17, 2024
404a730
add test coverage
alissacrane-cb Dec 17, 2024
42d3a11
add test coverage
alissacrane-cb Dec 17, 2024
fe9c92b
remove uneccesary error
alissacrane-cb Dec 17, 2024
215761b
remove project id
alissacrane-cb Dec 17, 2024
c7b0e8c
address pr comments
alissacrane-cb Dec 17, 2024
a43a105
fix lint
alissacrane-cb Dec 17, 2024
c9daec4
fix tests
alissacrane-cb Dec 17, 2024
322db40
refactor useOnrampeventlistener
alissacrane-cb Dec 17, 2024
ad8be9d
add test coverage
alissacrane-cb Dec 17, 2024
ebf9200
add test coverage
alissacrane-cb Dec 17, 2024
81e7ae4
add test coverage
alissacrane-cb Dec 17, 2024
ddd20db
add test coverage
alissacrane-cb Dec 17, 2024
e8672f6
re require transactionReceipt
alissacrane-cb Dec 17, 2024
0b88800
fix test
alissacrane-cb Dec 17, 2024
fbf23c8
add buy to playground
alissacrane-cb Dec 17, 2024
f9b0211
add buy to exporst
alissacrane-cb Dec 17, 2024
01f96ff
update buy button
alissacrane-cb Dec 17, 2024
4fd9db9
update buy button
alissacrane-cb Dec 17, 2024
ec56e5a
update buy button
alissacrane-cb Dec 17, 2024
cc6d4c2
update buy button
alissacrane-cb Dec 17, 2024
0e306b5
add quote validation
alissacrane-cb Dec 17, 2024
f9a05f3
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 17, 2024
727e826
validate quote
alissacrane-cb Dec 17, 2024
265e646
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 17, 2024
27320d9
adjust styling
alissacrane-cb Dec 17, 2024
6767f99
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 17, 2024
a082d8e
fix lint
alissacrane-cb Dec 17, 2024
8117757
add test coverage
alissacrane-cb Dec 17, 2024
8c13dd2
add test coverage
alissacrane-cb Dec 17, 2024
ede2e92
add test coverage
alissacrane-cb Dec 17, 2024
96de464
fix lint
alissacrane-cb Dec 17, 2024
727bb99
add changelog
alissacrane-cb Dec 18, 2024
d3f2280
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 18, 2024
47bff74
adjust margin
alissacrane-cb Dec 18, 2024
1090562
address qa comments
alissacrane-cb Dec 18, 2024
bbda91a
fix test
alissacrane-cb Dec 18, 2024
9d42ae9
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 18, 2024
c0d7c02
remove fromToken
alissacrane-cb Dec 18, 2024
433750e
fix lint
alissacrane-cb Dec 18, 2024
e99f54a
add z-index
alissacrane-cb Dec 18, 2024
dc415b8
add z index
alissacrane-cb Dec 18, 2024
4843432
fix lint
alissacrane-cb Dec 18, 2024
5b3afb3
add missing required fields message and address pr comments
alissacrane-cb Dec 18, 2024
7c63536
add test coverage
alissacrane-cb Dec 18, 2024
19512c4
fix lint
alissacrane-cb Dec 18, 2024
1cf2f1a
fix test and build
alissacrane-cb Dec 18, 2024
dd96265
Merge remote-tracking branch 'origin/alissa.crane/swap-lite-component…
alissacrane-cb Dec 18, 2024
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
5 changes: 5 additions & 0 deletions .changeset/happy-avocados-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

- **feat**: Added Buy component. By @abcrane123. #1729
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@
"import": "./esm/core/api/index.js",
"default": "./esm/core/api/index.js"
},
"./buy": {
"types": "./esm/buy/index.d.ts",
"module": "./esm/buy/index.js",
"import": "./esm/buy/index.js",
"default": "./esm/buy/index.js"
},
"./checkout": {
"types": "./esm/checkout/index.d.ts",
"module": "./esm/checkout/index.js",
Expand Down
2 changes: 2 additions & 0 deletions playground/nextjs-app-router/components/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { cn } from '@/lib/utils';
import { OnchainKitComponent } from '@/types/onchainkit';
import { useContext, useEffect, useState } from 'react';
import DemoOptions from './DemoOptions';
import BuyDemo from './demo/Buy';
import CheckoutDemo from './demo/Checkout';
import FundDemo from './demo/Fund';
import IdentityDemo from './demo/Identity';
Expand All @@ -21,6 +22,7 @@ import WalletDemo from './demo/Wallet';
import WalletDefaultDemo from './demo/WalletDefault';

const activeComponentMapping: Record<OnchainKitComponent, React.FC> = {
[OnchainKitComponent.Buy]: BuyDemo,
[OnchainKitComponent.Fund]: FundDemo,
[OnchainKitComponent.Identity]: IdentityDemo,
[OnchainKitComponent.Transaction]: TransactionDemo,
Expand Down
1 change: 1 addition & 0 deletions playground/nextjs-app-router/components/DemoOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const COMMON_OPTIONS = [
const COMPONENT_CONFIG: Partial<
Record<OnchainKitComponent, (() => React.JSX.Element)[]>
> = {
[OnchainKitComponent.Buy]: [Chain, PaymasterUrl, IsSponsored, SwapConfig],
[OnchainKitComponent.Checkout]: [
Chain,
PaymasterUrl,
Expand Down
67 changes: 67 additions & 0 deletions playground/nextjs-app-router/components/demo/Buy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ENVIRONMENT, ENVIRONMENT_VARIABLES } from '@/lib/constants';
import { Buy } from '@coinbase/onchainkit/buy';
import type { LifecycleStatus } from '@coinbase/onchainkit/swap';
import type { SwapError } from '@coinbase/onchainkit/swap';
import { useCallback, useContext } from 'react';
import type { TransactionReceipt } from 'viem';
import { base } from 'viem/chains';
import { degenToken } from '../../lib/constants';
import { AppContext } from '../AppProvider';

const FALLBACK_DEFAULT_MAX_SLIPPAGE = 3;

function BuyComponent() {
const { chainId, isSponsored, defaultMaxSlippage } = useContext(AppContext);
const handleOnStatus = useCallback((lifecycleStatus: LifecycleStatus) => {
console.log('Status:', lifecycleStatus);
}, []);
const handleOnSuccess = useCallback(
(transactionReceipt?: TransactionReceipt) => {
console.log('Success:', transactionReceipt);
},
[],
);
const handleOnError = useCallback((swapError: SwapError) => {
console.log('Error:', swapError);
}, []);
return (
<div className="relative mb-[50%] flex h-full w-full flex-col items-center">
{chainId !== base.id ? (
<div className="absolute top-0 left-0 z-10 flex h-full w-full flex-col justify-center rounded-xl bg-[#000000] bg-opacity-50 text-center">
<div className="mx-auto w-2/3 rounded-md bg-muted p-6 text-sm">
Swap Demo is only available on Base.
<br />
You're connected to a different network. Switch to Base to continue
using the app.
</div>
</div>
) : (
<></>
)}
{ENVIRONMENT_VARIABLES[ENVIRONMENT.ENVIRONMENT] === 'production' &&
chainId === base.id ? (
<div className="mb-5 italic">
Note: Swap is disabled on production. To test, run the app locally.
</div>
) : null}
<Buy
className="w-full"
onStatus={handleOnStatus}
onSuccess={handleOnSuccess}
onError={handleOnError}
config={{
maxSlippage: defaultMaxSlippage || FALLBACK_DEFAULT_MAX_SLIPPAGE,
}}
isSponsored={isSponsored}
toToken={degenToken}
/>
</div>
);
}
export default function BuyDemo() {
return (
<div className="mx-auto">
<BuyComponent />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function ActiveComponent() {
<SelectValue placeholder="Select component" />
</SelectTrigger>
<SelectContent>
<SelectItem value={OnchainKitComponent.Buy}>Buy</SelectItem>
<SelectItem value={OnchainKitComponent.Fund}>Fund</SelectItem>
<SelectItem value={OnchainKitComponent.Identity}>Identity</SelectItem>
<SelectItem value={OnchainKitComponent.IdentityCard}>
Expand Down
42 changes: 42 additions & 0 deletions playground/nextjs-app-router/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { Token } from '@coinbase/onchainkit/token';
import type { Address } from 'viem';
import { base } from 'viem/chains';

export const deployedContracts: Record<number, { click: Address }> = {
[8543]: {
Expand All @@ -25,3 +27,43 @@ export const ENVIRONMENT_VARIABLES: Record<EnvironmentKey, string | undefined> =
[ENVIRONMENT.PROJECT_ID]: process.env.NEXT_PUBLIC_PROJECT_ID,
[ENVIRONMENT.RESERVOIR_API_KEY]: process.env.NEXT_PUBLIC_RESERVOIR_API_KEY,
};

export const ethToken: Token = {
name: 'ETH',
address: '',
symbol: 'ETH',
decimals: 18,
image:
'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: base.id,
};

export const usdcToken: Token = {
name: 'USDC',
address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
symbol: 'USDC',
decimals: 6,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2',
chainId: base.id,
};

export const degenToken: Token = {
name: 'DEGEN',
address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed',
symbol: 'DEGEN',
decimals: 18,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm',
chainId: base.id,
};

export const daiToken: Token = {
name: 'DAI',
address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
symbol: 'DAI',
decimals: 18,
image:
'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/92/13/9213e31b84c98a693f4c624580fdbe6e4c1cb550efbba15aa9ea68fd25ffb90c-ZTE1NmNjMGUtZGVkYi00ZDliLWI2N2QtNTY2ZWRjMmYwZmMw',
chainId: base.id,
};
1 change: 1 addition & 0 deletions playground/nextjs-app-router/types/onchainkit.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum OnchainKitComponent {
Buy = 'buy',
Fund = 'fund',
Identity = 'identity',
IdentityCard = 'identity-card',
Expand Down
1 change: 1 addition & 0 deletions site/docs/pages/token/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type TokenChipReact = {
token: Token; // Rendered token
onClick?: (token: Token) => void;
className?: string;
isPressable?: boolean; // Default: true
};
```

Expand Down
158 changes: 158 additions & 0 deletions src/buy/components/Buy.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import {
type Config,
type UseConnectReturnType,
useAccount,
useConnect,
} from 'wagmi';
import { useOnchainKit } from '../../core-react/useOnchainKit';
import { degenToken } from '../../token/constants';
import { useOutsideClick } from '../../ui/react/internal/hooks/useOutsideClick';
import { Buy } from './Buy';
import { useBuyContext } from './BuyProvider';

vi.mock('./BuyProvider', () => ({
useBuyContext: vi.fn(),
BuyProvider: ({ children }: { children: React.ReactNode }) => (
<div data-testid="mock-BuyProvider">{children}</div>
),
}));

vi.mock('./BuyDropdown', () => ({
BuyDropdown: () => <div data-testid="mock-BuyDropdown">BuyDropdown</div>,
}));

vi.mock('../../core-react/internal/hooks/useTheme', () => ({
useTheme: vi.fn(),
}));

vi.mock('../../ui/react/internal/hooks/useOutsideClick', () => ({
useOutsideClick: vi.fn(),
}));

vi.mock('../../core-react/useOnchainKit', () => ({
useOnchainKit: vi.fn(),
}));

vi.mock('wagmi', () => ({
useAccount: vi.fn(),
useConnect: vi.fn(),
}));

type useOutsideClickType = ReturnType<
typeof vi.fn<
(
ref: React.RefObject<HTMLElement>,
callback: (event: MouseEvent) => void,
) => void
>
>;

describe('Buy', () => {
let mockSetIsOpen: ReturnType<typeof vi.fn>;
let mockOutsideClickCallback: (e: MouseEvent) => void;

beforeEach(() => {
mockSetIsOpen = vi.fn();
(useBuyContext as Mock).mockReturnValue({
isDropdownOpen: false,
setIsDropdownOpen: mockSetIsOpen,
lifecycleStatus: {
statusName: 'idle',
statusData: {
maxSlippage: 10,
},
},
to: {
token: degenToken,
amount: 10,
setAmount: vi.fn(),
},
address: '0x123',
});

(useAccount as Mock).mockReturnValue({
address: '0x123',
});

vi.mocked(useConnect).mockReturnValue({
connectors: [{ id: 'mockConnector' }],
connect: vi.fn(),
status: 'connected',
} as unknown as UseConnectReturnType<Config, unknown>);

(useOutsideClick as unknown as useOutsideClickType).mockImplementation(
(_, callback) => {
mockOutsideClickCallback = callback;
},
);

(useOnchainKit as Mock).mockReturnValue({
projectId: 'mock-project-id',
});

vi.clearAllMocks();
});

it('renders the Buy component', () => {
render(<Buy className="test-class" toToken={degenToken} />);

expect(screen.getByText('Buy')).toBeInTheDocument();
expect(screen.getByText('DEGEN')).toBeInTheDocument();
});

it('closes the dropdown when clicking outside the container', () => {
(useBuyContext as Mock).mockReturnValue({
isDropdownOpen: true,
setIsDropdownOpen: mockSetIsOpen,
lifecycleStatus: {
statusName: 'idle',
statusData: {
maxSlippage: 10,
},
},
to: {
token: degenToken,
amount: 10,
setAmount: vi.fn(),
},
});

render(<Buy className="test-class" toToken={degenToken} />);

expect(screen.getByTestId('mock-BuyDropdown')).toBeDefined();
mockOutsideClickCallback({} as MouseEvent);

expect(mockSetIsOpen).toHaveBeenCalledWith(false);
});

it('does not close the dropdown when clicking inside the container', () => {
(useBuyContext as Mock).mockReturnValue({
isDropdownOpen: true,
setIsDropdownOpen: mockSetIsOpen,
lifecycleStatus: {
statusName: 'idle',
statusData: {
maxSlippage: 10,
},
},
to: {
token: degenToken,
amount: 10,
setAmount: vi.fn(),
},
});

render(<Buy className="test-class" toToken={degenToken} />);

expect(screen.getByTestId('mock-BuyDropdown')).toBeDefined();
fireEvent.click(screen.getByTestId('mock-BuyDropdown'));
expect(mockSetIsOpen).not.toHaveBeenCalled();
});

it('should not trigger click handler when dropdown is closed', () => {
render(<Buy className="test-class" toToken={degenToken} />);
expect(screen.queryByTestId('mock-BuyDropdown')).not.toBeInTheDocument();
});
});
Loading
Loading