@ledgerhq/coin-ton
Version:
278 lines • 10.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BotScenario = exports.getLedgerTonPath = exports.getTonEstimatedFees = exports.getTransferExpirationTime = exports.commentIsValid = exports.buildTonTransaction = exports.findSubAccountById = exports.addressesAreEqual = exports.isAddressValid = void 0;
const index_1 = require("@ledgerhq/coin-framework/account/index");
const ton_1 = require("@ton/ton");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const api_1 = require("./bridge/bridgeHelpers/api");
const config_1 = require("./config");
const constants_1 = require("./constants");
/**
* Checks if the given recipient address is valid.
*/
const isAddressValid = (recipient) => {
try {
return Boolean((ton_1.Address.isRaw(recipient) || ton_1.Address.isFriendly(recipient)) &&
ton_1.Address.parse(recipient));
}
catch {
return false;
}
};
exports.isAddressValid = isAddressValid;
/**
* Compares two addresses to check if they are equal.
*/
const addressesAreEqual = (addr1, addr2) => {
try {
return ((0, exports.isAddressValid)(addr1) &&
(0, exports.isAddressValid)(addr2) &&
ton_1.Address.parse(addr1).equals(ton_1.Address.parse(addr2)));
}
catch {
return false;
}
};
exports.addressesAreEqual = addressesAreEqual;
/**
* Returns the known jetton ID and workchain for a given token address.
* Returns null if the token is not found in the known jettons list.
*/
function getKnownJettonId(tokenAddress, knownJettons) {
const index = knownJettons.findIndex(jetton => jetton.masterAddress.toString() === tokenAddress);
return index > -1 ? { jettonId: index, workchain: constants_1.WORKCHAIN } : null;
}
/**
* Finds a sub-account by its ID in a TON account.
* Returns undefined if no matching sub-account is found.
*/
function findSubAccountById(account, id) {
return account.subAccounts?.find(a => a.id === id);
}
exports.findSubAccountById = findSubAccountById;
/**
* Builds a TonTransaction object based on the given transaction details.
*/
function buildTonTransaction(transaction, seqno, account) {
const { subAccountId, useAllAmount, amount, comment: commentTx, recipient, payload, } = transaction;
let recipientParsed = recipient;
// if recipient is not valid calculate fees with empty address
// we handle invalid addresses in account bridge
try {
ton_1.Address.parse(recipientParsed);
}
catch {
recipientParsed = new ton_1.Address(0, Buffer.alloc(32)).toRawString();
}
// if there is a sub account, the transaction is a token transfer
const subAccount = findSubAccountById(account, subAccountId ?? "");
if (subAccount && !subAccount.jettonWallet) {
throw new Error("[ton] jetton wallet not found");
}
const finalAmount = subAccount
? (0, ton_1.toNano)(constants_1.TOKEN_TRANSFER_MAX_FEE) // for commission fees, excess will be returned
: useAllAmount
? BigInt(0)
: BigInt(amount.toFixed());
const to = subAccount?.jettonWallet ?? recipientParsed;
const tonTransaction = {
to: ton_1.Address.parse(to),
seqno,
amount: finalAmount,
bounce: ton_1.Address.isFriendly(to) ? ton_1.Address.parseFriendly(to).isBounceable : true,
timeout: (0, exports.getTransferExpirationTime)(),
sendMode: useAllAmount && !subAccount
? ton_1.SendMode.CARRY_ALL_REMAINING_BALANCE
: ton_1.SendMode.IGNORE_ERRORS + ton_1.SendMode.PAY_GAS_SEPARATELY,
payload,
};
if (commentTx.text.length) {
tonTransaction.payload = { type: "comment", text: commentTx.text };
}
if (subAccount) {
const forwardPayload = commentTx.text.length ? (0, ton_1.comment)(commentTx.text) : null;
const currencyConfig = (0, config_1.getCoinConfig)();
const knownJettons = currencyConfig.infra.KNOWN_JETTONS;
tonTransaction.payload = {
type: "jetton-transfer",
queryId: BigInt(constants_1.TOKEN_TRANSFER_QUERY_ID),
amount: BigInt(amount.toFixed()),
destination: ton_1.Address.parse(recipientParsed),
responseDestination: ton_1.Address.parse(account.freshAddress),
customPayload: null,
forwardAmount: BigInt(constants_1.TOKEN_TRANSFER_FORWARD_AMOUNT),
forwardPayload,
knownJetton: knownJettons
? getKnownJettonId(subAccount?.token.contractAddress, knownJettons)
: null,
};
}
return tonTransaction;
}
exports.buildTonTransaction = buildTonTransaction;
/**
* Validates if the given comment is valid.
*/
const commentIsValid = (msg) => !msg.isEncrypted && msg.text.length <= constants_1.MAX_COMMENT_BYTES && /^[\x20-\x7F]*$/.test(msg.text);
exports.commentIsValid = commentIsValid;
/**
* Gets the transfer expiration time.
*/
const getTransferExpirationTime = () => Math.floor(Date.now() / 1000 + 60);
exports.getTransferExpirationTime = getTransferExpirationTime;
/**
* Estimates the fees for a Ton transaction.
*/
const getTonEstimatedFees = async (account, needsInit, tx) => {
const { xpubOrAddress: pubKey } = (0, index_1.decodeAccountId)(account.id);
if (pubKey.length !== 64)
throw Error("[ton] pubKey can't be found");
// build body depending the payload type
let body;
let isJetton = false;
if (tx.payload) {
switch (tx.payload.type) {
case "comment":
body = (0, ton_1.comment)(tx.payload.text);
break;
case "jetton-transfer":
body = buildTokenTransferBody(tx.payload);
isJetton = true;
break;
}
}
const contract = ton_1.WalletContractV4.create({ workchain: 0, publicKey: Buffer.from(pubKey, "hex") });
const transfer = contract.createTransfer({
seqno: tx.seqno,
secretKey: Buffer.alloc(64), // secretKey set to 0, signature is not verified
messages: [
(0, ton_1.internal)({
bounce: tx.bounce,
to: tx.to,
value: tx.amount,
body,
}),
],
sendMode: tx.sendMode,
});
const initCode = needsInit ? contract.init.code.toBoc().toString("base64") : undefined;
const initData = needsInit ? contract.init.data.toBoc().toString("base64") : undefined;
const fee = await (0, api_1.estimateFee)(account.freshAddress, transfer.toBoc().toString("base64"), initCode, initData);
return isJetton
? (0, bignumber_js_1.default)((0, ton_1.toNano)(constants_1.TOKEN_TRANSFER_MAX_FEE).toString())
: (0, bignumber_js_1.default)(fee.fwd_fee + fee.gas_fee + fee.in_fwd_fee + fee.storage_fee);
};
exports.getTonEstimatedFees = getTonEstimatedFees;
/**
* Converts a Ledger path string to an array of numbers.length.
*/
const getLedgerTonPath = (path) => {
const numPath = [];
if (!path)
throw Error("[ton] Path is empty");
if (path.startsWith("m/"))
path = path.slice(2);
const pathEntries = path.split("/");
if (pathEntries.length !== 6)
throw Error(`[ton] Path length is not right ${path}`);
for (const entry of pathEntries) {
if (!entry.endsWith("'"))
throw Error(`[ton] Path entry is not hardened ${path}`);
const num = parseInt(entry.slice(0, entry.length - 1));
if (!Number.isInteger(num) || num < 0 || num >= 0x80000000)
throw Error(`[ton] Path entry is not right ${path}`);
numPath.push(num);
}
return numPath;
};
exports.getLedgerTonPath = getLedgerTonPath;
/**
* Builds the body of a token transfer transaction.
*/
function buildTokenTransferBody(params) {
const { queryId, amount, destination, responseDestination, forwardAmount } = params;
let forwardPayload = params.forwardPayload;
let builder = new ton_1.Builder()
.storeUint(constants_1.JettonOpCode.Transfer, 32)
.storeUint(queryId ?? generateQueryId(), 64)
.storeCoins(amount)
.storeAddress(destination)
.storeAddress(responseDestination)
.storeBit(false)
.storeCoins(forwardAmount ?? BigInt(0));
if (forwardPayload instanceof Uint8Array) {
forwardPayload = packBytesAsSnake(forwardPayload);
}
if (!forwardPayload) {
builder.storeBit(false);
}
else if (typeof forwardPayload === "string") {
builder = builder.storeBit(false).storeUint(0, 32).storeBuffer(Buffer.from(forwardPayload));
}
else if (forwardPayload instanceof Uint8Array) {
builder = builder.storeBit(false).storeBuffer(Buffer.from(forwardPayload));
}
else {
builder = builder.storeBit(true).storeRef(forwardPayload);
}
return builder.endCell();
}
/**
* Generates a random BigInt of the specified byte length.
*/
function bigintRandom(bytes) {
let value = BigInt(0);
for (const randomNumber of randomBytes(bytes)) {
const randomBigInt = BigInt(randomNumber);
value = (value << BigInt(8)) + randomBigInt;
}
return value;
}
/**
* Generates a random byte array of the specified size.
*/
function randomBytes(size) {
return self.crypto.getRandomValues(new Uint8Array(size));
}
/**
* Generates a random query ID.
*/
function generateQueryId() {
return bigintRandom(8);
}
/**
* Packs a byte array into a TonCell using a snake-like structure.
*/
function packBytesAsSnake(bytes) {
return packBytesAsSnakeCell(bytes);
}
/**
* Packs a byte array into a TonCell using a snake-like structure.
*/
function packBytesAsSnakeCell(bytes) {
const buffer = Buffer.from(bytes);
const mainBuilder = new ton_1.Builder();
let prevBuilder;
let currentBuilder = mainBuilder;
for (const [i, byte] of buffer.entries()) {
if (currentBuilder.availableBits < 8) {
prevBuilder?.storeRef(currentBuilder);
prevBuilder = currentBuilder;
currentBuilder = new ton_1.Builder();
}
currentBuilder = currentBuilder.storeUint(byte, 8);
if (i === buffer.length - 1) {
prevBuilder?.storeRef(currentBuilder);
}
}
return mainBuilder.asCell();
}
var BotScenario;
(function (BotScenario) {
BotScenario["DEFAULT"] = "default";
BotScenario["TOKEN_TRANSFER"] = "token-transfer";
})(BotScenario || (exports.BotScenario = BotScenario = {}));
//# sourceMappingURL=utils.js.map