Skip to main content

sns_wasm/
lib.rs

1//! # SNS-WASM
2//!
3//! Specs: <https://sns.guide/introduction.html>
4//!
5//! <div style="background: #fff3cd; color: #856404; padding: 15px; border: 1px solid #ffeeba;
6//! border-radius: 4px;"> <strong>Warning:</strong> This crate is experimental. Only one
7//! instruction is currently supported. </div>
8//! Client-side SNS-WASM SDK.
9
10#![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/// Params to create instruction to register new SNS domain record.
33#[derive(Debug, Default, Clone, Serialize, Deserialize)]
34pub struct CreateDomainCfg {
35    #[serde(flatten)]
36    inner: CreateSNSRecordCfgInner,
37    tld: SNSNodeWithOwner,
38}
39
40/// Params to create instruction to register new SNS subdomain record.
41#[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]
61/// Computes instruction to register new domain from given parameters.
62pub 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]
79/// Computes instruction to register new subdomain from given parameters.
80pub 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        // setup
111        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        // test
123        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        //wasm_bindgen_test::console_log!("instruction: {:#?}", inst);
129
130        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}