-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(solana): support actions and blinks
- Loading branch information
Showing
8 changed files
with
815 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ACTIONS_CORS_HEADERS, ActionsJson } from "@solana/actions"; | ||
|
||
export const GET = async () => { | ||
const payload: ActionsJson = { | ||
rules: [ | ||
{ | ||
pathPattern: "/donate", | ||
apiPath: "/api/actions/donate", | ||
}, | ||
], | ||
}; | ||
|
||
return Response.json(payload, { | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
}; | ||
|
||
// DO NOT FORGET TO INCLUDE THE `OPTIONS` HTTP METHOD | ||
// THIS WILL ENSURE CORS WORKS FOR BLINKS | ||
export const OPTIONS = GET; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { PublicKey } from "@solana/web3.js"; | ||
|
||
export const DEFAULT_SOL_ADDRESS: PublicKey = new PublicKey( | ||
"2sbEWeiYGyyLdgmBB8MUwZWcyWBSzoCQd5T4fG3MPkR2" | ||
); | ||
|
||
export const DEFAULT_SOL_AMOUNT: number = 1.0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
/** | ||
* Solana Actions Example | ||
*/ | ||
|
||
import { | ||
ActionPostResponse, | ||
ACTIONS_CORS_HEADERS, | ||
createPostResponse, | ||
ActionGetResponse, | ||
ActionPostRequest, | ||
} from "@solana/actions"; | ||
import { | ||
clusterApiUrl, | ||
ComputeBudgetProgram, | ||
Connection, | ||
LAMPORTS_PER_SOL, | ||
PublicKey, | ||
SystemProgram, | ||
Transaction, | ||
} from "@solana/web3.js"; | ||
import { DEFAULT_SOL_ADDRESS, DEFAULT_SOL_AMOUNT } from "./const"; | ||
|
||
export const GET = async (req: Request) => { | ||
try { | ||
const requestUrl = new URL(req.url); | ||
|
||
const baseHref = new URL( | ||
`/api/actions/donate`, | ||
requestUrl.origin | ||
).toString(); | ||
|
||
const payload: ActionGetResponse = { | ||
title: "Support Soona", | ||
icon: new URL("/chainsona-smb-gen3.png", requestUrl.origin).toString(), | ||
description: "Donate SOL to support Soona’s web3 journey on Solana!", | ||
label: "Donate", | ||
links: { | ||
actions: [ | ||
{ | ||
label: "0.1 SOL", | ||
href: `${baseHref}?amount=${"0.1"}`, | ||
}, | ||
{ | ||
label: "0.25 SOL", | ||
href: `${baseHref}?amount=${"0.25"}`, | ||
}, | ||
{ | ||
label: "0.5 SOL", | ||
href: `${baseHref}?amount=${"0.5"}`, | ||
}, | ||
{ | ||
label: "Donate", | ||
href: `${baseHref}?amount={amount}`, | ||
parameters: [ | ||
{ | ||
name: "amount", // parameter name in the `href` above | ||
label: "Amount of SOL to donate", | ||
required: true, | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
}; | ||
|
||
return Response.json(payload, { | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
} catch (err) { | ||
console.log(err); | ||
let message = "An unknown error occurred"; | ||
if (typeof err == "string") message = err; | ||
return new Response(message, { | ||
status: 400, | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
} | ||
}; | ||
|
||
// DO NOT FORGET TO INCLUDE THE `OPTIONS` HTTP METHOD | ||
// THIS WILL ENSURE CORS WORKS FOR BLINKS | ||
export const OPTIONS = GET; | ||
|
||
export const POST = async (req: Request) => { | ||
try { | ||
const requestUrl = new URL(req.url); | ||
const { amount } = validatedQueryParams(requestUrl); | ||
|
||
const body: ActionPostRequest = await req.json(); | ||
|
||
// validate the client provided input | ||
let fromPubkey: PublicKey; | ||
try { | ||
fromPubkey = new PublicKey(body.account); | ||
} catch (err) { | ||
return new Response('Invalid "from" provided', { | ||
status: 400, | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
} | ||
|
||
const toPubkey = new PublicKey(DEFAULT_SOL_ADDRESS); | ||
const connection = new Connection( | ||
process.env.SOLANA_RPC! || clusterApiUrl("mainnet-beta") | ||
); | ||
|
||
// ensure the receiving account will be rent exempt | ||
const minimumBalance = await connection.getMinimumBalanceForRentExemption( | ||
0 // note: simple accounts that just store native SOL have `0` bytes of data | ||
); | ||
if (amount * LAMPORTS_PER_SOL < minimumBalance) { | ||
throw `account may not be rent exempt: ${toPubkey.toBase58()}`; | ||
} | ||
|
||
const transaction = new Transaction(); | ||
|
||
transaction.add( | ||
// Set transaction compute units | ||
ComputeBudgetProgram.setComputeUnitLimit({ | ||
units: 800, | ||
}), | ||
SystemProgram.transfer({ | ||
fromPubkey: fromPubkey, | ||
toPubkey: toPubkey, | ||
lamports: amount * LAMPORTS_PER_SOL, | ||
}) | ||
); | ||
|
||
// set the end user as the fee payer | ||
transaction.feePayer = fromPubkey; | ||
|
||
transaction.recentBlockhash = ( | ||
await connection.getLatestBlockhash() | ||
).blockhash; | ||
|
||
const payload: ActionPostResponse = await createPostResponse({ | ||
fields: { | ||
transaction, | ||
message: `Send ${amount} SOL to ${toPubkey.toBase58()}`, | ||
}, | ||
// note: no additional signers are needed | ||
// signers: [], | ||
}); | ||
|
||
return Response.json(payload, { | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
} catch (err) { | ||
console.log(err); | ||
let message = "An unknown error occurred"; | ||
if (typeof err == "string") message = err; | ||
return new Response(message, { | ||
status: 400, | ||
headers: ACTIONS_CORS_HEADERS, | ||
}); | ||
} | ||
}; | ||
|
||
function validatedQueryParams(requestUrl: URL) { | ||
let toPubkey: PublicKey = DEFAULT_SOL_ADDRESS; | ||
let amount: number = DEFAULT_SOL_AMOUNT; | ||
|
||
try { | ||
if (requestUrl.searchParams.get("to")) { | ||
toPubkey = new PublicKey(requestUrl.searchParams.get("to")!); | ||
} | ||
} catch (err) { | ||
throw "Invalid input query parameter: to"; | ||
} | ||
|
||
try { | ||
if (requestUrl.searchParams.get("amount")) { | ||
amount = parseFloat(requestUrl.searchParams.get("amount")!); | ||
} | ||
|
||
if (amount <= 0) throw "amount is too small"; | ||
} catch (err) { | ||
throw "Invalid input query parameter: amount"; | ||
} | ||
|
||
return { | ||
amount, | ||
toPubkey, | ||
}; | ||
} |