@ledgerhq/coin-hedera
Version:
Ledger Hedera Coin integration
181 lines • 8.22 kB
JavaScript
import { AccountUpdateTransaction, ContractExecuteTransaction, ContractFunctionParameters, ContractId, Hbar, TokenAssociateTransaction, TransferTransaction, } from "@hashgraph/sdk";
import BigNumber from "bignumber.js";
import invariant from "invariant";
import { DEFAULT_GAS_LIMIT, HEDERA_TRANSACTION_MODES, TRANSACTION_VALID_DURATION_SECONDS, } from "../constants";
import { rpcClient } from "../network/rpc";
import { createTransactionId } from "../network/utils";
import { hasSpecificIntentData, serializeTransaction, toEVMAddress } from "./utils";
async function buildUnsignedCoinTransaction({ account, transaction, }) {
const accountId = account.accountId;
const hbarAmount = Hbar.fromTinybars(transaction.amount);
const tx = new TransferTransaction()
.setTransactionValidDuration(TRANSACTION_VALID_DURATION_SECONDS)
.setTransactionId(transaction.transactionId)
.setTransactionMemo(transaction.memo)
.addHbarTransfer(accountId, hbarAmount.negated())
.addHbarTransfer(transaction.recipient, hbarAmount);
if (transaction.maxFee) {
tx.setMaxTransactionFee(Hbar.fromTinybars(transaction.maxFee.toNumber()));
}
return tx.freezeWith(await rpcClient.getInstance());
}
async function buildUnsignedHTSTokenTransaction({ account, transaction, }) {
const accountId = account.accountId;
const tokenId = transaction.tokenAddress;
const tx = new TransferTransaction()
.setTransactionValidDuration(TRANSACTION_VALID_DURATION_SECONDS)
.setTransactionId(transaction.transactionId)
.setTransactionMemo(transaction.memo)
.addTokenTransfer(tokenId, accountId, transaction.amount.negated().toNumber())
.addTokenTransfer(tokenId, transaction.recipient, transaction.amount.toNumber());
if (transaction.maxFee) {
tx.setMaxTransactionFee(Hbar.fromTinybars(transaction.maxFee.toNumber()));
}
return tx.freezeWith(await rpcClient.getInstance());
}
async function buildUnsignedERC20TokenTransaction({ transaction, }) {
const contractId = ContractId.fromEvmAddress(0, 0, transaction.tokenAddress);
const recipientEvmAddress = await toEVMAddress(transaction.recipient);
invariant(recipientEvmAddress, `hedera: EVM address is missing ${transaction.recipient}`);
const gas = transaction.gasLimit.toNumber();
// create function parameters for ERC20 transfer function
// transfer(address to, uint256 amount) returns (bool)
const functionParameters = new ContractFunctionParameters()
.addAddress(recipientEvmAddress)
.addUint256(transaction.amount.toNumber());
const tx = new ContractExecuteTransaction()
.setTransactionValidDuration(TRANSACTION_VALID_DURATION_SECONDS)
.setTransactionId(transaction.transactionId)
.setTransactionMemo(transaction.memo ?? "")
.setContractId(contractId)
.setGas(gas)
.setFunction("transfer", functionParameters);
if (transaction.maxFee) {
tx.setMaxTransactionFee(Hbar.fromTinybars(transaction.maxFee.toNumber()));
}
return tx.freezeWith(await rpcClient.getInstance());
}
async function buildTokenAssociateTransaction({ account, transaction, }) {
const accountId = account.accountId;
const tx = new TokenAssociateTransaction()
.setTransactionValidDuration(TRANSACTION_VALID_DURATION_SECONDS)
.setTransactionId(transaction.transactionId)
.setTransactionMemo(transaction.memo)
.setAccountId(accountId)
.setTokenIds([transaction.tokenId]);
if (transaction.maxFee) {
tx.setMaxTransactionFee(Hbar.fromTinybars(transaction.maxFee.toNumber()));
}
return tx.freezeWith(await rpcClient.getInstance());
}
async function buildUnsignedUpdateAccountTransaction({ account, transaction, }) {
const accountId = account.accountId;
const tx = new AccountUpdateTransaction()
.setTransactionValidDuration(TRANSACTION_VALID_DURATION_SECONDS)
.setTransactionId(transaction.transactionId)
.setTransactionMemo(transaction.memo ?? "")
.setAccountId(accountId);
if (transaction.maxFee) {
tx.setMaxTransactionFee(Hbar.fromTinybars(transaction.maxFee.toNumber()));
}
if (typeof transaction.stakingNodeId === "number") {
tx.setStakedNodeId(transaction.stakingNodeId);
}
if (transaction.stakingNodeId === null) {
tx.clearStakedNodeId();
}
return tx.freezeWith(await rpcClient.getInstance());
}
function isStakingMode(type) {
return (type === HEDERA_TRANSACTION_MODES.Redelegate ||
type === HEDERA_TRANSACTION_MODES.Undelegate ||
type === HEDERA_TRANSACTION_MODES.Delegate);
}
export async function craftTransaction({ txIntent, customFees, config, }) {
const account = { accountId: txIntent.sender };
const maxFee = customFees ? new BigNumber(customFees.value.toString()) : undefined;
const transactionId = await createTransactionId(account.accountId, config);
let tx;
if (txIntent.type === HEDERA_TRANSACTION_MODES.TokenAssociate) {
invariant(txIntent.asset.type !== "native", "hedera: invalid asset type");
invariant("assetReference" in txIntent.asset, "hedera: assetReference is missing");
tx = await buildTokenAssociateTransaction({
account,
transaction: {
type: txIntent.type,
transactionId,
tokenId: txIntent.asset.assetReference,
memo: txIntent.memo.value,
maxFee,
},
});
}
else if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type === "hts") {
invariant("assetReference" in txIntent.asset, "hedera: no assetReference in token transfer");
const amount = new BigNumber(txIntent.amount.toString());
tx = await buildUnsignedHTSTokenTransaction({
account,
transaction: {
type: txIntent.type,
transactionId,
tokenAddress: txIntent.asset.assetReference,
amount,
recipient: txIntent.recipient,
memo: txIntent.memo.value,
maxFee,
},
});
}
else if (txIntent.type === HEDERA_TRANSACTION_MODES.Send && txIntent.asset.type === "erc20") {
invariant("assetReference" in txIntent.asset, "hedera: no assetReference in token transfer");
const amount = new BigNumber(txIntent.amount.toString());
const gasLimit = hasSpecificIntentData(txIntent, "erc20")
? new BigNumber(txIntent.data.gasLimit.toString())
: DEFAULT_GAS_LIMIT;
tx = await buildUnsignedERC20TokenTransaction({
transaction: {
type: txIntent.type,
transactionId,
tokenAddress: txIntent.asset.assetReference,
amount,
recipient: txIntent.recipient,
memo: txIntent.memo.value,
maxFee,
gasLimit,
},
});
}
else if (isStakingMode(txIntent.type)) {
const stakingNodeId = hasSpecificIntentData(txIntent, "staking")
? txIntent.data.stakingNodeId
: undefined;
tx = await buildUnsignedUpdateAccountTransaction({
account,
transaction: {
type: txIntent.type,
transactionId,
memo: txIntent.memo.value,
maxFee,
stakingNodeId,
},
});
}
// HEDERA_TRANSACTION_MODES.ClaimRewards is just a coin transfer that triggers staking rewards claim
else {
const amount = new BigNumber(txIntent.amount.toString());
tx = await buildUnsignedCoinTransaction({
account,
transaction: {
type: HEDERA_TRANSACTION_MODES.Send,
transactionId,
amount,
recipient: txIntent.recipient,
memo: txIntent.memo.value,
maxFee,
},
});
}
const serializedTx = serializeTransaction(tx);
return { tx, serializedTx };
}
//# sourceMappingURL=craftTransaction.js.map