@hiero-ledger/sdk
Version:
301 lines (266 loc) • 8.59 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import PrivateKey from "./PrivateKey.js";
import AccountId from "./account/AccountId.js";
import SignerSignature from "./SignerSignature.js";
import AccountBalanceQuery from "./account/AccountBalanceQuery.js";
import AccountInfoQuery from "./account/AccountInfoQuery.js";
import AccountRecordsQuery from "./account/AccountRecordsQuery.js";
import TransactionId from "./transaction/TransactionId.js";
import * as util from "./util.js";
/**
* @template RequestT
* @template ResponseT
* @template OutputT
* @typedef {import("./Executable.js").default<RequestT, ResponseT, OutputT>} Executable<RequestT, ResponseT, OutputT>
*/
/**
* @typedef {import("./Signer.js").Signer} Signer
* @typedef {import("./Provider.js").Provider} Provider
* @typedef {import("./LedgerId.js").default} LedgerId
* @typedef {import("./Key.js").default} Key
* @typedef {import("./transaction/Transaction.js").default} Transaction
* @typedef {import("./transaction/TransactionResponse.js").default} TransactionResponse
* @typedef {import("./transaction/TransactionReceipt.js").default} TransactionReceipt
* @typedef {import("./transaction/TransactionRecord.js").default} TransactionRecord
* @typedef {import("./account/AccountBalance.js").default} AccountBalance
* @typedef {import("./account/AccountInfo.js").default} AccountInfo
*/
/**
* @template {any} O
* @typedef {import("./query/Query.js").default<O>} Query<O>
*/
/**
* @implements {Signer}
*/
export default class Wallet {
/**
* NOTE: When using string for private key, the string needs to contain DER headers
*
* @param {AccountId | string} accountId
* @param {PrivateKey | string} privateKey
* @param {Provider=} provider
*/
constructor(accountId, privateKey, provider) {
/*
TODO: deprecate on a major version
the following lines were added because we didnt have
a way to check the algorithm of a der encoded private key
We need to keep the old behavior for the transition period.
*/
let key;
if (typeof privateKey === "string" && PrivateKey.isDerKey(privateKey)) {
key = PrivateKey.fromStringDer(privateKey);
} else if (typeof privateKey === "string") {
// eslint-disable-next-line deprecation/deprecation
key = PrivateKey.fromString(privateKey);
} else {
key = privateKey;
}
this.publicKey = key.publicKey;
/**
* @type {(message: Uint8Array) => Promise<Uint8Array>}
*/
this.signer = (message) => Promise.resolve(key.sign(message));
this.provider = provider;
this.accountId =
typeof accountId === "string"
? AccountId.fromString(accountId)
: accountId;
}
/**
* @returns {Promise<Wallet>}
* @deprecated
*/
static createRandomED25519() {
const privateKey = PrivateKey.generateED25519();
const publicKey = privateKey.publicKey;
const accountId = publicKey.toAccountId(0, 0);
return Promise.resolve(new Wallet(accountId, privateKey));
}
/**
* @returns {Promise<Wallet>}
* @deprecated
*/
static createRandomECDSA() {
const privateKey = PrivateKey.generateECDSA();
const publicKey = privateKey.publicKey;
const accountId = publicKey.toAccountId(0, 0);
return Promise.resolve(new Wallet(accountId, privateKey));
}
/**
* @returns {Provider=}
*/
getProvider() {
return this.provider;
}
/**
* @abstract
* @returns {AccountId}
*/
getAccountId() {
return this.accountId;
}
/**
* @returns {Key}
*/
getAccountKey() {
return this.publicKey;
}
/**
* @returns {LedgerId?}
*/
getLedgerId() {
return this.provider == null ? null : this.provider.getLedgerId();
}
/**
* @abstract
* @returns {{[key: string]: (string | AccountId)}}
*/
getNetwork() {
return this.provider == null ? {} : this.provider.getNetwork();
}
/**
* @abstract
* @returns {string[]}
*/
getMirrorNetwork() {
return this.provider == null ? [] : this.provider.getMirrorNetwork();
}
/**
* @param {Uint8Array[]} messages
* @returns {Promise<SignerSignature[]>}
*/
async sign(messages) {
const sigantures = [];
for (const message of messages) {
sigantures.push(
new SignerSignature({
publicKey: this.publicKey,
signature: await this.signer(message),
accountId: this.accountId,
}),
);
}
return sigantures;
}
/**
* @returns {Promise<AccountBalance>}
*/
getAccountBalance() {
return this.call(
new AccountBalanceQuery().setAccountId(this.accountId),
);
}
/**
* @abstract
* @returns {Promise<AccountInfo>}
*/
getAccountInfo() {
return this.call(new AccountInfoQuery().setAccountId(this.accountId));
}
/**
* @abstract
* @returns {Promise<TransactionRecord[]>}
*/
getAccountRecords() {
return this.call(
new AccountRecordsQuery().setAccountId(this.accountId),
);
}
/**
* @template {Transaction} T
* @param {T} transaction
* @returns {Promise<T>}
*/
signTransaction(transaction) {
return transaction.signWith(this.publicKey, this.signer);
}
/**
* @template {Transaction} T
* @param {T} transaction
* @returns {Promise<T>}
*/
checkTransaction(transaction) {
const transactionId = transaction.transactionId;
if (
transactionId != null &&
transactionId.accountId != null &&
transactionId.accountId.compare(this.accountId) != 0
) {
throw new Error(
"transaction's ID constructed with a different account ID",
);
}
if (this.provider == null) {
return Promise.resolve(transaction);
}
const nodeAccountIds = (
transaction.nodeAccountIds != null ? transaction.nodeAccountIds : []
).map((nodeAccountId) => nodeAccountId.toString());
const network = Object.values(this.provider.getNetwork()).map(
(nodeAccountId) => nodeAccountId.toString(),
);
if (
!nodeAccountIds.reduce(
(previous, current) => previous && network.includes(current),
true,
)
) {
throw new Error(
"Transaction already set node account IDs to values not within the current network",
);
}
return Promise.resolve(transaction);
}
/**
* @template {Transaction} T
* @param {T} transaction
* @returns {Promise<T>}
*/
populateTransaction(transaction) {
transaction._freezeWithAccountId(this.accountId);
if (transaction.transactionId == null) {
transaction.setTransactionId(
TransactionId.generate(this.accountId),
);
}
if (
transaction.nodeAccountIds != null &&
transaction.nodeAccountIds.length != 0
) {
return Promise.resolve(transaction.freeze());
}
if (this.provider == null) {
return Promise.resolve(transaction);
}
const nodeAccountIds = Object.values(this.provider.getNetwork()).map(
(id) => (typeof id === "string" ? AccountId.fromString(id) : id),
);
util.shuffle(nodeAccountIds);
transaction.setNodeAccountIds(
nodeAccountIds.slice(0, (nodeAccountIds.length + 3 - 1) / 3),
);
return Promise.resolve(transaction.freeze());
}
/**
* @template RequestT
* @template ResponseT
* @template OutputT
* @param {Executable<RequestT, ResponseT, OutputT>} request
* @returns {Promise<OutputT>}
*/
call(request) {
if (this.provider == null) {
throw new Error(
"cannot send request with an wallet that doesn't contain a provider",
);
}
return this.provider.call(
request._setOperatorWith(
this.accountId,
this.publicKey,
this.signer,
),
);
}
}