@hwlt/era-connect 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,15 +68,15 @@ verifyEvmSignature({ signData: rlpBytes, dataType: 1,
68
68
 
69
69
  ## Chain support
70
70
 
71
- The ERA device is multichain; the SDK ships dedicated modules for four chains
72
- today and speaks the rest through the same primitives.
71
+ The ERA device is multichain, and the SDK ships a dedicated module for every
72
+ chain family the current firmware supports.
73
73
 
74
74
  **Dedicated modules:**
75
75
 
76
76
  | Chain | Sign transaction | Sign message | Subpath |
77
77
  |---|---|---|---|
78
78
  | EVM (all chains) | `eth-sign-request` | personal_sign / EIP-712 | `@hwlt/era-connect/evm` |
79
- | Bitcoin | `crypto-psbt` (PSBT v0) | legacy P2PKH only | `@hwlt/era-connect/btc` |
79
+ | Bitcoin | `crypto-psbt` (PSBT v0) | BIP-44/49/84 on firmware 2.1.0+ (Taproot refused); legacy P2PKH on older | `@hwlt/era-connect/btc` |
80
80
  | Solana | `sol-sign-request` | off-chain messages | `@hwlt/era-connect/solana` |
81
81
  | Tron | structured envelope (any contract via `rawData`) | UTF-8 in `rawData` | `@hwlt/era-connect/tron` |
82
82
  | TON | `ton-sign-request` (BoC root-hash signing) | TON Connect proof | `@hwlt/era-connect/ton` |
@@ -1,7 +1,7 @@
1
1
  const require_shared = require("./shared-Cu2yynP9.cjs");
2
2
  const require_keypath = require("./keypath-pzm_YIfN.cjs");
3
3
  const require_cashaddr = require("./cashaddr-Ci0_8ITN.cjs");
4
- const require_gzip = require("./gzip-CLzDX7AZ.cjs");
4
+ const require_gzip = require("./gzip-D_OGwD99.cjs");
5
5
  const require_messages = require("./messages-orAQ52CI.cjs");
6
6
  //#region src/chains/bch.ts
7
7
  const MAX_COMPRESSED_BYTES = 8192;
@@ -136,4 +136,4 @@ Object.defineProperty(exports, "BchChain", {
136
136
  }
137
137
  });
138
138
 
139
- //# sourceMappingURL=bch-CpLACx0j.cjs.map
139
+ //# sourceMappingURL=bch-dcp3zT3g.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bch-CpLACx0j.cjs","names":["EraSdkError","bytesToHex","resolveContext","resolveRequestId","normalizeXfp","decodeCashAddr","encodeCashAddr","encodeBchSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bch-dcp3zT3g.cjs","names":["EraSdkError","bytesToHex","resolveContext","resolveRequestId","normalizeXfp","decodeCashAddr","encodeCashAddr","encodeBchSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { B as cbMap, H as cbText, K as EraSdkError, N as asBytes, W as mapGet, _ as Ur, c as resolveRequestId, g as uuidStringify, h as normalizeRequestId, l as toUr, n as makeSignRequest, o as requireUrType, r as requireReplyMap, s as resolveContext, w as bytesToHex, x as cborEncode, z as cbBytes } from "./shared-Bbgejrus.js";
2
2
  import { i as parsePath, r as normalizeXfp, s as xfpToHex } from "./keypath-Bvv_cfHt.js";
3
3
  import { n as decodeCashAddr, r as encodeCashAddr } from "./cashaddr-BIzawizX.js";
4
- import { n as gzipCompress, t as gunzipCapped } from "./gzip-DyZLtgJJ.js";
4
+ import { n as gzipCompress, t as gunzipCapped } from "./gzip-Dn-PQk1E.js";
5
5
  import { n as encodeBchSignRequestProto, t as decodeSignResultProto } from "./messages--XbVef_k.js";
6
6
  //#region src/chains/bch.ts
7
7
  const MAX_COMPRESSED_BYTES = 8192;
@@ -131,4 +131,4 @@ function signIdToBytes(signId) {
131
131
  //#endregion
132
132
  export { BchChain as t };
133
133
 
134
- //# sourceMappingURL=bch-dMESlQfv.js.map
134
+ //# sourceMappingURL=bch-yVz0AHvn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bch-dMESlQfv.js","names":["UrValue"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"bch-yVz0AHvn.js","names":["UrValue"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
package/dist/bch.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_shared = require("./shared-Cu2yynP9.cjs");
3
3
  const require_cashaddr = require("./cashaddr-Ci0_8ITN.cjs");
4
- const require_bch = require("./bch-CpLACx0j.cjs");
4
+ const require_bch = require("./bch-dcp3zT3g.cjs");
5
5
  exports.AnimatedUr = require_shared.AnimatedUr;
6
6
  exports.BchChain = require_bch.BchChain;
7
7
  exports.CASHADDR_PREFIX = require_cashaddr.CASHADDR_PREFIX;
package/dist/bch.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { K as EraSdkError, _ as Ur, d as UrScanner, f as AnimatedUr, u as TypedUrScanner } from "./shared-Bbgejrus.js";
2
2
  import { n as decodeCashAddr, r as encodeCashAddr, t as CASHADDR_PREFIX } from "./cashaddr-BIzawizX.js";
3
- import { t as BchChain } from "./bch-dMESlQfv.js";
3
+ import { t as BchChain } from "./bch-yVz0AHvn.js";
4
4
  export { AnimatedUr, BchChain, CASHADDR_PREFIX, EraSdkError, TypedUrScanner, Ur, UrScanner, decodeCashAddr, encodeCashAddr };
@@ -29,6 +29,7 @@ function gzipCompress(data) {
29
29
  function gunzipCapped(data, maxOutputBytes) {
30
30
  if (data.length < MIN_GZIP_BYTES) throw new require_shared.EraSdkError("gzip-error", "compressed payload is too short to be a gzip stream");
31
31
  if (data[0] !== 31 || data[1] !== 139) throw new require_shared.EraSdkError("gzip-error", "compressed payload is not a gzip stream");
32
+ if ((data[3] & 224) !== 0) throw new require_shared.EraSdkError("gzip-error", "compressed payload has reserved header flag bits set");
32
33
  const n = data.length;
33
34
  const isize = (data[n - 4] | data[n - 3] << 8 | data[n - 2] << 16 | data[n - 1] << 24) >>> 0;
34
35
  if (isize > maxOutputBytes) throw new require_shared.EraSdkError("gzip-error", `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`);
@@ -76,4 +77,4 @@ Object.defineProperty(exports, "gzipCompress", {
76
77
  }
77
78
  });
78
79
 
79
- //# sourceMappingURL=gzip-CLzDX7AZ.cjs.map
80
+ //# sourceMappingURL=gzip-D_OGwD99.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gzip-D_OGwD99.cjs","names":["gzipSync","EraSdkError","Gunzip","concatBytes","crc32"],"sources":["../src/tron-proto/gzip.ts"],"sourcesContent":["import { Gunzip, gzipSync } from 'fflate';\nimport { concatBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { crc32 } from '../ur/crc32';\n\n/** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */\nconst MIN_GZIP_BYTES = 18;\n\n/** Bytes fed to the inflater per step; bounds the overshoot past the cap. */\nconst SLICE_BYTES = 1024;\n\n/** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */\nexport function gzipCompress(data: Uint8Array): Uint8Array {\n return gzipSync(data, { level: 9, mtime: 0 });\n}\n\n/**\n * Inflate with a hard output ceiling.\n *\n * A one-shot gunzip allocates the entire output before anyone can refuse it —\n * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask\n * for an arbitrary allocation. The decoder is therefore driven in slices and\n * the output counted as it arrives; the moment the total would pass the cap,\n * feeding stops and the reply is refused.\n *\n * The trailer's ISIZE (declared inflated length) is used twice: as a cheap\n * refusal of honest bombs before any work, and as a truncation check at the\n * end — an inflater hands back a partial buffer for a truncated stream\n * without erroring, and a genuine device reply always declares honestly.\n */\nexport function gunzipCapped(data: Uint8Array, maxOutputBytes: number): Uint8Array {\n if (data.length < MIN_GZIP_BYTES) {\n throw new EraSdkError('gzip-error', 'compressed payload is too short to be a gzip stream');\n }\n if (data[0] !== 0x1f || data[1] !== 0x8b) {\n throw new EraSdkError('gzip-error', 'compressed payload is not a gzip stream');\n }\n // RFC 1952 requires the reserved FLG bits to be zero; the streaming\n // inflater below ignores them, so check here — the Dart SDK refuses these\n // and both sides must agree on every accept/refuse decision.\n if ((data[3]! & 0xe0) !== 0) {\n throw new EraSdkError('gzip-error', 'compressed payload has reserved header flag bits set');\n }\n const n = data.length;\n const isize =\n (data[n - 4]! | (data[n - 3]! << 8) | (data[n - 2]! << 16) | (data[n - 1]! << 24)) >>> 0;\n if (isize > maxOutputBytes) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`,\n );\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflowed = false;\n let malformed: string | null = null;\n\n const gunzip = new Gunzip((chunk) => {\n if (overflowed) return;\n if (total + chunk.length > maxOutputBytes) {\n overflowed = true;\n return;\n }\n chunks.push(chunk);\n total += chunk.length;\n });\n\n try {\n for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {\n const end = Math.min(offset + SLICE_BYTES, n);\n const isLast = end === n;\n gunzip.push(data.slice(offset, end), isLast);\n }\n } catch (e) {\n malformed = (e as Error).message ?? 'inflate error';\n }\n\n if (overflowed) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflates past the ${maxOutputBytes} byte ceiling`,\n );\n }\n if (malformed !== null) {\n throw new EraSdkError('gzip-error', `compressed payload is malformed: ${malformed}`);\n }\n const out = concatBytes(...chunks);\n if (out.length !== isize) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`,\n );\n }\n // The trailer CRC32 (little-endian, bytes n-8..n-5) must cover the inflated\n // output. The streaming inflater does not verify it, and the reference\n // implementation's native decoder does — without this check a corrupted\n // stream, or a CONCATENATED multi-member stream (whose final member's CRC\n // cannot cover the whole output), would be accepted here and refused there.\n const declaredCrc =\n (data[n - 8]! | (data[n - 7]! << 8) | (data[n - 6]! << 16) | (data[n - 5]! << 24)) >>> 0;\n if (crc32(out) !== declaredCrc) {\n throw new EraSdkError('gzip-error', 'compressed payload is malformed: CRC mismatch');\n }\n return out;\n}\n"],"mappings":";;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,cAAc;;AAGpB,SAAgB,aAAa,MAA8B;CACzD,QAAA,GAAOA,OAAAA,SAAAA,CAAS,MAAM;EAAE,OAAO;EAAG,OAAO;CAAE,CAAC;AAC9C;;;;;;;;;;;;;;;AAgBA,SAAgB,aAAa,MAAkB,gBAAoC;CACjF,IAAI,KAAK,SAAS,gBAChB,MAAM,IAAIC,eAAAA,YAAY,cAAc,qDAAqD;CAE3F,IAAI,KAAK,OAAO,MAAQ,KAAK,OAAO,KAClC,MAAM,IAAIA,eAAAA,YAAY,cAAc,yCAAyC;CAK/E,KAAK,KAAK,KAAM,SAAU,GACxB,MAAM,IAAIA,eAAAA,YAAY,cAAc,sDAAsD;CAE5F,MAAM,IAAI,KAAK;CACf,MAAM,SACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,QAAQ,gBACV,MAAM,IAAIA,eAAAA,YACR,cACA,+BAA+B,MAAM,mBAAmB,eAAe,cACzE;CAGF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,IAAI,YAA2B;CAE/B,MAAM,SAAS,IAAIC,OAAAA,QAAQ,UAAU;EACnC,IAAI,YAAY;EAChB,IAAI,QAAQ,MAAM,SAAS,gBAAgB;GACzC,aAAa;GACb;EACF;EACA,OAAO,KAAK,KAAK;EACjB,SAAS,MAAM;CACjB,CAAC;CAED,IAAI;EACF,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,CAAC,YAAY,UAAU,aAAa;GACrE,MAAM,MAAM,KAAK,IAAI,SAAS,aAAa,CAAC;GAC5C,MAAM,SAAS,QAAQ;GACvB,OAAO,KAAK,KAAK,MAAM,QAAQ,GAAG,GAAG,MAAM;EAC7C;CACF,SAAS,GAAG;EACV,YAAa,EAAY,WAAW;CACtC;CAEA,IAAI,YACF,MAAM,IAAID,eAAAA,YACR,cACA,wCAAwC,eAAe,cACzD;CAEF,IAAI,cAAc,MAChB,MAAM,IAAIA,eAAAA,YAAY,cAAc,oCAAoC,WAAW;CAErF,MAAM,MAAME,eAAAA,YAAY,GAAG,MAAM;CACjC,IAAI,IAAI,WAAW,OACjB,MAAM,IAAIF,eAAAA,YACR,cACA,kCAAkC,IAAI,OAAO,sBAAsB,MAAM,0BAC3E;CAOF,MAAM,eACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAIG,eAAAA,MAAM,GAAG,MAAM,aACjB,MAAM,IAAIH,eAAAA,YAAY,cAAc,+CAA+C;CAErF,OAAO;AACT"}
@@ -29,6 +29,7 @@ function gzipCompress(data) {
29
29
  function gunzipCapped(data, maxOutputBytes) {
30
30
  if (data.length < MIN_GZIP_BYTES) throw new EraSdkError("gzip-error", "compressed payload is too short to be a gzip stream");
31
31
  if (data[0] !== 31 || data[1] !== 139) throw new EraSdkError("gzip-error", "compressed payload is not a gzip stream");
32
+ if ((data[3] & 224) !== 0) throw new EraSdkError("gzip-error", "compressed payload has reserved header flag bits set");
32
33
  const n = data.length;
33
34
  const isize = (data[n - 4] | data[n - 3] << 8 | data[n - 2] << 16 | data[n - 1] << 24) >>> 0;
34
35
  if (isize > maxOutputBytes) throw new EraSdkError("gzip-error", `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`);
@@ -65,4 +66,4 @@ function gunzipCapped(data, maxOutputBytes) {
65
66
  //#endregion
66
67
  export { gzipCompress as n, gunzipCapped as t };
67
68
 
68
- //# sourceMappingURL=gzip-DyZLtgJJ.js.map
69
+ //# sourceMappingURL=gzip-Dn-PQk1E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gzip-Dn-PQk1E.js","names":[],"sources":["../src/tron-proto/gzip.ts"],"sourcesContent":["import { Gunzip, gzipSync } from 'fflate';\nimport { concatBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { crc32 } from '../ur/crc32';\n\n/** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */\nconst MIN_GZIP_BYTES = 18;\n\n/** Bytes fed to the inflater per step; bounds the overshoot past the cap. */\nconst SLICE_BYTES = 1024;\n\n/** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */\nexport function gzipCompress(data: Uint8Array): Uint8Array {\n return gzipSync(data, { level: 9, mtime: 0 });\n}\n\n/**\n * Inflate with a hard output ceiling.\n *\n * A one-shot gunzip allocates the entire output before anyone can refuse it —\n * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask\n * for an arbitrary allocation. The decoder is therefore driven in slices and\n * the output counted as it arrives; the moment the total would pass the cap,\n * feeding stops and the reply is refused.\n *\n * The trailer's ISIZE (declared inflated length) is used twice: as a cheap\n * refusal of honest bombs before any work, and as a truncation check at the\n * end — an inflater hands back a partial buffer for a truncated stream\n * without erroring, and a genuine device reply always declares honestly.\n */\nexport function gunzipCapped(data: Uint8Array, maxOutputBytes: number): Uint8Array {\n if (data.length < MIN_GZIP_BYTES) {\n throw new EraSdkError('gzip-error', 'compressed payload is too short to be a gzip stream');\n }\n if (data[0] !== 0x1f || data[1] !== 0x8b) {\n throw new EraSdkError('gzip-error', 'compressed payload is not a gzip stream');\n }\n // RFC 1952 requires the reserved FLG bits to be zero; the streaming\n // inflater below ignores them, so check here — the Dart SDK refuses these\n // and both sides must agree on every accept/refuse decision.\n if ((data[3]! & 0xe0) !== 0) {\n throw new EraSdkError('gzip-error', 'compressed payload has reserved header flag bits set');\n }\n const n = data.length;\n const isize =\n (data[n - 4]! | (data[n - 3]! << 8) | (data[n - 2]! << 16) | (data[n - 1]! << 24)) >>> 0;\n if (isize > maxOutputBytes) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`,\n );\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflowed = false;\n let malformed: string | null = null;\n\n const gunzip = new Gunzip((chunk) => {\n if (overflowed) return;\n if (total + chunk.length > maxOutputBytes) {\n overflowed = true;\n return;\n }\n chunks.push(chunk);\n total += chunk.length;\n });\n\n try {\n for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {\n const end = Math.min(offset + SLICE_BYTES, n);\n const isLast = end === n;\n gunzip.push(data.slice(offset, end), isLast);\n }\n } catch (e) {\n malformed = (e as Error).message ?? 'inflate error';\n }\n\n if (overflowed) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflates past the ${maxOutputBytes} byte ceiling`,\n );\n }\n if (malformed !== null) {\n throw new EraSdkError('gzip-error', `compressed payload is malformed: ${malformed}`);\n }\n const out = concatBytes(...chunks);\n if (out.length !== isize) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`,\n );\n }\n // The trailer CRC32 (little-endian, bytes n-8..n-5) must cover the inflated\n // output. The streaming inflater does not verify it, and the reference\n // implementation's native decoder does — without this check a corrupted\n // stream, or a CONCATENATED multi-member stream (whose final member's CRC\n // cannot cover the whole output), would be accepted here and refused there.\n const declaredCrc =\n (data[n - 8]! | (data[n - 7]! << 8) | (data[n - 6]! << 16) | (data[n - 5]! << 24)) >>> 0;\n if (crc32(out) !== declaredCrc) {\n throw new EraSdkError('gzip-error', 'compressed payload is malformed: CRC mismatch');\n }\n return out;\n}\n"],"mappings":";;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,cAAc;;AAGpB,SAAgB,aAAa,MAA8B;CACzD,OAAO,SAAS,MAAM;EAAE,OAAO;EAAG,OAAO;CAAE,CAAC;AAC9C;;;;;;;;;;;;;;;AAgBA,SAAgB,aAAa,MAAkB,gBAAoC;CACjF,IAAI,KAAK,SAAS,gBAChB,MAAM,IAAI,YAAY,cAAc,qDAAqD;CAE3F,IAAI,KAAK,OAAO,MAAQ,KAAK,OAAO,KAClC,MAAM,IAAI,YAAY,cAAc,yCAAyC;CAK/E,KAAK,KAAK,KAAM,SAAU,GACxB,MAAM,IAAI,YAAY,cAAc,sDAAsD;CAE5F,MAAM,IAAI,KAAK;CACf,MAAM,SACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,QAAQ,gBACV,MAAM,IAAI,YACR,cACA,+BAA+B,MAAM,mBAAmB,eAAe,cACzE;CAGF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,IAAI,YAA2B;CAE/B,MAAM,SAAS,IAAI,QAAQ,UAAU;EACnC,IAAI,YAAY;EAChB,IAAI,QAAQ,MAAM,SAAS,gBAAgB;GACzC,aAAa;GACb;EACF;EACA,OAAO,KAAK,KAAK;EACjB,SAAS,MAAM;CACjB,CAAC;CAED,IAAI;EACF,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,CAAC,YAAY,UAAU,aAAa;GACrE,MAAM,MAAM,KAAK,IAAI,SAAS,aAAa,CAAC;GAC5C,MAAM,SAAS,QAAQ;GACvB,OAAO,KAAK,KAAK,MAAM,QAAQ,GAAG,GAAG,MAAM;EAC7C;CACF,SAAS,GAAG;EACV,YAAa,EAAY,WAAW;CACtC;CAEA,IAAI,YACF,MAAM,IAAI,YACR,cACA,wCAAwC,eAAe,cACzD;CAEF,IAAI,cAAc,MAChB,MAAM,IAAI,YAAY,cAAc,oCAAoC,WAAW;CAErF,MAAM,MAAM,YAAY,GAAG,MAAM;CACjC,IAAI,IAAI,WAAW,OACjB,MAAM,IAAI,YACR,cACA,kCAAkC,IAAI,OAAO,sBAAsB,MAAM,0BAC3E;CAOF,MAAM,eACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,MAAM,GAAG,MAAM,aACjB,MAAM,IAAI,YAAY,cAAc,+CAA+C;CAErF,OAAO;AACT"}
package/dist/index.cjs CHANGED
@@ -4,13 +4,13 @@ const require_keypath = require("./keypath-pzm_YIfN.cjs");
4
4
  const require_derive = require("./derive-BuNsfWlg.cjs");
5
5
  const require_btc = require("./btc-BxNww6kW.cjs");
6
6
  const require_cardano = require("./cardano-CzYswGzK.cjs");
7
- const require_bch = require("./bch-CpLACx0j.cjs");
7
+ const require_bch = require("./bch-dcp3zT3g.cjs");
8
8
  const require_cosmos = require("./cosmos-DDsgtUki.cjs");
9
9
  const require_evm = require("./evm-yfKOc2dy.cjs");
10
10
  const require_solana = require("./solana-BoQBy0hu.cjs");
11
11
  const require_sui = require("./sui-CYRn331O.cjs");
12
12
  const require_ton = require("./ton-CI2s9puH.cjs");
13
- const require_tron = require("./tron-DYPOg5cm.cjs");
13
+ const require_tron = require("./tron-C_tq-WiJ.cjs");
14
14
  const require_xrp = require("./xrp-BrHXUnX5.cjs");
15
15
  //#region src/registry/multi-accounts.ts
16
16
  /** UR types a device links a watch-only wallet with. */
package/dist/index.js CHANGED
@@ -3,13 +3,13 @@ import { a as parsePathComponents, i as parsePath, n as keypath304, o as pathEqu
3
3
  import { a as btcP2wpkhAddressFromPublicKey, c as evmAddressFromPublicKey, d as suiAddressFromPublicKey, f as tronAddressFromPublicKey, i as btcP2pkhAddressFromPublicKey, l as serializeExtendedPublicKey, n as bchAddressFromPublicKey, o as cardanoSoftDerivePath, r as btcNestedSegwitAddressFromPublicKey, s as derivePublicKey, t as ZPUB_VERSION, u as solanaAddressFromPublicKey } from "./derive-00JUOkmn.js";
4
4
  import { t as BtcChain } from "./btc-BmVgvvBd.js";
5
5
  import { n as parseWitnessSet, t as CardanoChain } from "./cardano-BCT55gwf.js";
6
- import { t as BchChain } from "./bch-dMESlQfv.js";
6
+ import { t as BchChain } from "./bch-yVz0AHvn.js";
7
7
  import { n as CosmosDataType, t as CosmosChain } from "./cosmos-jUHNxgWC.js";
8
8
  import { n as EvmDataType, t as EvmChain } from "./evm-CI1cuZsp.js";
9
9
  import { n as SolanaChain, t as SolSignType } from "./solana-BukH3AI3.js";
10
10
  import { n as suiIntentDigest, t as SuiChain } from "./sui-BtbPG6Xt.js";
11
11
  import { n as TonDataType, t as TonChain } from "./ton-BSPKU5B9.js";
12
- import { t as TronChain } from "./tron-BpUEYTPT.js";
12
+ import { t as TronChain } from "./tron-evMsrbLH.js";
13
13
  import { t as XrpChain } from "./xrp-C8LJO5b-.js";
14
14
  //#region src/registry/multi-accounts.ts
15
15
  /** UR types a device links a watch-only wallet with. */
@@ -1,6 +1,6 @@
1
1
  const require_shared = require("./shared-Cu2yynP9.cjs");
2
2
  const require_keypath = require("./keypath-pzm_YIfN.cjs");
3
- const require_gzip = require("./gzip-CLzDX7AZ.cjs");
3
+ const require_gzip = require("./gzip-D_OGwD99.cjs");
4
4
  const require_messages = require("./messages-orAQ52CI.cjs");
5
5
  //#region src/chains/tron.ts
6
6
  /**
@@ -98,4 +98,4 @@ Object.defineProperty(exports, "TronChain", {
98
98
  }
99
99
  });
100
100
 
101
- //# sourceMappingURL=tron-DYPOg5cm.cjs.map
101
+ //# sourceMappingURL=tron-C_tq-WiJ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tron-DYPOg5cm.cjs","names":["resolveContext","resolveRequestId","normalizeXfp","EraSdkError","encodeSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped","splitSignedTronTx"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":";;;;;;;;;;;AAyEA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAE3B,MAAM,cAAc,CAAC,sBAAsB;;;;;;;AAQ3C,IAAa,YAAb,MAAuB;CAGrB,YAAY,QAA2B;EACrC,KAAK,UAAUA,eAAAA,eAAe,MAAM;CACtC;;CAGA,oBAAoB,OAA+D;EACjF,MAAM,YAAYC,eAAAA,iBAAiB,KAAK,SAAS,MAAM,SAAS;EAChE,gBAAA,UAAU,MAAM,IAAI;EACpB,MAAM,MAAMC,gBAAAA,aAAa,MAAM,GAAG;EAClC,IAAI,MAAM,QAAQ,WAAW,GAC3B,MAAM,IAAIC,eAAAA,YAAY,iBAAiB,2BAA2B;EAEpE,IAAI,CAAC,oBAAoB,KAAK,MAAM,YAAY,IAAI,GAClD,MAAM,IAAIA,eAAAA,YACR,iBACA,8FACF;EAGF,MAAM,QAAQC,iBAAAA,uBAAuB;GAKnC,QAAQC,gBAAAA,SAAS,GAAG;GACpB,QAAQC,eAAAA,cAAc,SAAS;GAC/B,QAAQ,MAAM;GACd,WAAW,MAAM,aAAa;GAC9B,UAAU,MAAM,SAAS,YAAY;GACrC,OAAO,MAAM,SAAS,SAAS;GAC/B,iBAAiB,MAAM,SAAS;GAChC,MAAM,MAAM,SAAS;GACrB,IAAI,MAAM,SAAS;GACnB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,SAAS;GACtB,KAAK,MAAM,SAAS;GACpB,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB,CAAC;EAED,MAAM,KAAK,IAAIC,eAAAA,GACb,yBACAC,eAAAA,WACEC,eAAAA,MAAM,CACJ,CAAC,GAAGC,eAAAA,QAAQC,aAAAA,aAAa,KAAK,CAAC,CAAC,GAChC,CAAC,GAAGC,eAAAA,OAAO,MAAM,UAAU,KAAK,QAAQ,MAAM,CAAC,CACjD,CAAC,CACH,CACF;EACA,OAAOC,eAAAA,gBAAgB;GACrB;GACA;GACA,YAAY;GACZ,SAAS,KAAK;GACd,QAAQ,UAAU,mBAAmB,OAAO,SAAS;EACvD,CAAC;CACH;;;;;;CAOA,eAAe,OAAoB,QAA6C;EAC9E,OAAO,mBACLC,eAAAA,KAAK,KAAK,GACV,QAAQ,cAAc,KAAA,IAAY,KAAA,IAAYC,eAAAA,mBAAmB,OAAO,SAAS,CACnF;CACF;AACF;AAEA,SAAS,mBACP,IACA,mBACqB;CACrB,eAAA,cAAc,IAAI,CAAC,GAAG,WAAW,GAAG,sBAAsB;CAC1D,MAAM,MAAMC,eAAAA,gBAAgB,IAAI,sBAAsB;CACtD,MAAM,aAAaC,eAAAA,QAAQC,eAAAA,OAAO,KAAK,CAAC,CAAC;CACzC,IAAI,CAAC,YACH,MAAM,IAAIf,eAAAA,YAAY,mBAAmB,qDAAqD;CAEhG,IAAI,WAAW,SAAS,sBACtB,MAAM,IAAIA,eAAAA,YACR,kBACA,mCAAmC,WAAW,OAAO,mBAAmB,qBAAqB,cAC/F;CAEF,MAAM,SAASgB,iBAAAA,sBAAsBC,aAAAA,aAAa,YAAY,kBAAkB,CAAC;CAKjF,IAAI,sBAAsB,KAAA,GAAW;EACnC,MAAM,WAAWd,eAAAA,cAAc,iBAAiB,CAAC,CAAC,YAAY;EAC9D,IAAI,OAAO,OAAO,YAAY,MAAM,UAClC,MAAM,IAAIH,eAAAA,YACR,uBACA,OAAO,WAAW,KACd,+DACA,oGACN;CAEJ;CACA,IAAI,OAAO,UAAU,IACnB,MAAM,IAAIA,eAAAA,YAAY,mBAAmB,gDAAgD;CAE3F,MAAM,WAAWkB,iBAAAA,kBAAkB,OAAO,KAAK;CAE/C,OAAO;EAAE,WADS,qBAAqB,cAAc,OAAO,MAAM;EAC9C,MAAM,OAAO;EAAM,OAAO,OAAO;EAAO;CAAS;AACvE;AAEA,SAAS,cAAc,QAA4B;CACjD,IAAI;EACF,OAAON,eAAAA,mBAAmB,MAAM;CAClC,QAAQ;EACN,uBAAO,IAAI,WAAW,EAAE;CAC1B;AACF"}
1
+ {"version":3,"file":"tron-C_tq-WiJ.cjs","names":["resolveContext","resolveRequestId","normalizeXfp","EraSdkError","encodeSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped","splitSignedTronTx"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { B as cbMap, H as cbText, K as EraSdkError, N as asBytes, W as mapGet, _ as Ur, c as resolveRequestId, g as uuidStringify, h as normalizeRequestId, l as toUr, n as makeSignRequest, o as requireUrType, r as requireReplyMap, s as resolveContext, x as cborEncode, z as cbBytes } from "./shared-Bbgejrus.js";
2
2
  import { i as parsePath, r as normalizeXfp, s as xfpToHex } from "./keypath-Bvv_cfHt.js";
3
- import { n as gzipCompress, t as gunzipCapped } from "./gzip-DyZLtgJJ.js";
3
+ import { n as gzipCompress, t as gunzipCapped } from "./gzip-Dn-PQk1E.js";
4
4
  import { i as splitSignedTronTx, r as encodeSignRequestProto, t as decodeSignResultProto } from "./messages--XbVef_k.js";
5
5
  //#region src/chains/tron.ts
6
6
  /**
@@ -93,4 +93,4 @@ function signIdToBytes(signId) {
93
93
  //#endregion
94
94
  export { TronChain as t };
95
95
 
96
- //# sourceMappingURL=tron-BpUEYTPT.js.map
96
+ //# sourceMappingURL=tron-evMsrbLH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tron-BpUEYTPT.js","names":["UrValue"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":";;;;;;;;;;;AAyEA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAE3B,MAAM,cAAc,CAAC,sBAAsB;;;;;;;AAQ3C,IAAa,YAAb,MAAuB;CAGrB,YAAY,QAA2B;EACrC,KAAK,UAAU,eAAe,MAAM;CACtC;;CAGA,oBAAoB,OAA+D;EACjF,MAAM,YAAY,iBAAiB,KAAK,SAAS,MAAM,SAAS;EAChE,UAAU,MAAM,IAAI;EACpB,MAAM,MAAM,aAAa,MAAM,GAAG;EAClC,IAAI,MAAM,QAAQ,WAAW,GAC3B,MAAM,IAAI,YAAY,iBAAiB,2BAA2B;EAEpE,IAAI,CAAC,oBAAoB,KAAK,MAAM,YAAY,IAAI,GAClD,MAAM,IAAI,YACR,iBACA,8FACF;EAGF,MAAM,QAAQ,uBAAuB;GAKnC,QAAQ,SAAS,GAAG;GACpB,QAAQ,cAAc,SAAS;GAC/B,QAAQ,MAAM;GACd,WAAW,MAAM,aAAa;GAC9B,UAAU,MAAM,SAAS,YAAY;GACrC,OAAO,MAAM,SAAS,SAAS;GAC/B,iBAAiB,MAAM,SAAS;GAChC,MAAM,MAAM,SAAS;GACrB,IAAI,MAAM,SAAS;GACnB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,SAAS;GACtB,KAAK,MAAM,SAAS;GACpB,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB,CAAC;EAED,MAAM,KAAK,IAAIA,GACb,yBACA,WACE,MAAM,CACJ,CAAC,GAAG,QAAQ,aAAa,KAAK,CAAC,CAAC,GAChC,CAAC,GAAG,OAAO,MAAM,UAAU,KAAK,QAAQ,MAAM,CAAC,CACjD,CAAC,CACH,CACF;EACA,OAAO,gBAAgB;GACrB;GACA;GACA,YAAY;GACZ,SAAS,KAAK;GACd,QAAQ,UAAU,mBAAmB,OAAO,SAAS;EACvD,CAAC;CACH;;;;;;CAOA,eAAe,OAAoB,QAA6C;EAC9E,OAAO,mBACL,KAAK,KAAK,GACV,QAAQ,cAAc,KAAA,IAAY,KAAA,IAAY,mBAAmB,OAAO,SAAS,CACnF;CACF;AACF;AAEA,SAAS,mBACP,IACA,mBACqB;CACrB,cAAc,IAAI,CAAC,GAAG,WAAW,GAAG,sBAAsB;CAC1D,MAAM,MAAM,gBAAgB,IAAI,sBAAsB;CACtD,MAAM,aAAa,QAAQ,OAAO,KAAK,CAAC,CAAC;CACzC,IAAI,CAAC,YACH,MAAM,IAAI,YAAY,mBAAmB,qDAAqD;CAEhG,IAAI,WAAW,SAAS,sBACtB,MAAM,IAAI,YACR,kBACA,mCAAmC,WAAW,OAAO,mBAAmB,qBAAqB,cAC/F;CAEF,MAAM,SAAS,sBAAsB,aAAa,YAAY,kBAAkB,CAAC;CAKjF,IAAI,sBAAsB,KAAA,GAAW;EACnC,MAAM,WAAW,cAAc,iBAAiB,CAAC,CAAC,YAAY;EAC9D,IAAI,OAAO,OAAO,YAAY,MAAM,UAClC,MAAM,IAAI,YACR,uBACA,OAAO,WAAW,KACd,+DACA,oGACN;CAEJ;CACA,IAAI,OAAO,UAAU,IACnB,MAAM,IAAI,YAAY,mBAAmB,gDAAgD;CAE3F,MAAM,WAAW,kBAAkB,OAAO,KAAK;CAE/C,OAAO;EAAE,WADS,qBAAqB,cAAc,OAAO,MAAM;EAC9C,MAAM,OAAO;EAAM,OAAO,OAAO;EAAO;CAAS;AACvE;AAEA,SAAS,cAAc,QAA4B;CACjD,IAAI;EACF,OAAO,mBAAmB,MAAM;CAClC,QAAQ;EACN,uBAAO,IAAI,WAAW,EAAE;CAC1B;AACF"}
1
+ {"version":3,"file":"tron-evMsrbLH.js","names":["UrValue"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":";;;;;;;;;;;AAyEA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAE3B,MAAM,cAAc,CAAC,sBAAsB;;;;;;;AAQ3C,IAAa,YAAb,MAAuB;CAGrB,YAAY,QAA2B;EACrC,KAAK,UAAU,eAAe,MAAM;CACtC;;CAGA,oBAAoB,OAA+D;EACjF,MAAM,YAAY,iBAAiB,KAAK,SAAS,MAAM,SAAS;EAChE,UAAU,MAAM,IAAI;EACpB,MAAM,MAAM,aAAa,MAAM,GAAG;EAClC,IAAI,MAAM,QAAQ,WAAW,GAC3B,MAAM,IAAI,YAAY,iBAAiB,2BAA2B;EAEpE,IAAI,CAAC,oBAAoB,KAAK,MAAM,YAAY,IAAI,GAClD,MAAM,IAAI,YACR,iBACA,8FACF;EAGF,MAAM,QAAQ,uBAAuB;GAKnC,QAAQ,SAAS,GAAG;GACpB,QAAQ,cAAc,SAAS;GAC/B,QAAQ,MAAM;GACd,WAAW,MAAM,aAAa;GAC9B,UAAU,MAAM,SAAS,YAAY;GACrC,OAAO,MAAM,SAAS,SAAS;GAC/B,iBAAiB,MAAM,SAAS;GAChC,MAAM,MAAM,SAAS;GACrB,IAAI,MAAM,SAAS;GACnB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,SAAS;GACtB,KAAK,MAAM,SAAS;GACpB,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB,CAAC;EAED,MAAM,KAAK,IAAIA,GACb,yBACA,WACE,MAAM,CACJ,CAAC,GAAG,QAAQ,aAAa,KAAK,CAAC,CAAC,GAChC,CAAC,GAAG,OAAO,MAAM,UAAU,KAAK,QAAQ,MAAM,CAAC,CACjD,CAAC,CACH,CACF;EACA,OAAO,gBAAgB;GACrB;GACA;GACA,YAAY;GACZ,SAAS,KAAK;GACd,QAAQ,UAAU,mBAAmB,OAAO,SAAS;EACvD,CAAC;CACH;;;;;;CAOA,eAAe,OAAoB,QAA6C;EAC9E,OAAO,mBACL,KAAK,KAAK,GACV,QAAQ,cAAc,KAAA,IAAY,KAAA,IAAY,mBAAmB,OAAO,SAAS,CACnF;CACF;AACF;AAEA,SAAS,mBACP,IACA,mBACqB;CACrB,cAAc,IAAI,CAAC,GAAG,WAAW,GAAG,sBAAsB;CAC1D,MAAM,MAAM,gBAAgB,IAAI,sBAAsB;CACtD,MAAM,aAAa,QAAQ,OAAO,KAAK,CAAC,CAAC;CACzC,IAAI,CAAC,YACH,MAAM,IAAI,YAAY,mBAAmB,qDAAqD;CAEhG,IAAI,WAAW,SAAS,sBACtB,MAAM,IAAI,YACR,kBACA,mCAAmC,WAAW,OAAO,mBAAmB,qBAAqB,cAC/F;CAEF,MAAM,SAAS,sBAAsB,aAAa,YAAY,kBAAkB,CAAC;CAKjF,IAAI,sBAAsB,KAAA,GAAW;EACnC,MAAM,WAAW,cAAc,iBAAiB,CAAC,CAAC,YAAY;EAC9D,IAAI,OAAO,OAAO,YAAY,MAAM,UAClC,MAAM,IAAI,YACR,uBACA,OAAO,WAAW,KACd,+DACA,oGACN;CAEJ;CACA,IAAI,OAAO,UAAU,IACnB,MAAM,IAAI,YAAY,mBAAmB,gDAAgD;CAE3F,MAAM,WAAW,kBAAkB,OAAO,KAAK;CAE/C,OAAO;EAAE,WADS,qBAAqB,cAAc,OAAO,MAAM;EAC9C,MAAM,OAAO;EAAM,OAAO,OAAO;EAAO;CAAS;AACvE;AAEA,SAAS,cAAc,QAA4B;CACjD,IAAI;EACF,OAAO,mBAAmB,MAAM;CAClC,QAAQ;EACN,uBAAO,IAAI,WAAW,EAAE;CAC1B;AACF"}
package/dist/tron.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_shared = require("./shared-Cu2yynP9.cjs");
3
3
  const require_messages = require("./messages-orAQ52CI.cjs");
4
- const require_tron = require("./tron-DYPOg5cm.cjs");
4
+ const require_tron = require("./tron-C_tq-WiJ.cjs");
5
5
  exports.AnimatedUr = require_shared.AnimatedUr;
6
6
  exports.EraSdkError = require_shared.EraSdkError;
7
7
  exports.TronChain = require_tron.TronChain;
package/dist/tron.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { K as EraSdkError, _ as Ur, d as UrScanner, f as AnimatedUr, u as TypedUrScanner } from "./shared-Bbgejrus.js";
2
2
  import { i as splitSignedTronTx } from "./messages--XbVef_k.js";
3
- import { t as TronChain } from "./tron-BpUEYTPT.js";
3
+ import { t as TronChain } from "./tron-evMsrbLH.js";
4
4
  export { AnimatedUr, EraSdkError, TronChain, TypedUrScanner, Ur, UrScanner, splitSignedTronTx };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hwlt/era-connect",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "ERA hardware wallet SDK: air-gapped UR/QR account linking and transaction signing for EVM, Bitcoin (+ LTC/DOGE/DASH/BCH), Solana, Tron, TON, Cardano, Sui, Cosmos and XRP",
5
5
  "keywords": [
6
6
  "era-wallet",
@@ -1 +0,0 @@
1
- {"version":3,"file":"gzip-CLzDX7AZ.cjs","names":["gzipSync","EraSdkError","Gunzip","concatBytes","crc32"],"sources":["../src/tron-proto/gzip.ts"],"sourcesContent":["import { Gunzip, gzipSync } from 'fflate';\nimport { concatBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { crc32 } from '../ur/crc32';\n\n/** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */\nconst MIN_GZIP_BYTES = 18;\n\n/** Bytes fed to the inflater per step; bounds the overshoot past the cap. */\nconst SLICE_BYTES = 1024;\n\n/** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */\nexport function gzipCompress(data: Uint8Array): Uint8Array {\n return gzipSync(data, { level: 9, mtime: 0 });\n}\n\n/**\n * Inflate with a hard output ceiling.\n *\n * A one-shot gunzip allocates the entire output before anyone can refuse it —\n * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask\n * for an arbitrary allocation. The decoder is therefore driven in slices and\n * the output counted as it arrives; the moment the total would pass the cap,\n * feeding stops and the reply is refused.\n *\n * The trailer's ISIZE (declared inflated length) is used twice: as a cheap\n * refusal of honest bombs before any work, and as a truncation check at the\n * end — an inflater hands back a partial buffer for a truncated stream\n * without erroring, and a genuine device reply always declares honestly.\n */\nexport function gunzipCapped(data: Uint8Array, maxOutputBytes: number): Uint8Array {\n if (data.length < MIN_GZIP_BYTES) {\n throw new EraSdkError('gzip-error', 'compressed payload is too short to be a gzip stream');\n }\n if (data[0] !== 0x1f || data[1] !== 0x8b) {\n throw new EraSdkError('gzip-error', 'compressed payload is not a gzip stream');\n }\n const n = data.length;\n const isize =\n (data[n - 4]! | (data[n - 3]! << 8) | (data[n - 2]! << 16) | (data[n - 1]! << 24)) >>> 0;\n if (isize > maxOutputBytes) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`,\n );\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflowed = false;\n let malformed: string | null = null;\n\n const gunzip = new Gunzip((chunk) => {\n if (overflowed) return;\n if (total + chunk.length > maxOutputBytes) {\n overflowed = true;\n return;\n }\n chunks.push(chunk);\n total += chunk.length;\n });\n\n try {\n for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {\n const end = Math.min(offset + SLICE_BYTES, n);\n const isLast = end === n;\n gunzip.push(data.slice(offset, end), isLast);\n }\n } catch (e) {\n malformed = (e as Error).message ?? 'inflate error';\n }\n\n if (overflowed) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflates past the ${maxOutputBytes} byte ceiling`,\n );\n }\n if (malformed !== null) {\n throw new EraSdkError('gzip-error', `compressed payload is malformed: ${malformed}`);\n }\n const out = concatBytes(...chunks);\n if (out.length !== isize) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`,\n );\n }\n // The trailer CRC32 (little-endian, bytes n-8..n-5) must cover the inflated\n // output. The streaming inflater does not verify it, and the reference\n // implementation's native decoder does — without this check a corrupted\n // stream, or a CONCATENATED multi-member stream (whose final member's CRC\n // cannot cover the whole output), would be accepted here and refused there.\n const declaredCrc =\n (data[n - 8]! | (data[n - 7]! << 8) | (data[n - 6]! << 16) | (data[n - 5]! << 24)) >>> 0;\n if (crc32(out) !== declaredCrc) {\n throw new EraSdkError('gzip-error', 'compressed payload is malformed: CRC mismatch');\n }\n return out;\n}\n"],"mappings":";;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,cAAc;;AAGpB,SAAgB,aAAa,MAA8B;CACzD,QAAA,GAAOA,OAAAA,SAAAA,CAAS,MAAM;EAAE,OAAO;EAAG,OAAO;CAAE,CAAC;AAC9C;;;;;;;;;;;;;;;AAgBA,SAAgB,aAAa,MAAkB,gBAAoC;CACjF,IAAI,KAAK,SAAS,gBAChB,MAAM,IAAIC,eAAAA,YAAY,cAAc,qDAAqD;CAE3F,IAAI,KAAK,OAAO,MAAQ,KAAK,OAAO,KAClC,MAAM,IAAIA,eAAAA,YAAY,cAAc,yCAAyC;CAE/E,MAAM,IAAI,KAAK;CACf,MAAM,SACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,QAAQ,gBACV,MAAM,IAAIA,eAAAA,YACR,cACA,+BAA+B,MAAM,mBAAmB,eAAe,cACzE;CAGF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,IAAI,YAA2B;CAE/B,MAAM,SAAS,IAAIC,OAAAA,QAAQ,UAAU;EACnC,IAAI,YAAY;EAChB,IAAI,QAAQ,MAAM,SAAS,gBAAgB;GACzC,aAAa;GACb;EACF;EACA,OAAO,KAAK,KAAK;EACjB,SAAS,MAAM;CACjB,CAAC;CAED,IAAI;EACF,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,CAAC,YAAY,UAAU,aAAa;GACrE,MAAM,MAAM,KAAK,IAAI,SAAS,aAAa,CAAC;GAC5C,MAAM,SAAS,QAAQ;GACvB,OAAO,KAAK,KAAK,MAAM,QAAQ,GAAG,GAAG,MAAM;EAC7C;CACF,SAAS,GAAG;EACV,YAAa,EAAY,WAAW;CACtC;CAEA,IAAI,YACF,MAAM,IAAID,eAAAA,YACR,cACA,wCAAwC,eAAe,cACzD;CAEF,IAAI,cAAc,MAChB,MAAM,IAAIA,eAAAA,YAAY,cAAc,oCAAoC,WAAW;CAErF,MAAM,MAAME,eAAAA,YAAY,GAAG,MAAM;CACjC,IAAI,IAAI,WAAW,OACjB,MAAM,IAAIF,eAAAA,YACR,cACA,kCAAkC,IAAI,OAAO,sBAAsB,MAAM,0BAC3E;CAOF,MAAM,eACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAIG,eAAAA,MAAM,GAAG,MAAM,aACjB,MAAM,IAAIH,eAAAA,YAAY,cAAc,+CAA+C;CAErF,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"gzip-DyZLtgJJ.js","names":[],"sources":["../src/tron-proto/gzip.ts"],"sourcesContent":["import { Gunzip, gzipSync } from 'fflate';\nimport { concatBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { crc32 } from '../ur/crc32';\n\n/** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */\nconst MIN_GZIP_BYTES = 18;\n\n/** Bytes fed to the inflater per step; bounds the overshoot past the cap. */\nconst SLICE_BYTES = 1024;\n\n/** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */\nexport function gzipCompress(data: Uint8Array): Uint8Array {\n return gzipSync(data, { level: 9, mtime: 0 });\n}\n\n/**\n * Inflate with a hard output ceiling.\n *\n * A one-shot gunzip allocates the entire output before anyone can refuse it —\n * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask\n * for an arbitrary allocation. The decoder is therefore driven in slices and\n * the output counted as it arrives; the moment the total would pass the cap,\n * feeding stops and the reply is refused.\n *\n * The trailer's ISIZE (declared inflated length) is used twice: as a cheap\n * refusal of honest bombs before any work, and as a truncation check at the\n * end — an inflater hands back a partial buffer for a truncated stream\n * without erroring, and a genuine device reply always declares honestly.\n */\nexport function gunzipCapped(data: Uint8Array, maxOutputBytes: number): Uint8Array {\n if (data.length < MIN_GZIP_BYTES) {\n throw new EraSdkError('gzip-error', 'compressed payload is too short to be a gzip stream');\n }\n if (data[0] !== 0x1f || data[1] !== 0x8b) {\n throw new EraSdkError('gzip-error', 'compressed payload is not a gzip stream');\n }\n const n = data.length;\n const isize =\n (data[n - 4]! | (data[n - 3]! << 8) | (data[n - 2]! << 16) | (data[n - 1]! << 24)) >>> 0;\n if (isize > maxOutputBytes) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`,\n );\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflowed = false;\n let malformed: string | null = null;\n\n const gunzip = new Gunzip((chunk) => {\n if (overflowed) return;\n if (total + chunk.length > maxOutputBytes) {\n overflowed = true;\n return;\n }\n chunks.push(chunk);\n total += chunk.length;\n });\n\n try {\n for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {\n const end = Math.min(offset + SLICE_BYTES, n);\n const isLast = end === n;\n gunzip.push(data.slice(offset, end), isLast);\n }\n } catch (e) {\n malformed = (e as Error).message ?? 'inflate error';\n }\n\n if (overflowed) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflates past the ${maxOutputBytes} byte ceiling`,\n );\n }\n if (malformed !== null) {\n throw new EraSdkError('gzip-error', `compressed payload is malformed: ${malformed}`);\n }\n const out = concatBytes(...chunks);\n if (out.length !== isize) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`,\n );\n }\n // The trailer CRC32 (little-endian, bytes n-8..n-5) must cover the inflated\n // output. The streaming inflater does not verify it, and the reference\n // implementation's native decoder does — without this check a corrupted\n // stream, or a CONCATENATED multi-member stream (whose final member's CRC\n // cannot cover the whole output), would be accepted here and refused there.\n const declaredCrc =\n (data[n - 8]! | (data[n - 7]! << 8) | (data[n - 6]! << 16) | (data[n - 5]! << 24)) >>> 0;\n if (crc32(out) !== declaredCrc) {\n throw new EraSdkError('gzip-error', 'compressed payload is malformed: CRC mismatch');\n }\n return out;\n}\n"],"mappings":";;;;AAMA,MAAM,iBAAiB;;AAGvB,MAAM,cAAc;;AAGpB,SAAgB,aAAa,MAA8B;CACzD,OAAO,SAAS,MAAM;EAAE,OAAO;EAAG,OAAO;CAAE,CAAC;AAC9C;;;;;;;;;;;;;;;AAgBA,SAAgB,aAAa,MAAkB,gBAAoC;CACjF,IAAI,KAAK,SAAS,gBAChB,MAAM,IAAI,YAAY,cAAc,qDAAqD;CAE3F,IAAI,KAAK,OAAO,MAAQ,KAAK,OAAO,KAClC,MAAM,IAAI,YAAY,cAAc,yCAAyC;CAE/E,MAAM,IAAI,KAAK;CACf,MAAM,SACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,QAAQ,gBACV,MAAM,IAAI,YACR,cACA,+BAA+B,MAAM,mBAAmB,eAAe,cACzE;CAGF,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI,aAAa;CACjB,IAAI,YAA2B;CAE/B,MAAM,SAAS,IAAI,QAAQ,UAAU;EACnC,IAAI,YAAY;EAChB,IAAI,QAAQ,MAAM,SAAS,gBAAgB;GACzC,aAAa;GACb;EACF;EACA,OAAO,KAAK,KAAK;EACjB,SAAS,MAAM;CACjB,CAAC;CAED,IAAI;EACF,KAAK,IAAI,SAAS,GAAG,SAAS,KAAK,CAAC,YAAY,UAAU,aAAa;GACrE,MAAM,MAAM,KAAK,IAAI,SAAS,aAAa,CAAC;GAC5C,MAAM,SAAS,QAAQ;GACvB,OAAO,KAAK,KAAK,MAAM,QAAQ,GAAG,GAAG,MAAM;EAC7C;CACF,SAAS,GAAG;EACV,YAAa,EAAY,WAAW;CACtC;CAEA,IAAI,YACF,MAAM,IAAI,YACR,cACA,wCAAwC,eAAe,cACzD;CAEF,IAAI,cAAc,MAChB,MAAM,IAAI,YAAY,cAAc,oCAAoC,WAAW;CAErF,MAAM,MAAM,YAAY,GAAG,MAAM;CACjC,IAAI,IAAI,WAAW,OACjB,MAAM,IAAI,YACR,cACA,kCAAkC,IAAI,OAAO,sBAAsB,MAAM,0BAC3E;CAOF,MAAM,eACH,KAAK,IAAI,KAAO,KAAK,IAAI,MAAO,IAAM,KAAK,IAAI,MAAO,KAAO,KAAK,IAAI,MAAO,QAAS;CACzF,IAAI,MAAM,GAAG,MAAM,aACjB,MAAM,IAAI,YAAY,cAAc,+CAA+C;CAErF,OAAO;AACT"}