Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
'PX-',
'QA-',
'RA-',
'SI-',
'SO-',
'SC-',
'ST-',
Expand Down
104 changes: 104 additions & 0 deletions modules/sdk-coin-hbar/src/lib/accountUpdateBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import Long from 'long';
import { proto } from '@hashgraph/proto';
import { BaseKey, BuildTransactionError, SigningError, TransactionType } from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import { TransactionBuilder } from './transactionBuilder';
import { buildHederaAccountID, isValidAddress, stringifyAccountId } from './utils';
import { DEFAULT_SIGNER_NUMBER } from './constants';

export class AccountUpdateBuilder extends TransactionBuilder {
private readonly _txBodyData: proto.CryptoUpdateTransactionBody;
private _accountId: string;
private _stakedNodeId?: Long;
private _declineStakingReward?: boolean;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._txBodyData = new proto.CryptoUpdateTransactionBody();
this._txBody.cryptoUpdateAccount = this._txBodyData;
}

/** @inheritdoc */
initBuilder(tx: Transaction): void {
super.initBuilder(tx);
const updateBody = tx.txBody.cryptoUpdateAccount;
if (updateBody) {
if (updateBody.accountIDToUpdate) {
this._accountId = stringifyAccountId(updateBody.accountIDToUpdate);
}
if (updateBody.stakedNodeId != null) {
this._stakedNodeId = Long.fromValue(updateBody.stakedNodeId);
}
if (updateBody.declineReward != null) {
const raw = updateBody.declineReward;
this._declineStakingReward = typeof raw === 'boolean' ? raw : (raw as { value: boolean }).value;
}
}
}

/** @inheritdoc */
protected signImplementation(key: BaseKey): Transaction {
if (this._multiSignerKeyPairs.length >= DEFAULT_SIGNER_NUMBER) {
throw new SigningError('A maximum of ' + DEFAULT_SIGNER_NUMBER + ' can sign the transaction.');
}
return super.signImplementation(key);
}

/** @inheritdoc */
protected async buildImplementation(): Promise<Transaction> {
this._txBodyData.accountIDToUpdate = buildHederaAccountID(this._accountId || this._source.address);
if (this._stakedNodeId !== undefined) {
this._txBodyData.stakedNodeId = this._stakedNodeId;
}
if (this._declineStakingReward !== undefined) {
this._txBodyData.declineReward = { value: this._declineStakingReward };
}
this.transaction.setTransactionType(TransactionType.AccountUpdate);
return await super.buildImplementation();
}

/** @inheritdoc */
validateMandatoryFields(): void {
if (this._stakedNodeId === undefined) {
throw new BuildTransactionError('Invalid transaction: missing stakedNodeId');
}
super.validateMandatoryFields();
}

/**
* Set the account to update. Defaults to the source account if not set.
*
* @param {string} accountId - The account ID in format <shard>.<realm>.<account>
* @returns {AccountUpdateBuilder} - This builder
*/
account(accountId: string): this {
if (!isValidAddress(accountId)) {
throw new BuildTransactionError('Invalid account address: ' + accountId);
}
this._accountId = accountId;
return this;
}

/**
* Set the staked node ID. Use -1 to unstake.
*
* @param {number} nodeId - The consensus node ID to stake to, or -1 to clear staking
* @returns {AccountUpdateBuilder} - This builder
*/
stakedNodeId(nodeId: number): this {
this._stakedNodeId = Long.fromNumber(nodeId);
return this;
}

/**
* Set whether to decline staking rewards.
*
* @param {boolean} decline - True to decline rewards, false to accept
* @returns {AccountUpdateBuilder} - This builder
*/
declineStakingReward(decline: boolean): this {
this._declineStakingReward = decline;
return this;
}
}
1 change: 1 addition & 0 deletions modules/sdk-coin-hbar/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export enum HederaTransactionTypes {
CreateAccount = 'cryptoCreateAccount',
TokenAssociateToAccount = 'tokenAssociate',
TokenDissociateFromAccount = 'tokenAssociate',
AccountUpdate = 'cryptoUpdateAccount',
}
11 changes: 10 additions & 1 deletion modules/sdk-coin-hbar/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface AddressDetails {
memoId?: string;
}

export type InstructionParams = Transfer | AssociateAccount;
export type InstructionParams = Transfer | AssociateAccount | AccountUpdateInstruction;

