forked from anza-xyz/octane
-
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.
add core library to handle most of the logic, move functions from ver…
…cel to next
- Loading branch information
1 parent
fd05065
commit 5301e93
Showing
27 changed files
with
638 additions
and
147 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,6 @@ keys | |
!.env.example | ||
|
||
node_modules/ | ||
|
||
.next | ||
next-env.d.ts |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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
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,12 @@ | ||
import type { NextApiRequest, NextApiResponse } from 'next'; | ||
import { connection } from '../../src/helpers'; | ||
import { rateLimit } from '../../src/middleware'; | ||
|
||
// Endpoint to get the most recent blockhash seen by Octane's RPC node | ||
export default async function (request: NextApiRequest, response: NextApiResponse) { | ||
await rateLimit(request, response); | ||
|
||
const blockhash = await connection.getRecentBlockhash(); | ||
|
||
response.status(200).send({ blockhash }); | ||
} |
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,16 @@ | ||
import type { NextApiRequest, NextApiResponse } from 'next'; | ||
import config from '../../config.json'; | ||
import { ENV_FEE_PAYER } from '../../src/helpers'; | ||
import { rateLimit } from '../../src/middleware'; | ||
|
||
const body = { | ||
feePayer: ENV_FEE_PAYER.toBase58(), | ||
...config, | ||
}; | ||
|
||
// Endpoint to get Octane's configuration | ||
export default async function (request: NextApiRequest, response: NextApiResponse) { | ||
await rateLimit(request, response); | ||
|
||
response.status(200).send(body); | ||
} |
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,53 @@ | ||
import { PublicKey, Transaction } from '@solana/web3.js'; | ||
import type { NextApiRequest, NextApiResponse } from 'next'; | ||
import base58 from 'bs58'; | ||
import { signWithTokenFee } from '../../src'; | ||
import { cache, connection, ENV_SECRET_KEYPAIR } from '../../src/helpers'; | ||
import { cors, rateLimit } from '../../src/middleware'; | ||
import config from '../../config.json'; | ||
|
||
// Endpoint to pay for transactions with an SPL token transfer | ||
export default async function (request: NextApiRequest, response: NextApiResponse) { | ||
await cors(request, response); | ||
await rateLimit(request, response); | ||
|
||
// Deserialize a base58 wire-encoded transaction from the request | ||
const serialized = request.body?.transaction; | ||
if (typeof serialized !== 'string') { | ||
response.status(400).send({ status: 'error', message: 'request should contain transaction' }); | ||
return; | ||
} | ||
|
||
let transaction: Transaction; | ||
try { | ||
transaction = Transaction.from(base58.decode(serialized)); | ||
} catch (e) { | ||
response.status(400).send({ status: 'error', message: "can't decode transaction" }); | ||
return; | ||
} | ||
|
||
try { | ||
const { signature } = await signWithTokenFee( | ||
connection, | ||
transaction, | ||
ENV_SECRET_KEYPAIR, | ||
config.maxSignatures, | ||
config.lamportsPerSignature, | ||
config.endpoints.transfer.tokens.map((token) => ({ | ||
mint: new PublicKey(token.mint), | ||
account: new PublicKey(token.account), | ||
decimals: token.decimals, | ||
fee: BigInt(token.fee), | ||
})), | ||
cache | ||
); | ||
// Respond with the confirmed transaction signature | ||
response.status(200).send({ status: 'ok', signature }); | ||
} catch (error) { | ||
let message: string = ''; | ||
if (error instanceof Error) { | ||
message = error.message; | ||
} | ||
response.status(400).send({ status: 'error', message }); | ||
} | ||
} |
Empty file.
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 @@ | ||
export * from './signIfTokenFeePaid'; |
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,64 @@ | ||
import { Transaction, Connection, Keypair } from '@solana/web3.js'; | ||
import type { Cache } from 'cache-manager'; | ||
import base58 from 'bs58'; | ||
import { sha256, simulateRawTransaction, validateTransaction, validateTransfer, AllowedToken } from '../core'; | ||
|
||
/** | ||
* Sign transaction by fee payer if the first instruction is a transfer of token fee to given account | ||
* | ||
* @param connection Connection to a Solana node | ||
* @param transaction Transaction to sign | ||
* @param maxSignatures Maximum allowed signatures in the transaction including fee payer's | ||
* @param lamportsPerSignature Maximum fee payment in lamports | ||
* @param allowedTokens List of tokens that can be used with token fee receiver accounts and fee details | ||
* @param feePayer Keypair for fee payer | ||
* @param cache A cache to store duplicate transactions | ||
* | ||
* @return {signature: string} Transaction signature by fee payer | ||
*/ | ||
export async function signWithTokenFee( | ||
connection: Connection, | ||
transaction: Transaction, | ||
feePayer: Keypair, | ||
maxSignatures: number, | ||
lamportsPerSignature: number, | ||
allowedTokens: AllowedToken[], | ||
cache: Cache | ||
): Promise<{ signature: string }> { | ||
// Prevent simple duplicate transactions using a hash of the message | ||
let key = `transaction/${base58.encode(sha256(transaction.serializeMessage()))}`; | ||
if (await cache.get(key)) throw new Error('duplicate transaction'); | ||
await cache.set(key, true); | ||
|
||
// Check that the transaction is basically valid, sign it, and serialize it, verifying the signatures | ||
const { signature, rawTransaction } = await validateTransaction( | ||
connection, | ||
transaction, | ||
feePayer, | ||
maxSignatures, | ||
lamportsPerSignature | ||
); | ||
|
||
// Check that the transaction contains a valid transfer to Octane's token account | ||
const transfer = await validateTransfer(connection, transaction, allowedTokens); | ||
|
||
/* | ||
An attacker could make multiple signing requests before the transaction is confirmed. If the source token account | ||
has the minimum fee balance, validation and simulation of all these requests may succeed. All but the first | ||
confirmed transaction will fail because the account will be empty afterward. To prevent this race condition, | ||
simulation abuse, or similar attacks, we implement a simple lockout for the source token account until the | ||
transaction succeeds or fails. | ||
*/ | ||
key = `transfer/${transfer.keys.source.pubkey.toBase58()}`; | ||
if (await cache.get(key)) throw new Error('duplicate transfer'); | ||
await cache.set(key, true); | ||
|
||
try { | ||
// Simulate, send, and confirm the transaction | ||
await simulateRawTransaction(connection, rawTransaction); | ||
} finally { | ||
await cache.del(key); | ||
} | ||
|
||
return { signature: signature }; | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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
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
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
Oops, something went wrong.