SHA256
Переписать shine_users и shine_login_guard на чистый Rust
This commit is contained in:
Generated
+2
-4
@@ -3564,7 +3564,7 @@ dependencies = [
|
||||
name = "shine_login_guard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"solana-program 2.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3580,9 +3580,7 @@ dependencies = [
|
||||
name = "shine_users"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"common",
|
||||
"shine_login_guard",
|
||||
"solana-program 2.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
Адрес пользовательской PDA вычисляется по логину:
|
||||
|
||||
- seed prefix: `login=`;
|
||||
- seed prefix: `user_login=`;
|
||||
- второй seed: нормализованный логин в нижнем регистре;
|
||||
- program id: программа `shine_users`.
|
||||
|
||||
@@ -90,8 +90,8 @@ UserPdaRecordV1
|
||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||
| `55` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||
| `50` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||
|
||||
Правила:
|
||||
@@ -280,7 +280,7 @@ AccessServersBlock
|
||||
|
||||
```text
|
||||
SessionsBlock
|
||||
- block_type: u8 = 55
|
||||
- block_type: u8 = 50
|
||||
- block_version: u8 = 0
|
||||
- sessions_mode: u8
|
||||
- sessions_count: u8
|
||||
@@ -326,7 +326,7 @@ SessionRecord
|
||||
|
||||
```text
|
||||
TrustedStateBlock
|
||||
- block_type: u8 = 50
|
||||
- block_type: u8 = 70
|
||||
- block_version: u8 = 0
|
||||
- trusted_count: u8 = 0
|
||||
```
|
||||
|
||||
@@ -61,11 +61,19 @@
|
||||
|
||||
- `classify_login(login: String)`
|
||||
|
||||
Бинарный ABI инструкции:
|
||||
|
||||
```text
|
||||
- tag: u8 = 1
|
||||
- login_len: u32 LE
|
||||
- login_bytes[login_len]: UTF-8
|
||||
```
|
||||
|
||||
Аккаунты:
|
||||
|
||||
- signer
|
||||
- аккаунты не требуются
|
||||
|
||||
Signer здесь технический и нужен как согласованный интерфейс вызова; бизнес-смысл подписи отсутствует.
|
||||
Подпись транзакции для самой классификации не имеет бизнес-смысла. Если вызов идёт через CPI из `shine_users`, программа `shine_login_guard` не требует signer-аккаунтов вообще.
|
||||
|
||||
## 7. Выходные классы
|
||||
|
||||
|
||||
@@ -52,26 +52,26 @@
|
||||
|
||||
Пользовательская запись строится так:
|
||||
|
||||
- seed prefix: `login=`
|
||||
- seed prefix: `user_login=`
|
||||
- второй seed: логин в нижнем регистре
|
||||
- program id: `shine_users`
|
||||
|
||||
Формула:
|
||||
|
||||
```text
|
||||
user_pda = PDA(["login=", lower(login)], shine_users_program_id)
|
||||
user_pda = PDA(["user_login=", lower(login)], shine_users_program_id)
|
||||
```
|
||||
|
||||
### 3.2. Economy config PDA
|
||||
|
||||
PDA экономических настроек:
|
||||
|
||||
- seed: `shine_users_economy_config`
|
||||
- seed: `shine_users_economy_config_v2`
|
||||
|
||||
Формула:
|
||||
|
||||
```text
|
||||
users_economy_config_pda = PDA(["shine_users_economy_config"], shine_users_program_id)
|
||||
users_economy_config_pda = PDA(["shine_users_economy_config_v2"], shine_users_program_id)
|
||||
```
|
||||
|
||||
## 4. Состояния программы
|
||||
@@ -105,8 +105,8 @@ users_economy_config_pda = PDA(["shine_users_economy_config"], shine_users_progr
|
||||
|
||||
Базовые значения из текущей логики:
|
||||
|
||||
- seed `user_pda`: `login=`
|
||||
- seed economy config: `shine_users_economy_config`
|
||||
- seed `user_pda`: `user_login=`
|
||||
- seed economy config: `shine_users_economy_config_v2`
|
||||
- стартовый размер `user_pda`: `768` байт
|
||||
- `LIMIT_STEP = 10_000`
|
||||
- `START_REGISTRATION_FEE_LAMPORTS = 10_000_000`
|
||||
@@ -199,9 +199,15 @@ On-chain инвариант только один:
|
||||
### Правила
|
||||
|
||||
- PDA должна ещё не существовать;
|
||||
- адрес PDA обязан совпадать с seed `shine_users_economy_config`;
|
||||
- адрес PDA обязан совпадать с seed `shine_users_economy_config_v2`;
|
||||
- в PDA записывается стартовый `UsersEconomyConfigState`.
|
||||
|
||||
### Бинарный ABI
|
||||
|
||||
```text
|
||||
- tag: u8 = 1
|
||||
```
|
||||
|
||||
## 9. Инструкция `update_users_economy_config`
|
||||
|
||||
### Назначение
|
||||
@@ -223,6 +229,15 @@ On-chain инвариант только один:
|
||||
- PDA должна существовать и принадлежать программе;
|
||||
- `lamports_per_limit_step > 0`.
|
||||
|
||||
### Бинарный ABI
|
||||
|
||||
```text
|
||||
- tag: u8 = 2
|
||||
- registration_fee_lamports: u64 LE
|
||||
- lamports_per_limit_step: u64 LE
|
||||
- start_bonus_limit: u64 LE
|
||||
```
|
||||
|
||||
## 10. Инструкция `create_user_pda`
|
||||
|
||||
### Назначение
|
||||
@@ -259,6 +274,18 @@ On-chain инвариант только один:
|
||||
- mutable fields записи
|
||||
- `root` signature по unsigned части
|
||||
|
||||
### Бинарный ABI
|
||||
|
||||
```text
|
||||
- tag: u8 = 3
|
||||
- login: string_u8
|
||||
- root_key: [u8; 32]
|
||||
- created_at_ms: u64 LE
|
||||
- additional_limit: u64 LE
|
||||
- fields: UserMutableFieldsV1
|
||||
- root_signature: [u8; 64]
|
||||
```
|
||||
|
||||
### Обязательные проверки
|
||||
|
||||
1. Логин валиден по синтаксису.
|
||||
@@ -352,16 +379,20 @@ topup_fee = limit_fee(additional_limit)
|
||||
|
||||
Если `additional_limit = 0`, доплата не требуется.
|
||||
|
||||
### Важная пометка о текущем состоянии
|
||||
### Бинарный ABI
|
||||
|
||||
Логика `update_user_pda` в текущей Anchor-версии описана именно так и должна работать именно так.
|
||||
|
||||
Но фактическая текущая Anchor-реализация на момент подготовки этого документа имеет runtime-проблемы в Solana BPF:
|
||||
|
||||
- create-сценарий работает корректно;
|
||||
- update-сценарий логически реализован, но требует исправления багов реализации.
|
||||
|
||||
При переписи на чистый Rust нужно сохранять именно правила этого документа, а не привязываться к деталям текущей Anchor-реализации.
|
||||
```text
|
||||
- tag: u8 = 4
|
||||
- login: string_u8
|
||||
- root_key: [u8; 32]
|
||||
- created_at_ms: u64 LE
|
||||
- updated_at_ms: u64 LE
|
||||
- version: u32 LE
|
||||
- prev_hash: [u8; 32]
|
||||
- additional_limit: u64 LE
|
||||
- fields: UserMutableFieldsV1
|
||||
- root_signature: [u8; 64]
|
||||
```
|
||||
|
||||
## 12. Ed25519-проверки и порядок инструкций
|
||||
|
||||
@@ -426,6 +457,36 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
|
||||
## 15. Правила серверных и сессионных полей
|
||||
|
||||
### UserMutableFieldsV1
|
||||
|
||||
```text
|
||||
- device_key: [u8; 32]
|
||||
- blockchain_public_key: [u8; 32]
|
||||
- blockchain_name: string_u8
|
||||
- used_bytes: u64 LE
|
||||
- last_block_number: u32 LE
|
||||
- last_block_hash: [u8; 32]
|
||||
- last_block_signature: [u8; 64]
|
||||
- arweave_tx_id: string_u8
|
||||
- is_server: u8
|
||||
- if is_server = 1:
|
||||
- address_format_type: u8
|
||||
- address_format_version: u8
|
||||
- server_address: string_u8
|
||||
- sync_servers_count: u8
|
||||
- sync_servers[sync_servers_count]: string_u8[]
|
||||
- access_servers_count: u8
|
||||
- access_servers[access_servers_count]: string_u8[]
|
||||
- sessions_mode: u8
|
||||
- sessions_count: u8
|
||||
- sessions[sessions_count]:
|
||||
- session_type: u8
|
||||
- session_version: u8
|
||||
- session_name: string_u8
|
||||
- session_pub_key: [u8; 32]
|
||||
- trusted_count: u8
|
||||
```
|
||||
|
||||
### Server profile
|
||||
|
||||
Если `is_server = false`:
|
||||
@@ -483,7 +544,7 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
|
||||
## 18. Что должно сохраниться при переписи без Anchor
|
||||
|
||||
При переписи на чистый Rust нужно сохранить без изменений:
|
||||
В текущей чисто-rust реализации уже сохранены:
|
||||
|
||||
- те же PDA seed-правила;
|
||||
- тот же формат `user_pda`;
|
||||
@@ -493,9 +554,9 @@ signature = Ed25519(blockchain_private_key, message_hash)
|
||||
- ту же валидацию логина и CPI в `shine_login_guard`;
|
||||
- ту же зависимость от inflow vault программы `shine_payments`.
|
||||
|
||||
Что не обязано сохраниться:
|
||||
Сознательно не сохранялись:
|
||||
|
||||
- структура Anchor `Context`;
|
||||
- Anchor discriminator'ы;
|
||||
- внутренние helper-функции текущей реализации;
|
||||
- текущие runtime-баги update-path.
|
||||
- Anchor discriminator'ы и Anchor-ABI инструкций;
|
||||
- старые seed'ы, которые конфликтовали с уже существующим Anchor-состоянием в devnet;
|
||||
- внутренние helper-функции старой реализации.
|
||||
|
||||
@@ -12,15 +12,9 @@ doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.31.1"
|
||||
solana-program = "2.1.21"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
anchor-debug = []
|
||||
custom-heap = []
|
||||
custom-panic = []
|
||||
cpi = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
@@ -1,39 +1,83 @@
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_lang::solana_program::program::set_return_data;
|
||||
|
||||
declare_id!("3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo");
|
||||
use solana_program::{
|
||||
account_info::AccountInfo,
|
||||
entrypoint,
|
||||
entrypoint::ProgramResult,
|
||||
program::set_return_data,
|
||||
program_error::ProgramError,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
mod wordlist {
|
||||
include!(concat!(env!("OUT_DIR"), "/generated_dictionary.rs"));
|
||||
}
|
||||
|
||||
const CLASS_FREE: u32 = 0;
|
||||
const CLASS_PREMIUM: u32 = 1;
|
||||
const CLASS_TRADEMARK: u32 = 2;
|
||||
const MAX_WORDS_PER_LOGIN: usize = 3;
|
||||
pub const INSTRUCTION_CLASSIFY_LOGIN: u8 = 1;
|
||||
pub const CLASS_FREE: u32 = 0;
|
||||
pub const CLASS_PREMIUM: u32 = 1;
|
||||
pub const CLASS_TRADEMARK: u32 = 2;
|
||||
pub const MAX_WORDS_PER_LOGIN: usize = 3;
|
||||
pub const MAX_LOGIN_LEN: usize = 20;
|
||||
|
||||
#[program]
|
||||
pub mod shine_login_guard {
|
||||
use super::*;
|
||||
solana_program::declare_id!("3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo");
|
||||
|
||||
pub fn classify_login(_ctx: Context<ClassifyLogin>, login: String) -> Result<()> {
|
||||
let class = classify(&login);
|
||||
set_return_data(&class.to_le_bytes());
|
||||
Ok(())
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
_accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
let class = match parse_instruction(instruction_data)? {
|
||||
Instruction::ClassifyLogin { login } => classify(&login),
|
||||
};
|
||||
set_return_data(&class.to_le_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Instruction {
|
||||
ClassifyLogin { login: String },
|
||||
}
|
||||
|
||||
fn parse_instruction(data: &[u8]) -> Result<Instruction, ProgramError> {
|
||||
let (&tag, rest) = data
|
||||
.split_first()
|
||||
.ok_or(ProgramError::InvalidInstructionData)?;
|
||||
match tag {
|
||||
INSTRUCTION_CLASSIFY_LOGIN => {
|
||||
let login = read_string_u32(rest)?;
|
||||
Ok(Instruction::ClassifyLogin { login })
|
||||
}
|
||||
_ => Err(ProgramError::InvalidInstructionData),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ClassifyLogin<'info> {
|
||||
pub signer: Signer<'info>,
|
||||
fn read_string_u32(data: &[u8]) -> Result<String, ProgramError> {
|
||||
if data.len() < 4 {
|
||||
return Err(ProgramError::InvalidInstructionData);
|
||||
}
|
||||
let len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
|
||||
let body = data
|
||||
.get(4..4 + len)
|
||||
.ok_or(ProgramError::InvalidInstructionData)?;
|
||||
std::str::from_utf8(body)
|
||||
.map(|s| s.to_string())
|
||||
.map_err(|_| ProgramError::InvalidInstructionData)
|
||||
}
|
||||
|
||||
fn classify(login: &str) -> u32 {
|
||||
pub fn build_classify_login_instruction_data(login: &str) -> Vec<u8> {
|
||||
let bytes = login.as_bytes();
|
||||
let mut out = Vec::with_capacity(1 + 4 + bytes.len());
|
||||
out.push(INSTRUCTION_CLASSIFY_LOGIN);
|
||||
out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
|
||||
out.extend_from_slice(bytes);
|
||||
out
|
||||
}
|
||||
|
||||
pub fn classify(login: &str) -> u32 {
|
||||
let Some(normalized) = normalize_login(login) else {
|
||||
return CLASS_PREMIUM;
|
||||
};
|
||||
// Сначала пытаемся классифицировать по словарям (в т.ч. trademark/company),
|
||||
// и только если не нашли совпадений — применяем правило короткого логина.
|
||||
if let Some(v) = classify_split(&normalized) {
|
||||
return v;
|
||||
}
|
||||
@@ -44,7 +88,7 @@ fn classify(login: &str) -> u32 {
|
||||
}
|
||||
|
||||
fn normalize_login(login: &str) -> Option<String> {
|
||||
if login.is_empty() || login.len() > 20 {
|
||||
if login.is_empty() || login.len() > MAX_LOGIN_LEN {
|
||||
return None;
|
||||
}
|
||||
let mut out = String::with_capacity(login.len());
|
||||
@@ -57,7 +101,7 @@ fn normalize_login(login: &str) -> Option<String> {
|
||||
}
|
||||
out.push(ch.to_ascii_lowercase());
|
||||
}
|
||||
if out.is_empty() || out.len() > 20 {
|
||||
if out.is_empty() || out.len() > MAX_LOGIN_LEN {
|
||||
return None;
|
||||
}
|
||||
Some(out)
|
||||
@@ -74,7 +118,7 @@ fn classify_split(login: &str) -> Option<u32> {
|
||||
if depth >= MAX_WORDS_PER_LOGIN {
|
||||
return None;
|
||||
}
|
||||
let max_piece = rest.len().min(20);
|
||||
let max_piece = rest.len().min(MAX_LOGIN_LEN);
|
||||
let mut premium_found = false;
|
||||
for i in 1..=max_piece {
|
||||
let candidate = &rest[..i];
|
||||
@@ -87,7 +131,7 @@ fn classify_split(login: &str) -> Option<u32> {
|
||||
Some(CLASS_TRADEMARK) => return Some(CLASS_TRADEMARK),
|
||||
Some(CLASS_PREMIUM) => premium_found = true,
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
if premium_found {
|
||||
Some(CLASS_PREMIUM)
|
||||
@@ -105,3 +149,35 @@ fn is_premium_word(word: &str) -> bool {
|
||||
fn is_trademark_word(word: &str) -> bool {
|
||||
wordlist::TRADEMARK_WORDS.binary_search(&word).is_ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn normalize_removes_underscores_and_lowercases() {
|
||||
assert_eq!(normalize_login("Ab_C1"), Some("abc1".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_rejects_invalid_chars() {
|
||||
assert_eq!(normalize_login("ab-c"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_unknown_login_is_premium() {
|
||||
assert_eq!(classify("abc123"), CLASS_PREMIUM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_unknown_login_is_free() {
|
||||
assert_eq!(classify("abcdefgh9"), CLASS_FREE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_roundtrip() {
|
||||
let data = build_classify_login_instruction_data("Alice_1");
|
||||
let parsed = parse_instruction(&data).unwrap();
|
||||
assert_eq!(parsed, Instruction::ClassifyLogin { login: "Alice_1".to_string() });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,10 @@ doctest = false
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.31.1"
|
||||
common = { path = "../common" }
|
||||
shine_login_guard = { path = "../shine_login_guard", features = ["cpi", "no-entrypoint"] }
|
||||
|
||||
solana-program = "2.1.21"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
anchor-debug = []
|
||||
custom-heap = []
|
||||
custom-panic = []
|
||||
cpi = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,27 @@
|
||||
use common::deploy_config;
|
||||
|
||||
/// `USER_PDA_SEED_PREFIX` — префикс seed для пользовательского PDA (`login=<...>`).
|
||||
pub const USER_PDA_SEED_PREFIX: &str = "login=";
|
||||
/// `USERS_ECONOMY_CONFIG_SEED` — seed PDA с экономическими параметрами программы `shine_users`.
|
||||
pub const USERS_ECONOMY_CONFIG_SEED: &[u8] = b"shine_users_economy_config";
|
||||
/// `USER_PDA_SPACE` — стартовый размер PDA пользователя, дальше запись может расширяться через realloc.
|
||||
/// Префикс seed для пользовательского PDA новой чисто-rust реализации `shine_users`.
|
||||
pub const USER_PDA_SEED_PREFIX: &str = "user_login=";
|
||||
/// Seed PDA с экономическими параметрами программы `shine_users`.
|
||||
pub const USERS_ECONOMY_CONFIG_SEED: &[u8] = b"shine_users_economy_config_v2";
|
||||
/// Стартовый размер PDA пользователя, дальше запись может расширяться через realloc.
|
||||
pub const USER_PDA_SPACE: usize = 768;
|
||||
/// `USERS_ECONOMY_CONFIG_SPACE` — размер PDA с экономическими параметрами `shine_users`.
|
||||
pub const USERS_ECONOMY_CONFIG_SPACE: usize = 8 + 96;
|
||||
/// Размер PDA с экономическими параметрами `shine_users`.
|
||||
pub const USERS_ECONOMY_CONFIG_SPACE: usize = 32;
|
||||
|
||||
/// `DAO_AUTHORITY` — адрес DAO-авторити, который имеет право обновлять economy-конфиг.
|
||||
pub const DAO_AUTHORITY: &str = deploy_config::DAO_AUTHORITY;
|
||||
/// Адрес DAO authority, который имеет право обновлять economy-конфиг.
|
||||
pub const DAO_AUTHORITY: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P";
|
||||
|
||||
/// `SHINE_PAYMENTS_PROGRAM_ID` — адрес программы `shine_payments`, от которой вычисляется PDA inflow-вольта.
|
||||
pub const SHINE_PAYMENTS_PROGRAM_ID: &str = deploy_config::SHINE_PAYMENTS_PROGRAM_ID;
|
||||
/// `SHINE_PAYMENTS_INFLOW_VAULT_SEED` — seed inflow-вольта в программе `shine_payments` (должен совпадать с payments settings).
|
||||
/// Адрес программы `shine_payments`, от которой вычисляется PDA inflow-вольта.
|
||||
pub const SHINE_PAYMENTS_PROGRAM_ID: &str = "m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR";
|
||||
/// Seed inflow-вольта в программе `shine_payments`.
|
||||
pub const SHINE_PAYMENTS_INFLOW_VAULT_SEED: &[u8] = b"shine_payments_inflow_vault";
|
||||
/// `SHINE_LOGIN_GUARD_PROGRAM_ID` — адрес отдельной программы проверки премиальности логина.
|
||||
pub const SHINE_LOGIN_GUARD_PROGRAM_ID: &str = deploy_config::SHINE_LOGIN_GUARD_PROGRAM_ID;
|
||||
/// `START_REGISTRATION_FEE_LAMPORTS` — стартовая комиссия регистрации (0.01 SOL) для initial economy-конфига.
|
||||
/// Адрес отдельной программы проверки премиальности логина.
|
||||
pub const SHINE_LOGIN_GUARD_PROGRAM_ID: &str = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
|
||||
/// Стартовая комиссия регистрации (0.01 SOL) для initial economy-конфига.
|
||||
pub const START_REGISTRATION_FEE_LAMPORTS: u64 = 10_000_000;
|
||||
|
||||
/// `LIMIT_STEP` — шаг пополнения лимита; `additional_limit` должен быть кратен этому значению.
|
||||
/// Шаг пополнения лимита.
|
||||
pub const LIMIT_STEP: u64 = 10_000;
|
||||
/// `START_LAMPORTS_PER_LIMIT_STEP` — стартовая цена одного шага лимита (0.0001 SOL за 10_000 лимита).
|
||||
/// Стартовая цена одного шага лимита.
|
||||
pub const START_LAMPORTS_PER_LIMIT_STEP: u64 = 100_000;
|
||||
|
||||
/// `START_BONUS_LIMIT` — стартовый бонус лимита, выдаваемый пользователю при создании записи.
|
||||
/// Стартовый бонус лимита.
|
||||
pub const START_BONUS_LIMIT: u64 = 100_000;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user