export interface Transfer {
type: HederaTransactionTypes.Transfer;
Expand All @@ -55,3 +55,12 @@ export interface AssociateAccount {
tokenNames: string[];
};
}

export interface AccountUpdateInstruction {
type: HederaTransactionTypes.AccountUpdate;
params: {
accountId: string;
stakedNodeId?: string;
declineReward?: boolean;
};
}
1 change: 1 addition & 0 deletions modules/sdk-coin-hbar/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export { TransferBuilder } from './transferBuilder';
export { CoinTransferBuilder } from './coinTransferBuilder';
export { TokenTransferBuilder } from './tokenTransferBuilder';
export { TokenAssociateBuilder } from './tokenAssociateBuilder';
export { AccountUpdateBuilder } from './accountUpdateBuilder';
export { Recipient } from './iface';
export { Utils };
43 changes: 43 additions & 0 deletions modules/sdk-coin-hbar/src/lib/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export class Transaction extends BaseTransaction {
case HederaTransactionTypes.TokenAssociateToAccount:
this.setTransactionType(TransactionType.AssociatedTokenAccountInitialization);
break;
case HederaTransactionTypes.AccountUpdate:
this.setTransactionType(TransactionType.AccountUpdate);
break;
}
}

Expand Down Expand Up @@ -109,6 +112,12 @@ export class Transaction extends BaseTransaction {
params: this.getAccountAssociateData(),
};
break;
case HederaTransactionTypes.AccountUpdate:
result.instructionsData = {
type: HederaTransactionTypes.AccountUpdate,
params: this.getAccountUpdateData(),
};
break;
}

return result;
Expand Down Expand Up @@ -152,6 +161,27 @@ export class Transaction extends BaseTransaction {
};
}

/**
* Get the account update staking data from this transaction
*
* @returns {object} The account update parameters including stakedNodeId and declineReward
*/
private getAccountUpdateData(): { accountId: string; stakedNodeId?: string; declineReward?: boolean } {
const updateBody = this._txBody.cryptoUpdateAccount!;
return {
accountId: stringifyAccountId(updateBody.accountIDToUpdate!),
...(updateBody.stakedNodeId != null && {
stakedNodeId: Long.fromValue(updateBody.stakedNodeId).toString(),
}),
...(updateBody.declineReward != null && {
declineReward:
typeof updateBody.declineReward === 'boolean'
? updateBody.declineReward
: (updateBody.declineReward as { value: boolean }).value,
}),
};
}

/**
* Get the recipient account and the amount
* transferred on this transaction
Expand Down Expand Up @@ -252,6 +282,19 @@ export class Transaction extends BaseTransaction {
outputs.push(tokenEntry);
});
break;

case HederaTransactionTypes.AccountUpdate:
inputs.push({
address: txJson.from,
value: '0',
coin: this._coinConfig.name,
});
outputs.push({
address: instruction.params.accountId,
value: '0',
coin: this._coinConfig.name,
});
break;
}
this._inputs = inputs;
this._outputs = outputs;
Expand Down
10 changes: 10 additions & 0 deletions modules/sdk-coin-hbar/src/lib/transactionBuilderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Transaction } from './transaction';
import { isTokenTransfer, isValidRawTransactionFormat } from './utils';
import { TokenAssociateBuilder } from './tokenAssociateBuilder';
import { TokenTransferBuilder } from './tokenTransferBuilder';
import { AccountUpdateBuilder } from './accountUpdateBuilder';

export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
constructor(_coinConfig: Readonly<CoinConfig>) {
Expand Down Expand Up @@ -42,6 +43,13 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.initializeBuilder(tx, new TokenAssociateBuilder(this._coinConfig));
}

/**
* Returns a builder to create an account update transaction (staking operations)
*/
getAccountUpdateBuilder(tx?: Transaction): AccountUpdateBuilder {
return this.initializeBuilder(tx, new AccountUpdateBuilder(this._coinConfig));
}

/** @inheritDoc */
from(raw: Uint8Array | string): TransactionBuilder {
this.validateRawTransaction(raw);
Expand All @@ -55,6 +63,8 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
return this.getWalletInitializationBuilder(tx);
case TransactionType.AssociatedTokenAccountInitialization:
return this.getTokenAssociateBuilder(tx);
case TransactionType.AccountUpdate:
return this.getAccountUpdateBuilder(tx);
default:
throw new InvalidTransactionError('Invalid transaction ' + tx.txBody.data);
}
Expand Down
Loading
Loading