Skip to content

Commit

Permalink
feat: jupiter swap and tx frame DCP-1806 (#3)
Browse files Browse the repository at this point in the history
* fix: remove GITHUB_TOKEN

Signed-off-by: Jimmy Madrigal <[email protected]>

* feat: transaction example

Signed-off-by: Jimmy Madrigal <[email protected]>

* fix: remove receive from frame transactions

Signed-off-by: Jimmy Madrigal <[email protected]>

* fix: remove legacy button

Signed-off-by: Jimmy Madrigal <[email protected]>

* feat: jupiter swap example

Signed-off-by: Jimmy Madrigal <[email protected]>

* fix: pr feedback

Signed-off-by: Jimmy Madrigal <[email protected]>

---------

Signed-off-by: Jimmy Madrigal <[email protected]>
  • Loading branch information
jimmymadrigal authored May 2, 2024
1 parent 6acd3f9 commit e423d2f
Show file tree
Hide file tree
Showing 68 changed files with 15,574 additions and 49 deletions.
1 change: 0 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
registry=https://registry.npmjs.org
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
1 change: 0 additions & 1 deletion examples/carousel/.npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
registry=https://registry.npmjs.org
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
5 changes: 0 additions & 5 deletions examples/carousel/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React from 'react';

export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
};

export default function RootLayout({
children,
}: {
Expand Down
1 change: 0 additions & 1 deletion examples/getting-started/.npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
registry=https://registry.npmjs.org
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
5 changes: 0 additions & 5 deletions examples/getting-started/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import React from 'react';

export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
};

export default function RootLayout({
children,
}: {
Expand Down
6 changes: 3 additions & 3 deletions examples/getting-started/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default async function Page(props: NextServerPageProps) {
<FrameContainer
postUrl="/frames"
pathname="/"
state={{}}
state={state}
previousFrame={previousFrame}
>
<FrameImage>
Expand Down Expand Up @@ -77,7 +77,7 @@ export default async function Page(props: NextServerPageProps) {
<FrameContainer
postUrl="/frames"
pathname="/"
state={{}}
state={state}
previousFrame={previousFrame}
>
<FrameImage>
Expand All @@ -95,7 +95,7 @@ export default async function Page(props: NextServerPageProps) {
<FrameContainer
postUrl="/frames"
pathname="/"
state={{}}
state={state}
previousFrame={previousFrame}
>
<FrameImage>
Expand Down
3 changes: 3 additions & 0 deletions examples/jupiter-swap/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions examples/jupiter-swap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions examples/jupiter-swap/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
registry=https://registry.npmjs.org
4 changes: 4 additions & 0 deletions examples/jupiter-swap/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
42 changes: 42 additions & 0 deletions examples/jupiter-swap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Jupiter Swap

This frame example allow the usage of jupiter swap api.

This is a [Next.js](https://nextjs.org/) project that uses frames.js, [DSCVR frames adapter](https://docs.dscvr.one/build/frames/frame-concepts.html#frames-adapter) and [DSCVR API](https://docs.dscvr.one/build/dscvr-api/).

## Getting Started

First, install the package

```bash
pnpm install
```

Then, run the development server:

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result, a blank screen will be shown, but the html headers will show the frames information.

## Debug your changes

Since all of the action happens in the headers, go ahead an try our [DSCVR frame validator](https://dscvr.one/dev/frames):

1. Once the project is running on port 3000, run `npx localtunnel --port 3000`
2. Copy the provided url
3. Go to [DSCVR frame validator](https://dscvr.one/dev/frames)
4. Play with your frame!

## Learn More

In this example a frame message is validated by the [DSCVR frame adapter](https://docs.dscvr.one/build/frames/frame-concepts.html#frames-adapter) to make sure is a DSCVR message, then the information for the current user and content is gathered using the [DSCVR API](https://docs.dscvr.one/build/dscvr-api/) and shown as a response.

To learn more about Frames visit our [documentation](https://docs.dscvr.one/).

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
23 changes: 23 additions & 0 deletions examples/jupiter-swap/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
// matching all API routes
source: '/:path*',
headers: [
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST' },
{
key: 'Access-Control-Allow-Headers',
value:
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
},
],
},
];
},
};

export default nextConfig;
33 changes: 33 additions & 0 deletions examples/jupiter-swap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "jupiter-swap",
"version": "0.1.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@dscvr-one/frames-adapter": "^2.0.3",
"@solana/web3.js": "^1.91.4",
"@urql/core": "^4.3.0",
"bs58": "^5.0.0",
"frames.js": "^0.8.5",
"gql.tada": "^1.3.0",
"next": "14.1.2",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@0no-co/graphqlsp": "^1.4.2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.2",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
6 changes: 6 additions & 0 deletions examples/jupiter-swap/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
59 changes: 59 additions & 0 deletions examples/jupiter-swap/src/api/dscvr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Client, cacheExchange, fetchExchange } from '@urql/core';
import { initGraphQLTada } from 'gql.tada';
import { introspection } from '../graphql-env';

export const graphql = initGraphQLTada<{
introspection: introspection;
scalars: {
DscvrId: string;
ContentId: string;
};
}>();

const getDataQuery = graphql(`
query getData($id: DscvrId!, $contentId: ContentId!, $hasContent: Boolean!) {
user(id: $id) {
id
username
}
content(id: $contentId) @include(if: $hasContent) {
id
creator {
username
}
contentType
portal {
name
iconUrl
}
}
}
`);

export const dscvrApiUrl = 'https://api.dscvr.one/graphql';

export const getData = async (id: string, contentId?: string) => {
const client = new Client({
url: dscvrApiUrl,
exchanges: [cacheExchange, fetchExchange],
});

const result = await client
.query(getDataQuery, {
id,
contentId: contentId || '',
hasContent: !!contentId,
})
.toPromise();
if (result.error) {
let message = result.error.message;
if (result.error.graphQLErrors.length) {
message = result.error.graphQLErrors
.map((e) => e.originalError?.message)
.join(', ');
}

throw new Error(message);
}
return result.data;
};
107 changes: 107 additions & 0 deletions examples/jupiter-swap/src/api/jupiter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
type PublicKey,
VersionedTransaction,
type Cluster,
clusterApiUrl,
Connection,
} from '@solana/web3.js';
import type {
JupiterQuoteResponse,
JupiterSwapResponse,
Token,
TokenType,
} from '../types';
import { tokens } from './tokens';

const getApiUrl = () => {
return process.env.JUPITER_API_URL || 'https://quote-api.jup.ag/v6';
};

export const getTransactionDetails = (
cluster: Cluster,
transactionId: string,
) => {
const connection = new Connection(clusterApiUrl(cluster));
return connection.getParsedTransaction(transactionId, {
commitment: 'confirmed',
maxSupportedTransactionVersion: 0,
});
};

export const getTokenList = (): Token[] => {
return tokens.map((token) => ({
type: token.symbol,
name: token.name,
icon: token.logoURI,
mintAddress: token.address,
decimals: token.decimals,
}));
};

export const getToken = async (
token?: TokenType,
): Promise<Token | undefined> => {
const list = getTokenList();
return list.find((t) => t.type === token);
};

export const quote = async (
sourceTokenType: TokenType,
targetTokenType: TokenType,
amount: number,
) => {
const sourceToken = await getToken(sourceTokenType);
const targetToken = await getToken(targetTokenType);
if (!sourceToken || !targetToken) {
throw new Error('Invalid tokens');
}
const newAmount = amount * 10 ** sourceToken?.decimals;
const apiUrl = getApiUrl();
const quoteUrl = `${apiUrl}/quote?inputMint=${sourceToken.mintAddress}&outputMint=${targetToken.mintAddress}&amount=${newAmount}`;
const quoteResponse: JupiterQuoteResponse = await fetch(quoteUrl).then(
(response) => response.json(),
);
if ('error' in quoteResponse) {
throw new Error(quoteResponse.error);
}
return quoteResponse;
};

export const swap = async (
userAddressPubkey: PublicKey,
sourceTokenType: TokenType,
targetTokenType: TokenType,
amount: number,
) => {
const quoteResponse = await quote(sourceTokenType, targetTokenType, amount);
const apiUrl = getApiUrl();
const swapUrl = `${apiUrl}/swap`;
const payload = {
// quoteResponse from /quote api
quoteResponse,
// user public key to be used for the swap
userPublicKey: userAddressPubkey.toString(),
// auto wrap and unwrap SOL. default is true
wrapAndUnwrapSol: true,
// feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API.
// feeAccount: "fee_account_public_key"
};
const body = JSON.stringify(payload);

const swapResponse: JupiterSwapResponse = await fetch(swapUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body,
}).then((response) => response.json());

if ('error' in swapResponse) {
throw new Error(swapResponse.error);
}
const swapTransactionBuf = Buffer.from(
swapResponse.swapTransaction,
'base64',
);
return VersionedTransaction.deserialize(swapTransactionBuf);
};
Loading

0 comments on commit e423d2f

Please sign in to comment.