1#![warn(clippy::large_stack_arrays)]
11
12pub mod constants;
13pub mod instruction_builder;
14pub mod name_record;
15pub mod pda;
16
17pub use constants::{
18 HASH_PREFIX, ROOT_TLD_ADDRESS, SNS_PROGRAM_ID, SOL_TLD_ADDRESS, SOL_TLD_NAME_HASH,
19 SOL_TLD_OWNER_ADDRESS_MAINNET,
20};
21pub use instruction_builder::create::calculate_rent_exemption;
22pub use name_record::{Domain, Subdomain, TLDomain};
23pub use pda::{SNSNode, derive_domain, derive_subdomain, derive_tld, name_hash};
24
25use serde::{Deserialize, Serialize};
26use serde_wasm_bindgen::{from_value, to_value};
27use solana_pubkey::Pubkey;
28use wasm_bindgen::{JsError, JsValue, prelude::wasm_bindgen};
29
30use crate::name_record::SNSNodeWithOwner;
31
32#[derive(Debug, Default, Clone, Serialize, Deserialize)]
34pub struct CreateDomainCfg {
35 #[serde(flatten)]
36 inner: CreateSNSRecordCfgInner,
37 tld: SNSNodeWithOwner,
38}
39
40#[derive(Debug, Default, Clone, Serialize, Deserialize)]
42pub struct CreateSubdomainCfg {
43 #[serde(flatten)]
44 inner: CreateSNSRecordCfgInner,
45 domain: SNSNodeWithOwner,
46}
47
48#[derive(Debug, Default, Clone, Serialize, Deserialize)]
49struct CreateSNSRecordCfgInner {
50 payer: Pubkey,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 owner: Option<Pubkey>,
53 #[serde(skip_serializing_if = "Option::is_none")]
54 class: Option<Pubkey>,
55 name: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 space: Option<u32>,
58}
59
60#[wasm_bindgen]
61pub fn build_create_domain_instruction(cfg: JsValue) -> Result<JsValue, JsError> {
63 let CreateDomainCfg {
64 inner: CreateSNSRecordCfgInner { payer, owner, class, name, space },
65 tld,
66 } = from_value(cfg)?;
67
68 let builder =
69 Domain::create_instruction_builder(payer, TLDomain::new(tld.pda, tld.owner), &name)
70 .owner(owner)
71 .class(class)
72 .space(space);
73 let inst = builder.build();
74
75 Ok(to_value(&inst)?)
76}
77
78#[wasm_bindgen]
79pub fn build_create_subdomain_instruction(cfg: JsValue) -> Result<JsValue, JsError> {
81 let CreateSubdomainCfg {
82 inner: CreateSNSRecordCfgInner { payer, owner, class, name, space },
83 domain,
84 } = from_value(cfg)?;
85
86 let builder =
87 Subdomain::create_instruction_builder(payer, Domain::new(domain.pda, domain.owner), &name)
88 .owner(owner)
89 .class(class)
90 .space(space);
91 let inst = builder.build();
92
93 Ok(to_value(&inst)?)
94}
95
96#[cfg(test)]
97mod tests {
98 use solana_instruction::Instruction;
99 use solana_program_error::ProgramError;
100 use solana_pubkey::pubkey;
101 use wasm_bindgen_test::wasm_bindgen_test;
102
103 use super::*;
104 use crate::instruction_builder::NameRegistryInstruction;
105
106 const BONFIDA_DOMAIN_ADDRESS: Pubkey = pubkey!("Crf8hzfthWGbGbLTVCiqRqV5MVnbpHB1L9KQMd6gsinb");
107
108 #[wasm_bindgen_test]
109 fn test_build_create_instruction() {
110 let wallet = Pubkey::new_unique();
112 let name = "bonfida";
113 let cfg = CreateDomainCfg {
114 inner: CreateSNSRecordCfgInner {
115 payer: wallet,
116 name: name.to_string(),
117 ..Default::default()
118 },
119 tld: TLDomain::sol_mainnet().into(),
120 };
121 let js_cfg = to_value(&cfg).expect("should serialize");
122 let js_inst = build_create_domain_instruction(js_cfg).expect("should build instruction");
124
125 let res: Result<Instruction, ProgramError> =
126 from_value(js_inst).expect("should deserialize");
127 let inst = res.expect("should return instruction");
128 let [_, payer, pda, owner, class, parent, parent_owner] = &inst.accounts[..] else {
131 unreachable!()
132 };
133 let NameRegistryInstruction::Create { hashed_name, lamports, space } =
134 bincode::deserialize(inst.data.as_slice()).expect("should deserialize")
135 else {
136 unreachable!("wrong instruction")
137 };
138
139 assert_eq!(inst.program_id, SNS_PROGRAM_ID);
140 assert_eq!(payer.pubkey, wallet);
141 assert_eq!(pda.pubkey, BONFIDA_DOMAIN_ADDRESS);
142 assert_eq!(owner.pubkey, wallet);
143 assert_eq!(class.pubkey, Pubkey::default());
144 assert_eq!(parent.pubkey, SOL_TLD_ADDRESS);
145 assert_eq!(parent_owner.pubkey, SOL_TLD_OWNER_ADDRESS_MAINNET);
146 assert_eq!(hashed_name, name_hash(name).to_bytes().to_vec());
147 assert_eq!(lamports, calculate_rent_exemption(0));
148 assert_eq!(space, 0)
149 }
150}