Skip to content

Commit

Permalink
Feat: Arweave Integration (#145)
Browse files Browse the repository at this point in the history
* feat: intial setup for arweave integration

* fix: additional pages links/routing

* fix: added missing pages/components/hooks

* feat: [wip] arweave account

* feat: [wip] new post

* feat: [wip] new post

* feat: [wip] new post

* feat: upload profile picture

* feat: [wip] tx page

* feat: tx page

* fix: remove connect wallet button from sidebar, warnings fixed

* feat: couple eth address, pending txs, insufficient error component

* fix: minor UI improvements

* fix: mobile layout

* fix: formatting

* fix: mobile layout

* fix: insufficient balance

* fix: format errors

* fix: add binance link to Ar exchange

* fix: false insufficient balance error

* fix: post details mobile layout

* fix: format and lint errors

* fix: update lockfile

* fix: format and lint errors

---------

Co-authored-by: Q <[email protected]>
  • Loading branch information
marthendalnunes and stackedq authored Aug 23, 2023
1 parent 893bd18 commit 95490ef
Show file tree
Hide file tree
Showing 50 changed files with 2,358 additions and 12 deletions.
6 changes: 6 additions & 0 deletions app/(general)/integration/arweave/account/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'
import { ArweaveAccountEdit } from '@/integrations/arweave/components/arweave-account/form'

export default function ArweaveEditAccountPage() {
return <ArweaveAccountEdit />
}
6 changes: 6 additions & 0 deletions app/(general)/integration/arweave/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'
import { ArweaveAccount } from '@/integrations/arweave/components/arweave-account'

export default function ArweaveAccountPage() {
return <ArweaveAccount />
}
66 changes: 66 additions & 0 deletions app/(general)/integration/arweave/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use client'
import { ReactNode } from 'react'

import { motion } from 'framer-motion'
import Image from 'next/image'
import Balancer from 'react-wrap-balancer'

import { IsDarkTheme } from '@/components/shared/is-dark-theme'
import { IsLightTheme } from '@/components/shared/is-light-theme'
import { LinkComponent } from '@/components/shared/link-component'
import { FADE_DOWN_ANIMATION_VARIANTS } from '@/config/design'
import { turboIntegrations } from '@/data/turbo-integrations'
import { ArweaveWalletProvider } from '@/integrations/arweave/context/arweave-wallet'

import { SideBar } from './sidebar'

const integrationData = turboIntegrations.arweave

export default function ArweaveLayout({ children }: { children: ReactNode }) {
return (
<ArweaveWalletProvider>
<motion.div
animate="show"
className="flex-center w-full flex-col items-center justify-center text-center"
initial="hidden"
viewport={{ once: true }}
whileInView="show"
variants={{
hidden: {},
show: {
transition: {
staggerChildren: 0.15,
},
},
}}>
<motion.div className="max-w-3xl content-center items-center px-5 text-center xl:px-0" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<IsLightTheme>
<Image alt="Starter logo" className="mx-auto" height={100} src={integrationData.imgDark} width={100} />
</IsLightTheme>
<IsDarkTheme>
<Image alt="Starter logo" className="mx-auto" height={100} src={integrationData.imgLight} width={100} />
</IsDarkTheme>
<motion.h1
className="text-gradient-sand my-8 text-center text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-8xl md:leading-[6rem]"
variants={FADE_DOWN_ANIMATION_VARIANTS}>
{integrationData.name}
</motion.h1>
<motion.p className="my-4 text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<Balancer>{integrationData.description}</Balancer>
</motion.p>
<motion.div className="my-4 text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<LinkComponent className="btn btn-primary" href={integrationData.url}>
Documentation
</LinkComponent>
</motion.div>
</motion.div>
<motion.div className="container my-4 w-full text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<section className="mt-10 flex flex-col overflow-hidden rounded-xl bg-neutral-200 dark:bg-neutral-900 sm:flex-row">
<SideBar />
<div className="flex-center min-h-[600px] flex-1 flex-col items-center justify-center p-10 text-center">{children}</div>
</section>
</motion.div>
</motion.div>
</ArweaveWalletProvider>
)
}
9 changes: 9 additions & 0 deletions app/(general)/integration/arweave/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IntegrationOgImage } from '@/components/ui/social/og-image-integrations'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default IntegrationOgImage('arweave')
6 changes: 6 additions & 0 deletions app/(general)/integration/arweave/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'
import { ConnectArweaveWallet } from '@/integrations/arweave/components/connect-arweave-wallet'

