@fundtokens/builders 0.1.0-rc13 → 0.1.0-rc14

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.
@@ -29,11 +29,11 @@ const sortDecreasingTokenAmount = (a, b) => {
29
29
  const aAmount = a.token?.amount ?? 0n;
30
30
  const bAmount = b.token?.amount ?? 0n;
31
31
 
32
- if(aAmount === bAmount) {
32
+ if (aAmount === bAmount) {
33
33
  return 0;
34
34
  }
35
35
 
36
- if(aAmount > bAmount) {
36
+ if (aAmount > bAmount) {
37
37
  return -1;
38
38
  }
39
39
 
@@ -94,7 +94,7 @@ export default class FundTokenTransactionBuilder extends TransactionBuilder {
94
94
  outflow: swapEndianness(system.outflow),
95
95
  authorization: swapEndianness(system.authorization),
96
96
  fee: {
97
- nft: swapEndianness(system.fee.nft),
97
+ nft: swapEndianness(system.fee.nft), // TODO: enable passing global setting structure for easy usage
98
98
  },
99
99
  };
100
100
  this.#fund = {
@@ -0,0 +1,82 @@
1
+ import { FundTypes } from "./constants";
2
+
3
+ function clone(obj) {
4
+ return JSON.parse(JSON.stringify(obj));
5
+ }
6
+
7
+ async function fetchJson(url) {
8
+ const httpResponse = await fetch(url);
9
+ if(httpResponse.status !== 200) {
10
+ throw new Error('Server returned unsuccessful status');
11
+ }
12
+ const response = await httpResponse.json();
13
+ return response;
14
+ }
15
+
16
+ export default class FundTokensRegistry {
17
+ #network;
18
+ #url;
19
+
20
+ #current = {};
21
+ #instances = [];
22
+
23
+ constructor ({ network, url }) {
24
+ this.#network = network || 'chipnet';
25
+ this.#url = url || `https://${this.#network}-registry.fundtokens.cash/`;
26
+ }
27
+
28
+ clear() {
29
+ this.#current = {};
30
+ this.#instances = [];
31
+ }
32
+
33
+ async getHealth() {
34
+ const httpResponse = await fetch(this.#url + 'api/health');
35
+ const status = httpResponse.status;
36
+ const response = await httpResponse.text();
37
+ const healthy = status === 200 && response === 'OK';
38
+ return {
39
+ status: httpResponse.status,
40
+ text: status === 200 ? status : 'Unhealthy',
41
+ ready: healthy,
42
+ }
43
+ }
44
+
45
+ // return the registry data
46
+ async getCurrent(type = FundTypes.FixedBasket.code) { // fixed-basket, mean-reversion, etc
47
+ if(this.#current[type]) {
48
+ return clone(this.#current[type].parameters);
49
+ }
50
+
51
+ const current = await fetchJson(this.#url + 'api/current');
52
+ const instance = await fetchJson(this.#url + 'api/instances/' + current[type]);
53
+
54
+ this.#current[type] = instance;
55
+ return clone(instance.parameters);
56
+ }
57
+
58
+ async getInstance({ id, hash }) {
59
+ if(this.#instances.some(i => i.id === id || i.hash === hash)) {
60
+ return clone(this.#instances.find(i => i.id === id || i.hash === hash).parameters);
61
+ }
62
+
63
+ if(id) {
64
+ const instance = await fetchJson(this.#url + 'api/instances/' + id);
65
+ this.#instances.push(instance);
66
+ return clone(instance.parameters);
67
+ }
68
+
69
+ if(hash) {
70
+ await this.fetchInstances();
71
+ return clone(this.#instances.find(i => i.hash === hash)?.parameters);
72
+ }
73
+
74
+ throw new Error('id or hash must be provided');
75
+ }
76
+
77
+ async fetchInstances() {
78
+ const instances = await fetchJson(this.#url + 'api/instances');
79
+ this.#instances = instances;
80
+ return clone(this.#instances);
81
+ }
82
+ }
@@ -11,9 +11,10 @@ import {
11
11
  } from '@bitauth/libauth';
12
12
  import {
13
13
  getBestFee,
14
- getFundBin,
14
+ getFundCommitment,
15
15
  getFundHex,
16
16
  getRandomInt,
17
+ hashFund,
17
18
  withDust,
18
19
  } from './utils.js';
19
20
  import FundTokenTransactionBuilder from './FundTokenTransactionBuilder.js';
@@ -202,7 +203,7 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
202
203
  } else {
203
204
  // verify authhead output
204
205
  const authhead = this.outputs[0];
205
- if(authhead.to != authHeadVaultContract.tokenAddress || authhead.token) {
206
+ if (authhead.to != authHeadVaultContract.tokenAddress || authhead.token) {
206
207
  throw new Error('Authhead output is incorrect, expecting to send to authhead vault with no tokens');
207
208
  }
208
209
  }
@@ -234,10 +235,13 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
234
235
 
235
236
  const fundTokenAmount = 9223372036854775807n;
236
237
 
238
+ const fundHex = getFundHex(fund);
239
+ const fundHash = hashFund(fund);
240
+
237
241
  this.addInputs([
238
242
  {
239
243
  ...startupUtxo,
240
- unlocker: startupContract.unlock.start(getFundBin(fund)),
244
+ unlocker: startupContract.unlock.start(fundHex),
241
245
  },
242
246
  {
243
247
  ...inflowUtxo,
@@ -253,7 +257,7 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
253
257
  },
254
258
  {
255
259
  ...publicFundUtxo,
256
- unlocker: publicFundContract.unlock.broadcast(getFundBin(fund))
260
+ unlocker: publicFundContract.unlock.broadcast()
257
261
  }
258
262
  ])
259
263
  .addOutputs([
@@ -279,7 +283,7 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
279
283
  ...inflowUtxo.token,
280
284
  nft: {
281
285
  capability: 'none',
282
- commitment: swapEndianness(genesisUtxo.txid) + binToHex(hash256(getFundBin(fund))),
286
+ commitment: '02' + swapEndianness(genesisUtxo.txid) + fundHash,
283
287
  }
284
288
  }
285
289
  }),
@@ -289,7 +293,7 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
289
293
  ...outflowUtxo.token,
290
294
  nft: {
291
295
  capability: 'none',
292
- commitment: swapEndianness(genesisUtxo.txid) + binToHex(hash256(getFundBin(fund))),
296
+ commitment: '02' + swapEndianness(genesisUtxo.txid) + fundHash,
293
297
  }
294
298
  }
295
299
  }),
@@ -302,34 +306,18 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
302
306
  }),
303
307
  withDust({
304
308
  to: publicFundContract.tokenAddress,
305
- token: {
306
- category: this.#system.publicFund,
307
- amount: 0n,
308
- nft: {
309
- capability: 'minting',
310
- commitment: '',
311
- }
312
- }
309
+ token: publicFundUtxo.token,
313
310
  }),
314
311
  ]);
315
312
 
316
313
 
317
- const maxSize = 128 * 2;
318
-
319
- const fundHex = getFundHex(fund);
320
- const fundHexParts = [];
321
314
 
315
+ const maxSize = 128 * 2; // NFT commitment max size 128 bytes Layla - May 2026
322
316
  let curr = 0;
323
317
  let next = maxSize;
324
318
 
325
-
326
- while (curr < fundHex.length) {
327
- fundHexParts.push(fundHex.slice(curr, next));
328
- curr = next;
329
- next += maxSize
330
- }
331
-
332
- fundHexParts.forEach(part => {
319
+ const fundCommitment = getFundCommitment(fund);
320
+ while (curr < fundCommitment.length) {
333
321
  this.addOutput(withDust({
334
322
  to: publicFundVaultContract.tokenAddress,
335
323
  token: {
@@ -337,10 +325,12 @@ export default class PublicFundTransactionBuilder extends TransactionBuilder {
337
325
  amount: 0n,
338
326
  nft: {
339
327
  capability: 'none',
340
- commitment: part
328
+ commitment: fundCommitment.slice(curr, next)
341
329
  }
342
330
  }
343
- }))
344
- });
331
+ }));
332
+ curr = next;
333
+ next += maxSize
334
+ }
345
335
  }
346
336
  }
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
- # FundTokens.Contracts
1
+ # FundTokens Builders
2
2
 
