ts-xdbchain-sdk
Version:
A TypeScript SDK for interacting with XDBCHAIN - a Stellar-based blockchain network
1,574 lines • 64 kB
JavaScript
import { Horizon, Keypair, Asset, TransactionBuilder, Operation, Memo, Claimant, StrKey } from 'stellar-sdk';
export class XDBChainError extends Error {
constructor(message, code, details) {
super(message);
this.code = code;
this.details = details;
this.name = 'XDBChainError';
}
}
export class XDBNetworkError extends XDBChainError {
constructor(message, details) {
super(message, 'NETWORK_ERROR', details);
this.name = 'XDBNetworkError';
}
}
export class XDBValidationError extends XDBChainError {
constructor(message, details) {
super(message, 'VALIDATION_ERROR', details);
this.name = 'XDBValidationError';
}
}
export class XDBTransactionError extends XDBChainError {
constructor(message, details) {
super(message, 'TRANSACTION_ERROR', details);
this.name = 'XDBTransactionError';
}
}
export class XDBAssetError extends XDBChainError {
constructor(message, details) {
super(message, 'ASSET_ERROR', details);
this.name = 'XDBAssetError';
}
}
export class XDBOfferError extends XDBChainError {
constructor(message, details) {
super(message, 'OFFER_ERROR', details);
this.name = 'XDBOfferError';
}
}
export const ACCOUNT_FLAGS = {
AUTH_REQUIRED_FLAG: 0x1,
AUTH_REVOCABLE_FLAG: 0x2,
AUTH_IMMUTABLE_FLAG: 0x4,
AUTH_CLAWBACK_ENABLED_FLAG: 0x8
};
export const ASSET_TYPE = {
NATIVE: 'native',
CREDIT_ALPHANUM4: 'credit_alphanum4',
CREDIT_ALPHANUM12: 'credit_alphanum12'
};
export const OPERATION_TYPE = {
CREATE_ACCOUNT: 'create_account',
PAYMENT: 'payment',
PATH_PAYMENT_STRICT_RECEIVE: 'path_payment_strict_receive',
PATH_PAYMENT_STRICT_SEND: 'path_payment_strict_send',
MANAGE_SELL_OFFER: 'manage_sell_offer',
MANAGE_BUY_OFFER: 'manage_buy_offer',
CREATE_PASSIVE_SELL_OFFER: 'create_passive_sell_offer',
SET_OPTIONS: 'set_options',
CHANGE_TRUST: 'change_trust',
ALLOW_TRUST: 'allow_trust',
ACCOUNT_MERGE: 'account_merge',
INFLATION: 'inflation',
MANAGE_DATA: 'manage_data',
BUMP_SEQUENCE: 'bump_sequence',
CREATE_CLAIMABLE_BALANCE: 'create_claimable_balance',
CLAIM_CLAIMABLE_BALANCE: 'claim_claimable_balance',
BEGIN_SPONSORING_FUTURE_RESERVES: 'begin_sponsoring_future_reserves',
END_SPONSORING_FUTURE_RESERVES: 'end_sponsoring_future_reserves',
REVOKE_SPONSORSHIP: 'revoke_sponsorship',
CLAWBACK: 'clawback',
CLAWBACK_CLAIMABLE_BALANCE: 'clawback_claimable_balance',
SET_TRUST_LINE_FLAGS: 'set_trust_line_flags'
};
export class XDBChainSDK {
constructor(config) {
this.config = {
...XDBChainSDK.DEFAULT_CONFIGS[config.network],
...config
};
this.server = new Horizon.Server(this.config.horizonUrl, {
allowHttp: this.config.allowHttp
});
this.networkPassphrase = this.config.networkPassphrase;
}
async createWallet() {
try {
const keypair = Keypair.random();
const wallet = {
publicKey: keypair.publicKey(),
secretKey: keypair.secret(),
network: this.config.network
};
console.log('✅ Wallet created successfully:', wallet.publicKey);
return wallet;
}
catch (error) {
throw new XDBChainError(`Failed to create wallet: ${error}`);
}
}
async loadWallet(secretKey) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key format');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
return {
publicKey: keypair.publicKey(),
secretKey: secretKey,
network: this.config.network,
account: account
};
}
catch (error) {
throw new XDBChainError(`Failed to load wallet: ${error}`);
}
}
async createWalletFromSeed(seed) {
try {
const keypair = Keypair.fromRawEd25519Seed(typeof seed === 'string' ? Buffer.from(seed, 'hex') : seed);
const wallet = {
publicKey: keypair.publicKey(),
secretKey: keypair.secret(),
network: this.config.network
};
return wallet;
}
catch (error) {
throw new XDBChainError(`Failed to create wallet from seed: ${error}`);
}
}
isValidAddress(address) {
try {
return StrKey.isValidEd25519PublicKey(address);
}
catch {
return false;
}
}
isValidSecretKey(secretKey) {
try {
return StrKey.isValidEd25519SecretSeed(secretKey);
}
catch {
return false;
}
}
generateKeypair() {
return Keypair.random();
}
keypairFromSecret(secretKey) {
return Keypair.fromSecret(secretKey);
}
keypairFromPublicKey(publicKey) {
return Keypair.fromPublicKey(publicKey);
}
async loadAccount(publicKey) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
return await this.server.loadAccount(publicKey);
}
catch (error) {
throw new XDBChainError(`Failed to load account: ${error}`);
}
}
async createAccount(source, destination, startingBalance = this.config.startingBalance) {
try {
if (!this.isValidSecretKey(source)) {
throw new XDBValidationError('Invalid source secret key');
}
if (!this.isValidAddress(destination)) {
throw new XDBValidationError('Invalid destination address');
}
const sourceKeypair = Keypair.fromSecret(source);
const sourceAccount = await this.server.loadAccount(sourceKeypair.publicKey());
const transaction = new TransactionBuilder(sourceAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.createAccount({
destination: destination,
startingBalance: startingBalance
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(sourceKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Account created successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to create account: ${error}`);
}
}
async setAccountOptions(secretKey, options) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const operation = {};
if (options.homeDomain !== undefined)
operation.homeDomain = options.homeDomain;
if (options.inflationDest !== undefined)
operation.inflationDest = options.inflationDest;
if (options.masterWeight !== undefined)
operation.masterWeight = options.masterWeight;
if (options.lowThreshold !== undefined)
operation.lowThreshold = options.lowThreshold;
if (options.medThreshold !== undefined)
operation.medThreshold = options.medThreshold;
if (options.highThreshold !== undefined)
operation.highThreshold = options.highThreshold;
if (options.setFlags !== undefined)
operation.setFlags = options.setFlags;
if (options.clearFlags !== undefined)
operation.clearFlags = options.clearFlags;
if (options.signer !== undefined)
operation.signer = options.signer;
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.setOptions(operation))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Account options set successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to set account options: ${error}`);
}
}
async mergeAccount(sourceSecret, destination) {
try {
if (!this.isValidSecretKey(sourceSecret)) {
throw new XDBValidationError('Invalid source secret key');
}
if (!this.isValidAddress(destination)) {
throw new XDBValidationError('Invalid destination address');
}
const sourceKeypair = Keypair.fromSecret(sourceSecret);
const sourceAccount = await this.server.loadAccount(sourceKeypair.publicKey());
const transaction = new TransactionBuilder(sourceAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.accountMerge({
destination: destination
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(sourceKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Account merged successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to merge account: ${error}`);
}
}
async bumpSequence(secretKey, bumpTo) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.bumpSequence({
bumpTo: bumpTo
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Sequence bumped successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to bump sequence: ${error}`);
}
}
async getBalances(publicKey) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const account = await this.server.loadAccount(publicKey);
return account.balances.map((balance) => ({
assetType: balance.asset_type,
assetCode: balance.asset_code || 'XDB',
assetIssuer: balance.asset_issuer || null,
balance: balance.balance,
limit: balance.limit || null,
buyingLiabilities: balance.buying_liabilities,
sellingLiabilities: balance.selling_liabilities,
lastModifiedLedger: balance.last_modified_ledger,
isAuthorized: balance.is_authorized,
isAuthorizedToMaintainLiabilities: balance.is_authorized_to_maintain_liabilities
}));
}
catch (error) {
throw new XDBChainError(`Failed to get balances: ${error}`);
}
}
async getXDBBalance(publicKey) {
const balances = await this.getBalances(publicKey);
const xdbBalance = balances.find(b => b.assetCode === 'XDB');
return xdbBalance?.balance || '0';
}
watchBalances(publicKey, callback) {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const accountCallBuilder = this.server.accounts();
const closeHandler = accountCallBuilder
.accountId(publicKey)
.stream({
onmessage: (account) => {
const balances = account.balances.map((balance) => ({
assetType: balance.asset_type,
assetCode: balance.asset_code || 'XDB',
assetIssuer: balance.asset_issuer || null,
balance: balance.balance,
limit: balance.limit || null,
buyingLiabilities: balance.buying_liabilities,
sellingLiabilities: balance.selling_liabilities,
lastModifiedLedger: balance.last_modified_ledger,
isAuthorized: balance.is_authorized,
isAuthorizedToMaintainLiabilities: balance.is_authorized_to_maintain_liabilities
}));
callback(balances);
},
onerror: (error) => {
console.error('Balance stream error:', error);
}
});
return closeHandler;
}
async sendPayment(senderSecret, options) {
try {
if (!this.isValidSecretKey(senderSecret)) {
throw new XDBValidationError('Invalid sender secret key');
}
if (!this.isValidAddress(options.destination)) {
throw new XDBValidationError('Invalid destination address');
}
const senderKeypair = Keypair.fromSecret(senderSecret);
const senderAccount = await this.server.loadAccount(senderKeypair.publicKey());
const asset = this.createAsset(options.asset);
const transactionBuilder = new TransactionBuilder(senderAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
});
transactionBuilder.addOperation(Operation.payment({
destination: options.destination,
asset: asset,
amount: options.amount,
source: options.source
}));
if (options.memo) {
const memo = this.createMemo(options.memo, options.memoType);
transactionBuilder.addMemo(memo);
}
const transaction = transactionBuilder.setTimeout(this.config.timeout).build();
transaction.sign(senderKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Payment sent successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Payment failed: ${error}`);
}
}
async sendPathPaymentStrictReceive(senderSecret, options) {
try {
if (!this.isValidSecretKey(senderSecret)) {
throw new XDBValidationError('Invalid sender secret key');
}
if (!this.isValidAddress(options.destination)) {
throw new XDBValidationError('Invalid destination address');
}
const senderKeypair = Keypair.fromSecret(senderSecret);
const senderAccount = await this.server.loadAccount(senderKeypair.publicKey());
const sendAsset = this.createAsset(options.sendAsset);
const destAsset = this.createAsset(options.destAsset);
const path = options.path ? options.path.map(asset => this.createAsset(asset)) : [];
const transactionBuilder = new TransactionBuilder(senderAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
});
transactionBuilder.addOperation(Operation.pathPaymentStrictReceive({
sendAsset: sendAsset,
sendMax: options.sendMax,
destination: options.destination,
destAsset: destAsset,
destAmount: options.destAmount,
path: path,
source: options.source
}));
if (options.memo) {
const memo = this.createMemo(options.memo, options.memoType);
transactionBuilder.addMemo(memo);
}
const transaction = transactionBuilder.setTimeout(this.config.timeout).build();
transaction.sign(senderKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Path payment sent successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Path payment failed: ${error}`);
}
}
async sendPathPaymentStrictSend(senderSecret, options) {
try {
if (!this.isValidSecretKey(senderSecret)) {
throw new XDBValidationError('Invalid sender secret key');
}
if (!this.isValidAddress(options.destination)) {
throw new XDBValidationError('Invalid destination address');
}
const senderKeypair = Keypair.fromSecret(senderSecret);
const senderAccount = await this.server.loadAccount(senderKeypair.publicKey());
const sendAsset = this.createAsset(options.sendAsset);
const destAsset = this.createAsset(options.destAsset);
const path = options.path ? options.path.map(asset => this.createAsset(asset)) : [];
const transactionBuilder = new TransactionBuilder(senderAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
});
transactionBuilder.addOperation(Operation.pathPaymentStrictSend({
sendAsset: sendAsset,
sendAmount: options.sendMax,
destination: options.destination,
destAsset: destAsset,
destMin: options.destAmount,
path: path,
source: options.source
}));
if (options.memo) {
const memo = this.createMemo(options.memo, options.memoType);
transactionBuilder.addMemo(memo);
}
const transaction = transactionBuilder.setTimeout(this.config.timeout).build();
transaction.sign(senderKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Path payment (strict send) sent successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Path payment (strict send) failed: ${error}`);
}
}
async sendBatchPayments(senderSecret, payments) {
try {
if (!this.isValidSecretKey(senderSecret)) {
throw new XDBValidationError('Invalid sender secret key');
}
if (!payments.length) {
throw new XDBValidationError('No payments provided');
}
const senderKeypair = Keypair.fromSecret(senderSecret);
const senderAccount = await this.server.loadAccount(senderKeypair.publicKey());
const transactionBuilder = new TransactionBuilder(senderAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
});
for (const payment of payments) {
if (!this.isValidAddress(payment.destination)) {
throw new XDBValidationError(`Invalid destination address: ${payment.destination}`);
}
const asset = this.createAsset(payment.asset);
transactionBuilder.addOperation(Operation.payment({
destination: payment.destination,
asset: asset,
amount: payment.amount,
source: payment.source
}));
}
const transaction = transactionBuilder.setTimeout(this.config.timeout).build();
transaction.sign(senderKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Batch payments sent successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Batch payment failed: ${error}`);
}
}
async addTrustline(walletSecret, options) {
try {
if (!this.isValidSecretKey(walletSecret)) {
throw new XDBValidationError('Invalid wallet secret key');
}
const keypair = Keypair.fromSecret(walletSecret);
const account = await this.server.loadAccount(keypair.publicKey());
const asset = new Asset(options.assetCode, options.assetIssuer);
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.changeTrust({
asset: asset,
limit: options.limit,
source: options.source
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Trustline added successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to add trustline: ${error}`);
}
}
async removeTrustline(walletSecret, options) {
return this.addTrustline(walletSecret, { ...options, limit: '0' });
}
async getTrustlines(publicKey) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const account = await this.server.loadAccount(publicKey);
return account.balances.filter((balance) => balance.asset_type !== 'native');
}
catch (error) {
throw new XDBChainError(`Failed to get trustlines: ${error}`);
}
}
async allowTrust(trustorSecret, trusteePublicKey, assetCode, authorize) {
try {
if (!this.isValidSecretKey(trustorSecret)) {
throw new XDBValidationError('Invalid trustor secret key');
}
if (!this.isValidAddress(trusteePublicKey)) {
throw new XDBValidationError('Invalid trustee public key');
}
const trustorKeypair = Keypair.fromSecret(trustorSecret);
const trustorAccount = await this.server.loadAccount(trustorKeypair.publicKey());
const transaction = new TransactionBuilder(trustorAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.allowTrust({
trustor: trusteePublicKey,
assetCode: assetCode,
authorize: authorize
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(trustorKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Trust authorization updated successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to update trust authorization: ${error}`);
}
}
createAssetObject(code, issuer) {
if (code === 'XDB' || code === 'native') {
const nativeAsset = Asset.native();
nativeAsset.code = 'XDB';
return nativeAsset;
}
if (!issuer) {
throw new XDBAssetError('Asset issuer is required for non-native assets');
}
return new Asset(code, issuer);
}
async issueAsset(issuerSecret, distributorPublicKey, assetCode, amount) {
try {
if (!this.isValidSecretKey(issuerSecret)) {
throw new XDBValidationError('Invalid issuer secret key');
}
if (!this.isValidAddress(distributorPublicKey)) {
throw new XDBValidationError('Invalid distributor public key');
}
const issuerKeypair = Keypair.fromSecret(issuerSecret);
const issuerAccount = await this.server.loadAccount(issuerKeypair.publicKey());
const asset = new Asset(assetCode, issuerKeypair.publicKey());
const transaction = new TransactionBuilder(issuerAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.payment({
destination: distributorPublicKey,
asset: asset,
amount: amount
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(issuerKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Asset issued successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to issue asset: ${error}`);
}
}
async createSellOffer(secretKey, options) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const selling = this.createAsset(options.selling);
const buying = this.createAsset(options.buying);
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.manageSellOffer({
selling: selling,
buying: buying,
amount: options.amount,
price: options.price,
offerId: options.offerId || '0',
source: options.source
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Sell offer created successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBOfferError(`Failed to create sell offer: ${error}`);
}
}
async createBuyOffer(secretKey, options) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const selling = this.createAsset(options.selling);
const buying = this.createAsset(options.buying);
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.manageBuyOffer({
selling: selling,
buying: buying,
buyAmount: options.amount,
price: options.price,
offerId: options.offerId || '0',
source: options.source
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Buy offer created successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBOfferError(`Failed to create buy offer: ${error}`);
}
}
async createPassiveSellOffer(secretKey, options) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const selling = this.createAsset(options.selling);
const buying = this.createAsset(options.buying);
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.createPassiveSellOffer({
selling: selling,
buying: buying,
amount: options.amount,
price: options.price,
source: options.source
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Passive sell offer created successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBOfferError(`Failed to create passive sell offer: ${error}`);
}
}
async cancelOffer(secretKey, offerId, selling, buying) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const sellingAsset = this.createAsset(selling);
const buyingAsset = this.createAsset(buying);
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.manageSellOffer({
selling: sellingAsset,
buying: buyingAsset,
amount: '0',
price: '1',
offerId: offerId
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Offer cancelled successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBOfferError(`Failed to cancel offer: ${error}`);
}
}
async manageData(secretKey, options) {
try {
if (!this.isValidSecretKey(secretKey)) {
throw new XDBValidationError('Invalid secret key');
}
const keypair = Keypair.fromSecret(secretKey);
const account = await this.server.loadAccount(keypair.publicKey());
const transaction = new TransactionBuilder(account, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.manageData({
name: options.name,
value: options.value,
source: options.source
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(keypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Data managed successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to manage data: ${error}`);
}
}
async getTransactionHistory(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor, includeFailed = false } = options;
let transactionBuilder = this.server
.transactions()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
transactionBuilder = transactionBuilder.cursor(cursor);
}
if (includeFailed) {
transactionBuilder = transactionBuilder.includeFailed(true);
}
const result = await transactionBuilder.call();
return result.records.map((tx) => ({
hash: tx.hash,
ledger: tx.ledger_attr,
createdAt: tx.created_at,
fee: tx.fee_charged,
operationCount: tx.operation_count,
memo: tx.memo,
memoType: tx.memo_type,
successful: tx.successful,
pagingToken: tx.paging_token,
sourceAccount: tx.source_account,
sourceAccountSequence: tx.source_account_sequence,
feeAccount: tx.fee_account,
feeCharged: tx.fee_charged,
maxFee: tx.max_fee,
timeBounds: tx.time_bounds
}));
}
catch (error) {
throw new XDBChainError(`Failed to get transaction history: ${error}`);
}
}
async getTransactionsForLedger(ledgerSequence, options = {}) {
try {
const { limit = 10, order = 'desc', cursor, includeFailed = false } = options;
let transactionBuilder = this.server
.transactions()
.forLedger(ledgerSequence)
.order(order)
.limit(limit);
if (cursor) {
transactionBuilder = transactionBuilder.cursor(cursor);
}
if (includeFailed) {
transactionBuilder = transactionBuilder.includeFailed(true);
}
const result = await transactionBuilder.call();
return result.records.map((tx) => ({
hash: tx.hash,
ledger: tx.ledger_attr,
createdAt: tx.created_at,
fee: tx.fee_charged,
operationCount: tx.operation_count,
memo: tx.memo,
memoType: tx.memo_type,
successful: tx.successful,
pagingToken: tx.paging_token,
sourceAccount: tx.source_account,
sourceAccountSequence: tx.source_account_sequence,
feeAccount: tx.fee_account,
feeCharged: tx.fee_charged,
maxFee: tx.max_fee,
timeBounds: tx.time_bounds
}));
}
catch (error) {
throw new XDBChainError(`Failed to get transactions for ledger: ${error}`);
}
}
watchTransactions(publicKey, callback) {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const closeHandler = this.server
.transactions()
.forAccount(publicKey)
.cursor('now')
.stream({
onmessage: (transaction) => {
const xdbTransaction = {
hash: transaction.hash,
ledger: transaction.ledger_attr,
createdAt: transaction.created_at,
fee: transaction.fee_charged,
operationCount: transaction.operation_count,
memo: transaction.memo,
memoType: transaction.memo_type,
successful: transaction.successful,
pagingToken: transaction.paging_token,
sourceAccount: transaction.source_account,
sourceAccountSequence: transaction.source_account_sequence,
feeAccount: transaction.fee_account,
feeCharged: transaction.fee_charged,
maxFee: transaction.max_fee,
timeBounds: transaction.time_bounds
};
callback(xdbTransaction);
},
onerror: (error) => {
console.error('Transaction stream error:', error);
}
});
return closeHandler;
}
async getOperations(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor, includeFailed = false } = options;
let operationBuilder = this.server
.operations()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
operationBuilder = operationBuilder.cursor(cursor);
}
if (includeFailed) {
operationBuilder = operationBuilder.includeFailed(true);
}
const result = await operationBuilder.call();
return result.records;
}
catch (error) {
throw new XDBChainError(`Failed to get operations: ${error}`);
}
}
async getOperationsForLedger(ledgerSequence, options = {}) {
try {
const { limit = 10, order = 'desc', cursor, includeFailed = false } = options;
let operationBuilder = this.server
.operations()
.forLedger(ledgerSequence)
.order(order)
.limit(limit);
if (cursor) {
operationBuilder = operationBuilder.cursor(cursor);
}
if (includeFailed) {
operationBuilder = operationBuilder.includeFailed(true);
}
const result = await operationBuilder.call();
return result.records;
}
catch (error) {
throw new XDBChainError(`Failed to get operations for ledger: ${error}`);
}
}
async getOperationsForTransaction(transactionHash, options = {}) {
try {
const { limit = 10, order = 'desc', cursor } = options;
let operationBuilder = this.server
.operations()
.forTransaction(transactionHash)
.order(order)
.limit(limit);
if (cursor) {
operationBuilder = operationBuilder.cursor(cursor);
}
const result = await operationBuilder.call();
return result.records;
}
catch (error) {
throw new XDBChainError(`Failed to get operations for transaction: ${error}`);
}
}
async getPayments(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor, includeFailed = false } = options;
let paymentBuilder = this.server
.payments()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
paymentBuilder = paymentBuilder.cursor(cursor);
}
if (includeFailed) {
paymentBuilder = paymentBuilder.includeFailed(true);
}
const result = await paymentBuilder.call();
return result.records.map((payment) => ({
id: payment.id,
pagingToken: payment.paging_token,
transactionSuccessful: payment.transaction_successful,
sourceAccount: payment.source_account,
type: payment.type,
createdAt: payment.created_at,
transactionHash: payment.transaction_hash,
assetType: payment.asset_type,
assetCode: payment.asset_code,
assetIssuer: payment.asset_issuer,
from: payment.from,
to: payment.to,
amount: payment.amount,
memo: payment.memo,
memoType: payment.memo_type
}));
}
catch (error) {
throw new XDBChainError(`Failed to get payments: ${error}`);
}
}
watchPayments(publicKey, callback) {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const closeHandler = this.server
.payments()
.forAccount(publicKey)
.cursor('now')
.stream({
onmessage: (payment) => {
const xdbPayment = {
id: payment.id,
pagingToken: payment.paging_token,
transactionSuccessful: payment.transaction_successful,
sourceAccount: payment.source_account,
type: payment.type,
createdAt: payment.created_at,
transactionHash: payment.transaction_hash,
assetType: payment.asset_type,
assetCode: payment.asset_code,
assetIssuer: payment.asset_issuer,
from: payment.from,
to: payment.to,
amount: payment.amount,
memo: payment.memo,
memoType: payment.memo_type
};
callback(xdbPayment);
},
onerror: (error) => {
console.error('Payment stream error:', error);
}
});
return closeHandler;
}
async getEffects(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor } = options;
let effectBuilder = this.server
.effects()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
effectBuilder = effectBuilder.cursor(cursor);
}
const result = await effectBuilder.call();
return result.records.map((effect) => ({
id: effect.id,
type: effect.type,
account: effect.account,
createdAt: effect.created_at,
operationId: effect.operation_id,
details: effect
}));
}
catch (error) {
throw new XDBChainError(`Failed to get effects: ${error}`);
}
}
async getEffectsForOperation(operationId, options = {}) {
try {
const { limit = 10, order = 'desc', cursor } = options;
let effectBuilder = this.server
.effects()
.forOperation(operationId)
.order(order)
.limit(limit);
if (cursor) {
effectBuilder = effectBuilder.cursor(cursor);
}
const result = await effectBuilder.call();
return result.records.map((effect) => ({
id: effect.id,
type: effect.type,
account: effect.account,
createdAt: effect.created_at,
operationId: effect.operation_id,
details: effect
}));
}
catch (error) {
throw new XDBChainError(`Failed to get effects for operation: ${error}`);
}
}
async getTrades(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor } = options;
let tradeBuilder = this.server
.trades()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
tradeBuilder = tradeBuilder.cursor(cursor);
}
const result = await tradeBuilder.call();
return result.records.map((trade) => ({
id: trade.id,
pagingToken: trade.paging_token,
ledgerCloseTime: trade.ledger_close_time,
offerIds: trade.offer_ids,
baseAccount: trade.base_account,
baseAmount: trade.base_amount,
baseAssetType: trade.base_asset_type,
baseAssetCode: trade.base_asset_code,
baseAssetIssuer: trade.base_asset_issuer,
counterAccount: trade.counter_account,
counterAmount: trade.counter_amount,
counterAssetType: trade.counter_asset_type,
counterAssetCode: trade.counter_asset_code,
counterAssetIssuer: trade.counter_asset_issuer,
baseIsSeller: trade.base_is_seller,
price: trade.price
}));
}
catch (error) {
throw new XDBChainError(`Failed to get trades: ${error}`);
}
}
async getTradesForAssetPair(baseAsset, counterAsset, options = {}) {
try {
const { limit = 10, order = 'desc', cursor } = options;
const base = this.createAsset(baseAsset);
const counter = this.createAsset(counterAsset);
let tradeBuilder = this.server
.trades()
.forAssetPair(base, counter)
.order(order)
.limit(limit);
if (cursor) {
tradeBuilder = tradeBuilder.cursor(cursor);
}
const result = await tradeBuilder.call();
return result.records.map((trade) => ({
id: trade.id,
pagingToken: trade.paging_token,
ledgerCloseTime: trade.ledger_close_time,
offerIds: trade.offer_ids,
baseAccount: trade.base_account,
baseAmount: trade.base_amount,
baseAssetType: trade.base_asset_type,
baseAssetCode: trade.base_asset_code,
baseAssetIssuer: trade.base_asset_issuer,
counterAccount: trade.counter_account,
counterAmount: trade.counter_amount,
counterAssetType: trade.counter_asset_type,
counterAssetCode: trade.counter_asset_code,
counterAssetIssuer: trade.counter_asset_issuer,
baseIsSeller: trade.base_is_seller,
price: trade.price
}));
}
catch (error) {
throw new XDBChainError(`Failed to get trades for asset pair: ${error}`);
}
}
async getOrderbook(selling, buying, limit = 10) {
try {
const sellingAsset = this.createAsset(selling);
const buyingAsset = this.createAsset(buying);
const result = await this.server
.orderbook(sellingAsset, buyingAsset)
.limit(limit)
.call();
return {
bids: result.bids.map((bid) => ({
price: bid.price,
amount: bid.amount
})),
asks: result.asks.map((ask) => ({
price: ask.price,
amount: ask.amount
})),
base: selling,
counter: buying
};
}
catch (error) {
throw new XDBChainError(`Failed to get orderbook: ${error}`);
}
}
async getOffers(publicKey, options = {}) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const { limit = 10, order = 'desc', cursor } = options;
let offerBuilder = this.server
.offers()
.forAccount(publicKey)
.order(order)
.limit(limit);
if (cursor) {
offerBuilder = offerBuilder.cursor(cursor);
}
const result = await offerBuilder.call();
return result.records.map((offer) => ({
id: offer.id,
pagingToken: offer.paging_token,
seller: offer.seller,
selling: {
code: offer.selling.asset_code || 'XDB',
issuer: offer.selling.asset_issuer,
type: offer.selling.asset_type
},
buying: {
code: offer.buying.asset_code || 'XDB',
issuer: offer.buying.asset_issuer,
type: offer.buying.asset_type
},
amount: offer.amount,
price: offer.price,
lastModifiedLedger: offer.last_modified_ledger,
lastModifiedTime: offer.last_modified_time
}));
}
catch (error) {
throw new XDBChainError(`Failed to get offers: ${error}`);
}
}
async getLedger(sequence) {
try {
const result = await this.server.ledgers().ledger(sequence).call();
return {
id: result.id,
pagingToken: result.paging_token,
hash: result.hash,
sequence: result.sequence,
transactionCount: result.successful_transaction_count || result.transaction_count || 0,
operationCount: result.operation_count,
closedAt: result.closed_at,
totalCoins: result.total_coins,
feePool: result.fee_pool,
baseFee: result.base_fee_in_stroops || result.base_fee || 100,
baseReserve: result.base_reserve_in_stroops || result.base_reserve || '0',
maxTxSetSize: result.max_tx_set_size,
protocolVersion: result.protocol_version,
headerXdr: result.header_xdr
};
}
catch (error) {
throw new XDBChainError(`Failed to get ledger: ${error}`);
}
}
async getLedgers(options = {}) {
try {
const { limit = 10, order = 'desc', cursor } = options;
let ledgerBuilder = this.server
.ledgers()
.order(order)
.limit(limit);
if (cursor) {
ledgerBuilder = ledgerBuilder.cursor(cursor);
}
const result = await ledgerBuilder.call();
return result.records.map((ledger) => ({
id: ledger.id,
pagingToken: ledger.paging_token,
hash: ledger.hash,
sequence: ledger.sequence,
transactionCount: ledger.successful_transaction_count || 0,
operationCount: ledger.operation_count,
closedAt: ledger.closed_at,
totalCoins: ledger.total_coins,
feePool: ledger.fee_pool,
baseFee: ledger.base_fee_in_stroops,
baseReserve: ledger.base_reserve_in_stroops,
maxTxSetSize: ledger.max_tx_set_size,
protocolVersion: ledger.protocol_version,
headerXdr: ledger.header_xdr
}));
}
catch (error) {
throw new XDBChainError(`Failed to get ledgers: ${error}`);
}
}
async createClaimableBalance(senderSecret, claimantPublicKey, amount, asset) {
try {
if (!this.isValidSecretKey(senderSecret)) {
throw new XDBValidationError('Invalid sender secret key');
}
if (!this.isValidAddress(claimantPublicKey)) {
throw new XDBValidationError('Invalid claimant public key');
}
const senderKeypair = Keypair.fromSecret(senderSecret);
const senderAccount = await this.server.loadAccount(senderKeypair.publicKey());
const assetObj = this.createAsset(asset);
const claimant = new Claimant(claimantPublicKey);
const transaction = new TransactionBuilder(senderAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.createClaimableBalance({
asset: assetObj,
amount: amount,
claimants: [claimant]
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(senderKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Claimable balance created successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to create claimable balance: ${error}`);
}
}
async claimBalance(claimantSecret, balanceId) {
try {
if (!this.isValidSecretKey(claimantSecret)) {
throw new XDBValidationError('Invalid claimant secret key');
}
const claimantKeypair = Keypair.fromSecret(claimantSecret);
const claimantAccount = await this.server.loadAccount(claimantKeypair.publicKey());
const transaction = new TransactionBuilder(claimantAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
})
.addOperation(Operation.claimClaimableBalance({
balanceId: balanceId
}))
.setTimeout(this.config.timeout)
.build();
transaction.sign(claimantKeypair);
const result = await this.server.submitTransaction(transaction);
console.log('✅ Balance claimed successfully:', result.hash);
return result;
}
catch (error) {
throw new XDBTransactionError(`Failed to claim balance: ${error}`);
}
}
async getClaimableBalances(publicKey) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const result = await this.server.claimableBalances().claimant(publicKey).call();
return result.records.map((cb) => ({
id: cb.id,
assetCode: cb.asset === 'native' ? 'XDB' : cb.asset.split(':')[0],
assetIssuer: cb.asset === 'native' ? null : cb.asset.split(':')[1],
amount: cb.amount,
claimants: cb.claimants.map((claimant) => ({
destination: claimant.destination,
predicate: claimant.predicate
})),
sponsor: cb.sponsor,
flags: cb.flags,
lastModifiedLedger: cb.last_modified_ledger,
lastModifiedTime: cb.last_modified_time
}));
}
catch (error) {
throw new XDBChainError(`Failed to get claimable balances: ${error}`);
}
}
async getNetworkInfo() {
try {
const response = await this.server.ledgers().order('desc').limit(1).call();
return response;
}
catch (error) {
throw new XDBNetworkError(`Failed to get network info: ${error}`);
}
}
async getBaseFee() {
try {
return await this.server.fetchBaseFee();
}
catch (error) {
throw new XDBNetworkError(`Failed to get base fee: ${error}`);
}
}
async getFeeStats() {
try {
return await this.server.feeStats();
}
catch (error) {
throw new XDBNetworkError(`Failed to get fee stats: ${error}`);
}
}
async fundTestnetAccount(publicKey) {
try {
if (!this.isValidAddress(publicKey)) {
throw new XDBValidationError('Invalid public key');
}
const response = await fetch(`https://friendbot.futurenet.xdbchain.com?addr=${encodeURIComponent(publicKey)}`);
if (response.ok) {
console.log('✅ Futurenet account funded successfully');
return true;
}
else {
throw new Error(`Funding failed with status: ${response.status}`);
}
}
catch (error) {
throw new XDBNetworkError(`Failed to fund testnet account: ${error}`);
}
}
async estimateFee(operationCount = 1) {
try {
const baseFee = await this.getBaseFee();
return baseFee * operationCount;
}
catch (error) {
throw new XDBNetworkError(`Failed to estimate fee: ${error}`);
}
}
async getPaymentPaths(sourcePublicKey, destination, amount) {
try {
const destinationAsset = this.createAsset(destination);
return await this.server
.strictReceivePaths(sourcePublicKey, destinationAsset, amount)
.call();
}
catch (error) {
throw new XDBChainError(`Failed to get payment paths: ${error}`);
}
}
async getStrictReceivePaths(sourceAccount, destination, amount) {
try {
const destinationAsset = this.createAsset(destination);
return await this.server
.strictReceivePaths(sourceAccount, destinationAsset, amount)
.call();
}
catch (error) {
throw new XDBChainError(`Failed to get strict receive paths: ${error}`);
}
}
async getStrictSendPaths(sourceAsset, destinationAssets, amount) {
try {
const source = this.createAsset(sourceAsset);
const destinations = destinationAssets.map(asset => this.createAsset(asset));
return await this.server
.strictSendPaths(source, amount, destinations)
.call();
}
catch (error) {
throw new XDBChainError(`Failed to get strict send paths: ${error}`);
}
}
signTransaction(transaction, signers) {
try {
for (const signer of signers) {
transaction.sign(signer);
}
return transaction;
}
catch (error) {
throw new XDBTransactionError(`Failed to sign transaction: ${error}`);
}
}
async createMultiSigTransaction(sourceAccount, operations, signers) {
try {
const transactionBuilder = new TransactionBuilder(sourceAccount, {
fee: (await this.server.fetchBaseFee()).toString(),
networkPassphrase: this.networkPassphrase
});
for (const operation of operations) {
transactionBuilder.addOperation(operation);
}
const transaction = transactionBuilder.setTimeout(this.config.timeout).build();
return this.signTransaction(transaction, signers);
}
catch (error) {
throw new XDBTransactionError(`Failed to create multi-sig transaction: ${error}`);
}
}
createAsset(asset) {
if (!asset || asset.code === 'XDB' || asset.code === 'native') {
return Asset.native();
}
if (!asset.issuer) {
throw new XDBValidationError('Asset issuer is required for non-native assets');
}
return new Asset(asset.code, asset.issuer);
}
createMemo(memo, memoType = 'text') {
switch (memoType) {
case 'text':
return Memo.text(memo);
case 'id':
return Memo.id(memo);
case 'hash':
return Memo.hash(memo);
case 'return':
return Memo.return(memo);
default:
return Memo.text(memo);
}
}
getServer() {
return this.server;
}
getNetworkPassphrase() {
return this.networkPassphrase;
}
getConfig() {
return { ...this.config };
}
async submitTransaction(transaction) {
try {
return await this.server.submitTransaction(transaction);
}
catch (error) {
throw new XDBTransactionError(`Failed to submit transaction: ${error}`);
}
}
fromXDR(xdr) {
try {
return TransactionBuilder.fromXDR(xdr, this.networkPassphrase);
}
catch (error) {
throw new XDBValidationError(`Failed to parse XDR: ${error}`);
}
}
}
XDBChainSDK.DEFAULT_CONFIGS = {
mainnet: {
network: 'mainnet',
horizonUrl: 'https://horizon.livenet.xdbchain.com/',
networkPassphrase: 'LiveNet Global XDBChain Network ; November 2023',
startingBalance: '30',
timeout: 30000,
allowHttp: false
},
futurenet: {
network: 'futurenet',
horizonUrl: 'https://horizon.futurenet.xdbchain.com/',
networkPassphrase: 'Future XDBChain Network ; November 2023',
startingBalance: '30',
timeout: 30000,
allowHttp: false
}
};
export function createXDBChainSDK(config) {
return new XDBChainSDK(config);
}
export function createMainnetSDK() {
return new XDBChainSDK({ network: 'mainnet' });
}
export function createFuturenetSDK() {
return new XDBChainSDK({ network: 'futurenet' });
}
export function xdbToStroops(amount) {
return (parseFloat(amount.toString()) * 10000000).toFixed(0);
}
export function stroopsToXdb(stroops) {
return (parseFloat(stroops.toString()) / 10000000).toFixed(7);
}
export function isValidAmount(amount) {
const num = parseFloat(amount);
return !isNaN(num) && num > 0 && num <= 922337203685.4775807;
}
export function formatAmount(amount, decimals = 7) {
const num = parseFloat(amount);
return num.toFixed(decimals);
}
export function generateNonce() {
return Math.random().toString(36).substring(2, 15);
}
export function isValidMemo(memo, type = 'text') {
if (type === 'text') {
return memo.length <= 28;
}
return true;
}
export function calculateFee(operationCount, baseFee) {
return operationCount * baseFee;
}
export async function accountExists(server, publicKey) {
try {
await server.loadAccount(publicKey);
return true;
}
catch {
return false;
}
}
export default XDBChainSDK;
//# sourceMappingURL=index.js.map