export default function ArweaveHome() {
return <ConnectArweaveWallet />
}
10 changes: 10 additions & 0 deletions app/(general)/integration/arweave/posts/[txId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use client'

import { Post } from '@/integrations/arweave/components/post'
import { ArweaveTxId } from '@/integrations/arweave/utils/types'

export default function ERC20({ params }: { params: { txId: ArweaveTxId } }) {
const { txId } = params

return <Post txId={txId} />
}
6 changes: 6 additions & 0 deletions app/(general)/integration/arweave/posts/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'
import { FormNewPost } from '@/integrations/arweave/components/form-new-post'

export default function FormNewPostPage() {
return <FormNewPost />
}
6 changes: 6 additions & 0 deletions app/(general)/integration/arweave/posts/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'
import { ListPosts } from '@/integrations/arweave/components/list-posts'

export default function ListPostsPage() {
return <ListPosts />
}
7 changes: 7 additions & 0 deletions app/(general)/integration/arweave/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client'

import { ArweaveSettings } from '@/integrations/arweave/components/settings'

export default function ArweaveSettingsPage() {
return <ArweaveSettings />
}
62 changes: 62 additions & 0 deletions app/(general)/integration/arweave/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { LinkComponent } from '@/components/shared/link-component'
import { turboIntegrations } from '@/data/turbo-integrations'
import { ArweaveAccountPreview } from '@/integrations/arweave/components/arweave-account/sidebar-preview'

export const SideBar = () => {
const arweaveBaseUrl = turboIntegrations.arweave.href
return (
<aside aria-label="Sidebar" className="w-full sm:w-64">
<div className="overflow-y-auto bg-neutral-50 px-3 py-4 text-left dark:bg-neutral-800 sm:h-full">
<ArweaveAccountPreview />
<ul className="space-y-2 font-medium">
<li>
<LinkComponent
className="group flex items-center rounded-lg p-2 text-neutral-900 hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700"
href={`${arweaveBaseUrl}/account`}>
<svg
aria-hidden="true"
className="h-5 w-5 text-neutral-500 transition duration-75 group-hover:text-neutral-900 dark:text-neutral-400 dark:group-hover:text-white"
fill="currentColor"
viewBox="0 0 22 21"
xmlns="http://www.w3.org/2000/svg">
<path d="M16.975 11H10V4.025a1 1 0 0 0-1.066-.998 8.5 8.5 0 1 0 9.039 9.039.999.999 0 0 0-1-1.066h.002Z" />
<path d="M12.5 0c-.157 0-.311.01-.565.027A1 1 0 0 0 11 1.02V10h8.975a1 1 0 0 0 1-.935c.013-.188.028-.374.028-.565A8.51 8.51 0 0 0 12.5 0Z" />
</svg>
<span className="ml-3">Arweave account</span>
</LinkComponent>
</li>
<li>
<LinkComponent
className="group flex items-center rounded-lg p-2 text-neutral-900 hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700"
href={`${arweaveBaseUrl}/posts`}>
<svg
aria-hidden="true"
className="h-5 w-5 shrink-0 text-neutral-500 transition duration-75 group-hover:text-neutral-900 dark:text-neutral-400 dark:group-hover:text-white"
fill="currentColor"
viewBox="0 0 18 18"
xmlns="http://www.w3.org/2000/svg">
<path d="M6.143 0H1.857A1.857 1.857 0 0 0 0 1.857v4.286C0 7.169.831 8 1.857 8h4.286A1.857 1.857 0 0 0 8 6.143V1.857A1.857 1.857 0 0 0 6.143 0Zm10 0h-4.286A1.857 1.857 0 0 0 10 1.857v4.286C10 7.169 10.831 8 11.857 8h4.286A1.857 1.857 0 0 0 18 6.143V1.857A1.857 1.857 0 0 0 16.143 0Zm-10 10H1.857A1.857 1.857 0 0 0 0 11.857v4.286C0 17.169.831 18 1.857 18h4.286A1.857 1.857 0 0 0 8 16.143v-4.286A1.857 1.857 0 0 0 6.143 10Zm10 0h-4.286A1.857 1.857 0 0 0 10 11.857v4.286c0 1.026.831 1.857 1.857 1.857h4.286A1.857 1.857 0 0 0 18 16.143v-4.286A1.857 1.857 0 0 0 16.143 10Z" />
</svg>
<span className="ml-3 flex-1 whitespace-nowrap">Posts</span>
</LinkComponent>
</li>
<li>
<LinkComponent
className="group flex items-center rounded-lg p-2 text-neutral-900 hover:bg-neutral-100 dark:text-white dark:hover:bg-neutral-700"
href={`${arweaveBaseUrl}/posts/new`}>
<svg
aria-hidden="true"
className="h-5 w-5 shrink-0 text-neutral-500 transition duration-75 group-hover:text-neutral-900 dark:text-neutral-400 dark:group-hover:text-white"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="m17.418 3.623-.018-.008a6.713 6.713 0 0 0-2.4-.569V2h1a1 1 0 1 0 0-2h-2a1 1 0 0 0-1 1v2H9.89A6.977 6.977 0 0 1 12 8v5h-2V8A5 5 0 1 0 0 8v6a1 1 0 0 0 1 1h8v4a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1v-4h6a1 1 0 0 0 1-1V8a5 5 0 0 0-2.582-4.377ZM6 12H4a1 1 0 0 1 0-2h2a1 1 0 0 1 0 2Z" />
</svg>
<span className="ml-3 flex-1 whitespace-nowrap">New Post</span>
</LinkComponent>
</li>
</ul>
</div>
</aside>
)
}
9 changes: 9 additions & 0 deletions app/(general)/integration/arweave/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Image from './opengraph-image'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default Image
15 changes: 15 additions & 0 deletions app/(general)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,21 @@ const features = [
</div>
),
},
{
title: turboIntegrations.arweave.name,
description: turboIntegrations.arweave.description,
href: turboIntegrations.arweave.href,
demo: (
<div className="flex items-center justify-center space-x-20">
<IsLightTheme>
<Image alt="Arweave logo" height={100} src={turboIntegrations.arweave.imgDark} width={100} />
</IsLightTheme>
<IsDarkTheme>
<Image alt="Arweave logo" height={100} src={turboIntegrations.arweave.imgLight} width={100} />
</IsDarkTheme>
</div>
),
},
{
title: turboIntegrations.starter.name,
description: turboIntegrations.starter.description,
Expand Down
11 changes: 10 additions & 1 deletion data/turbo-integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export const turboIntegrations = {
imgLight: '/integrations/connext.png',
imgDark: '/integrations/connext.png',
},

gelato: {
name: 'Gelato',
href: '/integration/gelato',
Expand All @@ -112,6 +111,7 @@ export const turboIntegrations = {
imgLight: '/integrations/moralis.png',
imgDark: '/integrations/moralis.png',
},

aave: {
name: 'Aave',
href: '/integration/aave',
Expand All @@ -120,6 +120,15 @@ export const turboIntegrations = {
imgLight: '/integrations/aave.png',
imgDark: '/integrations/aave.png',
},
arweave: {
name: 'Arweave',
href: '/integration/arweave',
url: 'https://arwiki.arweave.dev',
description:
'Arweave is the first protocol that enables permanent data storage. Its design allows anyone to preserve data forever with just a single, one-time fee.',
imgLight: '/integrations/arweave-light.png',
imgDark: '/integrations/arweave-dark.png',
},
starter: {
name: 'Starter Template',
href: '/integration/starter',
Expand Down
2 changes: 1 addition & 1 deletion integrations/aave/components/list-supplied-assets.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAave } from '../hooks/use-aave'
import { SuppliedAssetsItem } from './supplied-assets-item'
import { useAave } from '../hooks/use-aave'

export const ListSuppliedAssets = () => {
const { usdData, balanceInUsd, collateralInUsd, averageSupplyApy } = useAave()
Expand Down
62 changes: 62 additions & 0 deletions integrations/arweave/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Starter TurboETH Integration

Welcome to the Starter TurboETH Integration! This folder serves as a blueprint for creating new integrations in TurboETH. If you're looking to contribute a new integration, simply copy this directory, and also the starter page located at `app/integration/starter`, to begin your development.

## Creating a new integration

Below are the steps to create a new integration.

1. Copy the integration folder template from `/integrations/starter` and add your integration code, adhering to the file structure patterns evident in this folder.

2. Duplicate the integration page from `/app/(general)/integration/starter` and populate it with your integration pages' code.

3. Locate any API endpoints associated with your integration in the `/api` folder within the page folder of your integration. An example API endpoint can be found at `/app/(general)/integration/starter/api/hello-world/route.ts`. These API endpoints should follow the new [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) patterns of Next.js 13.

4. Enter the data related to your integration in `/data/turbo-integrations.ts`. Here, add a new object with the name, description, image, and URL of your integration.

5. Update the OG image configuration of your integration page in the `opengraph-image.tsx` file. Do this by replacing the argument of the `IntegrationOgImage` function with the object key of your integration used in the previous step.

## Understanding the Starter template

Each component of the Starter TurboETH template is designed to help streamline your development process:

- **abis/**: Put your contract's ABI here. Each ABI should be in its own TypeScript file.

- **client/**: Any client initialization for your chosen module or SDK should be placed here.

- **components/**: This is the home for your React components. 'Read' components, which display data from a contract, and 'write' components, that send transactions, should all be placed here.

- **hooks/**: Place your custom React hooks in this folder. These hooks are intended to manage state updates and encapsulate the logic for interacting with Ethereum contracts.

- **starter-wagmi.ts**: This is a generated file from [wagmi-cli](https://wagmi.sh/cli/getting-started). It includes hooks for your contracts .

- **index.ts**: Consider this as the entry point for your integration. It should export all the hooks, components, and utility functions that your integration provides.

- **wagmi.config.ts**: This file should hold the wagmi-cli configuration for your integration, which includes settings like compiler version and optimization.

- **README.md**: Here, you should document your integration. Explain its purpose, its use, and any important information a new developer or user should know.

Each of these elements plays a crucial role in making your integration functional and accessible.

## File Structure

```
integrations/starter
├─ abis/
│ ├─ starter-abi.ts
├─ client/
│ ├─ index.ts
├─ components/
│ ├─ starter-header.tsx
├─ generated/
│ ├─ starter-wagmi.ts
├─ hooks/
│ ├─ use-starter.ts
├─ utils/
│ ├─ types.ts
├─ index.ts
├─ README.md
├─ wagmi.config.ts
```

By using this template, you'll create well-organized and understandable integrations that are easy for you and others to navigate. Happy coding!
58 changes: 58 additions & 0 deletions integrations/arweave/arweave-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { JWKInterface } from 'arweave/node/lib/wallet'
import Account, { ArAccount } from 'arweave-account'
import { ArAccountEncoded, T_profile } from 'arweave-account/lib/types'

import { createArweaveDataTx, getArweaveWalletAddress, signAndSendArweaveTx } from '.'
import { SignAndSendArweaveTxResponse } from './utils/types'

export const ArweaveAccount = new Account()

export const getUserAccount = async (wallet: JWKInterface): Promise<ArAccount> => {
const acc: ArAccount = await ArweaveAccount.get(await getArweaveWalletAddress(wallet))
return acc
}
export const getAccountByAddress = async (address: string): Promise<ArAccount> => {
const acc: ArAccount = await ArweaveAccount.get(address)
return acc
}

export type UpdateArweaveAccountPayload = Partial<T_profile> & Pick<T_profile, 'handleName'>

export const updateArweaveAccount = async (wallet: JWKInterface, payload: UpdateArweaveAccountPayload): Promise<SignAndSendArweaveTxResponse> => {
const tx = await createArweaveDataTx(wallet, JSON.stringify(encode(payload)))
const tags = [
{ name: 'Protocol-Name', value: 'Account-0.3' },
{ name: 'handle', value: payload.handleName },
]
return await signAndSendArweaveTx(wallet, tx, tags)
}

function encode(profile: UpdateArweaveAccountPayload): ArAccountEncoded | null {
let data: ArAccountEncoded = { handle: profile.handleName }
if (profile.avatar) data = { ...data, avatar: profile.avatar }
if (profile.banner) data = { ...data, banner: profile.banner }
if (profile.name) data = { ...data, name: profile.name }
if (profile.bio) data = { ...data, bio: profile.bio }
if (profile.email) data = { ...data, email: profile.email }
if (profile.links) data = { ...data, links: profile.links }
if (profile.wallets) data = { ...data, wallets: profile.wallets }
return data
}

export const uploadArweaveAccountAvatar = async (
wallet: JWKInterface,
profile: T_profile,
avatar: ArrayBuffer,
avatarFileType: string
): Promise<SignAndSendArweaveTxResponse> => {
const avatarTx = await createArweaveDataTx(wallet, avatar)
const tags = [{ name: 'Content-type', value: avatarFileType }]
const { txId, response, insufficientBalance } = await signAndSendArweaveTx(wallet, avatarTx, tags)
if (insufficientBalance) return { txId, response, insufficientBalance }
if (response?.status === 200) {
const avatarUrl = `ar://${txId}`
const payload = { ...profile, avatar: avatarUrl }
return await updateArweaveAccount(wallet, payload)
}
return { txId, response, insufficientBalance: false }
}
Loading

1 comment on commit 95490ef

@vercel
Copy link

@vercel vercel bot commented on 95490ef Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.