Skip to content

Commit

Permalink
everstake stake action
Browse files Browse the repository at this point in the history
  • Loading branch information
Messer4 committed Jul 5, 2024
1 parent 3dd4759 commit d952b8b
Show file tree
Hide file tree
Showing 25 changed files with 763 additions and 6,650 deletions.
3 changes: 0 additions & 3 deletions .env

This file was deleted.

3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ENVIRONMENT=development
# production
RPC_URL=https://api.devnet.solana.com
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ lerna-debug.log*

# misc
.DS_Store
.env
8 changes: 2 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @dialectlabs/actions
# @everstake/actions

This repository contains reference implementations of Solana Actions using [Hono](https://hono.dev/).
This repository contains implementations of Evertake Solana Actions using [Hono](https://hono.dev/).

## Getting Started

Expand All @@ -10,10 +10,6 @@ npm install
npm run dev
```

## Presets
### Tensor Buy Floor
* Place your api key to `TENSOR_API_KEY` constant in tensor-api.ts

## How To

### Actions Development
Expand Down
98 changes: 64 additions & 34 deletions examples/donate/route.ts → actions/everstake/route.ts
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
VersionedTransaction,
StakeProgram,
Keypair,
Transaction,
Authorized,
ComputeBudgetProgram
} from '@solana/web3.js';
import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
import {
Expand All @@ -12,38 +16,41 @@ import {
} from '../openapi';
import { prepareTransaction } from '../../shared/transaction-utils';
import { ActionGetResponse, ActionPostRequest, ActionPostResponse } from '@solana/actions';
import { connection } from '../../shared/connection';
require('dotenv').config();

const DONATION_DESTINATION_WALLET =
'3h4AtoLTh3bWwaLhdtgQtcC3a3Tokb8NJbtqR9rhp7p6';
const DONATION_AMOUNT_SOL_OPTIONS = [1, 5, 10];
const DEFAULT_DONATION_AMOUNT_SOL = 1;
const VALIDATOR_ADDRESS = '9QU2QSxhb24FUX3Tu2FpczXjpK3VYrvRudywSZaM29mF';
const VALIDATOR_ADDRESS_DEVNET ='FwR3PbjS5iyqzLiLugrBqKSa5EKZ4vK9SKs7eQXtT59f';
const STAKE_AMOUNT_SOL_OPTIONS = [1, 5, 10];
const DEFAULT_STAKE_AMOUNT_SOL = 1;

const app = new OpenAPIHono();

app.openapi(
createRoute({
method: 'get',
path: '/',
tags: ['Donate'],
tags: ['Stake'],
responses: actionsSpecOpenApiGetResponse,
}),
(c) => {
const { icon, title, description } = getDonateInfo();
(c) => {
const requestUrl = new URL(c.req.url);
const { icon, title, description } = getStakeInfo(requestUrl.origin);
const amountParameterName = 'amount';
const response: ActionGetResponse = {
icon,
label: `${DEFAULT_DONATION_AMOUNT_SOL} SOL`,
label: `${DEFAULT_STAKE_AMOUNT_SOL} SOL`,
title,
description,
links: {
actions: [
...DONATION_AMOUNT_SOL_OPTIONS.map((amount) => ({
...STAKE_AMOUNT_SOL_OPTIONS.map((amount) => ({
label: `${amount} SOL`,
href: `/api/donate/${amount}`,
href: `/api/everstake/stake/${amount}`,
})),
{
href: `/api/donate/{${amountParameterName}}`,
label: 'Donate',
href: `/api/everstake/stake/{${amountParameterName}}`,
label: 'Stake',
parameters: [
{
name: amountParameterName,
Expand All @@ -63,7 +70,7 @@ app.openapi(
createRoute({
method: 'get',
path: '/{amount}',
tags: ['Donate'],
tags: ['Stake'],
request: {
params: z.object({
amount: z.string().openapi({
Expand All @@ -80,7 +87,8 @@ app.openapi(
}),
(c) => {
const amount = c.req.param('amount');
const { icon, title, description } = getDonateInfo();
const requestUrl = new URL(c.req.url);
const { icon, title, description } = getStakeInfo(requestUrl.origin);
const response: ActionGetResponse = {
icon,
label: `${amount} SOL`,
Expand All @@ -95,7 +103,7 @@ app.openapi(
createRoute({
method: 'post',
path: '/{amount}',
tags: ['Donate'],
tags: ['Stake'],
request: {
params: z.object({
amount: z
Expand All @@ -117,13 +125,18 @@ app.openapi(
}),
async (c) => {
const amount =
c.req.param('amount') ?? DEFAULT_DONATION_AMOUNT_SOL.toString();
c.req.param('amount') ?? DEFAULT_STAKE_AMOUNT_SOL.toString();
const { account } = (await c.req.json()) as ActionPostRequest;

let validator_address = VALIDATOR_ADDRESS;
if (process.env.ENVIRONMENT === 'development') {
validator_address = VALIDATOR_ADDRESS_DEVNET;
}

const parsedAmount = parseFloat(amount);
const transaction = await prepareDonateTransaction(
const transaction = await prepareStakeTransaction(
new PublicKey(account),
new PublicKey(DONATION_DESTINATION_WALLET),
new PublicKey(validator_address),
parsedAmount * LAMPORTS_PER_SOL,
);
const response: ActionPostResponse = {
Expand All @@ -133,31 +146,48 @@ app.openapi(
},
);

function getDonateInfo(): Pick<
function getStakeInfo(baseURL: string): Pick<
ActionGetResponse,
'icon' | 'title' | 'description'
> {
const icon =
'https://ucarecdn.com/7aa46c85-08a4-4bc7-9376-88ec48bb1f43/-/preview/880x864/-/quality/smart/-/format/auto/';
const title = 'Donate to Alice';
const icon = new URL("/static/Everstake.png", baseURL).toString();
const title = 'Stake SOL with Everstake, earn 7% APR';
const description =
'Cybersecurity Enthusiast | Support my research with a donation.';
"Everstake, the biggest staking provider in the blockchain industry, trusted by 735,000+ users!";
return { icon, title, description };
}
async function prepareDonateTransaction(

async function prepareStakeTransaction(
sender: PublicKey,
recipient: PublicKey,
validatorVoteAccount: PublicKey,
lamports: number,
): Promise<VersionedTransaction> {
const payer = new PublicKey(sender);
const instructions = [
SystemProgram.transfer({
fromPubkey: payer,
toPubkey: new PublicKey(recipient),
lamports: lamports,
const stakeAccount = Keypair.generate();

// Calculate how much we want to stake
const minimumRent = await connection.getMinimumBalanceForRentExemption(
StakeProgram.space,
)

const tx = new Transaction().add(
ComputeBudgetProgram.setComputeUnitPrice({microLamports: 50}),
StakeProgram.createAccount({
authorized: new Authorized(sender, sender),
fromPubkey: sender,
lamports: lamports + minimumRent,
stakePubkey: stakeAccount.publicKey,
}),
];
return prepareTransaction(instructions, payer);
StakeProgram.delegate({
stakePubkey: stakeAccount.publicKey,
authorizedPubkey: sender,
votePubkey: validatorVoteAccount
})
);

let versionedTX = await prepareTransaction(tx.instructions, sender);
versionedTX.sign([stakeAccount]);

return versionedTX;
}

export default app;
109 changes: 109 additions & 0 deletions actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { serve } from '@hono/node-server';
import everstake from './everstake/route';
import { cors } from 'hono/cors';
import { swaggerUI } from '@hono/swagger-ui';
import * as path from "node:path";
import { readFileSync } from "node:fs";
import { OpenAPIHono, createRoute } from '@hono/zod-openapi';
import { z } from "zod";
var mime = require('mime-types')

const app = new OpenAPIHono();
app.use('/*', cors());

// <--Actions-->
app.route('/api/everstake/stake', everstake);
// </--Actions-->

app.doc('/doc', {
info: {
title: 'An API',
version: 'v1',
},
openapi: '3.1.0',
});

app.get(
'/swagger-ui',
swaggerUI({
url: '/doc',
}),
);

app.get("/actions.json", async (c) => {
const [payload, mime_type] = loadFile('actions.json');
return new Response(payload, {
headers: {
"content-type": mime_type,
},
status: 200,
});
});

// Download File
const downloadFileRoute = createRoute({
method: "get",
path: "/static/{file}",
summary: "static files",
tags: ['Static'],
request: {
params: z.object({
file: z.string().openapi({
param: {
name: 'file',
in: 'path',
},
type: 'string',
example: 'file.jpg',
}),
}),
},
responses: {
200: {
content: {
"image/png": {
schema: z.any().openapi({
type: "object",
format: "binary",
}),
}
},
description:
"Return file, contentType can be image/png or image/jpeg",
},
},
});

// disable ts check because hono openapi cannot validate raw response
// @ts-ignore: Unreachable code error
app.openapi(downloadFileRoute, async (c) => {
const file = c.req.param('file');
const [payload, mime_type] = loadFile(file);
return new Response(payload, {
headers: {
"content-type": mime_type,
},
status: 200,
});
});

function loadFile(
file: string
): [Buffer, string] {
const payload = readFileSync(path.join("./static", file));
const mime_type = mime.lookup(path.join("./static", file));
return [payload, mime_type]
}

const port = 3000;
console.log(
`Server is running on port ${port}
Visit http://localhost:${port}/swagger-ui to explore existing actions
Visit https://actions.dialect.to to unfurl action into a Blink
`,
);

serve({
fetch: app.fetch,
port,
});
File renamed without changes.
Loading

0 comments on commit d952b8b

Please sign in to comment.