mnee
Version:
A simple package for interacting with the MNEE USD
1 lines • 86.5 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/mneeCosignTemplate.ts","../src/utils/applyInscription.ts","../src/utils/helper.ts","../src/constants.ts","../src/mneeService.ts","../src/index.ts"],"sourcesContent":["import {\n Hash,\n LockingScript,\n OP,\n type PrivateKey,\n type PublicKey,\n type Script,\n type ScriptTemplate,\n type Transaction,\n TransactionSignature,\n UnlockingScript,\n Utils,\n} from \"@bsv/sdk\";\n\n/**\n * P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.\n *\n * This class provides methods to create Pay To Public Key Hash locking and unlocking scripts, including the unlocking of P2PKH UTXOs with the private key.\n */\nexport default class CosignTemplate implements ScriptTemplate {\n /**\n * Creates a P2PKH locking script for a given public key hash or address string\n *\n * @param {number[] | string} userPKHash or address - An array or address representing the public key hash of the owning user.\n * @param {PublicKey} approverPubKey - Public key of the approver.\n * @returns {LockingScript} - A P2PKH locking script.\n */\n lock(\n userPKHash: string | number[],\n approverPubKey: PublicKey\n ): LockingScript {\n let pkhash: number[] = [];\n if (typeof userPKHash === \"string\") {\n const hash = Utils.fromBase58Check(userPKHash);\n if (hash.prefix[0] !== 0x00 && hash.prefix[0] !== 0x6f)\n throw new Error(\"only P2PKH is supported\");\n pkhash = hash.data as number[];\n } else {\n pkhash = userPKHash;\n }\n const lockingScript = new LockingScript();\n lockingScript\n .writeOpCode(OP.OP_DUP)\n .writeOpCode(OP.OP_HASH160)\n .writeBin(pkhash)\n .writeOpCode(OP.OP_EQUALVERIFY)\n .writeOpCode(OP.OP_CHECKSIGVERIFY)\n .writeBin(approverPubKey.encode(true) as number[])\n .writeOpCode(OP.OP_CHECKSIG);\n\n return lockingScript;\n }\n\n /**\n * Creates a function that generates a P2PKH unlocking script along with its signature and length estimation.\n *\n * The returned object contains:\n * 1. `sign` - A function that, when invoked with a transaction and an input index,\n * produces an unlocking script suitable for a P2PKH locked output.\n * 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes.\n *\n * @param {PrivateKey} userPrivateKey - The private key used for signing the transaction.\n * @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs.\n * @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.\n * @param {number} sourceSatoshis - Optional. The amount being unlocked. Otherwise the input.sourceTransaction is required.\n * @param {Script} lockingScript - Optional. The lockinScript. Otherwise the input.sourceTransaction is required.\n * @returns {Object} - An object containing the `sign` and `estimateLength` functions.\n */\n userUnlock(\n userPrivateKey: PrivateKey,\n signOutputs: \"all\" | \"none\" | \"single\" = \"all\",\n anyoneCanPay = false,\n sourceSatoshis?: number,\n lockingScript?: Script\n ): {\n sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>;\n estimateLength: () => Promise<182>;\n } {\n return {\n sign: async (tx: Transaction, inputIndex: number) => {\n let signatureScope = TransactionSignature.SIGHASH_FORKID;\n if (signOutputs === \"all\") {\n signatureScope |= TransactionSignature.SIGHASH_ALL;\n }\n if (signOutputs === \"none\") {\n signatureScope |= TransactionSignature.SIGHASH_NONE;\n }\n if (signOutputs === \"single\") {\n signatureScope |= TransactionSignature.SIGHASH_SINGLE;\n }\n if (anyoneCanPay) {\n signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY;\n }\n\n const input = tx.inputs[inputIndex];\n\n const otherInputs = tx.inputs.filter(\n (_, index) => index !== inputIndex\n );\n\n const sourceTXID = input.sourceTXID\n ? input.sourceTXID\n : input.sourceTransaction?.id(\"hex\");\n if (!sourceTXID) {\n throw new Error(\n \"The input sourceTXID or sourceTransaction is required for transaction signing.\"\n );\n }\n sourceSatoshis ||=\n input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis;\n if (!sourceSatoshis) {\n throw new Error(\n \"The sourceSatoshis or input sourceTransaction is required for transaction signing.\"\n );\n }\n lockingScript ||=\n input.sourceTransaction?.outputs[input.sourceOutputIndex]\n .lockingScript;\n if (!lockingScript) {\n throw new Error(\n \"The lockingScript or input sourceTransaction is required for transaction signing.\"\n );\n }\n\n const preimage = TransactionSignature.format({\n sourceTXID,\n sourceOutputIndex: input.sourceOutputIndex,\n sourceSatoshis,\n transactionVersion: tx.version,\n otherInputs,\n inputIndex,\n outputs: tx.outputs,\n inputSequence: input.sequence || 0xffffffff,\n subscript: lockingScript,\n lockTime: tx.lockTime,\n scope: signatureScope,\n });\n const rawSignature = userPrivateKey.sign(Hash.sha256(preimage));\n const sig = new TransactionSignature(\n rawSignature.r,\n rawSignature.s,\n signatureScope\n );\n const unlockScript = new UnlockingScript();\n unlockScript.writeBin(sig.toChecksigFormat());\n unlockScript.writeBin(\n userPrivateKey.toPublicKey().encode(true) as number[]\n );\n return unlockScript;\n },\n estimateLength: async () => {\n // public key (1+33) + signature (1+73) + approver signature (1+73)\n // Note: We add 1 to each element's length because of the associated OP_PUSH\n return 182;\n },\n };\n }\n\n /**\n * Creates a function that generates a P2PKH unlocking script along with its signature and length estimation.\n *\n * The returned object contains:\n * 1. `sign` - A function that, when invoked with a transaction and an input index,\n * produces an unlocking script suitable for a P2PKH locked output.\n * 2. `estimateLength` - A function that returns the estimated length of the unlocking script in bytes.\n *\n * @param {PrivateKey} approverPrivateKey - The private key used for signing the transaction.\n * @param {'all'|'none'|'single'} signOutputs - The signature scope for outputs.\n * @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.\n * @param {number} sourceSatoshis - Optional. The amount being unlocked. Otherwise the input.sourceTransaction is required.\n * @param {Script} lockingScript - Optional. The lockinScript. Otherwise the input.sourceTransaction is required.\n * @returns {Object} - An object containing the `sign` and `estimateLength` functions.\n */\n unlock(\n approverPrivateKey: PrivateKey,\n userSigScript: Script,\n signOutputs: \"all\" | \"none\" | \"single\" = \"all\",\n anyoneCanPay = false,\n sourceSatoshis?: number,\n lockingScript?: Script\n ): {\n sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>;\n estimateLength: () => Promise<182>;\n } {\n return {\n sign: async (tx: Transaction, inputIndex: number) => {\n let signatureScope = TransactionSignature.SIGHASH_FORKID;\n if (signOutputs === \"all\") {\n signatureScope |= TransactionSignature.SIGHASH_ALL;\n }\n if (signOutputs === \"none\") {\n signatureScope |= TransactionSignature.SIGHASH_NONE;\n }\n if (signOutputs === \"single\") {\n signatureScope |= TransactionSignature.SIGHASH_SINGLE;\n }\n if (anyoneCanPay) {\n signatureScope |= TransactionSignature.SIGHASH_ANYONECANPAY;\n }\n\n const input = tx.inputs[inputIndex];\n\n const otherInputs = tx.inputs.filter(\n (_, index) => index !== inputIndex\n );\n\n const sourceTXID = input.sourceTXID\n ? input.sourceTXID\n : input.sourceTransaction?.id(\"hex\");\n if (!sourceTXID) {\n throw new Error(\n \"The input sourceTXID or sourceTransaction is required for transaction signing.\"\n );\n }\n sourceSatoshis ||=\n input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis;\n if (!sourceSatoshis) {\n throw new Error(\n \"The sourceSatoshis or input sourceTransaction is required for transaction signing.\"\n );\n }\n lockingScript ||=\n input.sourceTransaction?.outputs[input.sourceOutputIndex]\n .lockingScript;\n if (!lockingScript) {\n throw new Error(\n \"The lockingScript or input sourceTransaction is required for transaction signing.\"\n );\n }\n\n const preimage = TransactionSignature.format({\n sourceTXID,\n sourceOutputIndex: input.sourceOutputIndex,\n sourceSatoshis,\n transactionVersion: tx.version,\n otherInputs,\n inputIndex,\n outputs: tx.outputs,\n inputSequence: input.sequence || 0xffffffff,\n subscript: lockingScript,\n lockTime: tx.lockTime,\n scope: signatureScope,\n });\n const rawSignature = approverPrivateKey.sign(Hash.sha256(preimage));\n const sig = new TransactionSignature(\n rawSignature.r,\n rawSignature.s,\n signatureScope\n );\n const unlockScript = new UnlockingScript();\n unlockScript.writeBin(sig.toChecksigFormat());\n unlockScript.writeScript(userSigScript);\n return unlockScript;\n },\n estimateLength: async () => {\n // public key (1+33) + signature (1+73) + approver signature (1+73)\n // Note: We add 1 to each element's length because of the associated OP_PUSH\n return 182;\n },\n };\n }\n}\n","import { LockingScript } from '@bsv/sdk';\n\n/**\n * MAP (Magic Attribute Protocol) metadata object with stringified values for writing to the blockchain\n * @typedef {Object} MAP\n * @property {string} app - Application identifier\n * @property {string} type - Metadata type\n * @property {string} [prop] - Optional. Additional metadata properties\n */\nexport type MAP = {\n app: string;\n type: string;\n [prop: string]: string;\n};\n\nexport type Inscription = {\n dataB64: string;\n contentType: string;\n};\n\n/**\n * Converts a string to its hexadecimal representation\n *\n * @param {string} utf8Str - The string to convert\n * @returns {string} The hexadecimal representation of the input string\n */\nconst toHex = (utf8Str: string): string => {\n return Buffer.from(utf8Str).toString('hex');\n};\n\nexport const MAP_PREFIX = '1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5';\n\nexport const applyInscription = (\n lockingScript: LockingScript,\n inscription?: Inscription,\n metaData?: MAP,\n withSeparator = false,\n) => {\n let ordAsm = '';\n // This can be omitted for reinscriptions that just update metadata\n if (inscription?.dataB64 !== undefined && inscription?.contentType !== undefined) {\n const ordHex = toHex('ord');\n const fsBuffer = Buffer.from(inscription.dataB64, 'base64');\n const fileHex = fsBuffer.toString('hex').trim();\n if (!fileHex) {\n throw new Error('Invalid file data');\n }\n const fileMediaType = toHex(inscription.contentType);\n if (!fileMediaType) {\n throw new Error('Invalid media type');\n }\n ordAsm = `OP_0 OP_IF ${ordHex} OP_1 ${fileMediaType} OP_0 ${fileHex} OP_ENDIF`;\n }\n\n let inscriptionAsm = `${\n ordAsm ? `${ordAsm} ${withSeparator ? 'OP_CODESEPARATOR ' : ''}` : ''\n }${lockingScript.toASM()}`;\n\n // MAP.app and MAP.type keys are required\n if (metaData && (!metaData.app || !metaData.type)) {\n throw new Error('MAP.app and MAP.type are required fields');\n }\n\n if (metaData?.app && metaData?.type) {\n const mapPrefixHex = toHex(MAP_PREFIX);\n const mapCmdValue = toHex('SET');\n inscriptionAsm = `${inscriptionAsm ? `${inscriptionAsm} ` : ''}OP_RETURN ${mapPrefixHex} ${mapCmdValue}`;\n\n for (const [key, value] of Object.entries(metaData)) {\n if (key !== 'cmd') {\n inscriptionAsm = `${inscriptionAsm} ${toHex(key)} ${toHex(value as string)}`;\n }\n }\n }\n\n return LockingScript.fromASM(inscriptionAsm);\n};\n","import { Hash, OP, Script, Transaction, Utils } from '@bsv/sdk';\nimport {\n Inscription,\n MNEEConfig,\n MneeInscription,\n MneeSync,\n ParsedCosigner,\n TxHistory,\n TxStatus,\n TxType,\n} from '../mnee.types';\n\nexport const parseInscription = (script: Script) => {\n let fromPos: number | undefined;\n for (let i = 0; i < script.chunks.length; i++) {\n const chunk = script.chunks[i];\n if (\n i >= 2 &&\n chunk.data?.length === 3 &&\n Utils.toUTF8(chunk.data) == 'ord' &&\n script.chunks[i - 1].op == OP.OP_IF &&\n script.chunks[i - 2].op == OP.OP_FALSE\n ) {\n fromPos = i + 1;\n }\n }\n if (fromPos === undefined) return;\n\n const insc = {\n file: { hash: '', size: 0, type: '' },\n fields: {},\n } as Inscription;\n\n for (let i = fromPos; i < script.chunks.length; i += 2) {\n const field = script.chunks[i];\n if (field.op == OP.OP_ENDIF) {\n break;\n }\n if (field.op > OP.OP_16) return;\n const value = script.chunks[i + 1];\n if (value.op > OP.OP_PUSHDATA4) return;\n\n if (field.data?.length) continue;\n\n let fieldNo = 0;\n if (field.op > OP.OP_PUSHDATA4 && field.op <= OP.OP_16) {\n fieldNo = field.op - 80;\n } else if (field.data?.length) {\n fieldNo = field.data[0];\n }\n switch (fieldNo) {\n case 0:\n insc.file!.size = value.data?.length || 0;\n if (!value.data?.length) break;\n insc.file!.hash = Utils.toBase64(Hash.sha256(value.data));\n insc.file!.content = value.data;\n break;\n case 1:\n insc.file!.type = Buffer.from(value.data || []).toString();\n break;\n }\n }\n\n return insc;\n};\n\nexport const parseCosignerScripts = (scripts: any): ParsedCosigner[] => {\n return scripts.map((script: any) => {\n const chunks = script.chunks;\n for (let i = 0; i <= chunks.length - 4; i++) {\n if (\n chunks.length > i + 6 &&\n chunks[0 + i].op === OP.OP_DUP &&\n chunks[1 + i].op === OP.OP_HASH160 &&\n chunks[2 + i].data?.length === 20 &&\n chunks[3 + i].op === OP.OP_EQUALVERIFY &&\n chunks[4 + i].op === OP.OP_CHECKSIGVERIFY &&\n chunks[5 + i].data?.length === 33 &&\n chunks[6 + i].op === OP.OP_CHECKSIG\n ) {\n return {\n cosigner: Utils.toHex(chunks[5 + i].data || []),\n address: Utils.toBase58Check(chunks[2 + i].data || [], [0]),\n };\n } else if (\n // P2PKH\n chunks[0 + i].op === OP.OP_DUP &&\n chunks[1 + i].op === OP.OP_HASH160 &&\n chunks[2 + i].data?.length === 20 &&\n chunks[3 + i].op === OP.OP_EQUALVERIFY &&\n chunks[4 + i].op === OP.OP_CHECKSIG\n ) {\n return {\n cosigner: '',\n address: Utils.toBase58Check(chunks[2 + i].data || [], [0]),\n };\n }\n }\n });\n};\n\nexport const parseSyncToTxHistory = (sync: MneeSync, address: string, config: MNEEConfig): TxHistory | null => {\n const txType: TxType = sync.senders.includes(address) ? 'send' : 'receive';\n const txStatus: TxStatus = sync.height > 0 ? 'confirmed' : 'unconfirmed';\n\n if (!sync.rawtx) return null;\n\n const txArray = Utils.toArray(sync.rawtx, 'base64');\n const txHex = Utils.toHex(txArray);\n const tx = Transaction.fromHex(txHex);\n\n const outScripts = tx.outputs.map((output) => output.lockingScript);\n const mneeScripts = parseCosignerScripts(outScripts);\n const parsedOutScripts = outScripts.map(parseInscription);\n const mneeAddresses = mneeScripts.map((script) => script.address);\n\n const feeAddressIndex = mneeAddresses.indexOf(config.feeAddress);\n const sender = sync.senders[0]; // only one sender for now\n\n let fee = 0;\n const counterpartyAmounts = new Map<string, number>();\n\n parsedOutScripts.forEach((parsedScript, index) => {\n const content = parsedScript?.file?.content;\n if (!content) return;\n\n const inscriptionData = Utils.toUTF8(content);\n if (!inscriptionData) return;\n\n let inscriptionJson: MneeInscription;\n try {\n inscriptionJson = JSON.parse(inscriptionData);\n } catch (err) {\n console.error('Failed to parse inscription JSON:', err);\n return;\n }\n\n if (inscriptionJson.p !== 'bsv-20' || inscriptionJson.id !== config.tokenId) return;\n\n const inscriptionAmt = parseInt(inscriptionJson.amt, 10);\n if (Number.isNaN(inscriptionAmt)) return;\n\n if (feeAddressIndex === index && sender === address) {\n fee += inscriptionAmt;\n return;\n }\n\n const outAddr = mneeAddresses[index];\n const prevAmt = counterpartyAmounts.get(outAddr) || 0;\n counterpartyAmounts.set(outAddr, prevAmt + inscriptionAmt);\n });\n\n const amountSentToAddress = counterpartyAmounts.get(address) || 0;\n\n if (txType === 'send') {\n const senderAmt = counterpartyAmounts.get(sender) || 0;\n counterpartyAmounts.set(sender, senderAmt - amountSentToAddress);\n }\n\n let counterparties: { address: string; amount: number }[] = [];\n if (txType === 'receive') {\n counterparties = [{ address: sender, amount: amountSentToAddress }];\n } else {\n counterparties = Array.from(counterpartyAmounts.entries())\n .map(([addr, amt]) => ({ address: addr, amount: amt }))\n .filter((cp) => cp.address !== address && cp.address !== config.feeAddress && cp.amount > 0);\n }\n\n const totalCounterpartyAmount = counterparties.reduce((sum, cp) => sum + cp.amount, 0);\n\n return {\n txid: sync.txid,\n height: sync.height,\n type: txType,\n status: txStatus,\n amount: totalCounterpartyAmount,\n fee,\n score: sync.score,\n counterparties,\n };\n};\n","export const PUBLIC_PROD_MNEE_API_TOKEN = '92982ec1c0975f31979da515d46bae9f';\nexport const PUBLIC_SANDBOX_MNEE_API_TOKEN = '54f1fd1688ba66a58a67675b82feb93e';\nexport const PROD_TOKEN_ID = 'ae59f3b898ec61acbdb6cc7a245fabeded0c094bf046f35206a3aec60ef88127_0';\nexport const PROD_APPROVER = '020a177d6a5e6f3a8689acd2e313bd1cf0dcf5a243d1cc67b7218602aee9e04b2f';\nexport const PROD_ADDRESS = '1inHbiwj2jrEcZPiSYnfgJ8FmS1Bmk4Dh';\nexport const DEV_TOKEN_ID = '833a7720966a2a435db28d967385e8aa7284b6150ebb39482cc5228b73e1703f_0';\nexport const DEV_ADDRESS = '1A1QNEkLuvAALsmG4Me3iubP8zb5C6jpv5';\nexport const QA_TOKEN_ID = '55cde0733049a226fdb6abc387ee9dcd036e859f7cbc69ab90050c0435139f00_0';\nexport const QA_ADDRESS = '1BW7cejD27vDLiHsbK1Hvf1y4JTKvC1Yue';\nexport const STAGE_TOKEN_ID = '833a7720966a2a435db28d967385e8aa7284b6150ebb39482cc5228b73e1703f_0';\nexport const STAGE_ADDRESS = '1AZNdbFYBDFTAEgzZMfPzANxyNrpGJZAUY';\n\nexport const MNEE_PROXY_API_URL = 'https://proxy-api.mnee.net';\nexport const SANDBOX_MNEE_API_URL = 'https://sandbox-proxy-api.mnee.net';\n","import {\n Hash,\n P2PKH,\n PrivateKey,\n PublicKey,\n Script,\n Transaction,\n TransactionSignature,\n UnlockingScript,\n Utils,\n} from '@bsv/sdk';\nimport {\n Environment,\n GetSignatures,\n MNEEBalance,\n MNEEConfig,\n MneeInscription,\n SdkConfig,\n MNEEOperation,\n MneeSync,\n MNEEUtxo,\n ParseTxResponse,\n SendMNEE,\n SignatureRequest,\n SignatureResponse,\n TxHistory,\n TxHistoryResponse,\n TxOperation,\n AddressHistoryParams,\n} from './mnee.types.js';\nimport CosignTemplate from './mneeCosignTemplate.js';\nimport { applyInscription } from './utils/applyInscription.js';\nimport { parseCosignerScripts, parseInscription, parseSyncToTxHistory } from './utils/helper.js';\nimport {\n MNEE_PROXY_API_URL,\n SANDBOX_MNEE_API_URL,\n PROD_TOKEN_ID,\n PROD_ADDRESS,\n DEV_ADDRESS,\n QA_ADDRESS,\n STAGE_ADDRESS,\n PROD_APPROVER,\n QA_TOKEN_ID,\n DEV_TOKEN_ID,\n STAGE_TOKEN_ID,\n PUBLIC_PROD_MNEE_API_TOKEN,\n PUBLIC_SANDBOX_MNEE_API_TOKEN,\n} from './constants.js';\nexport class MNEEService {\n private mneeApiKey: string;\n private mneeConfig: MNEEConfig | undefined;\n private mneeApi: string;\n constructor(config: SdkConfig) {\n if (config.environment !== 'production' && config.environment !== 'sandbox') {\n throw new Error('Invalid environment. Must be either \"production\" or \"sandbox\"');\n }\n\n const isProd = config.environment === 'production';\n if (config?.apiKey) {\n this.mneeApiKey = config.apiKey;\n } else {\n this.mneeApiKey = isProd ? PUBLIC_PROD_MNEE_API_TOKEN : PUBLIC_SANDBOX_MNEE_API_TOKEN;\n }\n this.mneeApi = isProd ? MNEE_PROXY_API_URL : SANDBOX_MNEE_API_URL;\n this.getCosignerConfig();\n }\n\n public async getCosignerConfig(): Promise<MNEEConfig | undefined> {\n try {\n const response = await fetch(`${this.mneeApi}/v1/config?auth_token=${this.mneeApiKey}`, { method: 'GET' });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data: MNEEConfig = await response.json();\n this.mneeConfig = data;\n return data;\n } catch (error) {\n console.error('Failed to fetch config:', error);\n return undefined;\n }\n }\n\n public toAtomicAmount(amount: number): number {\n if (!this.mneeConfig) throw new Error('Config not fetched');\n return Math.round(amount * 10 ** this.mneeConfig.decimals);\n }\n\n public fromAtomicAmount(amount: number): number {\n if (!this.mneeConfig) throw new Error('Config not fetched');\n return amount / 10 ** this.mneeConfig.decimals;\n }\n\n private async createInscription(recipient: string, amount: number, config: MNEEConfig) {\n const inscriptionData = {\n p: 'bsv-20',\n op: 'transfer',\n id: config.tokenId,\n amt: amount.toString(),\n };\n return {\n lockingScript: applyInscription(\n new CosignTemplate().lock(recipient, PublicKey.fromString(config.approver)),\n {\n dataB64: Buffer.from(JSON.stringify(inscriptionData)).toString('base64'),\n contentType: 'application/bsv-20',\n },\n ),\n satoshis: 1,\n };\n }\n\n private async getUtxos(\n address: string | string[],\n ops: MNEEOperation[] = ['transfer', 'deploy+mint'],\n ): Promise<MNEEUtxo[]> {\n try {\n const arrayAddress = Array.isArray(address) ? address : [address];\n const response = await fetch(`${this.mneeApi}/v1/utxos?auth_token=${this.mneeApiKey}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(arrayAddress),\n });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data: MNEEUtxo[] = await response.json();\n if (ops.length) {\n return data.filter((utxo) =>\n ops.includes(utxo.data.bsv21.op.toLowerCase() as 'transfer' | 'burn' | 'deploy+mint'),\n );\n }\n return data;\n } catch (error) {\n console.error('Failed to fetch UTXOs:', error);\n return [];\n }\n }\n\n private async fetchRawTx(txid: string): Promise<Transaction> {\n const resp = await fetch(`${this.mneeApi}/v1/tx/${txid}?auth_token=${this.mneeApiKey}`);\n if (resp.status === 404) throw new Error('Transaction not found');\n if (resp.status !== 200) {\n throw new Error(`${resp.status} - Failed to fetch rawtx for txid: ${txid}`);\n }\n const { rawtx } = await resp.json();\n return Transaction.fromHex(Buffer.from(rawtx, 'base64').toString('hex'));\n }\n\n private async getSignatures(\n request: GetSignatures,\n privateKey: PrivateKey,\n ): Promise<{\n sigResponses?: SignatureResponse[];\n error?: { message: string; cause?: any };\n }> {\n try {\n const DEFAULT_SIGHASH_TYPE = 65;\n let tx: Transaction;\n switch (request.format) {\n case 'beef':\n tx = Transaction.fromHexBEEF(request.rawtx);\n break;\n case 'ef':\n tx = Transaction.fromHexEF(request.rawtx);\n break;\n default:\n tx = Transaction.fromHex(request.rawtx);\n break;\n }\n const sigResponses: SignatureResponse[] = request.sigRequests.flatMap((sigReq: SignatureRequest) => {\n return [privateKey].map((privKey: PrivateKey) => {\n const preimage = TransactionSignature.format({\n sourceTXID: sigReq.prevTxid,\n sourceOutputIndex: sigReq.outputIndex,\n sourceSatoshis: sigReq.satoshis,\n transactionVersion: tx.version,\n otherInputs: tx.inputs.filter((_, index) => index !== sigReq.inputIndex),\n inputIndex: sigReq.inputIndex,\n outputs: tx.outputs,\n inputSequence: tx.inputs[sigReq.inputIndex].sequence || 0,\n subscript: sigReq.script\n ? Script.fromHex(sigReq.script)\n : new P2PKH().lock(privKey.toPublicKey().toAddress()),\n lockTime: tx.lockTime,\n scope: sigReq.sigHashType || DEFAULT_SIGHASH_TYPE,\n });\n const rawSignature = privKey.sign(Hash.sha256(preimage));\n const sig = new TransactionSignature(\n rawSignature.r,\n rawSignature.s,\n sigReq.sigHashType || DEFAULT_SIGHASH_TYPE,\n );\n return {\n sig: Utils.toHex(sig.toChecksigFormat()),\n pubKey: privKey.toPublicKey().toString(),\n inputIndex: sigReq.inputIndex,\n sigHashType: sigReq.sigHashType || DEFAULT_SIGHASH_TYPE,\n csIdx: sigReq.csIdx,\n };\n });\n });\n return Promise.resolve({ sigResponses });\n } catch (err: any) {\n console.error('getSignatures error', err);\n return {\n error: {\n message: err.message ?? 'unknown',\n cause: err.cause,\n },\n };\n }\n }\n\n public async transfer(request: SendMNEE[], wif: string): Promise<{ txid?: string; rawtx?: string; error?: string }> {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n\n const totalAmount = request.reduce((sum, req) => sum + req.amount, 0);\n if (totalAmount <= 0) return { error: 'Invalid amount' };\n const totalAtomicTokenAmount = this.toAtomicAmount(totalAmount);\n\n const privateKey = PrivateKey.fromWif(wif);\n const address = privateKey.toAddress();\n const utxos = await this.getUtxos(address);\n const totalUtxoAmount = utxos.reduce((sum, utxo) => sum + (utxo.data.bsv21.amt || 0), 0);\n if (totalUtxoAmount < totalAtomicTokenAmount) {\n return { error: 'Insufficient MNEE balance' };\n }\n\n const fee =\n request.find((req) => req.address === config.burnAddress) !== undefined\n ? 0\n : config.fees.find(\n (fee: { min: number; max: number }) =>\n totalAtomicTokenAmount >= fee.min && totalAtomicTokenAmount <= fee.max,\n )?.fee;\n if (fee === undefined) return { error: 'Fee ranges inadequate' };\n\n const tx = new Transaction(1, [], [], 0);\n let tokensIn = 0;\n const signingAddresses: string[] = [];\n let changeAddress = '';\n\n while (tokensIn < totalAtomicTokenAmount + fee) {\n const utxo = utxos.shift();\n if (!utxo) return { error: 'Insufficient MNEE balance' };\n\n const sourceTransaction = await this.fetchRawTx(utxo.txid);\n if (!sourceTransaction) return { error: 'Failed to fetch source transaction' };\n\n signingAddresses.push(utxo.owners[0]);\n changeAddress = changeAddress || utxo.owners[0];\n tx.addInput({\n sourceTXID: utxo.txid,\n sourceOutputIndex: utxo.vout,\n sourceTransaction,\n unlockingScript: new UnlockingScript(),\n });\n tokensIn += utxo.data.bsv21.amt;\n }\n\n for (const req of request) {\n tx.addOutput(await this.createInscription(req.address, this.toAtomicAmount(req.amount), config));\n }\n if (fee > 0) tx.addOutput(await this.createInscription(config.feeAddress, fee, config));\n\n const change = tokensIn - totalAtomicTokenAmount - fee;\n if (change > 0) {\n tx.addOutput(await this.createInscription(changeAddress, change, config));\n }\n\n const sigRequests: SignatureRequest[] = tx.inputs.map((input, index) => {\n if (!input.sourceTXID) throw new Error('Source TXID is undefined');\n return {\n prevTxid: input.sourceTXID,\n outputIndex: input.sourceOutputIndex,\n inputIndex: index,\n address: signingAddresses[index],\n script: input.sourceTransaction?.outputs[input.sourceOutputIndex].lockingScript.toHex(),\n satoshis: input.sourceTransaction?.outputs[input.sourceOutputIndex].satoshis || 1,\n sigHashType:\n TransactionSignature.SIGHASH_ALL |\n TransactionSignature.SIGHASH_ANYONECANPAY |\n TransactionSignature.SIGHASH_FORKID,\n };\n });\n\n const rawtx = tx.toHex();\n const res = await this.getSignatures({ rawtx, sigRequests }, privateKey);\n if (!res?.sigResponses) return { error: 'Failed to get signatures' };\n\n for (const sigResponse of res.sigResponses) {\n tx.inputs[sigResponse.inputIndex].unlockingScript = new Script()\n .writeBin(Utils.toArray(sigResponse.sig, 'hex'))\n .writeBin(Utils.toArray(sigResponse.pubKey, 'hex'));\n }\n\n const base64Tx = Utils.toBase64(tx.toBinary());\n const response = await fetch(`${this.mneeApi}/v1/transfer?auth_token=${this.mneeApiKey}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ rawtx: base64Tx }),\n });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const { rawtx: responseRawtx } = await response.json();\n if (!responseRawtx) return { error: 'Failed to broadcast transaction' };\n\n const decodedBase64AsBinary = Utils.toArray(responseRawtx, 'base64');\n const tx2 = Transaction.fromBinary(decodedBase64AsBinary);\n\n return { txid: tx2.id('hex'), rawtx: Utils.toHex(decodedBase64AsBinary) };\n } catch (error) {\n let errorMessage = 'Transaction submission failed';\n if (error instanceof Error) {\n errorMessage = error.message;\n if (error.message.includes('HTTP error')) {\n // Add more specific error handling if needed based on response status\n console.error('HTTP error details:', error);\n }\n }\n console.error('Failed to transfer tokens:', errorMessage);\n return { error: errorMessage };\n }\n }\n\n public async getBalance(address: string): Promise<MNEEBalance> {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n const utxos = await this.getUtxos(address);\n const balance = utxos.reduce((acc, utxo) => {\n if (utxo.data.bsv21.op === 'transfer') {\n acc += utxo.data.bsv21.amt;\n }\n return acc;\n }, 0);\n const decimalAmount = this.fromAtomicAmount(balance);\n return { address, amount: balance, decimalAmount };\n } catch (error) {\n console.error('Failed to fetch balance:', error);\n return { address, amount: 0, decimalAmount: 0 };\n }\n }\n\n public async getBalances(addresses: string[]): Promise<MNEEBalance[]> {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n const utxos = await this.getUtxos(addresses);\n return addresses.map((addr) => {\n const addressUtxos = utxos.filter((utxo) => utxo.owners.includes(addr));\n const balance = addressUtxos.reduce((acc, utxo) => {\n if (utxo.data.bsv21.op === 'transfer') {\n acc += utxo.data.bsv21.amt;\n }\n return acc;\n }, 0);\n return { address: addr, amount: balance, decimalAmount: this.fromAtomicAmount(balance) };\n });\n } catch (error) {\n console.error('Failed to fetch balances:', error);\n return addresses.map((addr) => ({ address: addr, amount: 0, decimalAmount: 0 }));\n }\n }\n\n public async validateMneeTx(rawTx: string, request?: SendMNEE[]) {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n const tx = Transaction.fromHex(rawTx);\n const scripts = tx.outputs.map((output) => output.lockingScript);\n const parsedScripts = parseCosignerScripts(scripts);\n\n if (!request) {\n parsedScripts.forEach((parsed) => {\n if (parsed?.cosigner !== '' && parsed?.cosigner !== config.approver) {\n throw new Error('Invalid or missing cosigner');\n }\n });\n } else {\n request.forEach((req, idx) => {\n const { address, amount } = req;\n const cosigner = parsedScripts.find((parsed) => parsed?.cosigner === config.approver);\n if (!cosigner) {\n throw new Error(`Cosigner not found for address: ${address} at index: ${idx}`);\n }\n\n const addressFromScript = parsedScripts.find((parsed) => parsed?.address === address);\n if (!addressFromScript) {\n throw new Error(`Address not found in script for address: ${address} at index: ${idx}`);\n }\n const script = tx.outputs[idx].lockingScript;\n const inscription = parseInscription(script);\n const content = inscription?.file?.content;\n if (!content) throw new Error('Invalid inscription content');\n const inscriptionData = Utils.toUTF8(content);\n if (!inscriptionData) throw new Error('Invalid inscription content');\n const inscriptionJson: MneeInscription = JSON.parse(inscriptionData);\n if (inscriptionJson.p !== 'bsv-20') throw new Error(`Invalid bsv 20 protocol: ${inscriptionJson.p}`);\n if (inscriptionJson.op !== 'transfer') throw new Error(`Invalid operation: ${inscriptionJson.op}`);\n if (inscriptionJson.id !== config.tokenId) throw new Error(`Invalid token id: ${inscriptionJson.id}`);\n if (inscriptionJson.amt !== this.toAtomicAmount(amount).toString()) {\n throw new Error(`Invalid amount: ${inscriptionJson.amt}`);\n }\n });\n }\n\n return true;\n } catch (error) {\n console.error(error);\n return false;\n }\n }\n\n private async getMneeSyncs(\n addresses: string | string[],\n fromScore = 0,\n limit = 100,\n ): Promise<{ address: string; syncs: MneeSync[] }[]> {\n try {\n const addressArray = Array.isArray(addresses) ? addresses : [addresses];\n const response = await fetch(\n `${this.mneeApi}/v1/sync?auth_token=${this.mneeApiKey}&from=${fromScore}&limit=${limit}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(addressArray),\n },\n );\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data: MneeSync[] = await response.json();\n\n // Group syncs by address\n const syncsByAddress = addressArray.map((address) => {\n const filteredSyncs = data.filter((sync) => sync.senders.includes(address) || sync.receivers.includes(address));\n return { address, syncs: filteredSyncs };\n });\n\n return syncsByAddress;\n } catch (error) {\n console.error('Failed to fetch syncs:', error);\n return Array.isArray(addresses)\n ? addresses.map((address) => ({ address, syncs: [] }))\n : [{ address: addresses, syncs: [] }];\n }\n }\n\n public async getRecentTxHistory(address: string, fromScore?: number, limit?: number): Promise<TxHistoryResponse> {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n\n const syncsByAddress = await this.getMneeSyncs(address, fromScore, limit);\n const { syncs } = syncsByAddress[0]; // We're only requesting one address\n\n if (!syncs || syncs.length === 0) return { address, history: [], nextScore: fromScore || 0 };\n\n const txHistory: TxHistory[] = [];\n for (const sync of syncs) {\n const historyItem = parseSyncToTxHistory(sync, address, config);\n if (historyItem) {\n txHistory.push(historyItem);\n }\n }\n\n const nextScore = txHistory.length > 0 ? txHistory[txHistory.length - 1].score : fromScore || 0;\n\n if (limit && txHistory.length > limit) {\n return {\n address,\n history: txHistory.slice(0, limit),\n nextScore,\n };\n }\n\n return {\n address,\n history: txHistory,\n nextScore,\n };\n } catch (error) {\n console.error('Failed to fetch tx history:', error);\n return { address, history: [], nextScore: fromScore || 0 };\n }\n }\n\n public async getRecentTxHistories(params: AddressHistoryParams[]): Promise<TxHistoryResponse[]> {\n try {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n\n // Group addressParams by fromScore and limit to batch requests efficiently\n const groupedParams: Record<string, AddressHistoryParams[]> = {};\n params.forEach((param) => {\n const key = `${param.fromScore || 0}:${param.limit || 100}`;\n if (!groupedParams[key]) {\n groupedParams[key] = [];\n }\n groupedParams[key].push(param);\n });\n\n // Process each group in parallel\n const groupPromises = Object.entries(groupedParams).map(async ([key, addressParams]) => {\n const [fromScore, limit] = key.split(':').map(Number);\n const addresses = addressParams.map((p) => p.address);\n\n const syncsByAddress = await this.getMneeSyncs(addresses, fromScore, limit);\n\n // Process each address's syncs\n return syncsByAddress.map(({ address, syncs }) => {\n const param = addressParams.find((p) => p.address === address);\n if (!syncs || syncs.length === 0) {\n return {\n address,\n history: [],\n nextScore: param?.fromScore || 0,\n };\n }\n\n const txHistory: TxHistory[] = [];\n for (const sync of syncs) {\n const historyItem = parseSyncToTxHistory(sync, address, config);\n if (historyItem) {\n txHistory.push(historyItem);\n }\n }\n\n const nextScore = txHistory.length > 0 ? txHistory[txHistory.length - 1].score : param?.fromScore || 0;\n const paramLimit = param?.limit;\n\n if (paramLimit && txHistory.length > paramLimit) {\n return {\n address,\n history: txHistory.slice(0, paramLimit),\n nextScore,\n };\n }\n\n return {\n address,\n history: txHistory,\n nextScore,\n };\n });\n });\n\n // Flatten the results\n const results = await Promise.all(groupPromises);\n return results.flat();\n } catch (error) {\n console.error('Failed to fetch tx histories:', error);\n return params.map(({ address, fromScore }) => ({\n address,\n history: [],\n nextScore: fromScore || 0,\n }));\n }\n }\n\n private async parseTransaction(tx: Transaction, config: MNEEConfig): Promise<ParseTxResponse> {\n const txid = tx.id('hex');\n const outScripts = tx.outputs.map((output) => output.lockingScript);\n const sourceTxs = tx.inputs.map((input) => {\n return { txid: input.sourceTXID, vout: input.sourceOutputIndex };\n });\n\n let inputs = [];\n let outputs = [];\n let inputTotal = 0n;\n let outputTotal = 0n;\n let environment: Environment = 'production';\n let type: TxOperation = 'transfer';\n for (const sourceTx of sourceTxs) {\n if (!sourceTx.txid) continue;\n const fetchedTx = await this.fetchRawTx(sourceTx.txid);\n const output = fetchedTx.outputs[sourceTx.vout];\n const parsedCosigner = parseCosignerScripts([output.lockingScript])[0];\n if (parsedCosigner?.address === config.mintAddress) {\n type = txid === config.tokenId.split('_')[0] ? 'deploy' : 'mint';\n }\n const insc = parseInscription(output.lockingScript);\n const content = insc?.file?.content;\n if (!content) continue;\n const inscriptionData = Utils.toUTF8(content);\n if (!inscriptionData) continue;\n const inscriptionJson: MneeInscription = JSON.parse(inscriptionData);\n if (inscriptionJson) {\n const isProdToken = inscriptionJson.id === PROD_TOKEN_ID;\n const isProdApprover = parsedCosigner.cosigner === PROD_APPROVER;\n const isEmptyCosigner = parsedCosigner.cosigner === '';\n const isMint = inscriptionJson.op === 'deploy+mint';\n const isProdAddress = parsedCosigner.address === PROD_ADDRESS;\n const isDevAddress = parsedCosigner.address === DEV_ADDRESS;\n const isQaAddress = parsedCosigner.address === QA_ADDRESS;\n const isStageAddress = parsedCosigner.address === STAGE_ADDRESS;\n\n if (!isProdToken || !isProdApprover) {\n if (isEmptyCosigner && isMint && isProdAddress) {\n environment = 'production';\n type = 'mint';\n } else {\n environment = 'sandbox';\n }\n }\n\n if (type === 'transfer' && (isProdAddress || isDevAddress || isQaAddress || isStageAddress)) {\n type = 'mint';\n }\n\n inputTotal += BigInt(inscriptionJson.amt);\n inputs.push({\n address: parsedCosigner.address,\n amount: parseInt(inscriptionJson.amt),\n });\n }\n }\n\n for (const script of outScripts) {\n const parsedCosigner = parseCosignerScripts([script])[0];\n const insc = parseInscription(script);\n const content = insc?.file?.content;\n if (!content) continue;\n const inscriptionData = Utils.toUTF8(content);\n if (!inscriptionData) continue;\n const inscriptionJson = JSON.parse(inscriptionData);\n if (inscriptionJson) {\n if (inscriptionJson.op === 'burn') {\n type = 'burn';\n }\n const isProdToken = inscriptionJson.id === PROD_TOKEN_ID;\n const isProdApprover = parsedCosigner.cosigner === PROD_APPROVER;\n const isEmptyCosigner = parsedCosigner.cosigner === '';\n const isProdAddress = parsedCosigner.address === PROD_ADDRESS;\n const isDeploy = inscriptionJson.op === 'deploy+mint';\n\n if (isDeploy) {\n type = 'deploy';\n }\n\n if (!isProdToken || !isProdApprover) {\n if (isEmptyCosigner && isProdAddress) {\n environment = 'production';\n } else {\n environment = 'sandbox';\n }\n }\n outputTotal += BigInt(inscriptionJson.amt);\n outputs.push({\n address: parsedCosigner.address,\n amount: parseInt(inscriptionJson.amt),\n });\n }\n }\n\n if (type !== 'deploy' && inputTotal !== outputTotal) {\n throw new Error('Inputs and outputs are not equal');\n }\n\n if (txid === PROD_TOKEN_ID.split('_')[0]) {\n environment = 'production';\n } else if ([DEV_TOKEN_ID, QA_TOKEN_ID, STAGE_TOKEN_ID].some((id) => txid === id.split('_')[0])) {\n environment = 'sandbox';\n }\n\n return { txid, environment, type, inputs, outputs };\n }\n\n public async parseTx(txid: string): Promise<ParseTxResponse> {\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n const tx = await this.fetchRawTx(txid);\n if (!tx) throw new Error('Failed to fetch transaction');\n return await this.parseTransaction(tx, config);\n }\n\n public async parseTxFromRawTx(rawTxHex: string): Promise<ParseTxResponse> {\n const tx = Transaction.fromHex(rawTxHex);\n const config = this.mneeConfig || (await this.getCosignerConfig());\n if (!config) throw new Error('Config not fetched');\n return await this.parseTransaction(tx, config);\n }\n}\n","import { MNEEService } from './mneeService.js';\nimport {\n MNEEBalance,\n MNEEConfig,\n SdkConfig,\n ParseTxResponse,\n SendMNEE,\n TransferResponse,\n TxHistoryResponse,\n AddressHistoryParams,\n} from './mnee.types.js';\nexport * from './mnee.types.js';\n\nexport interface MneeInterface {\n config(): Promise<MNEEConfig | undefined>;\n balance(address: string): Promise<MNEEBalance>;\n balances(addresses: string[]): Promise<MNEEBalance[]>;\n validateMneeTx(rawtx: string, request?: SendMNEE[]): Promise<boolean>;\n transfer(request: SendMNEE[], wif: string): Promise<TransferResponse>;\n toAtomicAmount(amount: number): number;\n fromAtomicAmount(amount: number): number;\n recentTxHistory(address: string, fromScore?: number, limit?: number): Promise<TxHistoryResponse>;\n recentTxHistories(params: AddressHistoryParams[]): Promise<TxHistoryResponse[]>;\n parseTx(txid: string): Promise<ParseTxResponse>;\n parseTxFromRawTx(rawTxHex: string): Promise<ParseTxResponse>;\n}\n\n/**\n * Represents the Mnee class that provides methods to interact with the MNEE service.\n */\nexport default class Mnee implements MneeInterface {\n private service: MNEEService;\n\n constructor(config: SdkConfig) {\n this.service = new MNEEService(config);\n }\n\n /**\n * Validates an MNEE transaction.\n *\n * @param rawtx - The raw transaction to validate.\n * @param request - An array of SendMNEE objects representing the transfer details. Use this parameter to validate the transaction against the specified transfer details. If it is not provided, it will only validate that the transaction is well-formed with the cosigner.\n * @returns A promise that resolves to a boolean indicating whether the transaction is valid.\n */\n async validateMneeTx(rawtx: string, request?: SendMNEE[]): Promise<boolean> {\n return this.service.validateMneeTx(rawtx, request);\n }\n\n /**\n * Converts a given amount to its atomic representation based on the specified number.\n *\n * @param amount - The amount to be converted.\n * @returns The atomic representation of the given amount.\n *\n * @example\n * ```typescript\n * toAtomicAmount(1.5); // 150000\n * ```\n */\n toAtomicAmount(amount: number): number {\n return this.service.toAtomicAmount(amount);\n }\n\n /**\n * Converts a given atomic amount to its human-readable representation.\n *\n * @param amount - The atomic amount to be converted.\n * @returns The human-readable representation of the given atomic amount.\n *\n * @example\n * ```typescript\n * fromAtomicAmount(150000); // 1.5\n * ```\n */\n fromAtomicAmount(amount: number): number {\n return this.service.fromAtomicAmount(amount);\n }\n\n /**\n * Retrieves the configuration for the MNEE service.\n *\n * @returns {Promise<MNEEConfig | undefined>} A promise that resolves to the MNEE configuration object,\n * or undefined if the configuration could not be retrieved.\n */\n async config(): Promise<MNEEConfig | undefined> {\n return this.service.getCosignerConfig();\n }\n\n /**\n * Retrieves the balance for a given address.\n *\n * @param address - The address to retrieve the balance for.\n * @returns A promise that resolves to a `MNEEBalance` object containing the balance details.\n */\n async balance(address: string): Promise<MNEEBalance> {\n return this.service.getBalance(address);\n }\n\n /**\n * Retrieves the balances for multiple addresses.\n *\n * @param addresses - An array of addresses to retrieve the balances for.\n * @returns A promise that resolves to an array of `MNEEBalance` objects containing the balance details for each address.\n */\n async balances(addresses: string[]): Promise<MNEEBalance[]> {\n return this.service.getBalances(addresses);\n }\n\n /**\n * Transfers the specified MNEE tokens using the provided WIF (Wallet Import Format) key.\n *\n * @param {SendMNEE[]} request - An array of SendMNEE objects representing the transfer details.\n * @param {string} wif - The Wallet Import Format key used to authorize the transfer.\n * @returns {Promise<TransferResponse>} A promise that resolves to a TransferResponse object containing the result of the transfer.\n */\n async transfer(request: SendMNEE[], wif: string): Promise<TransferResponse> {\n return this.service.transfer(request, wif);\n }\n\n /**\n * Retrieves the recent transaction history for a given address.\n *\n * @param address - The address to retrieve the transaction history for.\n * @param fromScore - The starting score to retrieve the transaction history fr