dotcv-solana
v0.0.8
Published
Smart Contract Library
Readme
DotCv SDK
A TypeScript client library for interacting with DotCv's Solana program. This SDK enables domain registration, subdomain management, record creation and editing, domain resolution, payment routing, and webhook setup for monitoring transactions.
Features
- Initialize connections to Solana devnet or mainnet.
- Set up and manage parent domains on the Solana blockchain.
- Create domain registrars for subdomain management.
- Register subdomains as an admin or user.
- Create, edit, and resolve records (e.g., Twitter handles, wallets).
- Transfer subdomain ownership.
- Set up payment routers for fund distribution.
- Distribute SOL or SPL tokens via payment routers.
- Configure webhooks for real-time transaction monitoring using Helius.
Installation
Install the package via npm or yarn:
npm install dotcv-solana --saveOR
yarn add dotcv-solanaThis package depends on external libraries like @solana/web3.js, @coral-xyz/anchor, @bonfida/spl-name-service, base58, and helius-sdk. Ensure they are installed or handled via peer dependencies.
Usage
Domain Setup
Before using the SDK for subdomain management or other features, you need to set up a parent domain on the Solana blockchain. Below is a step-by-step guide for initializing and registering a parent domain.
Step 1: Create a Keypair
Generate a new keypair for the parent domain and save the private key to a file for later use. This keypair will act as the authority for admin operations.
import { Keypair } from '@solana/web3.js';
import fs from 'fs';
import base58 from 'bs58';
const createKeypair = async () => {
const parentKeypair = Keypair.generate();
const privateKeyBase58 = base58.encode(parentKeypair.secretKey);
fs.writeFileSync('parentKeypair.txt', privateKeyBase58);
console.log(
'Keypair created and saved to parentKeypair.txt:',
parentKeypair.publicKey.toBase58()
);
return parentKeypair;
};
// Run once to generate keypair
createKeypair();- Purpose: Creates a new Solana keypair and saves the private key in
parentKeypair.txt. - Important: Run this step only once and secure the
parentKeypair.txtfile. Comment out or remove the call after execution to avoid generating new keypairs unnecessarily. - Airdrop: For devnet testing, airdrop SOL to the public key of the generated keypair to cover transaction fees (e.g., use
solana airdrop 1 <PUBLIC_KEY> --url https://api.devnet.solana.com).
Step 2: Fetch the Keypair
Load the saved keypair from the file for use in subsequent operations.
import { Keypair } from '@solana/web3.js';
import fs from 'fs';
import base58 from 'bs58';
const fetchKeypair = async () => {
const privateKeyBase58FromFile = fs.readFileSync('parentKeypair.txt', 'utf8');
const privateKeyFromFile = base58.decode(privateKeyBase58FromFile);
const keypair = Keypair.fromSecretKey(privateKeyFromFile);
return keypair;
};
// Fetch keypair
fetchKeypair().then(() => console.log('Keypair fetched'));- Purpose: Retrieves the private key from
parentKeypair.txtand creates aKeypairobject. - Important: Ensure
parentKeypair.txtexists and contains the correct base58-encoded private key.
Step 3: Create the Parent Domain
Register the parent domain on the Solana blockchain. This step involves creating a name registry and registering the domain using the @bonfida/spl-name-service library.
import {
Connection,
Keypair,
SystemProgram,
Transaction,
} from '@solana/web3.js';
import {
devnet,
getHashedNameSync,
NameRegistryState,
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddress,
createSyncNativeInstruction,
NATIVE_MINT,
signAndSendInstructions,
} from '@bonfida/spl-name-service';
import { sendAndConfirmTransaction } from '@solana/web3.js';
import base58 from 'bs58';
const connection = new Connection(
'https://api.devnet.solana.com', // Replace with your RPC URL
'confirmed'
);
const createDomain = async (domain: string, parentKeypair: Keypair) => {
const space = 1 * 1000; // Space for name registry (in bytes)
// Create associated token account (ATA) for Wrapped SOL (WSOL)
const parentAta = await getAssociatedTokenAddress(
NATIVE_MINT,
parentKeypair.publicKey
);
const wrapTransaction = new Transaction().add(
createAssociatedTokenAccountInstruction(
parentKeypair.publicKey,
parentAta,
parentKeypair.publicKey,
NATIVE_MINT
),
SystemProgram.transfer({
fromPubkey: parentKeypair.publicKey,
toPubkey: parentAta,
lamports: 0.45 * LAMPORTS_PER_SOL, // Amount to wrap
}),
createSyncNativeInstruction(parentAta)
);
const wrapTxSig = await sendAndConfirmTransaction(
connection,
wrapTransaction,
[parentKeypair]
);
console.log('SOL wrapped:', wrapTxSig);
// Register domain
const createDomainIx = await devnet.bindings.registerDomainNameV2(
connection,
domain,
space,
parentKeypair.publicKey,
parentAta,
NATIVE_MINT
);
// Create name registry
const hashedName = getHashedNameSync(domain);
const nameAccount = devnet.utils.getNameAccountKeySync(hashedName);
const createNameTx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: parentKeypair.publicKey,
toPubkey: nameAccount,
lamports: await connection.getMinimumBalanceForRentExemption(space),
}),
await devnet.bindings.createNameRegistry(
connection,
domain,
space,
parentKeypair.publicKey,
parentKeypair.publicKey
)
);
createNameTx.feePayer = parentKeypair.publicKey;
const createNameTxSig = await sendAndConfirmTransaction(
connection,
createNameTx,
[parentKeypair]
);
console.log('Registry Registered:', createNameTxSig);
// Finalize domain registration
const registerTxSig = await signAndSendInstructions(
connection,
[parentKeypair],
parentKeypair,
createDomainIx
);
console.log('Domain Registered:', registerTxSig);
// Verify domain
const domainKeys = devnet.utils.getDomainKeySync(domain);
const reverseKey = devnet.utils.getReverseKeySync(domain);
const { registry } = await NameRegistryState.retrieve(
connection,
nameAccount
);
console.log('Registry Owner:', registry.owner.toBase58());
};
// Run once to create domain
const domain = 'yourparentdomain'; // e.g., 'manuchem'
fetchKeypair().then(async (keypair) => {
await createDomain(domain, keypair);
console.log('Domain created');
});- Purpose: Registers the parent domain (e.g.,
manuchem.sol) on the Solana blockchain. - Steps:
- Create an associated token account (ATA) for Wrapped SOL (WSOL) to pay for registration.
- Create a name registry for the domain.
- Register the domain using
@bonfida/spl-name-service. - Verify the domain by retrieving its keys and registry owner.
- Important:
- Replace the RPC URL with a reliable endpoint (e.g., QuickNode or Helius).
- Ensure the wallet has sufficient SOL for fees (e.g., 0.45 SOL for wrapping).
- Run this step only once and comment it out afterward to avoid duplicate registrations.
- The
domainvariable should not include.sol(e.g., usemanucheminstead ofmanuchem.sol).
Initializing the SDK
After setting up the parent domain, initialize the DotCvWeb3 instance with the keypair and domain.
import { DotCvWeb3, Record, ApiEndpoints } from 'dotcv-solana';
import { Keypair } from '@solana/web3.js';
import fs from 'fs';
import base58 from 'bs58';
const apiEndpoints: ApiEndpoints = {
mainnet: 'https://api.mainnet-beta.solana.com',
devnet: 'https://api.devnet.solana.com',
};
const fetchKeypair = async () => {
const privateKeyBase58FromFile = fs.readFileSync('parentKeypair.txt', 'utf8');
return Keypair.fromSecretKey(base58.decode(privateKeyBase58FromFile));
};
fetchKeypair().then((keypair) => {
const web3 = new DotCvWeb3(
base58.encode(keypair.secretKey), // Private key (base58)
'devnet', // Network: 'devnet' or 'mainnet-beta'
apiEndpoints,
'yourparentdomain.sol' // Parent domain (e.g., 'manuchem.sol')
);
console.log('SDK initialized');
});- privateKey: Base58-encoded private key from
parentKeypair.txt. - network:
'devnet'for testing or'mainnet-beta'for production. - rpcEndpoints: Object with mainnet and devnet RPC URLs.
- domain: Parent domain with
.solsuffix (e.g.,manuchem.sol).
Domain Management
Create a Domain Registrar
A registrar is required to manage subdomain registrations under the parent domain.
const registrar = await web3.createRegistrar();
console.log('Registrar created:', registrar);Returns an object with registrar details (e.g., authority, fee account, price schedule).
Register a Subdomain (Admin)
Admins can register subdomains directly.
const { txHash, domainOwner } = await web3.adminRegisterSubDomain(
'mysubdomain'
);
console.log('Subdomain registered:', txHash);- subDomain: Subdomain name (without parent, e.g., 'mysubdomain' instead of 'mysubdomain.yourparentdomain.sol').
- Returns: Transaction hash and domain owner public key.
Register a Subdomain (User)
Users can register subdomains via a serialized transaction (to be signed on the frontend).
const { serializedTransaction, message } = await web3.registerSubDomain(
'mysubdomain',
'BUYER_PUBLIC_KEY_BASE58'
);
console.log('Serialized transaction for signing:', serializedTransaction);- subDomain: Subdomain name.
- buyer: Buyer's public key (base58).
- Returns: Serialized transaction (base58) for user signing and a message.
Transfer Subdomain Ownership
Transfer ownership of a subdomain to a new public key.
const { serializedTransaction, message } = await web3.transferSubDomain(
'mysubdomain',
'NEW_OWNER_PUBLIC_KEY_BASE58'
);
console.log('Transfer transaction:', serializedTransaction);- subDomain: Subdomain name.
- newOwner: New owner's public key (base58).
- Returns: Serialized transaction for signing.
Resolve a Domain
Retrieve information linked to a subdomain (e.g., associated SOL wallet).
const domainInfo = await web3.resolveDomain('mysubdomain');
console.log('Resolved SOL wallet:', domainInfo.solWallet);- subDomain: Subdomain name.
- Returns: Object with resolved data (e.g.,
{ solWallet: 'PUBLIC_KEY' }).
Record Management
Records allow linking data (e.g., Twitter handles, emails) to subdomains using the Record enum from @bonfida/spl-name-service.
Supported records include: Record.Twitter, Record.Discord, Record.Github, Record.SOL, etc.
Create a Record
const { serializedTransaction, message } = await web3.createRecord(
'mysubdomain',
'@twitterhandle',
'DOMAIN_OWNER_PUBLIC_KEY_BASE58',
Record.Twitter
);
console.log('Record creation transaction:', serializedTransaction);- subDomain: Subdomain name.
- content: Record content (e.g., Twitter username).
- domainOwner: Domain owner's public key (base58).
- record: Type of record (e.g.,
Record.Twitter). - Returns: Serialized transaction for signing.
Edit a Record
const { serializedTransaction, message } = await web3.editRecord(
'mysubdomain',
'@newtwitterhandle',
'DOMAIN_OWNER_PUBLIC_KEY_BASE58',
Record.Twitter
);
console.log('Record edit transaction:', serializedTransaction);Parameters are the same as createRecord.
Payment Router
Initialize a Payment Router
Set up a payment router for a subdomain to distribute incoming funds based on rules.
const distributionRules = [
{ wallet: 'WALLET1_PUBLIC_KEY_BASE58', percentage: 60 },
{ wallet: 'WALLET2_PUBLIC_KEY_BASE58', percentage: 40 },
];
const { serializedTx, paymentRouterPDA, bump } =
await web3.initializePaymentRouter('mysubdomain', distributionRules);
console.log('Payment router initialized:', paymentRouterPDA);- subDomain: Subdomain name.
- distributionRules: Array of rules (wallets and percentages; must sum to 100).
- Returns: Serialized transaction, PDA address, and bump.
Distribute Funds
Distribute SOL or SPL tokens from the payment router.
const { distributeTxSignature, paymentRouterPDA, isSol } =
await web3.distribute(
'mysubdomain',
1000000000, // Amount (lamports for SOL)
true // true for SOL, false for SPL tokens
// Optional: 'MINT_ADDRESS_BASE58' for SPL tokens
);
console.log('Funds distributed:', distributeTxSignature);Interface Types
ApiEndpoints
interface ApiEndpoints {
mainnet: string; // Mainnet RPC URL
devnet: string; // Devnet RPC URL
}DistributionRuleInput
interface DistributionRuleInput {
wallet: string; // Wallet public key (base58)
percentage: number; // Percentage (0-100)
}WebhookConfig (Internal, for Reference)
interface WebhookConfig {
webhookUrl: string;
accountAddresses: string[];
transactionTypes: string[];
webhookType: string;
}Error Handling
Methods throw descriptive errors. Always wrap calls in try-catch:
try {
await web3.adminRegisterSubDomain('mysubdomain');
} catch (error) {
console.error('Error:', error.message);
if (error.message.includes('unauthorized')) {
// Handle specific errors
}
}Common errors include invalid keys, insufficient funds, or network issues. For domain setup, ensure the wallet has SOL
