Initial commit from template
This commit is contained in:
23
.eslintrc.js
Normal file
23
.eslintrc.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
es2020: true,
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
sourceType: "module",
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
},
|
||||||
|
ignorePatterns: ["**/target/**", "node_modules"],
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
},
|
||||||
|
};
|
||||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
target
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
11
.stackclass/compile.sh
Normal file
11
.stackclass/compile.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This script is used to compile your program on StackClass
|
||||||
|
#
|
||||||
|
# This runs before .stackclass/run.sh
|
||||||
|
#
|
||||||
|
# Learn more: https://docs.stackclass.dev/challenges/program-interface
|
||||||
|
|
||||||
|
set -e # Exit on failure
|
||||||
|
|
||||||
|
cargo build --release
|
||||||
11
.stackclass/run.sh
Normal file
11
.stackclass/run.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This script is used to run your program on StackClass
|
||||||
|
#
|
||||||
|
# This runs after .stackclass/compile.sh
|
||||||
|
#
|
||||||
|
# Learn more: https://docs.stackclass.dev/challenges/program-interface
|
||||||
|
|
||||||
|
set -e # Exit on failure
|
||||||
|
|
||||||
|
exec target/release/stackclass-solana-swap-program "$@"
|
||||||
9
Anchor.toml
Normal file
9
Anchor.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[provider]
|
||||||
|
cluster = "Localnet"
|
||||||
|
wallet = "~/.config/solana/id.json"
|
||||||
|
|
||||||
|
[programs.localnet]
|
||||||
|
swap = "11111111111111111111111111111111"
|
||||||
|
|
||||||
|
[scripts]
|
||||||
|
test = "cargo test -- --nocapture"
|
||||||
2660
Cargo.lock
generated
Normal file
2660
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"programs/*"
|
||||||
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "stackclass-solana-swap-program"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["StackClass <hello@stackclass.dev>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
overflow-checks = true
|
||||||
|
lto = "fat"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
[profile.release.build-override]
|
||||||
|
opt-level = 3
|
||||||
|
incremental = false
|
||||||
|
codegen-units = 1
|
||||||
5
migrations/deploy.ts
Normal file
5
migrations/deploy.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
pub fn deploy() -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
19
package.json
Normal file
19
package.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "solana-swap",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Solana swap program built with Anchor",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "cargo test -- --nocapture",
|
||||||
|
"build": "anchor build",
|
||||||
|
"deploy": "anchor deploy"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@coral-xyz/anchor": "^0.30.0",
|
||||||
|
"@solana/web3.js": "^1.91.0",
|
||||||
|
"@solana/spl-token": "^0.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
programs/swap-program/Cargo.toml
Normal file
12
programs/swap-program/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "swap-program"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Solana swap program"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anchor-lang = "0.30.1"
|
||||||
|
anchor-spl = "0.30.1"
|
||||||
42
programs/swap-program/src/lib.rs
Normal file
42
programs/swap-program/src/lib.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
declare_id!("11111111111111111111111111111111");
|
||||||
|
|
||||||
|
#[program]
|
||||||
|
pub mod swap {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn make_offer(
|
||||||
|
ctx: Context<MakeOffer>,
|
||||||
|
id: u64,
|
||||||
|
token_a_offered_amount: u64,
|
||||||
|
token_b_wanted_amount: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Students will implement this function
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_offer(ctx: Context<TakeOffer>) -> Result<()> {
|
||||||
|
// Students will implement this function
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
#[instruction(id: u64)]
|
||||||
|
pub struct MakeOffer<'info> {
|
||||||
|
pub maker: Signer<'info>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct TakeOffer<'info> {
|
||||||
|
pub taker: Signer<'info>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn test_sample() {
|
||||||
|
assert_eq!(2 + 2, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
196
src/main.rs
Normal file
196
src/main.rs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::env;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct ProgramInfo {
|
||||||
|
program_id: String,
|
||||||
|
instructions: Vec<InstructionInfo>,
|
||||||
|
accounts: Vec<AccountInfo>,
|
||||||
|
errors: Vec<ErrorInfo>,
|
||||||
|
structs: Vec<StructInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct InstructionInfo {
|
||||||
|
name: String,
|
||||||
|
arguments: Vec<ArgumentInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct ArgumentInfo {
|
||||||
|
name: String,
|
||||||
|
type_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct AccountInfo {
|
||||||
|
name: String,
|
||||||
|
fields: Vec<FieldInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct FieldInfo {
|
||||||
|
name: String,
|
||||||
|
type_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct ErrorInfo {
|
||||||
|
name: String,
|
||||||
|
code: u32,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct StructInfo {
|
||||||
|
name: String,
|
||||||
|
fields: Vec<FieldInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
|
||||||
|
if args.len() > 1 && args[1] == "dump_info" {
|
||||||
|
dump_program_info();
|
||||||
|
} else {
|
||||||
|
// 默认行为:什么都不做
|
||||||
|
println!("Solana Swap Program - Use 'dump_info' command to export program definition");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dump_program_info() {
|
||||||
|
let project_root = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||||
|
let lib_path = project_root.join("programs/swap-program/src/lib.rs");
|
||||||
|
|
||||||
|
let lib_content = fs::read_to_string(&lib_path).unwrap_or_else(|_| {
|
||||||
|
eprintln!("Warning: Could not read lib.rs at {:?}", lib_path);
|
||||||
|
String::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
let program_info = parse_program_info(&lib_content);
|
||||||
|
|
||||||
|
println!("{}", serde_json::to_string_pretty(&program_info).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_program_info(content: &str) -> ProgramInfo {
|
||||||
|
let mut program_info = ProgramInfo {
|
||||||
|
program_id: String::new(),
|
||||||
|
instructions: Vec::new(),
|
||||||
|
accounts: Vec::new(),
|
||||||
|
errors: Vec::new(),
|
||||||
|
structs: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析 program_id
|
||||||
|
if let Some(captures) = regex::Regex::new(r#"declare_id!\("([^"]+)"\)"#).unwrap().captures(content) {
|
||||||
|
if let Some(id) = captures.get(1) {
|
||||||
|
program_info.program_id = id.as_str().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析指令 (pub fn)
|
||||||
|
let instruction_re = regex::Regex::new(r#"pub fn (\w+)\(ctx: Context<([^>]+)>(?:, ([^)]+))?\)"#).unwrap();
|
||||||
|
for caps in instruction_re.captures_iter(content) {
|
||||||
|
let name = caps.get(1).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
let _context = caps.get(2).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
let args_str = caps.get(3).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut arguments = Vec::new();
|
||||||
|
if !args_str.is_empty() {
|
||||||
|
for arg in args_str.split(',') {
|
||||||
|
let arg = arg.trim();
|
||||||
|
if let Some((name_part, type_part)) = arg.split_once(':') {
|
||||||
|
arguments.push(ArgumentInfo {
|
||||||
|
name: name_part.trim().to_string(),
|
||||||
|
type_name: type_part.trim().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program_info.instructions.push(InstructionInfo {
|
||||||
|
name,
|
||||||
|
arguments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析账户结构 (#[derive(Accounts)] pub struct)
|
||||||
|
let account_re = regex::Regex::new(r#"#\[derive\(Accounts\)\]\s+pub struct (\w+)<[^>]*>\s*\{([^}]+)\}"#).unwrap();
|
||||||
|
for caps in account_re.captures_iter(content) {
|
||||||
|
let name = caps.get(1).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
let fields_str = caps.get(2).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
let mut current_field = String::new();
|
||||||
|
|
||||||
|
for line in fields_str.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是属性行,继续累积
|
||||||
|
if line.starts_with('#') {
|
||||||
|
current_field.push_str(line);
|
||||||
|
current_field.push(' ');
|
||||||
|
} else {
|
||||||
|
// 如果是字段定义行
|
||||||
|
if let Some((name_part, type_part)) = line.split_once(':') {
|
||||||
|
let field_name = name_part.split_whitespace().last().unwrap_or("").to_string();
|
||||||
|
fields.push(FieldInfo {
|
||||||
|
name: field_name,
|
||||||
|
type_name: type_part.trim().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
current_field.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program_info.accounts.push(AccountInfo { name, fields });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析错误 (pub enum Error)
|
||||||
|
let error_re = regex::Regex::new(r#"pub enum Error\s*\{([^}]+)\}"#).unwrap();
|
||||||
|
if let Some(caps) = error_re.captures(content) {
|
||||||
|
let errors_str = caps.get(1).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
for (idx, line) in errors_str.lines().enumerate() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = line.split_whitespace().next().unwrap_or("").to_string();
|
||||||
|
program_info.errors.push(ErrorInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
code: idx as u32 + 6000,
|
||||||
|
message: name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析普通结构体 (pub struct)
|
||||||
|
let struct_re = regex::Regex::new(r#"pub struct (\w+)\s*\{([^}]+)\}"#).unwrap();
|
||||||
|
for caps in struct_re.captures_iter(content) {
|
||||||
|
let name = caps.get(1).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
let fields_str = caps.get(2).map(|m| m.as_str().to_string()).unwrap_or_default();
|
||||||
|
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
for line in fields_str.lines() {
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() || line.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some((name_part, type_part)) = line.split_once(':') {
|
||||||
|
fields.push(FieldInfo {
|
||||||
|
name: name_part.trim().to_string(),
|
||||||
|
type_name: type_part.trim().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program_info.structs.push(StructInfo { name, fields });
|
||||||
|
}
|
||||||
|
|
||||||
|
program_info
|
||||||
|
}
|
||||||
18
stackclass.yml
Normal file
18
stackclass.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Set this to true if you want debug logs.
|
||||||
|
#
|
||||||
|
# These can be VERY verbose, so we suggest turning them off
|
||||||
|
# unless you really need them.
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
# Use this to change the Rust version used to run your code
|
||||||
|
# on StackClass.
|
||||||
|
#
|
||||||
|
# Available versions: rust-1.87
|
||||||
|
language_pack: rust-1.87
|
||||||
|
|
||||||
|
# The executable required to build and run the this project,
|
||||||
|
# along with its minimum required version.
|
||||||
|
required_executable: cargo (1.87)
|
||||||
|
|
||||||
|
# The main source file that users can edit for this project.
|
||||||
|
user_editable_file: programs/swap-program/src/lib.rs
|
||||||
15
tests/swap.ts
Normal file
15
tests/swap.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import * as anchor from "@coral-xyz/anchor";
|
||||||
|
import { Program } from "@coral-xyz/anchor";
|
||||||
|
import { Swap } from "../target/types/swap";
|
||||||
|
|
||||||
|
describe("swap", async () => {
|
||||||
|
const provider = anchor.AnchorProvider.env();
|
||||||
|
anchor.setProvider(provider);
|
||||||
|
|
||||||
|
const program = anchor.workspace.Swap as Program<Swap>;
|
||||||
|
|
||||||
|
it("Is initialized!", async () => {
|
||||||
|
const tx = await program.methods.initialize().rpc();
|
||||||
|
console.log("Transaction signature:", tx);
|
||||||
|
});
|
||||||
|
});
|
||||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["mocha"],
|
||||||
|
"lib": ["es2020"],
|
||||||
|
"target": "es2020",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": ["tests/**/*", "migrations/**/*"]
|
||||||
|
}
|
||||||
24
your_program.sh
Normal file
24
your_program.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Use this script to run your program LOCALLY.
|
||||||
|
#
|
||||||
|
# Note: Changing this script WILL NOT affect how StackClass runs your program.
|
||||||
|
#
|
||||||
|
# Learn more: https://docs.stackclass.dev/challenges/program-interface
|
||||||
|
|
||||||
|
set -e # Exit early if any commands fail
|
||||||
|
|
||||||
|
# Copied from .stackclass/compile.sh
|
||||||
|
#
|
||||||
|
# - Edit this to change how your program compiles locally
|
||||||
|
# - Edit .stackclass/compile.sh to change how your program compiles remotely
|
||||||
|
(
|
||||||
|
cd "$(dirname "$0")" # Ensure compile steps are run within the repository directory
|
||||||
|
cargo build --release
|
||||||
|
)
|
||||||
|
|
||||||
|
# Copied from .stackclass/run.sh
|
||||||
|
#
|
||||||
|
# - Edit this to change how your program runs locally
|
||||||
|
# - Edit .stackclass/run.sh to change how your program runs remotely
|
||||||
|
exec target/release/stackclass-solana-swap-program "$@"
|
||||||
Reference in New Issue
Block a user