3
- A JavaScript library for interacting with FundTokens smart contracts on the Bitcoin Cash network. This library provides tools for creating, minting, and redeeming fund tokens while handling the complex multi-contract operations required by the FundTokens protocol.
3
+ A JavaScript library for interacting with FundTokens smart contracts on the Bitcoin Cash network. This library provides tools for discovering, creating, minting, and redeeming fund tokens while handling the complex multi-contract operations required by the FundTokens protocol.
4
4
 
5
5
  ## Features
6
6
 
7
+ - **Public Fund Discovery**: Find trustlessly created funds
7
8
  - **Fund Creation**: Create new public funds with custom asset compositions
8
9
  - **Token Minting**: Deposit assets to mint fund tokens
9
10
  - **Token Redemption**: Withdraw assets by redeeming fund tokens
@@ -30,9 +31,11 @@ const fund = {
30
31
  assets: [{ category: 'asset_token_id', amount: 2n }]
31
32
  };
32
33
 
33
- // add user's genesis UTXO
34
+ // Add user's genesis UTXO and additional inputs
35
+ // If adding outputs, ensure to add our identity output as the first output
34
36
  await publicBuilder.addBroadcast({ fund });
35
- // add additional IO
37
+ // Add additional IO
38
+ // Add Bitcoin change
36
39
  await publicBuilder.send();
37
40
  ```
38
41
 
@@ -45,8 +48,10 @@ const fundBuilder = new FundTokenTransactionBuilder({
45
48
  provider, system, fund
46
49
  });
47
50
 
51
+ // Add inputs/outputs but inputs.length must equal outputs.length before continuing
48
52
  await fundBuilder.addInflow({ amount: 1n });
49
53
  // Add user asset inputs and fund token outputs
54
+ // Add Bitcoin change output
50
55
  await fundBuilder.send();
51
56
  ```
52
57
 
@@ -57,8 +62,10 @@ const fundBuilder = new FundTokenTransactionBuilder({
57
62
  provider, system, fund
58
63
  });
59
64
 
65
+ // Add inputs/outputs but inputs.length must equal outputs.length before continuing
60
66
  await fundBuilder.addOutflow({ amount: 1n });
61
- // Add user inputs/outputs
67
+ // Add user fund token inputs and asset outputs
68
+ // Add Bitcoin change output
62
69
  await fundBuilder.send();
