@dolaned/wallet-sdk-ts
Version:
Wallet SDK for the Nexa blockchain
146 lines (123 loc) • 6 kB
text/typescript
import {PrivateKey, TransactionBuilder} from "libnexa-ts";
import {rostrumProvider} from "../../network/RostrumProvider";
import {
buildCreateGroupTransaction,
populateAndDuplicateTokenAuths,
populateNexaInputsAndChange,
populateTokenAuth,
populateTokenInputsAndChange,
prepareDeleteTransaction
} from "../../utils/TXUtils";
import {BaseAccount} from "../accounts/interfaces/BaseAccountInterface";
import {AddressKey} from "../../models/wallet.entities";
import {TransactionCreator} from "./interfaces/TransactionCreator";
import {keys} from "lodash-es";
import {PermissionLabel} from "../../models/transaction.entities";
export default class WalletTransactionCreator extends TransactionCreator {
private _keysToSign: PrivateKey[] = [];
private _account!: BaseAccount;
constructor(fromAccount: BaseAccount, tx?: TransactionBuilder | string | Buffer) {
super(tx)
this._account = fromAccount
this.validateAccount()
}
public fromAccount(fromAccount: BaseAccount): this {
this._account = fromAccount
return this
}
public parseTxHex(tx: string) {
this.builder.push(async () => {
const txBuilder = new TransactionBuilder(tx)
const newTxBuilder = new TransactionBuilder()
const oldInputs = txBuilder.transaction.inputs
for (const input of oldInputs) {
const utxo = await rostrumProvider.getUtxo(input.outpoint.toString('hex'))
newTxBuilder.from({
outpoint: utxo.tx_hash,
amount: utxo.amount,
scriptPubKey: utxo.scriptpubkey
})
const foundKey = this.findPrivateKeyFromAddress(utxo.addresses[0])
if(foundKey) {
this._keysToSign.push(foundKey.key.privateKey)
}
}
newTxBuilder.transaction.outputs = txBuilder.transaction.outputs
})
return this
}
public parseTxBuffer(tx: Buffer) {
this.builder.push(async () => {
this.transactionBuilder = new TransactionBuilder(tx);
})
return this
}
public mint(token: string, amount: string): this {
this.builder.push(async() => {
let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
this.tokenAction(toAddr, amount, token, 'mint')
})
return this
}
public melt(token: string, amount: string): this {
this.builder.push(async() => {
let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
this.tokenAction(toAddr, amount, token, 'melt')
})
return this
}
public populate(): this {
this.validateAccount();
this.builder.push(async () => {
let tK: PrivateKey[] = []
let nK: PrivateKey[] = []
if(this.tokens.size > 0) {
for (const tokenAction of this.tokens) {
if(tokenAction.action == 'mint' || tokenAction.action == 'melt') {
tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.action))
} else if (tokenAction.action == 'group') {
tK = tK.concat(await buildCreateGroupTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData?.opReturnData!, this.network))
} else if (tokenAction.action == 'subgroup') {
tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.parentToken!, 'subgroup', tokenAction.token, this._account.accountKeys.receiveKeys.at(-1)!.address));
} else if(tokenAction.action == 'renew') {
tK = tK.concat(await populateAndDuplicateTokenAuths(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.extraData!.perms!, tokenAction.extraData!.address!))
} else if(tokenAction.action == 'delete') {
tK = tK.concat(await prepareDeleteTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData!.outpoint!))
} else {
tK = tK.concat(await populateTokenInputsAndChange(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.amount))
}
this._keysToSign!.concat(tK)
}
}
nK = nK.concat(await populateNexaInputsAndChange(this.transactionBuilder, this._account.accountKeys, this.totalValue, this.txOptions))
this._keysToSign! = tK.concat(nK)
})
return this
}
public sign(): this {
this.builder.push(async () => {
const blockHeight = await rostrumProvider.getBlockTip()
this.transactionBuilder.lockUntilBlockHeight(blockHeight.height).sign(this._keysToSign!)
})
return this
}
/**
* Validates that the account has the necessary keys before performing operations
* @throws Error if account or keys are not properly initialized
*/
private validateAccount(): void {
if (!this._account) {
throw new Error('Account must be set before performing transactions');
}
if (!this._account.accountKeys) {
throw new Error('Account keys are not initialized');
}
if (!this._account.accountKeys.receiveKeys || this._account.accountKeys.receiveKeys.length === 0) {
throw new Error('No receive keys available in account');
}
}
private findPrivateKeyFromAddress(addr: string): AddressKey | undefined {
const keys: AddressKey[] = this._account.accountKeys.receiveKeys.concat(this._account.accountKeys.changeKeys)
return keys.find(key => key.address === addr)
}
}