mochimo-mesh-api-client
Version:
A TypeScript client library for interacting with the Mochimo blockchain API
534 lines (527 loc) • 17.8 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
MCM_CURRENCY: () => MCM_CURRENCY,
MochimoApiClient: () => MochimoApiClient,
NETWORK_IDENTIFIER: () => NETWORK_IDENTIFIER,
TransactionBuilder: () => TransactionBuilder,
formatMemo: () => formatMemo,
isValidMemo: () => isValidMemo
});
module.exports = __toCommonJS(index_exports);
// src/types.ts
var MCM_CURRENCY = {
symbol: "MCM",
decimals: 9
};
var NETWORK_IDENTIFIER = {
blockchain: "mochimo",
network: "mainnet"
};
// src/utils/logger.ts
var Logger = class _Logger {
constructor() {
this.isDebug = false;
}
static getInstance() {
if (!_Logger.instance) {
_Logger.instance = new _Logger();
}
return _Logger.instance;
}
enableDebug() {
this.isDebug = true;
}
replaceBigInt(key, value) {
if (typeof value === "bigint") {
return value.toString();
}
return value;
}
formatMessage(level, message, data) {
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
const dataString = data ? `
Data: ${JSON.stringify(data, this.replaceBigInt, 2)}` : "";
return `[${timestamp}] ${level.toUpperCase()}: ${message}${dataString}`;
}
debug(message, data) {
if (this.isDebug) {
console.debug(this.formatMessage("debug", message, data));
}
}
info(message, data) {
console.info(this.formatMessage("info", message, data));
}
warn(message, data) {
console.warn(this.formatMessage("warn", message, data));
}
error(message, error) {
console.error(this.formatMessage("error", message, error));
}
};
var logger = Logger.getInstance();
// src/api.ts
var MochimoApiClient = class {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.networkIdentifier = NETWORK_IDENTIFIER;
logger.debug("Construction initialized", { baseUrl, networkIdentifier: this.networkIdentifier });
}
headersToObject(headers) {
const result = {};
headers.forEach((value, key) => {
result[key] = value;
});
return result;
}
async handleResponse(response) {
const data = await response.json();
logger.debug("API Response", {
status: response.status,
url: response.url,
data,
headers: this.headersToObject(response.headers)
});
if ("code" in data) {
logger.error("API Error", {
endpoint: response.url,
status: response.status,
error: data
});
throw new Error(`Rosetta API Error: ${data.message}`);
}
return data;
}
async makeRequest(endpoint, body) {
const url = `${this.baseUrl}${endpoint}`;
logger.debug(`Making request to ${endpoint}`, {
url,
method: "POST",
headers: { "Content-Type": "application/json" },
body
});
try {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
return this.handleResponse(response);
} catch (error) {
logger.error(`Request failed to ${endpoint}`, {
url,
error: error instanceof Error ? error.message : error
});
throw error;
}
}
async derive(publicKey, tag) {
logger.debug("Deriving address", { publicKey, tag });
return this.makeRequest("/construction/derive", {
network_identifier: this.networkIdentifier,
public_key: {
hex_bytes: publicKey,
curve_type: "wotsp"
},
metadata: { tag }
});
}
async preprocess(operations, metadata) {
logger.debug("Preprocessing transaction", { operations, metadata });
return this.makeRequest("/construction/preprocess", {
network_identifier: this.networkIdentifier,
operations,
metadata
});
}
async metadata(options, publicKeys) {
logger.debug("Fetching metadata", { options, publicKeys });
return this.makeRequest("/construction/metadata", {
network_identifier: this.networkIdentifier,
options,
public_keys: publicKeys
});
}
async payloads(operations, metadata, publicKeys) {
logger.debug("Fetching payloads", { operations, metadata, publicKeys });
return this.makeRequest("/construction/payloads", {
network_identifier: this.networkIdentifier,
operations,
metadata,
public_keys: publicKeys
});
}
async combine(unsignedTransaction, signatures) {
logger.debug("Combining transaction", { unsignedTransaction, signatures });
return this.makeRequest("/construction/combine", {
network_identifier: this.networkIdentifier,
unsigned_transaction: unsignedTransaction,
signatures
});
}
async submit(signedTransaction) {
logger.debug("Submitting transaction", { signedTransaction });
return this.makeRequest("/construction/submit", {
network_identifier: this.networkIdentifier,
signed_transaction: signedTransaction
});
}
async parse(transaction, signed) {
logger.debug("Parsing transaction", { transaction, signed });
return this.makeRequest("/construction/parse", {
network_identifier: this.networkIdentifier,
transaction,
signed
});
}
async resolveTag(tag) {
return this.makeRequest("/call", {
network_identifier: this.networkIdentifier,
parameters: {
tag
},
method: "tag_resolve"
});
}
async getAccountBalance(address) {
return this.makeRequest("/account/balance", {
network_identifier: this.networkIdentifier,
account_identifier: { address }
});
}
async getBlock(identifier) {
return this.makeRequest("/block", {
network_identifier: this.networkIdentifier,
block_identifier: identifier
});
}
async getNetworkStatus() {
return this.makeRequest("/network/status", {
network_identifier: this.networkIdentifier
});
}
/**
* Get all transaction identifiers in the mempool
*/
async getMempoolTransactions() {
logger.debug("Fetching mempool transactions");
return this.makeRequest("/mempool", {
network_identifier: this.networkIdentifier
});
}
/**
* Get a specific transaction from the mempool
* @param transactionHash - The hash of the transaction to fetch
*/
async getMempoolTransaction(transactionHash) {
logger.debug("Fetching mempool transaction", { transactionHash });
return this.makeRequest("/mempool/transaction", {
network_identifier: this.networkIdentifier,
transaction_identifier: {
hash: transactionHash
}
});
}
/**
* Monitor the mempool for a specific transaction
* @param transactionHash - The hash of the transaction to monitor
* @param timeout - Maximum time to wait in milliseconds
* @param interval - Check interval in milliseconds
*/
async waitForTransaction(transactionHash, timeout = 6e4, interval = 1e3) {
logger.debug("Monitoring mempool for transaction", {
transactionHash,
timeout,
interval
});
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const response = await this.getMempoolTransaction(transactionHash);
return response;
} catch (error) {
if (Date.now() - startTime >= timeout) {
throw new Error(`Transaction ${transactionHash} not found in mempool after ${timeout}ms`);
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
throw new Error(`Timeout waiting for transaction ${transactionHash}`);
}
};
// src/transaction.ts
var import_mochimo_wots = require("mochimo-wots");
var import_crypto_js = __toESM(require("crypto-js"));
var TransactionBuilder = class {
constructor(baseUrl) {
this.construction = new MochimoApiClient(baseUrl);
logger.debug("TransactionBuilder initialized", { baseUrl });
}
createTransactionBytes(params) {
const sourceAddr = params.sourceAddress.startsWith("0x") ? params.sourceAddress.slice(2) : params.sourceAddress;
const destAddr = params.destinationTag.startsWith("0x") ? params.destinationTag.slice(2) : params.destinationTag;
const changePk = params.changePk.startsWith("0x") ? params.changePk.slice(2) : params.changePk;
const txBuffer = Buffer.alloc(2304);
txBuffer.writeUInt32LE(0, 0);
Buffer.from(sourceAddr, "hex").copy(txBuffer, 4);
Buffer.from(changePk, "hex").copy(txBuffer, 44);
Buffer.from(destAddr, "hex").copy(txBuffer, 84);
txBuffer.writeBigUInt64LE(params.amount, 124);
txBuffer.writeBigUInt64LE(params.fee, 132);
txBuffer.writeUInt32LE(params.blockToLive, 140);
if (params.memo) {
const memoBuffer = Buffer.from(params.memo);
memoBuffer.copy(txBuffer, 144, 0, Math.min(memoBuffer.length, 32));
}
return txBuffer;
}
async buildTransaction(params) {
try {
const logParams = {
...params,
amount: params.amount.toString(),
fee: params.fee.toString(),
sourceBalance: params.sourceBalance?.toString()
};
logger.info("Building transaction", logParams);
const txBytes = this.createTransactionBytes(params);
logger.debug("Created transaction bytes", {
length: txBytes.length,
hex: txBytes.toString("hex")
});
const operations = [
{
operation_identifier: { index: 0 },
type: "SOURCE_TRANSFER",
status: "SUCCESS",
account: { address: params.sourceTag },
amount: {
value: (-params.amount).toString(),
currency: MCM_CURRENCY
}
},
{
operation_identifier: { index: 1 },
type: "DESTINATION_TRANSFER",
status: "SUCCESS",
account: { address: params.destinationTag },
amount: {
value: params.amount.toString(),
currency: MCM_CURRENCY
},
metadata: { memo: params.memo || "" }
},
{
operation_identifier: { index: 2 },
type: "FEE",
status: "SUCCESS",
account: { address: params.sourceTag },
amount: {
value: params.fee.toString(),
currency: MCM_CURRENCY
}
}
];
logger.debug("Created operations", operations);
const preprocessResponse = await this.construction.preprocess(operations, {
block_to_live: params.blockToLive.toString(),
change_pk: params.changePk,
change_addr: params.changePk,
source_balance: params.sourceBalance ? params.sourceBalance.toString() : "179999501"
});
logger.debug("Preprocess response", preprocessResponse);
const metadataResponse = await this.construction.metadata(
preprocessResponse.options,
[{ hex_bytes: params.publicKey, curve_type: "wotsp" }]
);
logger.debug("Metadata response", metadataResponse);
const results = await this.construction.payloads(
operations,
metadataResponse.metadata,
[{ hex_bytes: params.publicKey, curve_type: "wotsp" }]
);
return results;
} catch (error) {
logger.error("Error building transaction", error);
throw error instanceof Error ? error : new Error("Unknown error occurred");
}
}
async submitSignedTransaction(signedTransaction) {
return await this.construction.submit(signedTransaction);
}
createSignature(publicKey, unsignedTx, signatureBytes) {
return {
signing_payload: {
hex_bytes: unsignedTx,
signature_type: "wotsp"
},
public_key: {
hex_bytes: Buffer.from(publicKey).toString("hex"),
curve_type: "wotsp"
},
signature_type: "wotsp",
hex_bytes: Buffer.from(signatureBytes).toString("hex")
};
}
/**
* Submit a transaction and wait for it to appear in the mempool
* @param signedTransaction - The signed transaction to submit
* @param timeout - Maximum time to wait for mempool appearance
*/
async submitAndMonitor(signedTransaction, timeout = 6e4) {
const submitResult = await this.submitSignedTransaction(signedTransaction);
logger.debug("Transaction submitted", submitResult);
if (!submitResult.transaction_identifier?.hash) {
throw new Error("No transaction hash in submit response");
}
return await this.construction.waitForTransaction(
submitResult.transaction_identifier.hash,
timeout
);
}
/**
* Get all transactions currently in the mempool
*/
async getMempoolTransactions() {
return this.construction.getMempoolTransactions();
}
/**
* Get a specific transaction from the mempool
*/
async getMempoolTransaction(transactionHash) {
return this.construction.getMempoolTransaction(transactionHash);
}
async buildAndSignTransaction(sourceWallet, changeWallet, destinationTag, amount, fee, memo, blockToLive = 0) {
const params = {
sourceTag: "0x" + Buffer.from(sourceWallet.getAddrTag()).toString("hex"),
sourceAddress: "0x" + Buffer.from(sourceWallet.getAddress()).toString("hex"),
destinationTag,
amount,
fee,
publicKey: Buffer.from(sourceWallet.getWots().slice(0, 2144)).toString("hex"),
changePk: "0x" + Buffer.from(changeWallet.getAddrHash()).toString("hex"),
memo,
blockToLive,
sourceBalance: amount + fee
// This should be fetched from network in production
};
const buildResult = await this.buildTransaction(params);
const unsignedTransaction = buildResult.unsigned_transaction;
const signedTransaction = sourceWallet.sign(
import_mochimo_wots.MochimoHasher.hash(new Uint8Array(Buffer.from(unsignedTransaction, "hex")))
);
const pub = sourceWallet.getWots().slice(2144, 2144 + 32);
const rnd = sourceWallet.getWots().slice(2144 + 32, 2144 + 32 + 32);
const combinedSig = new Uint8Array([
...signedTransaction,
...pub,
...rnd
]);
const sig = this.createSignature(
sourceWallet.getAddress(),
unsignedTransaction,
combinedSig
);
const combined = await this.construction.combine(unsignedTransaction, [sig]);
const submitResult = await this.submitSignedTransaction(combined.signed_transaction);
return {
buildResult,
submitResult,
signedTransaction: combined.signed_transaction
};
}
// used for testing; gives out two wots wallet instances from a seed and index
static createWallets(seed, index, parentWallet) {
const sourceWotsSeed = import_crypto_js.default.SHA256(seed + index).toString();
const changeWotsSeed = import_crypto_js.default.SHA256(seed + (index + 1)).toString();
const sourceWallet = import_mochimo_wots.WOTSWallet.create(
"source",
Buffer.from(sourceWotsSeed, "hex"),
parentWallet?.getAddrHash()
);
const changeWallet = import_mochimo_wots.WOTSWallet.create(
"change",
Buffer.from(changeWotsSeed, "hex"),
parentWallet?.getAddrHash()
);
return { sourceWallet, changeWallet };
}
};
// src/utils/memo.ts
function isValidMemo(memo) {
if (!memo) return true;
if (!/^[A-Z0-9-]+$/.test(memo)) {
return false;
}
if (memo.startsWith("-") || memo.endsWith("-")) {
return false;
}
const groups = memo.split("-");
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
if (!group) return false;
const isLetters = /^[A-Z]+$/.test(group);
const isNumbers = /^[0-9]+$/.test(group);
if (!isLetters && !isNumbers) {
return false;
}
if (i > 0) {
const prevGroup = groups[i - 1];
const prevIsLetters = /^[A-Z]+$/.test(prevGroup);
if (isLetters === prevIsLetters) {
return false;
}
}
}
return true;
}
function formatMemo(memo) {
const result = new Uint8Array(16).fill(0);
if (!memo || !isValidMemo(memo)) {
return result;
}
const memoBytes = Buffer.from(memo, "ascii");
const copyLength = Math.min(memoBytes.length, 15);
result.set(memoBytes.subarray(0, copyLength));
result[copyLength] = 0;
console.log("Result:", Buffer.from(result).toString("hex"));
console.log("Result ascii:", Buffer.from(result).toString("ascii"));
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MCM_CURRENCY,
MochimoApiClient,
NETWORK_IDENTIFIER,
TransactionBuilder,
formatMemo,
isValidMemo
});
;