63
70
  ```
64
71
 
@@ -67,18 +74,18 @@ await fundBuilder.send();
67
74
  ### Fund Lifecycle
68
75
 
69
76
  * Fund Creation - Broadcast fund parameters
70
- * Fund Operations - Mint and redeem tokens
77
+ * Fund Operations - Minting (inflow) and redeeming (outflow) fund tokens
71
78
 
72
79
  ## Security Model
73
80
 
74
81
  * Non-Custodial: Funds held in contract UTXOs controlled by code
75
- * Parameter Immutability: Fund details hashed and committed to tokens
82
+ * Parameter Immutability: Public fund details are hashed and committed to tokens
76
83
  * Contract Isolation: Each contract has single, verified responsibility
77
- * Thread Authorization: Operations require matching token presence
78
- * Atomic Validation: Multi-contract validation ensures consistency
84
+ * Atomic Validation: Orchestrated multi-contract validation ensures consistency
79
85
 
80
86
  ## Requirements
81
87
  * Bitcoin Cash network access
88
+ * Node
82
89
 
83
90
  ## License
84
91
 
package/art/asset.json CHANGED
@@ -1,35 +1,35 @@
1
- {
2
- "contractName": "AssetManager",
3
- "constructorInputs": [
4
- { "name": "outflowToken", "type": "bytes32" },
5
- { "name": "fundHash", "type": "bytes32" },
6
- { "name": "assetCategory", "type": "bytes32" }
7
- ],
8
- "abi": [
9
- { "name": "release", "inputs": [] }
10
- ],
11
- "bytecode": "OP_0 OP_INPUTINDEX OP_3 OP_SUB OP_BEGIN OP_DUP OP_0 OP_GREATERTHANOREQUAL OP_2 OP_PICK OP_NOT OP_BOOLAND OP_DUP OP_TOALTSTACK OP_IF OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT 40 OP_SPLIT OP_DROP 20 OP_SPLIT OP_NIP OP_4 OP_PICK OP_EQUALVERIFY OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_DUP OP_1SUB OP_NIP OP_ENDIF OP_FROMALTSTACK OP_NOT OP_UNTIL 24 OP_INPUTINDEX OP_1SUB OP_INPUTINDEX OP_INPUTBYTECODE OP_INPUTINDEX OP_INPUTBYTECODE OP_SIZE OP_NIP OP_1SUB OP_SPLIT OP_DROP OP_2 OP_PICK OP_SPLIT OP_NIP OP_OVER OP_INPUTBYTECODE OP_2 OP_PICK OP_INPUTBYTECODE OP_SIZE OP_NIP OP_1SUB OP_SPLIT OP_DROP OP_3 OP_ROLL OP_SPLIT OP_NIP OP_EQUAL OP_DUP OP_IF OP_OVER OP_INPUTBYTECODE 24 OP_SPLIT OP_DROP OP_4 OP_SPLIT OP_NIP OP_DUP 00 OP_CAT OP_BIN2NUM OP_8 OP_PICK 00 OP_CAT OP_BIN2NUM OP_2DUP OP_LESSTHAN OP_2 OP_PICK OP_2 OP_PICK OP_NUMEQUAL OP_BOOLOR OP_VERIFY OP_2DROP OP_DROP OP_ENDIF OP_3 OP_ROLL OP_SWAP OP_BOOLOR OP_VERIFY 0000000000000000000000000000000000000000000000000000000000000000 OP_5 OP_PICK OP_EQUAL OP_IF OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ELSE OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_5 OP_PICK OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_DROP OP_1",
12
- "source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * AssetManager: Holds individual fund assets\r\n * \r\n * Each asset in a fund has a dedicated AssetManager contract instance.\r\n * Releases assets\r\n * 1. if the preceeding contract is a AssetManager of a category that is lower (sorted) or the same asset\r\n * 2. If the outflow token is present with matching fund hash\r\n * \r\n * This contract gates asset access during redemption (outflow) transactions.\r\n * Amount verification is performed by the TransactionManager contract.\r\n * \r\n * Parameters:\r\n * outflowToken: Token category that authorizes redemption\r\n * fundHash: Hash of fund parameters (prevents spending wrong fund's assets)\r\n * assetCategory: The specific asset category this contract holds\r\n * (0x00...00 = Bitcoin satoshis, else = token category)\r\n */\r\ncontract AssetManager(bytes32 outflowToken, bytes32 fundHash, bytes32 assetCategory) \r\n{\r\n /**\r\n * release(): Releases held assets when outflow is authorized\r\n * \r\n * Ensures:\r\n * - Outflow token with matching fund hash is present (correct redemption)\r\n * - Asset type is verified (Bitcoin satoshis or tokens)\r\n * - Linked to a AssetManager or outflow token at \"expected\" input\r\n * \r\n * Note: AssetManager relies on TransactionManager for amount validation.\r\n * This contract only gates access; amounts are verified elsewhere.\r\n */\r\n function release() {\r\n bool prevOutflow = false;\r\n int inputIndex = this.activeInputIndex - 3; // at least this far but could be further\r\n\r\n // find the outflow, searching to the start\r\n while(inputIndex >= 0 && !prevOutflow) {\r\n if(tx.inputs[inputIndex].tokenCategory == outflowToken) {\r\n require(tx.inputs[inputIndex].nftCommitment.slice(32, 64) == fundHash);\r\n prevOutflow = true;\r\n }\r\n inputIndex = inputIndex - 1;\r\n }\r\n\r\n int parametersLength = 36;\r\n int prevInput = this.activeInputIndex - 1;\r\n bool additionalAssetManager = tx.inputs[this.activeInputIndex].unlockingBytecode.slice(parametersLength, tx.inputs[this.activeInputIndex].unlockingBytecode.length - 1) == tx.inputs[prevInput].unlockingBytecode.slice(parametersLength, tx.inputs[prevInput].unlockingBytecode.length - 1);\r\n if(additionalAssetManager) {\r\n bytes previousAsset = tx.inputs[prevInput].unlockingBytecode.slice(4, 36);\r\n\r\n int iPrevious = int(previousAsset + 0x00);\r\n int iCurrent = int(assetCategory + 0x00);\r\n\r\n require(iPrevious < iCurrent || iPrevious == iCurrent);\r\n }\r\n require(prevOutflow || additionalAssetManager);\r\n \r\n // Verify asset type matches\r\n bytes32 satoshiAsset = 0x0000000000000000000000000000000000000000000000000000000000000000;\r\n if(assetCategory == satoshiAsset) {\r\n // For Bitcoin: input should have no token (satoshis only)\r\n require(tx.inputs[this.activeInputIndex].tokenCategory == 0x);\r\n } else {\r\n // For tokens: input must have the correct asset category\r\n require(tx.inputs[this.activeInputIndex].tokenCategory == assetCategory);\r\n }\r\n }\r\n}",
13
- "fingerprint": "da74fe6c47b137f657a7ccd6ff8fa727499a25c3efc237c932425337ff52fa28",
14
- "debug": {
15
- "bytecode": "00c05394657600a25279919a766b6376ce5379876376cf01407f7501207f77547988517b757c68768c77686c91660124c08cc0cac0ca82778c7f7552797f7778ca5279ca82778c7f75537a7f7787766378ca01247f75547f777601007e81587901007e816e9f527952799c9b696d7568537a7c9b6920000000000000000000000000000000000000000000000000000000000000000055798763c0ce008867c0ce557988686d6d7551",
16
- "sourceMap": "34:27:34:32;35:25:35:46;:49::50;:25:::1;38:8:44:9:0;:14:38:24;:28::29;:14:::1;:34::45:0;;:33:::1;:14;;;:47:44:9:0;39:25:39:35;:15::50:1;:54::66:0;;:15:::1;:68:42:13:0;40:34:40:44;:24::59:1;:70::72:0;:24::73:1;;:66::68:0;:24::73:1;;:77::85:0;;:16::87:1;41:30:41:34:0;:16::35:1;;;39:68:42:13;43:25:43:35:0;:::39:1;:12::40;38:47:44:9;;:8;;46:31:46:33:0;47:24:47:45;:::49:1;48:48:48:69:0;:38::88:1;:123::144:0;:113::163:1;:::170;;:::174;:38::175;;:95::111:0;;:38::175:1;;:189::198:0;:179::217:1;:252::261:0;;:242::280:1;:::287;;:::291;:179::292;;:224::240:0;;:179::292:1;;:38;49:11:49:33:0;:35:56:9;50:44:50:53;:34::72:1;:82::84:0;:34::85:1;;:79::80:0;:34::85:1;;52:32:52:45:0;:48::52;:32:::1;:28::53;53:31:53:44:0;;:47::51;:31:::1;:27::52;55:20:55:40:0;::::1;:44::53:0;;:57::65;;:44:::1;:20;:12::67;49:35:56:9;;;57:16:57:27:0;;:31::53;:16:::1;:8::55;60:31:60:97:0;61:11:61:24;;:::40:1;:42:64:9:0;63:30:63:51;:20::66:1;:70::72:0;:12::74:1;64:15:67:9:0;66:30:66:51;:20::66:1;:70::83:0;;:12::85:1;64:15:67:9;33:23:68:5;;;",
17
- "logs": [],
18
- "requires": [
19
- { "ip": 34, "line": 40 },
20
- { "ip": 105, "line": 55 },
21
- { "ip": 113, "line": 57 },
22
- { "ip": 122, "line": 63 },
23
- { "ip": 128, "line": 66 }
24
- ]
25
- },
26
- "compiler": {
27
- "name": "cashc",
28
- "version": "0.13.0",
29
- "options": {
30
- "enforceFunctionParameterTypes": true,
31
- "enforceLocktimeGuard": true
32
- }
33
- },
34
- "updatedAt": "2026-07-29T19:00:42.208Z"
1
+ {
2
+ "contractName": "AssetManager",
3
+ "constructorInputs": [
4
+ { "name": "outflowToken", "type": "bytes32" },
5
+ { "name": "fundHash", "type": "bytes32" },
6
+ { "name": "assetCategory", "type": "bytes32" }
7
+ ],
8
+ "abi": [
9
+ { "name": "release", "inputs": [] }
10
+ ],
11
+ "bytecode": "OP_2 OP_PICK 0000000000000000000000000000000000000000000000000000000000000000 OP_EQUAL OP_IF OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ELSE OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_ENDIF OP_0 OP_INPUTINDEX OP_3 OP_SUB OP_BEGIN OP_DUP OP_0 OP_GREATERTHANOREQUAL OP_2 OP_PICK OP_NOT OP_BOOLAND OP_DUP OP_TOALTSTACK OP_IF OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT 41 OP_SPLIT OP_DROP 21 OP_SPLIT OP_NIP OP_4 OP_PICK OP_EQUALVERIFY OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_DUP OP_1SUB OP_NIP OP_ENDIF OP_FROMALTSTACK OP_NOT OP_UNTIL OP_INPUTINDEX OP_1SUB OP_INPUTBYTECODE OP_4 OP_SPLIT OP_NIP 20 OP_SPLIT OP_INPUTINDEX OP_INPUTBYTECODE OP_DUP OP_SIZE OP_NIP OP_1SUB OP_SPLIT OP_DROP 24 OP_SPLIT OP_NIP OP_EQUAL OP_DUP OP_IF OP_OVER 00 OP_CAT OP_BIN2NUM OP_7 OP_PICK 00 OP_CAT OP_BIN2NUM OP_2DUP OP_LESSTHAN OP_2 OP_PICK OP_2 OP_PICK OP_NUMEQUAL OP_BOOLOR OP_VERIFY OP_2DROP OP_ENDIF OP_3 OP_ROLL OP_SWAP OP_BOOLOR OP_VERIFY OP_2DROP OP_2DROP OP_DROP OP_1",
12
+ "source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * AssetManager: Holds individual assets for funds\r\n * \r\n * This contract gates asset access during redemption (outflow) transactions.\r\n * Amount verification is performed by the TransactionManager contract.\r\n * Each asset in a fund has a dedicated AssetManager contract instance.\r\n * \r\n * Parameters:\r\n * outflowToken: Token category that authorizes redemption\r\n * fundHash: Hash of fund parameters (prevents spending wrong fund's assets)\r\n * assetCategory: The specific asset category this contract holds\r\n * (0x00...00 = Bitcoin, else = token category)\r\n */\r\ncontract AssetManager(bytes32 outflowToken, bytes32 fundHash, bytes32 assetCategory) \r\n{\r\n /**\r\n * release(): Releases held assets when outflow is authorized\r\n *\r\n * AssetManager relies on TransactionManager for redeeming asset amount verification.\r\n * To release, checking for outflow token or linking w/ this contract.\r\n * \r\n * Ensures:\r\n * - Outflow token with matching fund hash is present (correct redemption)\r\n * - Asset type is verified (Bitcoin or tokens)\r\n * - Outflow token at \"expected\" input or uses efficient linking to a previous AssetManager\r\n */\r\n function release() {\r\n //\r\n // Verify this spends the correct asset\r\n // Bitcoin for 0x00... otherwise verify token category\r\n if(assetCategory == 0x0000000000000000000000000000000000000000000000000000000000000000) {\r\n require(tx.inputs[this.activeInputIndex].tokenCategory == 0x);\r\n } else {\r\n require(tx.inputs[this.activeInputIndex].tokenCategory == assetCategory);\r\n }\r\n\r\n //\r\n // Find the outflow, searching to the start\r\n bool prevOutflow = false;\r\n int inputIndex = this.activeInputIndex - 3; // at least this far but could be further\r\n while(inputIndex >= 0 && !prevOutflow) {\r\n if(tx.inputs[inputIndex].tokenCategory == outflowToken) {\r\n require(tx.inputs[inputIndex].nftCommitment.slice(33, 65) == fundHash); // type 0x02 + fund category 32 bytes + fund hash 32 bytes\r\n prevOutflow = true;\r\n }\r\n inputIndex = inputIndex - 1;\r\n }\r\n \r\n //\r\n // Check if the previous input is an AssetManager contract for this fund\r\n int prevInput = this.activeInputIndex - 1;\r\n bytes prevParamsAndBytecode = tx.inputs[prevInput].unlockingBytecode.split(4)[1];\r\n bytes prevCategory, bytes prevUnlockingBytecode = prevParamsAndBytecode.split(32);\r\n bytes thisUnlockingBytecode = tx.inputs[this.activeInputIndex].unlockingBytecode;\r\n bool additionalAssetManager = thisUnlockingBytecode.slice(36, thisUnlockingBytecode.length - 1) == prevUnlockingBytecode; // slice out the asset category\r\n if(additionalAssetManager) {\r\n int iPrevious = int(prevCategory + 0x00);\r\n int iCurrent = int(assetCategory + 0x00);\r\n\r\n require(iPrevious < iCurrent || iPrevious == iCurrent);\r\n }\r\n\r\n //\r\n // Must have redemption signal or previous AssetManager contract\r\n require(prevOutflow || additionalAssetManager);\r\n }\r\n}",
13
+ "fingerprint": "41f470287ebc55c0d733d7bb0e3c497457df2972daa8bc71c62cbdcd48724087",
14
+ "debug": {
15
+ "bytecode": "52792000000000000000000000000000000000000000000000000000000000000000008763c0ce008867c0ce5379886800c05394657600a25279919a766b6376ce5379876376cf01417f7501217f77547988517b757c68768c77686c9166c08cca547f7701207fc0ca7682778c7f7501247f778776637801007e81577901007e816e9f527952799c9b696d68537a7c9b696d6d7551",
16
+ "sourceMap": "33:11:33:24;;:28::94;:11:::1;:96:35:9:0;34:30:34:51;:20::66:1;:70::72:0;:12::74:1;35:15:37:9:0;36:30:36:51;:20::66:1;:70::83:0;;:12::85:1;35:15:37:9;41:27:41:32:0;42:25:42:46;:49::50;:25:::1;43:8:49:9:0;:14:43:24;:28::29;:14:::1;:34::45:0;;:33:::1;:14;;;:47:49:9:0;44:25:44:35;:15::50:1;:54::66:0;;:15:::1;:68:47:13:0;45:34:45:44;:24::59:1;:70::72:0;:24::73:1;;:66::68:0;:24::73:1;;:77::85:0;;:16::87:1;46:30:46:34:0;:16::35:1;;;44:68:47:13;48:25:48:35:0;:::39:1;:12::40;43:47:49:9;;:8;;53:24:53:45:0;:::49:1;54:38:54:76;:83::84:0;:38::85:1;:::88;55:86:55::0;:58::89:1;56:48:56:69:0;:38::88:1;57::57:91:0;:70::98:1;;:::102;:38::103;;:66::68:0;:38::103:1;;:::128;58:11:58:33:0;:35:63:9;59:32:59:44;:47::51;:32:::1;:28::52;60:31:60:44:0;;:47::51;:31:::1;:27::52;62:20:62:40:0;::::1;:44::53:0;;:57::65;;:44:::1;:20;:12::67;58:35:63:9;;67:16:67:27:0;;:31::53;:16:::1;:8::55;29:23:68:5;;;",
17
+ "logs": [],
18
+ "requires": [
19
+ { "ip": 11, "line": 34 },
20
+ { "ip": 17, "line": 36 },
21
+ { "ip": 50, "line": 45 },
22
+ { "ip": 102, "line": 62 },
23
+ { "ip": 109, "line": 67 }
24
+ ]
25
+ },
26
+ "compiler": {
27
+ "name": "cashc",
28
+ "version": "0.13.0",
29
+ "options": {
30
+ "enforceFunctionParameterTypes": true,
31
+ "enforceLocktimeGuard": true
32
+ }
33
+ },
34
+ "updatedAt": "2026-09-04T16:02:30.436Z"
35
35
  }
@@ -1,37 +1,36 @@
1
- {
2
- "contractName": "AuthHeadVault",
3
- "constructorInputs": [
4
- { "name": "authToken", "type": "bytes32" }
5
- ],
6
- "abi": [
7
- { "name": "update", "inputs": [] },
8
- { "name": "burn", "inputs": [] }
9
- ],
10
- "bytecode": "OP_OVER OP_0 OP_NUMEQUAL OP_IF OP_INPUTINDEX OP_0 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_0 OP_BEGIN OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT OP_3 OP_SPLIT OP_DROP OP_1 OP_SPLIT OP_NIP 0002 OP_AND 0002 OP_EQUAL OP_IF OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_ENDIF OP_DUP OP_1ADD OP_NIP OP_DUP OP_TXINPUTCOUNT OP_LESSTHAN OP_2 OP_PICK OP_NOT OP_BOOLAND OP_NOT OP_UNTIL OP_DROP OP_NIP OP_NIP OP_ELSE OP_SWAP OP_1 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTBYTECODE OP_1 OP_SPLIT OP_DROP 6a OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_OUTPUTVALUE OP_0 OP_NUMEQUALVERIFY OP_0 OP_0 OP_BEGIN OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT OP_3 OP_SPLIT OP_DROP OP_1 OP_SPLIT OP_NIP 0020 OP_AND 0020 OP_EQUAL OP_IF OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_ENDIF OP_DUP OP_1ADD OP_NIP OP_DUP OP_TXINPUTCOUNT OP_LESSTHAN OP_2 OP_PICK OP_NOT OP_BOOLAND OP_NOT OP_UNTIL OP_DROP OP_NIP OP_ENDIF",
11
- "source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * AuthHeadVault: Modified authorization vault with strict authhead requirements\r\n * \r\n * A authorization vault with additional output constraints to maintain authhead identity:\r\n * - Must be the first input (index 0)\r\n * - First output must not contain any tokens\r\n * - Must be authorized with permission bit 0x0002\r\n *\r\n * Authhead identity burning to support full lifecycle of BCMR identities\r\n * - No input positioning restriction, burn X identities in a single transaction\r\n * - First output must be a no value, no token OP_RETURN\r\n * - No restrictions on data embedded in OP_RETURN\r\n * - Must be authorized with permission bit 0x0020\r\n * \r\n * Used specifically for authorizing fund's BCMR update operations.\r\n * \r\n * Parameters:\r\n * authToken: Token category that authorizes fund creation\r\n */\r\ncontract AuthHeadVault(bytes32 authToken)\r\n{\r\n /**\r\n * update(): Authorizes authhead update with strict transaction structure\r\n * \r\n * Supports:\r\n * - No BCMR update restrictions\r\n *\r\n * Ensures:\r\n * - This vault is the first input (positioning control)\r\n * - First output has no tokens (clean state to maintain authhead integrity)\r\n * - Authorization token is present with bit 0x0002 (authhead update permission)\r\n */\r\n function update() {\r\n // This vault MUST be the first input, ensures only one update but prevents combining identities\r\n require(this.activeInputIndex == 0, \"expected to be the first input\");\r\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\r\n // First output must have no tokens (ensures clean state to maintain authhead integrity)\r\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x, \"no token allowed on authhead\");\r\n\r\n // Check for authorization token in ANY input\r\n // Bit 0x0002 in commitment indicates authhead (BCMR maintenance) authorization\r\n bool authorized = false;\r\n int inputIndex = 0;\r\n do {\r\n if(tx.inputs[inputIndex].tokenCategory == authToken) {\r\n if((bytes(tx.inputs[inputIndex].nftCommitment.slice(1, 3)) & bytes(0x0002)) == 0x0002) {\r\n authorized = true;\r\n }\r\n }\r\n inputIndex = inputIndex + 1;\r\n } while(inputIndex < tx.inputs.length && !authorized);\r\n require(authorized, \"unauthorized user\");\r\n }\r\n\r\n /**\r\n * burn(): Authorizes authhead identity burning with strict transaction structure\r\n *\r\n * Supports:\r\n * - Multiple identity burning in a single tx\r\n *\r\n * Ensures:\r\n * - The outputs' authhead is an OP_RETURN\r\n * - The outputs' authhead has no value or tokens\r\n * - Authorization token is present with bit 0x0020 (authhead identity burning permission)\r\n */\r\n function burn() {\r\n // First output must be an OP_RETURN\r\n require(tx.outputs[0].lockingBytecode.slice(0, 1) == 0x6a, \"first output must be an OP_RETURN\");\r\n // First output must have no tokens or value (ensures clean state to maintain authhead integrity)\r\n require(tx.outputs[0].tokenCategory == 0x, \"no token allowed on authhead\");\r\n require(tx.outputs[0].value == 0);\r\n\r\n // Check for authorization token in ANY input\r\n // Bit 0x0020 in commitment indicates authhead (BCMR maintenance) identity burning\r\n bool authorized = false;\r\n int inputIndex = 0;\r\n do {\r\n if(tx.inputs[inputIndex].tokenCategory == authToken) {\r\n if((bytes(tx.inputs[inputIndex].nftCommitment.slice(1, 3)) & bytes(0x0020)) == 0x0020) {\r\n authorized = true;\r\n }\r\n }\r\n inputIndex = inputIndex + 1;\r\n } while(inputIndex < tx.inputs.length && !authorized);\r\n require(authorized, \"unauthorized user\");\r\n }\r\n}",
12
- "fingerprint": "bb35184f788aa34f36da2c75c91f86d4d1ec1f62820a7421b89e74354f16f1fd",
13
- "debug": {
14
- "bytecode": "78009c63c0009dc0c7c0cd88c0d1008800006576ce5379876376cf537f75517f77020002840200028763517b757c6868768b7776c39f5279919a9166757777677c519d00cd517f75016a8800d1008800cc009d00006576ce5379876376cf537f75517f77020020840200208763517b757c6868768b7776c39f5279919a9166757768",
15
- "sourceMap": "35:4:55:5;;;;37:16:37:37;:41::42;:8::78:1;38:26:38:47:0;:16::64:1;:79::100:0;:68::117:1;:8::119;40:27:40:48:0;:16::63:1;:67::69:0;:8::103:1;44:26:44:31:0;45:25:45:26;46:8:53:62;47:25:47:35;:15::50:1;:54::63:0;;:15:::1;:65:51:13:0;48:36:48:46;:26::61:1;:71::72:0;:26::73:1;;:68::69:0;:26::73:1;;:83::89:0;:20::90:1;:95::101:0;:19:::1;:103:50:17:0;49:33:49:37;:20::38:1;;;48:103:50:17;47:65:51:13;52:25:52:35:0;:::39:1;:12::40;53:16:53:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;46:8::62;;35:22:55:5;;;:4;68::88::0;;;70:27:70:28;:16::45:1;:55::56:0;:16::57:1;;:61::65:0;:8::104:1;72:27:72:28:0;:16::43:1;:47::49:0;:8::83:1;73:27:73:28:0;:16::35:1;:39::40:0;:8::42:1;77:26:77:31:0;78:25:78:26;79:8:86:62;80:25:80:35;:15::50:1;:54::63:0;;:15:::1;:65:84:13:0;81:36:81:46;:26::61:1;:71::72:0;:26::73:1;;:68::69:0;:26::73:1;;:83::89:0;:20::90:1;:95::101:0;:19:::1;:103:83:17:0;82:33:82:37;:20::38:1;;;81:103:83:17;80:65:84:13;85:25:85:35:0;:::39:1;:12::40;86:16:86:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;79:8::62;;68:20:88:5;;22:0:89:1",
16
- "logs": [],
17
- "requires": [
18
- { "ip": 7, "line": 37, "message": "expected to be the first input" },
19
- { "ip": 12, "line": 38 },
20
- { "ip": 16, "line": 40, "message": "no token allowed on authhead" },
21
- { "ip": 57, "line": 54, "message": "unauthorized user" },
22
- { "ip": 70, "line": 70, "message": "first output must be an OP_RETURN" },
23
- { "ip": 74, "line": 72, "message": "no token allowed on authhead" },
24
- { "ip": 78, "line": 73 },
25
- { "ip": 119, "line": 87, "message": "unauthorized user" }
26
- ]
27
- },
28
- "compiler": {
29
- "name": "cashc",
30
- "version": "0.13.0",
31
- "options": {
32
- "enforceFunctionParameterTypes": true,
33
- "enforceLocktimeGuard": true
34
- }
35
- },
36
- "updatedAt": "2026-07-29T19:00:44.322Z"
1
+ {
2
+ "contractName": "AuthHeadVault",
3
+ "constructorInputs": [
4
+ { "name": "authToken", "type": "bytes32" }
5
+ ],
6
+ "abi": [
7
+ { "name": "update", "inputs": [] },
8
+ { "name": "burn", "inputs": [] }
9
+ ],
10
+ "bytecode": "OP_OVER OP_0 OP_NUMEQUAL OP_IF OP_INPUTINDEX OP_0 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_1 OP_BEGIN OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT OP_3 OP_SPLIT OP_DROP OP_1 OP_SPLIT OP_NIP 0002 OP_AND 0002 OP_EQUAL OP_IF OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_ENDIF OP_DUP OP_1ADD OP_NIP OP_DUP OP_TXINPUTCOUNT OP_LESSTHAN OP_2 OP_PICK OP_NOT OP_BOOLAND OP_NOT OP_UNTIL OP_DROP OP_NIP OP_NIP OP_ELSE OP_SWAP OP_1 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTBYTECODE OP_1 OP_SPLIT OP_DROP 6a OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_0 OP_BEGIN OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT OP_3 OP_SPLIT OP_DROP OP_1 OP_SPLIT OP_NIP 0020 OP_AND 0020 OP_EQUAL OP_IF OP_1 OP_ROT OP_DROP OP_SWAP OP_ENDIF OP_ENDIF OP_DUP OP_1ADD OP_NIP OP_DUP OP_TXINPUTCOUNT OP_LESSTHAN OP_2 OP_PICK OP_NOT OP_BOOLAND OP_NOT OP_UNTIL OP_DROP OP_NIP OP_ENDIF",
11
+ "source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * AuthHeadVault: Token identity vault with authorization requirements\r\n * \r\n * Used specifically for authorizing fund's BCMR update operations.\r\n * Operations are authorized via token using commitment as permission bits.\r\n * \r\n * Parameters:\r\n * authToken: Token category that authorizes contract operations\r\n */\r\ncontract AuthHeadVault(bytes32 authToken)\r\n{\r\n /**\r\n * update(): Authorizes authhead update with strict transaction structure\r\n * \r\n * There are no restrictions on updating BCMR or identity.\r\n * Preserves the identity UTXO without combining identites.\r\n * Only one identity can be updated in a tx.\r\n *\r\n * Ensures:\r\n * - This vault is the first input (positioning control)\r\n * - First output has no tokens (clean state to maintain authhead integrity)\r\n * - Authorization token is present with bit 0x0002 (authhead update permission)\r\n */\r\n function update() {\r\n //\r\n // Verify this input\r\n require(this.activeInputIndex == 0, \"expected to be the first input\");\r\n\r\n //\r\n // Verify the identity output\r\n require(tx.outputs[this.activeInputIndex].lockingBytecode == tx.inputs[this.activeInputIndex].lockingBytecode);\r\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x, \"no token allowed on authhead\");\r\n\r\n //\r\n // Check for authorization token in ANY input but can skip the identity\r\n // Bit 0x0002 in commitment indicates authhead (BCMR maintenance) authorization\r\n bool authorized = false;\r\n int inputIndex = 1;\r\n do {\r\n if(tx.inputs[inputIndex].tokenCategory == authToken) {\r\n if((bytes(tx.inputs[inputIndex].nftCommitment.slice(1, 3)) & bytes(0x0002)) == 0x0002) {\r\n authorized = true;\r\n }\r\n }\r\n inputIndex = inputIndex + 1;\r\n } while(inputIndex < tx.inputs.length && !authorized);\r\n require(authorized, \"unauthorized user\");\r\n }\r\n\r\n /**\r\n * burn(): Authorizes authhead identity burning with strict transaction structure\r\n *\r\n * Able to burn multiple identities in a single tx.\r\n * Used to signal this identity is no longer maintained and for UTXO cleanup.\r\n *\r\n * Ensures:\r\n * - The outputs' authhead is an OP_RETURN\r\n * - The outputs' authhead has no value or tokens\r\n * - Authorization token is present with bit 0x0020 (authhead identity burning permission)\r\n */\r\n function burn() {\r\n //\r\n // Verify the burned identity\r\n require(tx.outputs[0].lockingBytecode.slice(0, 1) == 0x6a, \"first output must be an OP_RETURN\");\r\n require(tx.outputs[0].tokenCategory == 0x, \"no token allowed on authhead\");\r\n\r\n //\r\n // Check for authorization token in ANY input\r\n // Bit 0x0020 in commitment indicates authhead (BCMR maintenance) identity burning\r\n bool authorized = false;\r\n int inputIndex = 0;\r\n do {\r\n if(tx.inputs[inputIndex].tokenCategory == authToken) {\r\n if((bytes(tx.inputs[inputIndex].nftCommitment.slice(1, 3)) & bytes(0x0020)) == 0x0020) {\r\n authorized = true;\r\n }\r\n }\r\n inputIndex = inputIndex + 1;\r\n } while(inputIndex < tx.inputs.length && !authorized);\r\n require(authorized, \"unauthorized user\");\r\n }\r\n}",
12
+ "fingerprint": "36471eb6fd442dd97f66c4ad592ff4b1ac1fd687fe4a83249db5b9632c71de11",
13
+ "debug": {
14
+ "bytecode": "78009c63c0009dc0cdc0c788c0d1008800516576ce5379876376cf537f75517f77020002840200028763517b757c6868768b7776c39f5279919a9166757777677c519d00cd517f75016a8800d1008800006576ce5379876376cf537f75517f77020020840200208763517b757c6868768b7776c39f5279919a9166757768",
15
+ "sourceMap": "26:4:50:5;;;;29:16:29:37;:41::42;:8::78:1;33:27:33:48:0;:16::65:1;:79::100:0;:69::117:1;:8::119;34:27:34:48:0;:16::63:1;:67::69:0;:8::103:1;39:26:39:31:0;40:25:40:26;41:8:48:62;42:25:42:35;:15::50:1;:54::63:0;;:15:::1;:65:46:13:0;43:36:43:46;:26::61:1;:71::72:0;:26::73:1;;:68::69:0;:26::73:1;;:83::89:0;:20::90:1;:95::101:0;:19:::1;:103:45:17:0;44:33:44:37;:20::38:1;;;43:103:45:17;42:65:46:13;47:25:47:35:0;:::39:1;:12::40;48:16:48:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;41:8::62;;26:22:50:5;;;:4;63::83::0;;;66:27:66:28;:16::45:1;:55::56:0;:16::57:1;;:61::65:0;:8::104:1;67:27:67:28:0;:16::43:1;:47::49:0;:8::83:1;72:26:72:31:0;73:25:73:26;74:8:81:62;75:25:75:35;:15::50:1;:54::63:0;;:15:::1;:65:79:13:0;76:36:76:46;:26::61:1;:71::72:0;:26::73:1;;:68::69:0;:26::73:1;;:83::89:0;:20::90:1;:95::101:0;:19:::1;:103:78:17:0;77:33:77:37;:20::38:1;;;76:103:78:17;75:65:79:13;80:25:80:35:0;:::39:1;:12::40;81:16:81:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;74:8::62;;63:20:83:5;;12:0:84:1",
16
+ "logs": [],
17
+ "requires": [
18
+ { "ip": 7, "line": 29, "message": "expected to be the first input" },
19
+ { "ip": 12, "line": 33 },
20
+ { "ip": 16, "line": 34, "message": "no token allowed on authhead" },
21
+ { "ip": 57, "line": 49, "message": "unauthorized user" },
22
+ { "ip": 70, "line": 66, "message": "first output must be an OP_RETURN" },
23
+ { "ip": 74, "line": 67, "message": "no token allowed on authhead" },
24
+ { "ip": 115, "line": 82, "message": "unauthorized user" }
25
+ ]
26
+ },
27
+ "compiler": {
28
+ "name": "cashc",
29
+ "version": "0.13.0",
30
+ "options": {
31
+ "enforceFunctionParameterTypes": true,
32
+ "enforceLocktimeGuard": true
33
+ }
34
+ },
35
+ "updatedAt": "2026-09-04T16:02:32.347Z"
37
36
  }