
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
JavaScript/TypeScript SDK for Sonic Name Service (SNS) V2 - Easy integration for dApps with bulk operations, contenthash, and enhanced features
JavaScript/TypeScript SDK for easy integration with Sonic Name Service - the decentralized domain name system for the Sonic blockchain.
🎉 Version 2.0 is here! This major update brings bulk operations, ENS-compatible contenthash, custom NFT images, and enhanced text record management.
npm install sns-sdk ethers@^6.0.0
yarn add sns-sdk ethers@^6.0.0
Note: This SDK requires ethers.js v6. If you're using ethers v5, please upgrade to v6.
import { SNS } from 'sns-sdk';
import { JsonRpcProvider } from 'ethers';
// Initialize the SDK
const provider = new JsonRpcProvider('https://rpc.soniclabs.com');
const sns = new SNS({ provider });
// Resolve a domain to an address
const address = await sns.resolve('krownlab.s');
console.log(address); // 0xc957215773a8b86c8d8bab235451e467caaf944c
// Reverse resolve an address to a domain
const domain = await sns.reverseResolve('0xc957215773a8b86c8d8bab235451e467caaf944c');
console.log(domain); // krownlab.s
// Check domain availability
const available = await sns.isAvailable('alice.s');
console.log(available); // true/false
// Get registration price
const price = await sns.getPrice('alice.s', 1); // 1 year
console.log(price.priceInEther); // "5.0"
import { SNS } from 'sns-sdk';
const sns = new SNS({
provider: provider, // Required: ethers.js provider
registryAddress: '0x...', // Optional: custom registry address
registrarAddress: '0x...', // Optional: custom registrar address
resolverAddress: '0x...', // Optional: custom resolver address
cacheEnabled: true, // Optional: enable caching (default: true)
cacheTTL: 5 * 60 * 1000, // Optional: cache TTL in ms (default: 5 min)
retryAttempts: 3, // Optional: retry attempts (default: 3)
retryDelay: 1000 // Optional: retry delay in ms (default: 1s)
});
resolve(domain: string): Promise<string>Resolve a domain name to an Ethereum address.
const address = await sns.resolve('krownlab.s');
reverseResolve(address: string): Promise<string>Reverse resolve an address to a domain name.
const domain = await sns.reverseResolve('0xc957215773a8b86c8d8bab235451e467caaf944c');
getContent(domain: string): Promise<string>Get content hash for a domain.
const contentHash = await sns.getContent('krownlab.s');
getText(domain: string, key: string): Promise<string>Get text record for a domain.
const github = await sns.getText('krownlab.s', 'github');
const x = await sns.getText('krownlab.s', 'x');
resolveBatch(domains: string[]): Promise<BatchResolutionResult>Resolve multiple domains in batches.
const result = await sns.resolveBatch(['krownlab.s', 'alice.s', 'bob.s']);
console.log(result.successful); // Successfully resolved domains
console.log(result.failed); // Failed resolutions with errors
reverseResolveBatch(addresses: string[]): Promise<BatchReverseResolutionResult>Reverse resolve multiple addresses in batches.
const result = await sns.reverseResolveBatch([
'0xc957215773a8b86c8d8bab235451e467caaf944c',
'0x123...',
'0x456...'
]);
registerBulk(domains: Array<{ name: string; years: number }>, signer: Signer): Promise<string>Register multiple domains in a single transaction (up to 20 domains).
const txHash = await sns.registerBulk([
{ name: 'domain1', years: 1 },
{ name: 'domain2', years: 2 },
{ name: 'domain3', years: 1 }
], wallet);
renewBulk(renewals: Array<{ domain: string; years: number }>, signer: Signer): Promise<string>Renew multiple domains in a single transaction.
const txHash = await sns.renewBulk([
{ domain: 'mydomain1.s', years: 1 },
{ domain: 'mydomain2.s', years: 2 }
], wallet);
calculateBulkPrice(domains: Array<{ name: string; years: number }>): Promise<BulkPriceResult>Calculate total price for bulk registration.
const result = await sns.calculateBulkPrice([
{ name: 'domain1', years: 1 },
{ name: 'domain2', years: 2 }
]);
console.log(result.totalPriceInEther); // "10.0"
console.log(result.breakdown); // Per-domain price breakdown
getContenthash(domain: string): Promise<string>Get ENS-compatible contenthash for a domain.
const contenthash = await sns.getContenthash('mydomain.s');
// Returns bytes in ENSIP-7 format
setContenthash(domain: string, hash: string, signer: Signer): Promise<string>Set contenthash for a domain (supports IPFS/Swarm).
// IPFS example
const txHash = await sns.setContenthash('mydomain.s', '0xe3010170...', wallet);
getTexts(domain: string, keys: string[]): Promise<Record<string, string>>Get multiple text records at once.
const records = await sns.getTexts('mydomain.s', [
'email', 'url', 'avatar', 'description'
]);
console.log(records.email); // "alice@example.com"
setTextBatch(domain: string, records: Record<string, string>, signer: Signer): Promise<string>Set multiple text records in a single transaction.
const txHash = await sns.setTextBatch('mydomain.s', {
'email': 'alice@example.com',
'com.twitter': '@alice',
'com.github': 'alice',
'url': 'https://alice.com'
}, wallet);
getSocials(domain: string): Promise<SocialRecords>Get social media records for a domain.
const socials = await sns.getSocials('mydomain.s');
console.log(socials.twitter); // Twitter handle
console.log(socials.github); // GitHub username
console.log(socials.discord); // Discord username
console.log(socials.telegram); // Telegram username
setPrimaryName(domain: string, signer: Signer): Promise<string>Set a domain as your primary name (replaces reverse records).
const txHash = await sns.setPrimaryName('mydomain.s', wallet);
clearPrimaryName(signer: Signer): Promise<string>Clear your primary name.
const txHash = await sns.clearPrimaryName(wallet);
getPrimaryName(address: string): Promise<string>Get the primary name for an address.
const primaryName = await sns.getPrimaryName('0x742d35Cc6aF66C59D32365bb44bB3eaA6b8F7e15');
console.log(primaryName); // "alice.s"
getCustomImage(domain: string): Promise<string>Get custom image URI for a domain.
const imageURI = await sns.getCustomImage('mydomain.s');
setCustomImage(domain: string, imageURI: string, signer: Signer): Promise<string>Set a custom image for your domain NFT.
// IPFS image
const txHash = await sns.setCustomImage(
'mydomain.s',
'ipfs://QmX...',
wallet
);
// HTTP image
const txHash = await sns.setCustomImage(
'mydomain.s',
'https://example.com/my-avatar.png',
wallet
);
clearCustomImage(domain: string, signer: Signer): Promise<string>Clear custom image and revert to default SVG.
const txHash = await sns.clearCustomImage('mydomain.s', wallet);
isAvailable(domain: string): Promise<boolean>Check if a domain is available for registration.
const available = await sns.isAvailable('newdomain.s');
getPrice(domain: string, years: number): Promise<PriceResult>Get registration price for a domain.
const price = await sns.getPrice('newdomain.s', 2); // 2 years
console.log(price.priceInEther); // Price in S
console.log(price.discount); // Discount percentage
console.log(price.discountAmount); // Discount amount in wei
register(domain: string, years: number, signer: Signer): Promise<string>Register a new domain.
import { Wallet } from 'ethers';
const wallet = new Wallet('your-private-key', provider);
const txHash = await sns.register('newdomain.s', 1, wallet);
console.log(`Registration transaction: ${txHash}`);
registerAndConfigure(domain: string, years: number, resolverAddress: string, setPrimary: boolean, signer: Signer): Promise<string> 🆕Register a domain and fully configure it in a single transaction. This is a gas-efficient convenience function that combines multiple operations:
import { Wallet } from 'ethers';
const wallet = new Wallet('your-private-key', provider);
// Register and configure in one transaction
const txHash = await sns.registerAndConfigure(
'mydomain.s', // domain name
1, // years
wallet.address, // address to resolve to
true, // set as primary name
wallet
);
console.log(`Domain registered and configured: ${txHash}`);
// After this transaction:
// - Domain is registered for 1 year
// - Resolver is set
// - Domain resolves to wallet.address
// - Domain is set as primary name for wallet.address
renew(domain: string, years: number, signer: Signer): Promise<string>Renew an existing domain.
const txHash = await sns.renew('mydomain.s', 1, wallet);
setAddress(domain: string, address: string, signer: Signer): Promise<string>Set the address record for a domain.
const txHash = await sns.setAddress(
'mydomain.s',
'0x742d35Cc6aF66C59D32365bb44bB3eaA6b8F7e15',
wallet
);
setText(domain: string, key: string, value: string, signer: Signer): Promise<string>Set a text record for a domain.
// Set email
await sns.setText('mydomain.s', 'email', 'alice@example.com', wallet);
// Set social media
await sns.setText('mydomain.s', 'x', 'alice_crypto', wallet);
await sns.setText('mydomain.s', 'github', 'alice', wallet);
setContent(domain: string, content: string, signer: Signer): Promise<string>Set content hash for a domain.
const txHash = await sns.setContent('mydomain.s', 'ipfs://Qm...', wallet);
getDomainInfo(domain: string): Promise<DomainRecord>Get comprehensive information about a domain.
const info = await sns.getDomainInfo('krownlab.s');
console.log(info.owner); // Domain owner address
console.log(info.expiryTime); // Expiry timestamp
console.log(info.expired); // Whether domain is expired
console.log(info.address); // Resolved address
validateDomain(domain: string): ValidationResultValidate a domain name according to SNS rules.
const validation = sns.validateDomain('my-domain');
console.log(validation.valid); // true/false
console.log(validation.errors); // Array of error messages
The SDK provides detailed error types for better error handling:
import {
SNS,
DomainNotFoundError,
DomainExpiredError,
ValidationError,
NetworkError
} from 'sns-sdk';
try {
const address = await sns.resolve('nonexistent.s');
} catch (error) {
if (error instanceof DomainNotFoundError) {
console.log('Domain not found');
} else if (error instanceof DomainExpiredError) {
console.log('Domain has expired');
} else if (error instanceof ValidationError) {
console.log('Invalid domain name');
} else if (error instanceof NetworkError) {
console.log('Network error occurred');
}
}
Domain pricing is based on length and registration duration:
Common text record keys supported:
import { TEXT_RECORD_KEYS } from 'sns-sdk';
// Standard keys
TEXT_RECORD_KEYS.EMAIL // 'email'
TEXT_RECORD_KEYS.WEBSITE // 'website'
TEXT_RECORD_KEYS.AVATAR // 'avatar'
TEXT_RECORD_KEYS.BANNER // 'banner'
TEXT_RECORD_KEYS.BIO // 'bio'
// Social media
TEXT_RECORD_KEYS.X // 'x'
TEXT_RECORD_KEYS.GITHUB // 'github'
TEXT_RECORD_KEYS.DISCORD // 'discord'
TEXT_RECORD_KEYS.INSTAGRAM // 'instagram'
TEXT_RECORD_KEYS.TELEGRAM // 'telegram'
import { SNS, getNetworkConfig } from 'sns-sdk';
// Get network config for a specific chain ID
const config = getNetworkConfig(146); // Sonic mainnet
if (config) {
const sns = new SNS({
provider,
registryAddress: config.contracts.registry,
registrarAddress: config.contracts.registrar,
resolverAddress: config.contracts.resolver
});
}
// Clear all caches
sns.clearCache();
// Get cache statistics
const stats = sns.getCacheStats();
console.log(stats.resolver.size); // Number of cached resolver entries
console.log(stats.registry.size); // Number of cached registry entries
import { retryWithBackoff } from 'sns-sdk';
// Custom retry logic
const result = await retryWithBackoff(
async () => await sns.resolve('domain.s'),
5, // attempts
2000 // delay in ms
);
0x0A6e0e2a41CD0a4BfB119b2e8183791E21600a7E0x0D9ECE9d71F038444BDB4317a058774867af39eB0x105E9Bfb2f06F4809B0c323Fb8299d813793742eThese addresses are automatically used by the SDK. If you need to use custom addresses, pass them in the configuration.
git checkout -b feature/your-feature)git commit -m 'Add some amazing feature')git push origin feature/your-feature)This project is licensed under the MIT License - see the LICENSE file for details.
The V2 SDK is mostly backward compatible with V1, but there are a few important changes:
V2 uses new upgraded contracts. Update your contract addresses if you're using custom addresses:
const sns = new SNS({
provider,
registryAddress: '0x...', // V2 registry address
registrarAddress: '0x...', // V2 registrar address
resolverAddress: '0x...' // V2 resolver address
});
The old setReverse() method still works but is deprecated. Use setPrimaryName() instead:
// ❌ V1 (deprecated)
await sns.setReverse('mydomain.s', wallet);
// ✅ V2 (recommended)
await sns.setPrimaryName('mydomain.s', wallet);
The DomainRecord type now includes additional V2 fields:
const info = await sns.getDomainInfo('mydomain.s');
// V2 additions:
info.contenthash // ENS-compatible contenthash
info.customImage // Custom NFT image URI
Save gas by registering/renewing multiple domains at once:
// Register 3 domains in one transaction
await sns.registerBulk([
{ name: 'domain1', years: 1 },
{ name: 'domain2', years: 2 },
{ name: 'domain3', years: 1 }
], wallet);
Store decentralized content hashes:
// Set IPFS contenthash
await sns.setContenthash('mydomain.s', '0xe3010170...', wallet);
Update multiple text records efficiently:
await sns.setTextBatch('mydomain.s', {
'email': 'alice@example.com',
'com.twitter': '@alice',
'com.github': 'alice'
}, wallet);
Personalize your domain NFTs:
await sns.setCustomImage('mydomain.s', 'ipfs://QmX...', wallet);
registerAndConfigure() - Register and configure domain in one transactionFAQs
JavaScript/TypeScript SDK for Sonic Name Service (SNS) V2 - Easy integration for dApps with bulk operations, contenthash, and enhanced features
We found that sns-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.