Skip to content

Commit 16b3390

Browse files
swdiscordiaclaude0xApotheosis
authored
feat: add a /developers landing page (#104)
* feat(developers): add a /developers landing page for integration partners Adds a dedicated marketing/onboarding page for teams integrating ShapeShift's swap widget or REST API (dApps, chains, wallets), plus a "Developers" entry in the site nav and footer that closes #98. ## What's new - `/developers`: hero (the real, live @shapeshiftoss/swap-widget SDK embedded in the page — not a mock), chain/asset stats, partner-protocol logos, an interactive widget-theming preview, the "why ShapeShift" routing pitch, a REST API walkthrough with live code samples, the affiliate revenue-share model, an integration-path FAQ, and a closing CTA. Full section-by-section breakdown in `app/[lang]/developers/README.md`. - Nav: a fourth "Developers" tab (desktop hover dropdown + mobile expandable section) alongside Products/Resources/DAO, and a matching footer column — both reuse the existing `TAppLink`/`headerTabs`/ `footerLinks` patterns this repo already has for the other three tabs. ## Dependencies Adds `@shapeshiftoss/swap-widget` and its peer stack (wagmi, viem, @tanstack/react-query, @reown/appkit + adapters, @solana/wallet-adapter-*) so the hero embeds the widget for real rather than a static mockup — the same component that runs on widget.shapeshift.com itself. This is a first-of-its-kind dependency category for this repo (no prior wallet/web3 libs here); happy to discuss alternatives if that footprint is a concern. `walletConnectProjectId` is a placeholder for now (documented TODO(shapeshift-business) in DevelopersHero.tsx) — the asset/chain selection and swap-form preview are fully functional and real, but actual wallet connection needs a real Reown Cloud project ID from the business side before it can go live; the "Connect Wallet" button hands off to the real widget instead in the meantime. `next.config.ts`'s `webpack` block stubs a few of the widget's optional transitive dependencies (wagmi's Coinbase Smart Wallet connector pulls in `@x402/*` payment-protocol packages that aren't actually used here and aren't installed) so `next build` resolves cleanly — see the comments there for the exact chain. ## CSP `middleware.ts` widens the CSP for the widget's own origins (Reown, CoinGecko, Coinbase SDK bootstrap) but scopes every addition to `/developers` specifically via `isDevelopersPath()`, so every other route's policy is byte-for-byte unchanged. ## Scope Verified this only touches the files listed above — no other page, component, or shared utility is modified. `bun run lint`, `tsc --noEmit`, and `next build` all pass clean, and I manually re-tested the existing Products/Resources/DAO nav dropdowns and footer, plus a handful of other routes, to confirm nothing regressed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): allow api.shapeshift.com in CSP so swap rates resolve The widget's actual rate-fetching calls (GET api.shapeshift.com/v1/swap/rates) were silently CSP-blocked -- connect-src only allowed app.shapeshift.com (a different, unrelated subdomain used for icon/asset data), never the main API domain the SDK defaults to. Every single swap pair showed "No routes available" as a result, since no rate request could ever reach the network -- confirmed by comparing against widget.shapeshift.com directly (same pair, same amount, real quote resolves there). Verified: 100 USDC (Arbitrum One) -> ETH (Ethereum) now resolves to a real quote (~0.0401 ETH), matching the official widget within normal price-movement variance. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): simplify CSP to match how the widget is actually documented Replaces a hand-picked connect-src hostname list (which already missed several domains the SDK needs -- rate-fetching, balance RPCs, WalletConnect relay/pairing) with a broad https:/wss: grant, scoped to /developers only. The SDK's own README integration guide never mentions CSP at all -- install, import the stylesheet, render <SwapWidget />. That's because most sites embedding it have no connect-src restriction to begin with. This repo does, so an exact allowlist means re-discovering one missing host at a time (as already happened twice), and staying accurate forever against a widget that adds chains/RPC endpoints over time. Every other route keeps the exact, unchanged restrictive default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): scope CSP to exactly what asset selection and rates need Replaces the previous broad https:/wss: connect-src grant with the 4 origins the currently-live feature set (asset/chain selection + real quotes) actually calls: api.shapeshift.com (rates), app.shapeshift.com (token/chain list), api.coingecko.com and api.proxy.shapeshift.com (market data). Verified this is both necessary and sufficient: dropping to just api.shapeshift.com broke the token picker entirely (empty list, no market data), and everything works end to end with these four. Wallet-connect-specific origins (WalletConnect relay, per-chain execution RPCs) are intentionally left out -- that functionality isn't live yet (placeholder walletConnectProjectId), so there's nothing to test it against. Add them alongside the real project ID once it exists, rather than guessing now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): match the documented integration exactly, fix real CSP bug Rewrote the widget embed to closely mirror the SDK's own README Quick Start: removed a custom click-interception hack that redirected "Connect Wallet" clicks elsewhere, and removed a ResizeObserver/ CSS-transform-scale wrapper that shrank the widget to fit its container -- neither is documented anywhere in the SDK's docs, both were invented workarounds. Replaced the sizing fix with a plain overflow-x-auto wrapper (standard CSS, no JS) since the widget has a fixed native width and the docs describe no sizing API. Also found and fixed the real cause of a blank-widget regression: middleware.ts applied 'strict-dynamic' to script-src unconditionally, but dev mode's script-src has no nonce -- per the CSP spec, 'strict-dynamic' with no nonce/hash present disables ALL host-based allowlisting and 'unsafe-inline', blocking every script on the page (including React itself). Now gated on having an actual nonce (production only). Verified against the live widget.shapeshift.com (same package, 0.9.0): identical 28-chain list, identical token lists on spot-checked chains, identical defaults and theme. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): propagate CSP headers on locale rewrite, hard-nav to /developers Two blocking issues from PR review: - middleware.ts: the default-locale rewrite branch of handleLocaleRouting built a brand-new NextResponse.rewrite(), discarding every header (CSP included) already set on `response`. Bare /developers (what internal links emit for English) shipped with zero Content-Security-Policy as a result -- pre-existing for `/` too. Now copies response's headers and cookies onto the rewritten response before returning it. - DesktopHeader.tsx / MobileHeader.tsx: CSP is a document-load header: it cannot change on a Next.js client-side transition. Reaching /developers via LocalizedLink (soft nav) left the previous page's narrower CSP in effect, blocking app.shapeshift.com/coingecko/api.proxy and breaking the embedded widget. Rather than widen the site-wide CSP, the Developers nav entry now uses a plain <a> so it always forces a real navigation, guaranteeing middleware re-runs. Also fixes a dead-click-zone bug in DesktopHeader where the padded wrapper showed a pointer cursor but only the inner text was clickable. Verified against a clean production build (next build && next start): CSP is present and correctly scoped on bare /developers, /en/developers, and unaffected on /, and a Playwright run clicking "Developers" from the homepage confirms the widget's chain/asset picker loads real data. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): correct API/URL content against the real API and docs Verified directly against the live API (api.shapeshift.com/docs, /v1/chains/count, /v1/assets/count) rather than assuming: - DevelopersApiSection.tsx: the code samples showed "X-API-Key: YOUR_KEY", a header that doesn't exist in the real API. No auth is required at all; the real optional header is "X-Partner-Code" (a registered partner code, not a secret), sent only on the swap endpoints for affiliate attribution. Also: the copy button flipped to "COPIED" even when clipboard.writeText() rejected -- now gated on actual success. - constants.tsx: the "Partners portal" link pointed at widget.shapeshift.com (the widget config sandbox), but its own description says "Get your affiliate code" -- that only happens at dashboard.affiliate.shapeshift.com, which is what the Economics section already correctly links to. Also exports developerDocsSectionUrl so it can be reused instead of hand-building URLs. - DevelopersFaq.tsx: the "View supported chains" link built `${developerDocsUrl}/v1/chains`, which doesn't resolve to a real page. Switched to the existing developerDocsSectionUrl('#tag/supported-chains') pattern already used elsewhere for the same docs section. Replaced the brittle question-text string match with an explicit hasChainsLink flag on the FAQ item. - dictionary/developers.ts: removed widget.features/steps/cardLabel, economics.steps/banner, and api.ctaButton -- confirmed via full-repo grep that no component reads any of them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): theme-switcher focus guard, box alignment, footer grid - DevelopersWidgetSection.tsx: focus events from the inner mock "Connect Wallet" button bubbled up to the theme-preview ring's onFocus handler, snapping the color preset and freezing the idle rotation on a click that was never meant to touch the ring. Guarded with event.target !== event.currentTarget. - DevelopersWhyShapeShift.tsx: the third box in the routing illustration had a stray sm:text-right with no matching treatment on the first box, reading as visually unbalanced. Removed it so all three boxes align consistently. - Footer.tsx: the footer grid was hardcoded to lg:grid-cols-4, so adding the Developers column (5th category) pushed Connect onto its own row. Now lg:grid-cols-5. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): dedupe CTA background image public/cta/moon-bg.png was byte-identical (verified via shasum) to the already-existing public/bannerFooterBg.png, duplicating a 3.2MB asset for no reason. DevelopersCta.tsx now points at the existing shared file and the duplicate is deleted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): drop the "snapshot" disclaimer under the stats row Per review feedback, the "figures are a snapshot and not updated automatically" caveat reads oddly on a marketing page. Numbers themselves (48+ chains, 30,000+ assets) are kept as-is. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(developers): reserve space for swap quotes * fix(developers): configure wallet access and address review feedback --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Apotheosis <0xapotheosis@gmail.com>
1 parent 5d258a1 commit 16b3390

29 files changed

Lines changed: 1839 additions & 20 deletions

‎.env.local.sample‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ NEXT_PUBLIC_ONRAMPER_URL=https://buy.onramper.com/?apiKey=pk_prod_01HWMA66BYRB22
33
NEXT_PUBLIC_STRAPI_URL=https://strapi.shapeshift.com
44
NEXT_PUBLIC_WEGLOT_API_KEY=obtain-from-engineering
55

6-
STRAPI_API_TOKEN=obtain-from-engineering
6+
STRAPI_API_TOKEN=obtain-from-engineering
7+
8+
# Public Reown project ID shared with the swap-widget service. Set before building.
9+
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=69a04035cb588540ee2eb062c394a87a
10+
11+
# ShapeShift registered affiliate code; confirm the payout account before configuring.
12+
NEXT_PUBLIC_SHAPESHIFT_PARTNER_CODE=

‎app/[lang]/_components/Footer.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function Footer(): ReactNode {
3030

3131
{/* Middle row: Menu items */}
3232
<div className={'mx-auto mt-12 w-full max-w-[720px]'}>
33-
<div className={'grid grid-cols-2 gap-8 lg:grid-cols-4'}>
33+
<div className={'grid grid-cols-2 gap-8 lg:grid-cols-5'}>
3434
{Object.entries(footerLinks).map(([category, links]) => (
3535
<div key={category} className={'flex flex-col gap-4'}>
3636
<h3 className={'cursor-default text-sm font-medium text-gray-500'}>{category}</h3>

‎app/[lang]/_components/header/DesktopHeader.tsx‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { dAppUrl, headerTabs } from '@/app/[lang]/_utils/constants'
1515

1616
import { containerAnimation } from './animations'
1717
import { DAOExpand } from './DaoExpand'
18+
import { DevelopersExpand } from './DevelopersExpand'
1819
import { LanguageExpand } from './LanguageExpand'
1920
import { ProductsExpand } from './ProductsExpand'
2021
import { ResourcesExpand } from './ResourcesExpand'
@@ -41,6 +42,7 @@ export function DesktopHeader({ className, switchLanguageAction, currentLanguage
4142
const tabContent: Record<string, ReactNode> = useMemo(
4243
() => ({
4344
products: <ProductsExpand setCurrentTab={setCurrentTab} />,
45+
developers: <DevelopersExpand setCurrentTab={setCurrentTab} />,
4446
resources: <ResourcesExpand setCurrentTab={setCurrentTab} />,
4547
dao: <DAOExpand setCurrentTab={setCurrentTab} />,
4648
language: <LanguageExpand switchLanguageAction={switchLanguageAction} currentLanguage={currentLanguage} />,
@@ -85,11 +87,18 @@ export function DesktopHeader({ className, switchLanguageAction, currentLanguage
8587
key={tab.name}
8688
onMouseEnter={() => setCurrentTab(tab.value)}
8789
className={cl(
88-
'cursor-pointer p-4 text-sm font-medium transition-colors',
90+
'text-sm font-medium transition-colors',
91+
tab.value === 'developers' ? '' : 'cursor-pointer p-4',
8992
currentTab && currentTab !== tab.value ? 'text-gray-500' : 'text-white'
9093
)}
9194
>
92-
{tab.name}
95+
{tab.value === 'developers' ? (
96+
<a href={`/${currentLanguage}${tab.href}`} className={'block h-full w-full cursor-pointer p-4'}>
97+
{tab.name}
98+
</a>
99+
) : (
100+
tab.name
101+
)}
93102
</div>
94103
))}
95104
</nav>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { motion } from 'framer-motion'
2+
3+
import { Button } from '@/app/[lang]/_components/Button'
4+
import { LocalizedLink } from '@/app/[lang]/_components/LocalizedLink'
5+
import { appDevelopers, developerDocsUrl } from '@/app/[lang]/_utils/constants'
6+
import { DEVELOPERS_DICT } from '@/app/[lang]/_utils/dictionary/developers'
7+
8+
import { expandAnimation } from './animations'
9+
10+
import type { TAppLink } from '@/app/[lang]/_utils/constants'
11+
import type { ReactNode } from 'react'
12+
13+
function DeveloperItem({
14+
name,
15+
href,
16+
target,
17+
description,
18+
icon,
19+
onClick,
20+
}: TAppLink & { onClick: () => void }): ReactNode {
21+
return (
22+
<LocalizedLink
23+
href={href}
24+
target={target}
25+
className={
26+
'flex w-1/3 max-w-[232px] grow gap-2 rounded-lg px-6 py-4 transition-colors duration-300 hover:bg-white/10'
27+
}
28+
onClick={onClick}
29+
>
30+
<div className={'w-6 shrink-0'}>{icon}</div>
31+
<div className={'ml-4 flex max-h-[120px] flex-col gap-1'}>
32+
<span className={'whitespace-normal break-words text-sm font-medium leading-5'}>{name}</span>
33+
{description && (
34+
<span className={'whitespace-normal break-words text-xs leading-5 text-gray-500'}>{description}</span>
35+
)}
36+
</div>
37+
</LocalizedLink>
38+
)
39+
}
40+
41+
export function DevelopersExpand({ setCurrentTab }: { setCurrentTab: (tab: string) => void }): ReactNode {
42+
return (
43+
<motion.div className={'grid grid-cols-12'} {...expandAnimation}>
44+
<div className={'col-span-4 flex flex-col border-r border-white/5 p-16'}>
45+
<p className={'mb-4 text-2xl font-medium'}>
46+
{DEVELOPERS_DICT.expand.titleLine1} <br />
47+
{DEVELOPERS_DICT.expand.titleLine2}
48+
</p>
49+
<p className={'mb-10 max-w-[327px] text-sm text-gray-500'}>{DEVELOPERS_DICT.expand.description}</p>
50+
<Button
51+
variant={'blue'}
52+
className={'whitespace-nowrap'}
53+
title={DEVELOPERS_DICT.expand.ctaButton}
54+
href={developerDocsUrl}
55+
/>
56+
</div>
57+
58+
<div className={'col-span-8 p-16'}>
59+
<div className={'flex flex-row flex-wrap gap-4'}>
60+
{appDevelopers.map((developerResource) => (
61+
<DeveloperItem
62+
onClick={() => setCurrentTab('empty')}
63+
key={developerResource.name}
64+
name={developerResource.name}
65+
href={developerResource.href}
66+
description={developerResource.description}
67+
icon={developerResource.icon}
68+
target={developerResource.target}
69+
/>
70+
))}
71+
</div>
72+
</div>
73+
</motion.div>
74+
)
75+
}

‎app/[lang]/_components/header/MobileHeader.tsx‎

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { IconClose } from '@/app/[lang]/_icons/IconClose'
1111
import { IconMenu } from '@/app/[lang]/_icons/IconMenu'
1212
import { IconPlanet } from '@/app/[lang]/_icons/IconPlanet'
1313
import { ShapeshiftLogo } from '@/app/[lang]/_icons/ShapeshiftLogo'
14-
import { appDao, appProducts, appResources, headerTabs } from '@/app/[lang]/_utils/constants'
14+
import { appDao, appDevelopers, appProducts, appResources, headerTabs } from '@/app/[lang]/_utils/constants'
1515
import { SUPPORTED_LANGUAGES } from '@/app/[lang]/_utils/i18nconfig'
1616

1717
import type { TAppLink } from '@/app/[lang]/_utils/constants'
@@ -23,6 +23,11 @@ const mobileTabs: { name: string; value: string; items: TAppLink[] }[] = [
2323
value: 'products',
2424
items: appProducts.slice(0, 6),
2525
},
26+
{
27+
name: 'Developers',
28+
value: 'developers',
29+
items: appDevelopers.slice(0, 6),
30+
},
2631
{
2732
name: 'Resources',
2833
value: 'resources',
@@ -111,17 +116,42 @@ export function MobileHeader({
111116
<div className={'space-y-2'}>
112117
{headerTabs.map((tab) => (
113118
<div key={tab.name} className={'overflow-hidden rounded-2xl bg-secondBg'}>
114-
<button
115-
onClick={() => {
116-
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
117-
}}
118-
className={'flex w-full items-center justify-between p-6 text-2xl'}
119-
>
120-
{tab.name}
121-
<div className={'flex items-center gap-2 rounded-[100%] bg-white/5 p-2'}>
122-
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
119+
{tab.value === 'developers' ? (
120+
<div className={'flex w-full items-center text-2xl'}>
121+
<a
122+
href={`/${currentLanguage}${tab.href}`}
123+
onClick={() => setIsMenuOpen(false)}
124+
className={'flex-1 p-6'}
125+
>
126+
{tab.name}
127+
</a>
128+
<button
129+
type={'button'}
130+
aria-label={'Toggle Developers links'}
131+
aria-expanded={expandedSection === tab.value}
132+
onClick={() => {
133+
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
134+
}}
135+
className={
136+
'mr-4 flex min-h-12 min-w-12 items-center justify-center rounded-full bg-white/5'
137+
}
138+
>
139+
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
140+
</button>
123141
</div>
124-
</button>
142+
) : (
143+
<button
144+
onClick={() => {
145+
setExpandedSection(expandedSection === tab.value ? '' : tab.value)
146+
}}
147+
className={'flex w-full items-center justify-between p-6 text-2xl'}
148+
>
149+
{tab.name}
150+
<div className={'flex items-center gap-2 rounded-[100%] bg-white/5 p-2'}>
151+
<AnimatedPlusMinusIcon isOpen={expandedSection === tab.value} />
152+
</div>
153+
</button>
154+
)}
125155
<AnimatePresence>
126156
{expandedSection === tab.value && (
127157
<motion.div className={'space-y-4 p-6 pt-0'} {...expandAnimation}>

‎app/[lang]/_icons/IconSettings.tsx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { ReactNode, SVGProps } from 'react'
2+
3+
export function IconSettings(props: SVGProps<SVGSVGElement>): ReactNode {
4+
return (
5+
<svg {...props} width={'24'} height={'24'} viewBox={'0 0 24 24'} fill={'none'} xmlns={'http://www.w3.org/2000/svg'}>
6+
<circle cx={'12'} cy={'12'} r={'3'} stroke={'currentColor'} strokeWidth={'2'} />
7+
<path
8+
d={
9+
'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82A1.65 1.65 0 0 0 3 13.09H2.91a2 2 0 0 1 0-4H3a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V2.91a2 2 0 0 1 4 0V3a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1h.09a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'
10+
}
11+
stroke={'currentColor'}
12+
strokeWidth={'2'}
13+
strokeLinecap={'round'}
14+
strokeLinejoin={'round'}
15+
/>
16+
</svg>
17+
)
18+
}

‎app/[lang]/_utils/constants.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export const dAppUrl =
3939

4040
const docsUrl = 'https://github.com/shapeshift/web'
4141

42+
export const developerDocsUrl = 'https://api.shapeshift.com/docs'
43+
44+
export const developerDocsSectionUrl = (section: string): string => `${developerDocsUrl}${section}`
45+
4246
const githubUrl = 'https://github.com/shapeshift'
4347

4448
export const footerButtonTitle = 'Donate'
@@ -226,6 +230,51 @@ export const appResources: TAppLink[] = [
226230
},
227231
]
228232

233+
export const appDevelopers: TAppLink[] = [
234+
{
235+
name: 'Swap Widget SDK',
236+
href: developerDocsSectionUrl('#tag/swap-widget-sdk'),
237+
description: 'Integrate the drop-in swap widget with built-in UI.',
238+
icon: <IconResource />,
239+
target: '_blank',
240+
},
241+
{
242+
name: 'Widget sandbox',
243+
href: 'https://widget.shapeshift.com/',
244+
description: 'Configure and preview the drop-in swap widget.',
245+
icon: <IconDocs />,
246+
target: '_blank',
247+
},
248+
{
249+
name: 'Swap API Doc',
250+
href: developerDocsSectionUrl('#tag/swaps'),
251+
description: 'Get rates, executable quotes, and swap status.',
252+
icon: <IconTriLink />,
253+
target: '_blank',
254+
},
255+
{
256+
name: 'Supported Assets',
257+
href: developerDocsSectionUrl('#tag/supported-assets'),
258+
description: 'List assets and fetch asset details by CAIP-19 ID.',
259+
icon: <IconWallet />,
260+
target: '_blank',
261+
},
262+
{
263+
name: 'Supported Chains',
264+
href: developerDocsSectionUrl('#tag/supported-chains'),
265+
description: 'Review the chains available through the public API.',
266+
icon: <IconChains />,
267+
target: '_blank',
268+
},
269+
{
270+
name: 'Partners portal',
271+
href: 'https://dashboard.affiliate.shapeshift.com/',
272+
description: 'Get your affiliate code and configure the widget.',
273+
icon: <IconActivityRings />,
274+
target: '_blank',
275+
},
276+
]
277+
229278
export const appDao: TAppLink[] = [
230279
{
231280
name: 'FOX Token',
@@ -314,12 +363,14 @@ export const appProducts: TAppLink[] = [
314363

315364
export const headerTabs = [
316365
{ name: 'Products', href: '/products', value: 'products' },
366+
{ name: 'Developers', href: '/developers', value: 'developers' },
317367
{ name: 'Resources', href: '/resources', value: 'resources' },
318368
{ name: 'DAO', href: '/dao', value: 'dao' },
319369
]
320370

321371
export const footerLinks: Record<string, TAppLink[]> = {
322372
Products: appProducts,
373+
Developers: appDevelopers,
323374
Resources: appResources,
324375
DAO: appDao,
325376
Connect: [
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
export const DEVELOPERS_DICT = {
2+
expand: {
3+
titleLine1: 'Build with',
4+
titleLine2: 'ShapeShift.',
5+
description: 'Explore API documentation, integration guides, and reference material for crypto applications.',
6+
ctaButton: 'View API Docs',
7+
},
8+
page: {
9+
widget: {
10+
title: 'Your brand. Make it feel native.',
11+
description:
12+
'Choose a theme, set the default assets, and preview a swap experience that feels built into your product.',
13+
ctaButton: 'Open the widget sandbox',
14+
},
15+
api: {
16+
title: 'Skip the UI and build your own',
17+
description:
18+
'Use the same routing engine through a REST API. We return unsigned transactions for your users to sign and broadcast.',
19+
endpoints: [
20+
{
21+
method: 'GET /v1/assets',
22+
title: 'List chains and assets',
23+
description: 'Enumerate what your users can trade, by CAIP-19 ID.',
24+
},
25+
{
26+
method: 'GET /v1/swap/rates',
27+
title: 'Fetch rates for a pair',
28+
description: 'One call returns a rate from every available swapper. No transaction yet.',
29+
},
30+
{
31+
method: 'POST /v1/swap/quote',
32+
title: 'Get an executable quote',
33+
description: 'Returns transaction data. Your user signs it in their own wallet, funds never touch us.',
34+
},
35+
],
36+
},
37+
economics: {
38+
title: 'How the revenue share works',
39+
description: 'One partner code follows every swap to on-chain settlement. No invoices or payout schedules.',
40+
},
41+
stats: {
42+
chains: { value: '48+', title: 'Supported chains' },
43+
assets: { value: '30,000+', title: 'Tradable assets' },
44+
volume: { value: '$1.7B+', title: 'Lifetime swap volume' },
45+
},
46+
faq: {
47+
title: 'Questions partners ask',
48+
items: [
49+
{
50+
question: 'Which chains are supported?',
51+
answer:
52+
'Bitcoin, Ethereum and the major L2s (Arbitrum, Base, Optimism), Solana, Avalanche, BNB Chain, Cosmos, and more. 48+ chains today, with new routes added as the underlying aggregators support them. The full, current list is served by the API: https://api.shapeshift.com/v1/chains',
53+
hasChainsLink: true,
54+
},
55+
{
56+
question: 'What does it cost to integrate?',
57+
answer:
58+
'Nothing. There is no license fee or API key charge. Your affiliate fee is added on top of the protocol fee and paid directly to you.',
59+
hasChainsLink: false,
60+
},
61+
{
62+
question: 'Who holds user funds?',
63+
answer:
64+
'Swaps are non-custodial end to end. Users sign transactions from their own wallets, and funds move directly through the underlying protocols.',
65+
hasChainsLink: false,
66+
},
67+
{
68+
question: 'What support do partners get?',
69+
hasChainsLink: false,
70+
answer:
71+
'Support does not stop at launch. Partners get a direct channel to our integration engineers for the lifetime of the integration, on both technical and marketing questions, plus example repos and a staging affiliate ID for testing.',
72+
},
73+
],
74+
},
75+
cta: {
76+
title: 'Integrate multichain swaps in your product',
77+
description: 'Configure the Widget, preview your theme, create a partner code, and start earning on every swap.',
78+
ctaPrimary: 'Configure the Widget',
79+
ctaSecondary: 'Talk with us',
80+
},
81+
},
82+
} as const

0 commit comments

Comments
 (0)