Skip to content

Commit 4ee5ac6

Browse files
sirdeggenclaude
andcommitted
fix(templates): encode MandalaToken assetId in outpoint byte order
encodeAssetId wrote the txid in display (id) byte order; on-chain it must be the internal/reversed (tx.hash()) order + little-endian vout, so the embedded assetId matches the genesis transaction's outpoint as it appears in a tx. decodeAssetId reverses back, leaving the "<txid>.<vout>" display string unchanged. Enables direct outpoint comparisons in smart-contract validation. Breaking on-chain format. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6b6952a commit 4ee5ac6

4 files changed

Lines changed: 24 additions & 4 deletions

File tree

packages/helpers/ts-templates/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ All notable changes to this project will be documented in this file. The format
1313
- (Include new features or significant user-visible enhancements here.)
1414

1515
### Changed
16-
- (Detail modifications that are non-breaking but relevant to the end-users.)
16+
- **MandalaToken assetId on-chain encoding (breaking on-chain format):** `encodeAssetId`
17+
now writes the txid in outpoint (internal/reversed, `tx.hash()`) byte order followed
18+
by the 4-byte little-endian vout, matching how an outpoint appears in a transaction.
19+
`decodeAssetId` reverses it back, so the `"<txid>.<vout>"` display string is
20+
unchanged. This lets smart contracts compare a token's embedded assetId directly
21+
against the genesis transaction's outpoint. Tokens minted under the previous
22+
(non-reversed) encoding will not decode to the same assetId.
1723

1824
### Deprecated
1925
- (List features that are in the process of being phased out or replaced.)

packages/helpers/ts-templates/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bsv/templates",
3-
"version": "1.7.1",
3+
"version": "1.7.2",
44
"type": "module",
55
"description": "BSV Blockchain Script Templates",
66
"main": "dist/cjs/mod.js",

packages/helpers/ts-templates/src/__tests/mandala-encoding.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ describe('mandala-encoding', () => {
3535
const assetId = `${'a'.repeat(64)}.4294967295`
3636
expect(decodeAssetId(encodeAssetId(assetId))).toBe(assetId)
3737
})
38+
39+
it('encodes the txid in outpoint (internal/reversed) byte order + LE vout', () => {
40+
// Distinct first/last bytes so reversal is observable.
41+
const txid = '11' + 'aa'.repeat(30) + '22' // 32 bytes: 0x11 .. 0x22
42+
const bytes = encodeAssetId(`${txid}.1`)
43+
expect(bytes[0]).toBe(0x22) // display last byte → first on-chain (reversed)
44+
expect(bytes[31]).toBe(0x11) // display first byte → last on-chain
45+
expect(bytes.slice(32)).toEqual([1, 0, 0, 0]) // vout little-endian
46+
})
3847
})

packages/helpers/ts-templates/src/mandala-encoding.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,19 @@ export const encodeAssetId = (assetId: string): number[] => {
6060
const vout = Number(assetId.slice(dot + 1))
6161
if (txid.length !== 64) throw new Error('assetId txid must be 32 bytes (64 hex chars)')
6262
if (!Number.isInteger(vout) || vout < 0) throw new Error('assetId vout must be a non-negative integer')
63-
const txidBytes = Utils.toArray(txid, 'hex')
63+
// On-chain assetId bytes use outpoint format: the txid in internal (hash) byte
64+
// order — i.e. the display hex reversed (tx.hash() vs tx.id('hex')) — followed by
65+
// the 4-byte little-endian vout. This lets a contract compare the embedded assetId
66+
// directly against the genesis transaction's outpoint as it appears in the tx.
67+
const txidBytes = Utils.toArray(txid, 'hex').reverse()
6468
const voutBytes = [vout & 0xff, (vout >> 8) & 0xff, (vout >> 16) & 0xff, (vout >> 24) & 0xff]
6569
return [...txidBytes, ...voutBytes]
6670
}
6771

6872
export const decodeAssetId = (bytes: number[]): string => {
6973
if (bytes.length !== 36) throw new Error('assetId bytes must be exactly 36 bytes')
70-
const txid = Utils.toHex(bytes.slice(0, 32))
74+
// Reverse the internal (hash) byte order back to display txid hex.
75+
const txid = Utils.toHex(bytes.slice(0, 32).reverse())
7176
const v = bytes.slice(32)
7277
const vout = (v[0] + (v[1] << 8) + (v[2] << 16) + (v[3] << 24)) >>> 0
7378
return `${txid}.${vout}`

0 commit comments

Comments
 (0)