diff --git a/psf-memo-client/acceptance/lib/handlers.js b/psf-memo-client/acceptance/lib/handlers.js index a251274..bec92cb 100644 --- a/psf-memo-client/acceptance/lib/handlers.js +++ b/psf-memo-client/acceptance/lib/handlers.js @@ -589,6 +589,16 @@ function txidWireHex (txid) { return Buffer.from(txid, 'hex').reverse().toString('hex') } +// Assert that the most recent broadcast's push at `index` decodes to the +// expected UTF-8 text. Shared by the topic/text/question push assertions. +function assertUtf8Push (world, index, expected, label) { + const push = broadcastPush(world, index) + const actual = push.toString('utf8') + if (actual !== expected) { + throw new Error(`${label} push "${actual}" did not match "${expected}".`) + } +} + // Resolve a literal value or a placeholder from the example store. function resolveParam (value, example) { const match = /^<([A-Za-z0-9_]+)>$/.exec(String(value).trim()) @@ -1113,11 +1123,7 @@ const handlers = [ name: 'second broadcast push is UTF-8 topic', pattern: /^the second broadcast push is the UTF-8 topic "(.+)"$/, run (m, example, world) { - const topic = resolveText(m[1], example) - const push = broadcastPush(world, 1) - if (push.toString('utf8') !== topic) { - throw new Error(`Second push "${push.toString('utf8')}" did not match topic "${topic}".`) - } + assertUtf8Push(world, 1, resolveText(m[1], example), 'Second') } }, { @@ -1135,11 +1141,7 @@ const handlers = [ name: 'third broadcast push is UTF-8 text', pattern: /^the third broadcast push is the UTF-8 text "(.+)"$/, run (m, example, world) { - const text = resolveText(m[1], example) - const push = broadcastPush(world, 2) - if (push.toString('utf8') !== text) { - throw new Error(`Third push "${push.toString('utf8')}" did not match text "${text}".`) - } + assertUtf8Push(world, 2, resolveText(m[1], example), 'Third') } }, { @@ -1157,11 +1159,7 @@ const handlers = [ name: 'fourth broadcast push is UTF-8 question', pattern: /^the fourth broadcast push is the UTF-8 question "(.+)"$/, run (m, example, world) { - const question = resolveText(m[1], example) - const push = broadcastPush(world, 3) - if (push.toString('utf8') !== question) { - throw new Error(`Fourth push "${push.toString('utf8')}" did not match question "${question}".`) - } + assertUtf8Push(world, 3, resolveText(m[1], example), 'Fourth') } }, { diff --git a/psf-memo-client/package-lock.json b/psf-memo-client/package-lock.json index 24d490c..5e0ea2e 100644 --- a/psf-memo-client/package-lock.json +++ b/psf-memo-client/package-lock.json @@ -17,6 +17,7 @@ "bch-message-lib": "2.2.1", "bch-token-sweep": "2.2.1", "bootstrap": "5.2.0", + "buffer": "^6.0.3", "qrcode.react": "4.2.0", "query-string": "7.1.1", "react": "19.0.0", @@ -6160,7 +6161,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "dev": true, "funding": [ { "type": "github", @@ -6637,7 +6637,8 @@ }, "node_modules/buffer": { "version": "6.0.3", - "dev": true, + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -11703,7 +11704,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "dev": true, "funding": [ { "type": "github", diff --git a/psf-memo-client/package.json b/psf-memo-client/package.json index c16cc04..6fe1bf4 100644 --- a/psf-memo-client/package.json +++ b/psf-memo-client/package.json @@ -11,6 +11,7 @@ "bch-message-lib": "2.2.1", "bch-token-sweep": "2.2.1", "bootstrap": "5.2.0", + "buffer": "^6.0.3", "qrcode.react": "4.2.0", "query-string": "7.1.1", "react": "19.0.0", diff --git a/psf-memo-client/specs/memo-multipush-encoding.feature b/psf-memo-client/specs/memo-multipush-encoding.feature index 84a339a..1f23de6 100644 --- a/psf-memo-client/specs/memo-multipush-encoding.feature +++ b/psf-memo-client/specs/memo-multipush-encoding.feature @@ -1,3 +1,7 @@ +# acceptance-mutation-manifest-begin +# {"version":1,"tested_at":"2026-09-17T04:24:27.366008662Z","feature_name":"Memo multi-push encoding","feature_path":"/home/trout/work/psf-memo/.worktrees/architect/psf-memo-client/specs/memo-multipush-encoding.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[]} +# acceptance-mutation-manifest-end + # Scenarios: Memo multi-push encoding - 1, Memo multi-push encoding - 2, Memo multi-push encoding - 3, Memo multi-push encoding - 4, Memo multi-push encoding - 5 # # Memo actions that carry more than one field encode each protocol field as diff --git a/psf-memo-client/src/services/hex.js b/psf-memo-client/src/services/hex.js index f78412e..617311d 100644 --- a/psf-memo-client/src/services/hex.js +++ b/psf-memo-client/src/services/hex.js @@ -47,5 +47,5 @@ function buildTxidTextPushes (txid, text, label = 'Poll txid') { module.exports = { hexToBytes, txidToWireBytes, buildTxidTextPushes } // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-16T22:01:27.280Z","module_hash":"454b45f3ce08c5ea6346c113db6384f7a44dddbd21e2fb0a35c41ce6701e62c8","functions":[{"id":"func/hexToBytes","name":"hexToBytes","line":12,"end_line":26,"hash":"dbf7e0a434598f85365f5a60a0ca227a17a1bcd6718a78bb5aaf7afb8eb2487b"},{"id":"func/txidToWireBytes","name":"txidToWireBytes","line":32,"end_line":34,"hash":"ac25753fecb32c929134b6e6379cae0bfddd4aa6f7ae438a08da650450e9ea1a"},{"id":"func/buildTxidTextPayload","name":"buildTxidTextPayload","line":39,"end_line":46,"hash":"068504ea13a037adabbbfeab122fb5ec6113625a9e0b009cbbe8b46b6bebdaee"}]} +// {"version":1,"tested_at":"2026-09-17T04:10:11.935Z","module_hash":"e3a608fad7d52d78c2c94a873161d6085c9afc2326a07fa04eb8a67815ed362a","functions":[{"id":"func/hexToBytes","name":"hexToBytes","line":14,"end_line":28,"hash":"dbf7e0a434598f85365f5a60a0ca227a17a1bcd6718a78bb5aaf7afb8eb2487b"},{"id":"func/txidToWireBytes","name":"txidToWireBytes","line":34,"end_line":36,"hash":"ac25753fecb32c929134b6e6379cae0bfddd4aa6f7ae438a08da650450e9ea1a"},{"id":"func/buildTxidTextPushes","name":"buildTxidTextPushes","line":41,"end_line":45,"hash":"815887680e60a7ed823b15fe6cc25b16f931e4390e37dd89aee409cf55836fd0"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-multipush.js b/psf-memo-client/src/services/memo-multipush.js index 8ec77d2..28e3982 100644 --- a/psf-memo-client/src/services/memo-multipush.js +++ b/psf-memo-client/src/services/memo-multipush.js @@ -15,6 +15,13 @@ the wallet wiring is the small environmentally unsuitable boundary. */ +// Import the browser-compatible Buffer implementation. `Buffer` is a Node +// global, but the CRA production bundle does not polyfill Node globals and the +// wallet script does not define window.Buffer, so a bare global would throw in +// the browser. The npm buffer package is the same implementation the bundled +// bitcoincashjs-lib uses, so its Buffer.isBuffer accepts these pushes. +const { Buffer } = require('buffer') + // Normalize one push value (a UTF-8 string or byte array) to a Buffer. function toPushBuffer (value) { if (typeof value === 'string') return Buffer.from(value, 'utf8') @@ -86,3 +93,7 @@ module.exports = { broadcastMultiPush, attachMultiPushOpReturn } + +// mutate4javascript-manifest-begin +// {"version":1,"tested_at":"2026-09-17T04:08:42.622Z","module_hash":"9bce63e69187b0a42ba210437ea14a32fdd7f53ccfd905264375bba997a3a4c3","functions":[{"id":"func/toPushBuffer","name":"toPushBuffer","line":26,"end_line":29,"hash":"0ff6744e881d65ce60d6456df450f5f0f26a39492d387b2b33aefec8c12e2e81"},{"id":"func/buildPushes","name":"buildPushes","line":33,"end_line":35,"hash":"46961294949a914dbab5064b1b2fcf5c3ba59c2e438b2a986275d3dfed40ea71"},{"id":"func/broadcastMultiPush","name":"broadcastMultiPush","line":40,"end_line":73,"hash":"ee5022606ea152763159d7fd9753c3849b723c5a8ab1cd1cb399feb4248479d6"},{"id":"func/attachMultiPushOpReturn","name":"attachMultiPushOpReturn","line":77,"end_line":88,"hash":"ef81a1526f376fc1ad500d72dafdd684d78ad8052202e938a39654bb182e8eeb"}]} +// mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-poll-create.js b/psf-memo-client/src/services/memo-poll-create.js index 4da2d34..7278709 100644 --- a/psf-memo-client/src/services/memo-poll-create.js +++ b/psf-memo-client/src/services/memo-poll-create.js @@ -102,5 +102,5 @@ MemoPollCreate.DEFAULT_POLL_TYPE = DEFAULT_POLL_TYPE module.exports = MemoPollCreate // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-28T22:45:49.496Z","module_hash":"3455852bee199b40530bbbe787f85429a7a15bf0116989231ac06f9ca9eadf36","functions":[{"id":"func/MemoPollCreate.constructor","name":"MemoPollCreate.constructor","line":36,"end_line":40,"hash":"9404cf1f0df5cc8756e0ea24abab27ade844fa461f84818162faac807b838dd1"},{"id":"func/MemoPollCreate.isTooLong","name":"MemoPollCreate.isTooLong","line":43,"end_line":45,"hash":"ca9087a454f1fba64ac35738a037372ade36ba6d4c2270d2025adc00b860412c"},{"id":"func/MemoPollCreate.create","name":"MemoPollCreate.create","line":48,"end_line":71,"hash":"b5e769b07f3795a5db0d4202106f4b25b5a0b69e30e70491a7a9c2ac52085fce"},{"id":"func/MemoPollCreate.reflect","name":"MemoPollCreate.reflect","line":74,"end_line":84,"hash":"ec559b778b279c1efb46bdb2440a0c197dae242052d7c1ebf5331078258b719f"},{"id":"func/buildCreatePollPayload","name":"buildCreatePollPayload","line":89,"end_line":96,"hash":"f5abdaad00f9e3c857c65766a981d6971e1bc7c64e0200cbb4105ab798bd94da"}]} +// {"version":1,"tested_at":"2026-09-17T04:15:33.060Z","module_hash":"f768a640a3cf8aff7dd9836bcbed38ca2d2fa6892cca97f8d078f4ad5d9d5287","functions":[{"id":"func/MemoPollCreate.constructor","name":"MemoPollCreate.constructor","line":36,"end_line":40,"hash":"9404cf1f0df5cc8756e0ea24abab27ade844fa461f84818162faac807b838dd1"},{"id":"func/MemoPollCreate.isTooLong","name":"MemoPollCreate.isTooLong","line":43,"end_line":45,"hash":"ca9087a454f1fba64ac35738a037372ade36ba6d4c2270d2025adc00b860412c"},{"id":"func/MemoPollCreate.create","name":"MemoPollCreate.create","line":48,"end_line":71,"hash":"dd0f77edcf6d20b5273c4949a0b7b52f7b60686556b33491139077bbb6866120"},{"id":"func/MemoPollCreate.reflect","name":"MemoPollCreate.reflect","line":74,"end_line":84,"hash":"ec559b778b279c1efb46bdb2440a0c197dae242052d7c1ebf5331078258b719f"},{"id":"func/buildCreatePollPushes","name":"buildCreatePollPushes","line":89,"end_line":96,"hash":"54b38bb7fba8a90968c93441588aa42754d2b961d75778daf8b764a017030334"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-poll-option.js b/psf-memo-client/src/services/memo-poll-option.js index b8455ce..38c4eaa 100644 --- a/psf-memo-client/src/services/memo-poll-option.js +++ b/psf-memo-client/src/services/memo-poll-option.js @@ -45,5 +45,5 @@ MemoPollOption.MAX_OPTION_BYTES = MAX_OPTION_BYTES module.exports = MemoPollOption // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-28T23:38:02.285Z","module_hash":"f8432d9ccb0073f5cce9f96415fdfb033876f498a7d80aadaca1ed96d89d4ce3","functions":[{"id":"func/MemoPollOption.add","name":"MemoPollOption.add","line":37,"end_line":39,"hash":"e0343ddce138d48e3fec2c5c0169e9d99f374835cc95d381383a254297d7f1dc"}]} +// {"version":1,"tested_at":"2026-09-17T04:16:42.577Z","module_hash":"4bcfeb64bcefaafb3d61272db2623e832b65930f966f5494bebfd417b29dc833","functions":[{"id":"func/MemoPollOption.add","name":"MemoPollOption.add","line":37,"end_line":39,"hash":"d8bdf2dda07743c5d6927b7a6ef69715f033aefe2e77b5ca6923c2afe3772088"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-poll-vote.js b/psf-memo-client/src/services/memo-poll-vote.js index 08d3d8e..81cf699 100644 --- a/psf-memo-client/src/services/memo-poll-vote.js +++ b/psf-memo-client/src/services/memo-poll-vote.js @@ -45,5 +45,5 @@ MemoPollVote.MAX_COMMENT_BYTES = MAX_COMMENT_BYTES module.exports = MemoPollVote // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-28T23:38:23.086Z","module_hash":"c60989293dede6eeaf8edd168d00745e7299620b80874f87680337cc2f4d36fc","functions":[{"id":"func/MemoPollVote.vote","name":"MemoPollVote.vote","line":37,"end_line":39,"hash":"c6c194dfaf6c92f5cafcf86772bd295d038536ea20079931e4144eb5141c7101"}]} +// {"version":1,"tested_at":"2026-09-17T04:17:10.193Z","module_hash":"e03a12dfad981e8a9ce4d4af763f0c57bd7a0f70a8bf22adc4b83d539b4e4725","functions":[{"id":"func/MemoPollVote.vote","name":"MemoPollVote.vote","line":37,"end_line":39,"hash":"e7ec8ae8f66c762cd7bb9fd9bb3001fe2a1b63843933d57372d6fd6f6a87b3dc"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-reply.js b/psf-memo-client/src/services/memo-reply.js index a7fa557..4855132 100644 --- a/psf-memo-client/src/services/memo-reply.js +++ b/psf-memo-client/src/services/memo-reply.js @@ -85,5 +85,5 @@ MemoReply.MAX_REPLY_BYTES = MAX_REPLY_BYTES module.exports = MemoReply // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-16T22:14:19.213Z","module_hash":"6c515a91afe94a6be8671a1541302446bb6b1015d03ce0e278644508ebd20090","functions":[{"id":"func/MemoReply.constructor","name":"MemoReply.constructor","line":36,"end_line":39,"hash":"23091c1b8f7847199bab3b54d8d81e9d8432c6da96138d72ac03fcf9426d542c"},{"id":"func/MemoReply.isTooLong","name":"MemoReply.isTooLong","line":42,"end_line":44,"hash":"2e867501d184010313ba9b27a6bb1e446df8f093514ee231b90ce77699ecbaf2"},{"id":"func/MemoReply.reply","name":"MemoReply.reply","line":48,"end_line":67,"hash":"00fb3598d5c3bdc3c6744d1316ad552364574fed105438af4aa011347f3306ce"},{"id":"func/MemoReply.reflect","name":"MemoReply.reflect","line":70,"end_line":79,"hash":"344e1bf304a4dfd475b02824b7ddbf555da0f3ec89f73b4f6009cf0bf097fb02"}]} +// {"version":1,"tested_at":"2026-09-17T04:12:17.098Z","module_hash":"53a16ce9198c0c94446ca0f89463072d83a9646de676708a1b9061086dbac40c","functions":[{"id":"func/MemoReply.constructor","name":"MemoReply.constructor","line":36,"end_line":39,"hash":"23091c1b8f7847199bab3b54d8d81e9d8432c6da96138d72ac03fcf9426d542c"},{"id":"func/MemoReply.isTooLong","name":"MemoReply.isTooLong","line":42,"end_line":44,"hash":"2e867501d184010313ba9b27a6bb1e446df8f093514ee231b90ce77699ecbaf2"},{"id":"func/MemoReply.reply","name":"MemoReply.reply","line":48,"end_line":67,"hash":"b8dc4228aefdae7a9e29273a7cef7d92177af9ba6d19d318d6455bf4d202fcc6"},{"id":"func/MemoReply.reflect","name":"MemoReply.reflect","line":70,"end_line":79,"hash":"344e1bf304a4dfd475b02824b7ddbf555da0f3ec89f73b4f6009cf0bf097fb02"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-topic-post.js b/psf-memo-client/src/services/memo-topic-post.js index ab9802f..77b9a14 100644 --- a/psf-memo-client/src/services/memo-topic-post.js +++ b/psf-memo-client/src/services/memo-topic-post.js @@ -88,5 +88,5 @@ MemoTopicPost.MAX_TOPIC_MESSAGE_BYTES = MAX_TOPIC_MESSAGE_BYTES module.exports = MemoTopicPost // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-28T18:18:11.190Z","module_hash":"c73b1395983accf085921554a84c8ef9ad8014ba8d8ddfa794e051ae4a487f04","functions":[{"id":"func/MemoTopicPost.constructor","name":"MemoTopicPost.constructor","line":34,"end_line":38,"hash":"34483e7579cbde3babf17cc43f64bc6535537b6391169a75cb51025029283289"},{"id":"func/MemoTopicPost.isTooLong","name":"MemoTopicPost.isTooLong","line":42,"end_line":44,"hash":"a8d16816cf0aeda1be759f796aee75e9cc5d89ccbcc288b5d2551917121be27a"},{"id":"func/MemoTopicPost.remainingBytes","name":"MemoTopicPost.remainingBytes","line":46,"end_line":48,"hash":"11a07f6539fa6b74feabfdd258f83ede4556213951aa85266a959871ff616df1"},{"id":"func/MemoTopicPost.post","name":"MemoTopicPost.post","line":51,"end_line":67,"hash":"dec3508e4969efd3edfefbc0752ca4608bac9521fbda62cf2ce5d7bf0a5696c7"},{"id":"func/MemoTopicPost.reflect","name":"MemoTopicPost.reflect","line":70,"end_line":79,"hash":"8a136fe98fa6752b2247b9bc0064b9860cfa34e518abaa33e50e95833120d529"}]} +// {"version":1,"tested_at":"2026-09-17T04:13:34.090Z","module_hash":"c718cc342a8316ce9369d79969492e7de83e2c8b9d881c2f3d0324ac92cdf0ff","functions":[{"id":"func/MemoTopicPost.constructor","name":"MemoTopicPost.constructor","line":34,"end_line":38,"hash":"34483e7579cbde3babf17cc43f64bc6535537b6391169a75cb51025029283289"},{"id":"func/MemoTopicPost.isTooLong","name":"MemoTopicPost.isTooLong","line":42,"end_line":44,"hash":"a8d16816cf0aeda1be759f796aee75e9cc5d89ccbcc288b5d2551917121be27a"},{"id":"func/MemoTopicPost.remainingBytes","name":"MemoTopicPost.remainingBytes","line":46,"end_line":48,"hash":"11a07f6539fa6b74feabfdd258f83ede4556213951aa85266a959871ff616df1"},{"id":"func/MemoTopicPost.post","name":"MemoTopicPost.post","line":51,"end_line":70,"hash":"626b580f6c5192b03254561430a28b04decf8d089fdb12542bea8a168803e945"},{"id":"func/MemoTopicPost.reflect","name":"MemoTopicPost.reflect","line":73,"end_line":82,"hash":"8a136fe98fa6752b2247b9bc0064b9860cfa34e518abaa33e50e95833120d529"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-txid-action.js b/psf-memo-client/src/services/memo-txid-action.js index b72d65c..ad80209 100644 --- a/psf-memo-client/src/services/memo-txid-action.js +++ b/psf-memo-client/src/services/memo-txid-action.js @@ -73,5 +73,5 @@ class MemoTxidAction extends MemoAction { module.exports = MemoTxidAction // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-28T23:37:21.781Z","module_hash":"b43e695aee6198c465b2cdbb0fbb405fccb0aadb573f56c3d3249de3920c1939","functions":[{"id":"func/MemoTxidAction.constructor","name":"MemoTxidAction.constructor","line":18,"end_line":22,"hash":"399be38855508b45b6a07e00361d4fb36186df3fdac5e2a68e004f7dcbdd353c"},{"id":"func/MemoTxidAction.broadcastTxid","name":"MemoTxidAction.broadcastTxid","line":26,"end_line":48,"hash":"395ea932dd691049556a66a3d4c674778818b8b53152c0ae7088d960524fc400"},{"id":"func/MemoTxidAction.reflect","name":"MemoTxidAction.reflect","line":53,"end_line":63,"hash":"7d61638162b7142298e021b02cbdeaca359f33503b3b84a308498f0479c80c5c"},{"id":"func/MemoTxidAction.isTooLong","name":"MemoTxidAction.isTooLong","line":67,"end_line":69,"hash":"7eeb0b9dcb35530de412b0aa0ade6b267e2fe22f5e49aa0f3ecf5b9124022a3b"}]} +// {"version":1,"tested_at":"2026-09-17T04:18:05.479Z","module_hash":"d0bda2cafd87fb912744a896f53c74aac9402bc087e106eccbf19144b9f93e35","functions":[{"id":"func/MemoTxidAction.constructor","name":"MemoTxidAction.constructor","line":18,"end_line":22,"hash":"399be38855508b45b6a07e00361d4fb36186df3fdac5e2a68e004f7dcbdd353c"},{"id":"func/MemoTxidAction.broadcastTxid","name":"MemoTxidAction.broadcastTxid","line":27,"end_line":49,"hash":"60e57cd10e406285825ad6750d9cce312ea5ecb09e02a697da358ac65f8312a5"},{"id":"func/MemoTxidAction.reflect","name":"MemoTxidAction.reflect","line":54,"end_line":64,"hash":"7d61638162b7142298e021b02cbdeaca359f33503b3b84a308498f0479c80c5c"},{"id":"func/MemoTxidAction.isTooLong","name":"MemoTxidAction.isTooLong","line":68,"end_line":70,"hash":"7eeb0b9dcb35530de412b0aa0ade6b267e2fe22f5e49aa0f3ecf5b9124022a3b"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/utf8.js b/psf-memo-client/src/services/utf8.js index dcbb0b0..5b7f2b0 100644 --- a/psf-memo-client/src/services/utf8.js +++ b/psf-memo-client/src/services/utf8.js @@ -21,5 +21,5 @@ function byteLength (str) { module.exports = { encodeUtf8, byteLength } // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-08-26T12:17:55.885Z","module_hash":"7f91541c49f2b6f421e8d4158bfe808b35a5449534cb26c567162fce6fec64bf","functions":[{"id":"func/byteLength","name":"byteLength","line":11,"end_line":13,"hash":"973c9dadcd1d8bbd53587252443880db13c8be3639fa74ce3c69a08ea358c8e2"}]} +// {"version":1,"tested_at":"2026-09-17T04:11:15.796Z","module_hash":"530eeae383fa202f8e50d83126a9bafaaa49b04268f297e9e91027a89cde71b4","functions":[{"id":"func/encodeUtf8","name":"encodeUtf8","line":12,"end_line":14,"hash":"8d56bdd5607b741578d5f51511391e0376cc410181401b2570fde0990b6bf976"},{"id":"func/byteLength","name":"byteLength","line":17,"end_line":19,"hash":"4d5266d6b3715a761b533e67477846156cfbd497460e5af448a8ea7a72bb580c"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/test/property/memo-multipush.property.test.js b/psf-memo-client/test/property/memo-multipush.property.test.js index e5cde2a..f29e2bb 100644 --- a/psf-memo-client/test/property/memo-multipush.property.test.js +++ b/psf-memo-client/test/property/memo-multipush.property.test.js @@ -24,6 +24,7 @@ const { buildPushes, attachMultiPushOpReturn } = require('../../src/services/memo-multipush') +const { encodeScript } = require('../support/script-encoding') const rng = seededRandom(20260916) @@ -61,17 +62,6 @@ function randomFields () { return fields } -// Encode a script the way Bitcoin does: an opcode number is one byte, a -// Buffer becomes a length-prefixed push. -function encodeScript (script) { - const parts = script.map((el) => { - if (typeof el === 'number') return Buffer.from([el]) - const buf = Buffer.from(el) - return Buffer.concat([Buffer.from([buf.length]), buf]) - }) - return Buffer.concat(parts) -} - // A double of the minimal-slp-wallet surface the adapter reuses. function makeWalletDouble () { const sent = [] diff --git a/psf-memo-client/test/support/script-encoding.js b/psf-memo-client/test/support/script-encoding.js new file mode 100644 index 0000000..4e20c3d --- /dev/null +++ b/psf-memo-client/test/support/script-encoding.js @@ -0,0 +1,20 @@ +/* + Test helper: encode a script the way Bitcoin does. + + An opcode number is one byte; a Buffer/string becomes a length-prefixed push. + Enough to observe that the multi-push adapter produced the expected pushes. + Shared by the memo-multipush unit and property tests. +*/ + +'use strict' + +function encodeScript (script) { + const parts = script.map((el) => { + if (typeof el === 'number') return Buffer.from([el]) + const buf = Buffer.from(el) + return Buffer.concat([Buffer.from([buf.length]), buf]) + }) + return Buffer.concat(parts) +} + +module.exports = { encodeScript } diff --git a/psf-memo-client/test/unit/memo-multipush.test.js b/psf-memo-client/test/unit/memo-multipush.test.js index 7d76835..9faced2 100644 --- a/psf-memo-client/test/unit/memo-multipush.test.js +++ b/psf-memo-client/test/unit/memo-multipush.test.js @@ -18,17 +18,7 @@ const { broadcastMultiPush, attachMultiPushOpReturn } = require('../../src/services/memo-multipush') - -// Encode a script the way Bitcoin does: an opcode number is one byte, a -// Buffer/string becomes a length-prefixed push. Enough to observe the pushes. -function encodeScript (script) { - const parts = script.map((el) => { - if (typeof el === 'number') return Buffer.from([el]) - const buf = Buffer.from(el) - return Buffer.concat([Buffer.from([buf.length]), buf]) - }) - return Buffer.concat(parts) -} +const { encodeScript } = require('../support/script-encoding') // A double of the minimal-slp-wallet surface the adapter reuses. function makeSlpWalletDouble () { @@ -75,6 +65,24 @@ test('toPushBuffer encodes a string as UTF-8 and passes bytes through', () => { assert.equal(toPushBuffer(Uint8Array.from([1, 2])).toString('hex'), '0102') }) +// The browser bundle has no global Buffer (CRA 5 does not polyfill Node +// globals and the wallet script does not set window.Buffer), so the adapter +// must import its own. Re-require it with the global removed to prove it. +test('the adapter builds pushes without a browser-global Buffer', () => { + const modulePath = require.resolve('../../src/services/memo-multipush') + const savedBuffer = global.Buffer + global.Buffer = undefined + delete require.cache[modulePath] + try { + const fresh = require('../../src/services/memo-multipush') + assert.equal(fresh.toPushBuffer('hi').toString('hex'), '6869') + assert.equal(fresh.buildPushes('6d03', ['hi'])[0].toString('hex'), '6d03') + } finally { + global.Buffer = savedBuffer + delete require.cache[modulePath] + } +}) + test('buildPushes puts the prefix first and each field in order', () => { const pushes = buildPushes('6d03', [Buffer.from('aa', 'hex'), 'hi']) @@ -116,13 +124,22 @@ test('a single field delegates to the original wallet sendOpReturn', async () => test('attaching twice does not double-wrap the wallet', async () => { const { wallet } = makeSlpWalletDouble() attachMultiPushOpReturn(wallet) + const wrapped = wallet.sendOpReturn attachMultiPushOpReturn(wallet) + // The second attach must leave the wrapper untouched rather than nesting it. + assert.equal(wallet.sendOpReturn, wrapped) + await wallet.sendOpReturn('single', '6d02') assert.equal(wallet.calls.length, 1) }) +test('attaching to a falsy wallet is a no-op', () => { + assert.equal(attachMultiPushOpReturn(null), null) + assert.equal(attachMultiPushOpReturn(undefined), undefined) +}) + test('broadcastMultiPush rejects a wallet without the OP_RETURN builder', async () => { await assert.rejects( broadcastMultiPush({ opReturn: {} }, ['hi'], '6d02'),