@fundtokens/builders 0.1.0-rc10
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/FundTokenTransactionBuilder.js +457 -0
- package/LICENSE +1 -0
- package/PublicFundTransactionBuilder.js +346 -0
- package/README.md +85 -0
- package/art/asset.json +60 -0
- package/art/authhead_vault.json +51 -0
- package/art/fee.json +101 -0
- package/art/fee_minter.json +82 -0
- package/art/fund.json +76 -0
- package/art/manager.json +206 -0
- package/art/mint_inflow.json +84 -0
- package/art/mint_outflow.json +84 -0
- package/art/public.json +149 -0
- package/art/public_vault.json +122 -0
- package/art/simple_minter.json +73 -0
- package/art/simple_vault.json +37 -0
- package/art/startup.json +113 -0
- package/constants.js +1 -0
- package/index.js +18 -0
- package/package.json +21 -0
- package/utils.js +254 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Contract,
|
|
3
|
+
TransactionBuilder,
|
|
4
|
+
} from 'cashscript';
|
|
5
|
+
import {
|
|
6
|
+
swapEndianness,
|
|
7
|
+
hash256,
|
|
8
|
+
hexToBin,
|
|
9
|
+
binToHex,
|
|
10
|
+
cashAddressToLockingBytecode,
|
|
11
|
+
} from '@bitauth/libauth';
|
|
12
|
+
import {
|
|
13
|
+
getBestFee,
|
|
14
|
+
getFundBin,
|
|
15
|
+
getFundHex,
|
|
16
|
+
getRandomInt,
|
|
17
|
+
withDust,
|
|
18
|
+
} from './utils.js';
|
|
19
|
+
import FundTokenTransactionBuilder from './FundTokenTransactionBuilder.js';
|
|
20
|
+
|
|
21
|
+
import feeJson from './art/fee.json' with { type: 'json' };
|
|
22
|
+
import startupJson from './art/startup.json' with { type: 'json' };
|
|
23
|
+
import mintInflowJson from './art/mint_inflow.json' with { type: 'json' };
|
|
24
|
+
import mintOutflowJson from './art/mint_outflow.json' with { type: 'json' };
|
|
25
|
+
import managerJson from './art/manager.json' with { type: 'json' };
|
|
26
|
+
import fundJson from './art/fund.json' with { type: 'json' };
|
|
27
|
+
import assetJson from './art/asset.json' with { type: 'json' };
|
|
28
|
+
import publicJson from './art/public.json' with { type: 'json' };
|
|
29
|
+
import simpleVaultJson from './art/simple_vault.json' with { type: 'json' };
|
|
30
|
+
import authHeadVaultJson from './art/authhead_vault.json' with { type: 'json' };
|
|
31
|
+
import publicFundVaultJson from './art/public_vault.json' with { type: 'json' };
|
|
32
|
+
|
|
33
|
+
export default class PublicFundTransactionBuilder extends TransactionBuilder {
|
|
34
|
+
#system = {
|
|
35
|
+
inflow: '', // 32 byte, token id
|
|
36
|
+
outflow: '', // 32 byte, token id
|
|
37
|
+
publicFund: '', // 32 byte, token id
|
|
38
|
+
authorization: '', // 32 byte, token id
|
|
39
|
+
fees: {
|
|
40
|
+
create: {
|
|
41
|
+
nft: '', // 32 byte, tx id/token id
|
|
42
|
+
value: -1n, // bigint
|
|
43
|
+
},
|
|
44
|
+
execute: {
|
|
45
|
+
nft: '', // 32 byte, tx id/token id
|
|
46
|
+
value: -1n, // bigint
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
#swapped = {
|
|
51
|
+
inflow: '',
|
|
52
|
+
outflow: '',
|
|
53
|
+
publicFund: '',
|
|
54
|
+
authorization: '',
|
|
55
|
+
fees: {
|
|
56
|
+
create: {
|
|
57
|
+
nft: '',
|
|
58
|
+
},
|
|
59
|
+
execute: {
|
|
60
|
+
nft: '',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
#contracts = {
|
|
65
|
+
startupContract: null,
|
|
66
|
+
mintInflowContract: null,
|
|
67
|
+
mintOutflowContract: null,
|
|
68
|
+
createFundFeeContract: null,
|
|
69
|
+
executeFundFeeContract: null,
|
|
70
|
+
publicFundContract: null,
|
|
71
|
+
feeVaultContract: null,
|
|
72
|
+
authHeadVaultContract: null,
|
|
73
|
+
publicFundVaultContract: null,
|
|
74
|
+
};
|
|
75
|
+
#logger = null;
|
|
76
|
+
|
|
77
|
+
constructor({
|
|
78
|
+
provider,
|
|
79
|
+
system,
|
|
80
|
+
logger,
|
|
81
|
+
}) {
|
|
82
|
+
if (!system) {
|
|
83
|
+
throw new Error('No system configuration provided, unable to continue');
|
|
84
|
+
}
|
|
85
|
+
super({ provider });
|
|
86
|
+
this.#system = system;
|
|
87
|
+
this.#swapped = {
|
|
88
|
+
inflow: swapEndianness(system.inflow),
|
|
89
|
+
outflow: swapEndianness(system.outflow),
|
|
90
|
+
publicFund: swapEndianness(system.publicFund),
|
|
91
|
+
authorization: swapEndianness(system.authorization),
|
|
92
|
+
fees: {
|
|
93
|
+
create: {
|
|
94
|
+
nft: swapEndianness(system.fees.create.nft),
|
|
95
|
+
},
|
|
96
|
+
execute: {
|
|
97
|
+
nft: swapEndianness(system.fees.execute.nft),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
this.#logger = logger ?? this.#logger;
|
|
102
|
+
this.#buildContracts();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// build and get the contracts
|
|
106
|
+
#buildContracts() {
|
|
107
|
+
const feeVaultContract = new Contract(simpleVaultJson, [this.#swapped.authorization], { provider: this.provider });
|
|
108
|
+
const feeVaultLockingBytecode = binToHex(cashAddressToLockingBytecode(feeVaultContract.tokenAddress).bytecode);
|
|
109
|
+
|
|
110
|
+
const createFundFeeContract = new Contract(feeJson, [this.#swapped.authorization, feeVaultLockingBytecode, this.#swapped.fees.create.nft, BigInt(this.#system.fees.create.value)], { provider: this.provider });
|
|
111
|
+
const executeFundFeeContract = new Contract(feeJson, [this.#swapped.authorization, feeVaultLockingBytecode, this.#swapped.fees.execute.nft, BigInt(this.#system.fees.execute.value)], { provider: this.provider });
|
|
112
|
+
|
|
113
|
+
const startupContract = new Contract(startupJson, [
|
|
114
|
+
binToHex(hash256(hexToBin(createFundFeeContract.bytecode))),
|
|
115
|
+
this.#swapped.inflow,
|
|
116
|
+
this.#swapped.outflow,
|
|
117
|
+
], { provider: this.provider });
|
|
118
|
+
const startupContractHash = binToHex(hash256(hexToBin(startupContract.bytecode)))
|
|
119
|
+
|
|
120
|
+
const mintInflowContract = new Contract(mintInflowJson, [
|
|
121
|
+
startupContractHash,
|
|
122
|
+
this.#swapped.inflow,
|
|
123
|
+
this.#swapped.outflow,
|
|
124
|
+
binToHex(hash256(hexToBin(executeFundFeeContract.bytecode))),
|
|
125
|
+
hexToBin(managerJson.debug.bytecode),
|
|
126
|
+
hexToBin(fundJson.debug.bytecode),
|
|
127
|
+
hexToBin(assetJson.debug.bytecode),
|
|
128
|
+
], { provider: this.provider });
|
|
129
|
+
|
|
130
|
+
const mintOutflowContract = new Contract(mintOutflowJson, [
|
|
131
|
+
startupContractHash,
|
|
132
|
+
this.#swapped.inflow,
|
|
133
|
+
this.#swapped.outflow,
|
|
134
|
+
binToHex(hash256(hexToBin(executeFundFeeContract.bytecode))),
|
|
135
|
+
hexToBin(managerJson.debug.bytecode),
|
|
136
|
+
hexToBin(fundJson.debug.bytecode),
|
|
137
|
+
hexToBin(assetJson.debug.bytecode),
|
|
138
|
+
], { provider: this.provider });
|
|
139
|
+
|
|
140
|
+
const authHeadVaultContract = new Contract(authHeadVaultJson, [this.#swapped.authorization], { provider: this.provider });
|
|
141
|
+
const authHeadVaultLockingBytecode = binToHex(cashAddressToLockingBytecode(authHeadVaultContract.tokenAddress).bytecode);
|
|
142
|
+
|
|
143
|
+
const publicFundVaultContract = new Contract(publicFundVaultJson, [this.#swapped.publicFund, this.#swapped.authorization], { provider: this.provider });
|
|
144
|
+
const publicFundVaultLockingBytecode = binToHex(cashAddressToLockingBytecode(publicFundVaultContract.tokenAddress).bytecode);
|
|
145
|
+
|
|
146
|
+
const publicFundContract = new Contract(publicJson, [
|
|
147
|
+
authHeadVaultLockingBytecode,
|
|
148
|
+
publicFundVaultLockingBytecode,
|
|
149
|
+
this.#swapped.publicFund,
|
|
150
|
+
startupContractHash,
|
|
151
|
+
fundJson.debug.bytecode,
|
|
152
|
+
this.#swapped.inflow,
|
|
153
|
+
this.#swapped.outflow,
|
|
154
|
+
], { provider: this.provider });
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
this.#contracts = {
|
|
158
|
+
startupContract,
|
|
159
|
+
mintInflowContract,
|
|
160
|
+
mintOutflowContract,
|
|
161
|
+
createFundFeeContract,
|
|
162
|
+
executeFundFeeContract,
|
|
163
|
+
publicFundContract,
|
|
164
|
+
feeVaultContract,
|
|
165
|
+
authHeadVaultContract,
|
|
166
|
+
publicFundVaultContract,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
getContracts() {
|
|
171
|
+
return this.#contracts;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getAuthHeadOutput() {
|
|
175
|
+
const { authHeadVaultContract } = this.#contracts;
|
|
176
|
+
return withDust({
|
|
177
|
+
to: authHeadVaultContract.tokenAddress,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async addBroadcast({
|
|
182
|
+
fund,
|
|
183
|
+
payBy,
|
|
184
|
+
}) {
|
|
185
|
+
const {
|
|
186
|
+
feeVaultContract,
|
|
187
|
+
createFundFeeContract,
|
|
188
|
+
startupContract,
|
|
189
|
+
mintInflowContract,
|
|
190
|
+
mintOutflowContract,
|
|
191
|
+
publicFundContract,
|
|
192
|
+
authHeadVaultContract,
|
|
193
|
+
publicFundVaultContract,
|
|
194
|
+
} = this.#contracts;
|
|
195
|
+
|
|
196
|
+
if (this.inputs.length === 0) {
|
|
197
|
+
throw new Error('User genesis input is expected to be added prior to calling this function');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this.outputs.length === 0) { // add authhead output
|
|
201
|
+
this.addOutput(this.getAuthHeadOutput());
|
|
202
|
+
} else {
|
|
203
|
+
// verify authhead output
|
|
204
|
+
const authhead = this.outputs[0];
|
|
205
|
+
if(authhead.to != authHeadVaultContract.tokenAddress || authhead.token) {
|
|
206
|
+
throw new Error('Authhead output is incorrect, expecting to send to authhead vault with no tokens');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const genesisUtxo = this.inputs[0];
|
|
211
|
+
|
|
212
|
+
if (genesisUtxo.vout !== 0 || genesisUtxo.token) {
|
|
213
|
+
throw new Error('First input must be a genesis input (vout is 0) with no tokens');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const bestFee = await getBestFee({ feeVaultContract, feeContract: createFundFeeContract, payBy, fee: this.#system.fees.create });
|
|
217
|
+
|
|
218
|
+
const startupUtxos = await startupContract.getUtxos();
|
|
219
|
+
const mintInflowUtxos = await mintInflowContract.getUtxos();
|
|
220
|
+
const mintOutflowUtxos = await mintOutflowContract.getUtxos();
|
|
221
|
+
const publicUtxos = await publicFundContract.getUtxos();
|
|
222
|
+
|
|
223
|
+
const inflowUtxos = mintInflowUtxos.filter(u => u.token?.category === this.#system.inflow);
|
|
224
|
+
const outflowUtxos = mintOutflowUtxos.filter(u => u.token?.category === this.#system.outflow);
|
|
225
|
+
const publicFundUtxos = publicUtxos.filter(u => u.token?.category === this.#system.publicFund)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
const startupUtxo = startupUtxos[getRandomInt(startupUtxos.length)];
|
|
229
|
+
const inflowUtxo = inflowUtxos[getRandomInt(inflowUtxos.length)];
|
|
230
|
+
const outflowUtxo = outflowUtxos[getRandomInt(outflowUtxos.length)];
|
|
231
|
+
const publicFundUtxo = publicFundUtxos[getRandomInt(publicFundUtxos.length)];
|
|
232
|
+
|
|
233
|
+
var { managerContract, fundContract } = new FundTokenTransactionBuilder({ provider: this.provider, system: { ...this.#system, fee: this.#system.fees.execute }, fund }).getContracts();
|
|
234
|
+
|
|
235
|
+
const fundTokenAmount = 9223372036854775807n;
|
|
236
|
+
|
|
237
|
+
this.addInputs([
|
|
238
|
+
{
|
|
239
|
+
...startupUtxo,
|
|
240
|
+
unlocker: startupContract.unlock.start(getFundBin(fund)),
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
...inflowUtxo,
|
|
244
|
+
unlocker: mintInflowContract.unlock.mint(),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
...outflowUtxo,
|
|
248
|
+
unlocker: mintOutflowContract.unlock.mint(),
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
...bestFee.utxo,
|
|
252
|
+
unlocker: createFundFeeContract.unlock.pay(),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
...publicFundUtxo,
|
|
256
|
+
unlocker: publicFundContract.unlock.broadcast(getFundBin(fund))
|
|
257
|
+
}
|
|
258
|
+
])
|
|
259
|
+
.addOutputs([
|
|
260
|
+
{
|
|
261
|
+
to: startupContract.tokenAddress,
|
|
262
|
+
amount: startupUtxo.satoshis,
|
|
263
|
+
token: startupUtxo.token,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
to: mintInflowContract.tokenAddress,
|
|
267
|
+
amount: inflowUtxo.satoshis,
|
|
268
|
+
token: inflowUtxo.token,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
to: mintOutflowContract.tokenAddress,
|
|
272
|
+
amount: outflowUtxo.satoshis,
|
|
273
|
+
token: outflowUtxo.token,
|
|
274
|
+
},
|
|
275
|
+
...bestFee.outputs,
|
|
276
|
+
withDust({
|
|
277
|
+
to: managerContract.tokenAddress,
|
|
278
|
+
token: {
|
|
279
|
+
...inflowUtxo.token,
|
|
280
|
+
nft: {
|
|
281
|
+
capability: 'none',
|
|
282
|
+
commitment: swapEndianness(genesisUtxo.txid) + binToHex(hash256(getFundBin(fund))),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
withDust({
|
|
287
|
+
to: managerContract.tokenAddress,
|
|
288
|
+
token: {
|
|
289
|
+
...outflowUtxo.token,
|
|
290
|
+
nft: {
|
|
291
|
+
capability: 'none',
|
|
292
|
+
commitment: swapEndianness(genesisUtxo.txid) + binToHex(hash256(getFundBin(fund))),
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}),
|
|
296
|
+
withDust({
|
|
297
|
+
to: fundContract.tokenAddress,
|
|
298
|
+
token: {
|
|
299
|
+
category: genesisUtxo.txid,
|
|
300
|
+
amount: fundTokenAmount,
|
|
301
|
+
}
|
|
302
|
+
}),
|
|
303
|
+
withDust({
|
|
304
|
+
to: publicFundContract.tokenAddress,
|
|
305
|
+
token: {
|
|
306
|
+
category: this.#system.publicFund,
|
|
307
|
+
amount: 0n,
|
|
308
|
+
nft: {
|
|
309
|
+
capability: 'minting',
|
|
310
|
+
commitment: '',
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}),
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
const maxSize = 128 * 2;
|
|
318
|
+
|
|
319
|
+
const fundHex = getFundHex(fund);
|
|
320
|
+
const fundHexParts = [];
|
|
321
|
+
|
|
322
|
+
let curr = 0;
|
|
323
|
+
let next = maxSize;
|
|
324
|
+
|
|
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 => {
|
|
333
|
+
this.addOutput(withDust({
|
|
334
|
+
to: publicFundVaultContract.tokenAddress,
|
|
335
|
+
token: {
|
|
336
|
+
category: this.#system.publicFund,
|
|
337
|
+
amount: 0n,
|
|
338
|
+
nft: {
|
|
339
|
+
capability: 'none',
|
|
340
|
+
commitment: part
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}))
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# FundTokens.Contracts
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Fund Creation**: Create new public funds with custom asset compositions
|
|
8
|
+
- **Token Minting**: Deposit assets to mint fund tokens
|
|
9
|
+
- **Token Redemption**: Withdraw assets by redeeming fund tokens
|
|
10
|
+
- **Multi-Contract Coordination**: Handles complex transaction flows across many smart contracts
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @fundtokens/builders
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Creating a Public Fund
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
import { PublicFundTransactionBuilder } from '@fundtokens/builders';
|
|
24
|
+
|
|
25
|
+
const publicBuilder = new PublicFundTransactionBuilder({ provider, system });
|
|
26
|
+
const fund = {
|
|
27
|
+
category: genesisUtxo.txid,
|
|
28
|
+
amount: 10n,
|
|
29
|
+
satoshis: 1000n,
|
|
30
|
+
assets: [{ category: 'asset_token_id', amount: 2n }]
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// add user's genesis UTXO
|
|
34
|
+
await publicBuilder.addBroadcast({ fund });
|
|
35
|
+
// add additional IO
|
|
36
|
+
await publicBuilder.send();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Minting Fund Tokens
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
import { FundTokenTransactionBuilder } from '@fundtokens/builders';
|
|
43
|
+
|
|
44
|
+
const fundBuilder = new FundTokenTransactionBuilder({
|
|
45
|
+
provider, system, fund
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await fundBuilder.addInflow({ amount: 1n });
|
|
49
|
+
// Add user asset inputs and fund token outputs
|
|
50
|
+
await fundBuilder.send();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Redeeming Fund Tokens
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
const fundBuilder = new FundTokenTransactionBuilder({
|
|
57
|
+
provider, system, fund
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await fundBuilder.addOutflow({ amount: 1n });
|
|
61
|
+
// Add user inputs/outputs
|
|
62
|
+
await fundBuilder.send();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Key Concepts
|
|
66
|
+
|
|
67
|
+
### Fund Lifecycle
|
|
68
|
+
|
|
69
|
+
* Fund Creation - Broadcast fund parameters
|
|
70
|
+
* Fund Operations - Mint and redeem tokens
|
|
71
|
+
|
|
72
|
+
## Security Model
|
|
73
|
+
|
|
74
|
+
* Non-Custodial: Funds held in contract UTXOs controlled by code
|
|
75
|
+
* Parameter Immutability: Fund details hashed and committed to tokens
|
|
76
|
+
* Contract Isolation: Each contract has single, verified responsibility
|
|
77
|
+
* Thread Authorization: Operations require matching token presence
|
|
78
|
+
* Atomic Validation: Multi-contract validation ensures consistency
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
* Bitcoin Cash network access
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
Copyright (c) 2026 FoldingCash LLC, doing business as Fun(d)Tokens. All rights reserved.
|
package/art/asset.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractName": "AssetManager",
|
|
3
|
+
"constructorInputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "outflowToken",
|
|
6
|
+
"type": "bytes32"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "fundHash",
|
|
10
|
+
"type": "bytes32"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "assetCategory",
|
|
14
|
+
"type": "bytes32"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"abi": [
|
|
18
|
+
{
|
|
19
|
+
"name": "release",
|
|
20
|
+
"inputs": []
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"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_INPUTBYTECODE OP_INPUTINDEX OP_INPUTBYTECODE OP_SIZE OP_NIP OP_1SUB OP_SPLIT OP_DROP OP_OVER OP_SPLIT OP_NIP OP_INPUTINDEX OP_1SUB OP_INPUTBYTECODE OP_INPUTINDEX OP_1SUB OP_INPUTBYTECODE OP_SIZE OP_NIP OP_1SUB OP_SPLIT OP_DROP OP_ROT OP_SPLIT OP_NIP OP_EQUAL OP_DUP OP_IF OP_INPUTINDEX OP_1SUB OP_INPUTBYTECODE 24 OP_SPLIT OP_DROP OP_4 OP_SPLIT OP_NIP OP_DUP 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_DROP OP_ENDIF OP_ROT OP_SWAP OP_BOOLOR OP_VERIFY 0000000000000000000000000000000000000000000000000000000000000000 OP_4 OP_PICK OP_EQUAL OP_IF OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ELSE OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_4 OP_PICK OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_1",
|
|
24
|
+
"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 bool additionalAssetManager = tx.inputs[this.activeInputIndex].unlockingBytecode.slice(parametersLength, tx.inputs[this.activeInputIndex].unlockingBytecode.length - 1) == tx.inputs[this.activeInputIndex - 1].unlockingBytecode.slice(parametersLength, tx.inputs[this.activeInputIndex - 1].unlockingBytecode.length - 1);\r\n if(additionalAssetManager) {\r\n bytes previousAsset = tx.inputs[this.activeInputIndex - 1].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}",
|
|
25
|
+
"debug": {
|
|
26
|
+
"bytecode": "00c05394657600a25279919a766b6376ce5379876376cf01407f7501207f77547988517b757c68768c77686c91660124c0cac0ca82778c7f75787f77c08ccac08cca82778c7f757b7f77877663c08cca01247f75547f777601007e81577901007e816e9f527952799c9b696d75687b7c9b6920000000000000000000000000000000000000000000000000000000000000000054798763c0ce008867c0ce547988686d6d51",
|
|
27
|
+
"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:48:47:69;:38::88:1;:123::144:0;:113::163:1;:::170;;:::174;:38::175;;:95::111:0;:38::175:1;;:189::210:0;:::214:1;:179::233;:268::289:0;:::293:1;:258::312;:::319;;:::323;:179::324;;:240::256:0;:179::324:1;;:38;48:11:48:33:0;:35:55:9;49:44:49:65;:::69:1;:34::88;:98::100:0;:34::101:1;;:95::96:0;:34::101:1;;51:32:51:45:0;:48::52;:32:::1;:28::53;52:31:52:44:0;;:47::51;:31:::1;:27::52;54:20:54:40:0;::::1;:44::53:0;;:57::65;;:44:::1;:20;:12::67;48:35:55:9;;;56:16:56:27:0;:31::53;:16:::1;:8::55;59:31:59:97:0;60:11:60:24;;:::40:1;:42:63:9:0;62:30:62:51;:20::66:1;:70::72:0;:12::74:1;63:15:66:9:0;65:30:65:51;:20::66:1;:70::83:0;;:12::85:1;63:15:66:9;33:4:67:5;;",
|
|
28
|
+
"logs": [],
|
|
29
|
+
"requires": [
|
|
30
|
+
{
|
|
31
|
+
"ip": 34,
|
|
32
|
+
"line": 40
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"ip": 103,
|
|
36
|
+
"line": 54
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"ip": 110,
|
|
40
|
+
"line": 56
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"ip": 119,
|
|
44
|
+
"line": 62
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"ip": 125,
|
|
48
|
+
"line": 65
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"compiler": {
|
|
53
|
+
"name": "cashc",
|
|
54
|
+
"version": "0.13.0-next.6",
|
|
55
|
+
"options": {
|
|
56
|
+
"enforceFunctionParameterTypes": true
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"updatedAt": "2026-05-06T19:09:26.326Z"
|
|
60
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractName": "AuthHeadVault",
|
|
3
|
+
"constructorInputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "authToken",
|
|
6
|
+
"type": "bytes32"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"abi": [
|
|
10
|
+
{
|
|
11
|
+
"name": "release",
|
|
12
|
+
"inputs": []
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"bytecode": "OP_INPUTINDEX OP_0 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE 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_2 OP_AND OP_2 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",
|
|
16
|
+
"source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * AuthHeadVault: Authorization vault with strict positioning requirements\r\n * \r\n * Extends SimpleVault with additional constraints to maintain authhead w/ no token:\r\n * - Must be the first input (index 0)\r\n * - First output must not contain any tokens\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 * release(): Authorizes authhead operations with strict transaction structure\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 0x02 (authhead permission)\r\n */\r\n function release() {\r\n // This vault MUST be the first input\r\n require(this.activeInputIndex == 0, \"expected to be the first input\");\r\n\r\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\r\n \r\n // First output must have no tokens (ensures clean state to maintain authhead integrity)\r\n require(tx.outputs[0].tokenCategory == 0x, \"no token allowed on authhead\");\r\n\r\n // Check for authorization token in ANY input\r\n // Bit 0x02 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((tx.inputs[inputIndex].nftCommitment & bytes(0x02)) == 0x02) {\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}",
|
|
17
|
+
"debug": {
|
|
18
|
+
"bytecode": "c0009dc0c7c0cd8800d1008800006576ce5379876376cf5284528763517b757c6868768b7776c39f5279919a91667577",
|
|
19
|
+
"sourceMap": "27:16:27:37;:41::42;:8::78:1;29:26:29:47:0;:16::64:1;:79::100:0;:68::117:1;:8::119;32:27:32:28:0;:16::43:1;:47::49:0;:8::83:1;36:26:36:31:0;37:25:37:26;38:8:45:62;39:25:39:35;:15::50:1;:54::63:0;;:15:::1;:65:43:13:0;40:30:40:40;:20::55:1;:64::68:0;:20::69:1;:74::78:0;:19:::1;:80:42:17:0;41:33:41:37;:20::38:1;;;40:80:42:17;39:65:43:13;44:25:44:35:0;:::39:1;:12::40;45:16:45:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;38:8::62;;25:4:47:5;",
|
|
20
|
+
"logs": [],
|
|
21
|
+
"requires": [
|
|
22
|
+
{
|
|
23
|
+
"ip": 3,
|
|
24
|
+
"line": 27,
|
|
25
|
+
"message": "expected to be the first input"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"ip": 8,
|
|
29
|
+
"line": 29
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"ip": 12,
|
|
33
|
+
"line": 32,
|
|
34
|
+
"message": "no token allowed on authhead"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"ip": 47,
|
|
38
|
+
"line": 46,
|
|
39
|
+
"message": "unauthorized user"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"compiler": {
|
|
44
|
+
"name": "cashc",
|
|
45
|
+
"version": "0.13.0-next.6",
|
|
46
|
+
"options": {
|
|
47
|
+
"enforceFunctionParameterTypes": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"updatedAt": "2026-05-06T19:09:28.415Z"
|
|
51
|
+
}
|
package/art/fee.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contractName": "FeeManager",
|
|
3
|
+
"constructorInputs": [
|
|
4
|
+
{
|
|
5
|
+
"name": "authToken",
|
|
6
|
+
"type": "bytes32"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "destination",
|
|
10
|
+
"type": "bytes"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "feeToken",
|
|
14
|
+
"type": "bytes32"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "defaultValue",
|
|
18
|
+
"type": "int"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"abi": [
|
|
22
|
+
{
|
|
23
|
+
"name": "close",
|
|
24
|
+
"inputs": []
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "pay",
|
|
28
|
+
"inputs": []
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"bytecode": "OP_4 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_0 OP_0 OP_BEGIN OP_DUP OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUAL OP_IF OP_DUP OP_UTXOTOKENCOMMITMENT OP_4 OP_AND OP_4 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_SWAP OP_VERIFY OP_0 OP_BEGIN OP_DUP OP_OUTPUTTOKENCATEGORY OP_5 OP_PICK OP_EQUAL OP_NOT OP_VERIFY OP_DUP OP_OUTPUTBYTECODE OP_INPUTINDEX OP_UTXOBYTECODE OP_EQUAL OP_NOT OP_VERIFY OP_DUP OP_1ADD OP_NIP OP_DUP OP_TXOUTPUTCOUNT OP_GREATERTHANOREQUAL OP_UNTIL OP_2DROP OP_2DROP OP_2DROP OP_DROP OP_1 OP_ELSE OP_4 OP_ROLL OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_INPUTINDEX OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_3 OP_ROLL OP_EQUAL OP_IF OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT 20 OP_SPLIT OP_DUP OP_8 OP_SPLIT OP_DUP OP_SIZE OP_NIP OP_0 OP_GREATERTHAN OP_IF OP_INPUTINDEX OP_1ADD OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_ELSE OP_INPUTINDEX OP_1ADD OP_OUTPUTBYTECODE OP_6 OP_PICK OP_EQUALVERIFY OP_ENDIF OP_3 OP_PICK OP_BIN2NUM OP_0 OP_NUMEQUAL OP_IF OP_INPUTINDEX OP_1ADD OP_OUTPUTVALUE OP_2 OP_PICK OP_BIN2NUM OP_NUMEQUALVERIFY OP_ELSE OP_INPUTINDEX OP_1ADD OP_OUTPUTTOKENCATEGORY OP_4 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_1ADD OP_OUTPUTTOKENAMOUNT OP_2 OP_PICK OP_BIN2NUM OP_NUMEQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_ELSE OP_INPUTINDEX OP_1ADD OP_OUTPUTBYTECODE OP_2 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_1ADD OP_OUTPUTVALUE OP_3 OP_PICK OP_NUMEQUALVERIFY OP_ENDIF OP_2DROP OP_DROP OP_1 OP_ENDIF",
|
|
32
|
+
"source": "pragma cashscript ~0.13.0;\r\n\r\n/**\r\n * FeeManager: Validates fee payments and routing\r\n * \r\n * Every fund transaction pays fees through this contract.\r\n * Validates that fees match their encoding and route to correct destination.\r\n * \r\n * Fee Token commitment format (stored in NFT):\r\n * [fee_category (32) | fee_amount (8) | fee_destination (variable)]\r\n * \r\n * Where:\r\n * - fee_category: 0x00 = Bitcoin satoshis, else = CashToken category\r\n * - fee_amount: Amount due (satoshis or token amount)\r\n * - fee_destination: Optional override destination (empty = use default)\r\n * Note: Variable-length intentionally - allows forward compatibility with future address formats\r\n * \r\n * Authorization:\r\n * - close() requires authToken to be present (protocol steward only)\r\n * - pay() validates the chosen fee is paid and routed\r\n * \r\n * Parameters:\r\n * authToken: Authorization token category (controls who can close)\r\n * destination: Default fee destination address (until override)\r\n * feeToken: Fee token category (with expected 'none' capability)\r\n * defaultValue: Default fee amount in satoshis (if no fee token encoding)\r\n */\r\ncontract FeeManager(bytes32 authToken, bytes destination, bytes32 feeToken, int defaultValue)\r\n{\r\n /**\r\n * close(): Close this fee by ensuring all fee tokens are burned\r\n * \r\n * Allows protocol steward (authToken holder) to close the manager and cleanup.\r\n * Ensures no active fee tokens remain (no dangling fees).\r\n * \r\n * Validates:\r\n * - Transaction includes authToken with bit 0x04 (fee close permission)\r\n * - No FeeManager fee tokens exist in any output (burned)\r\n * \r\n * Used to close a current fee offering.\r\n */\r\n function close() {\r\n // Check for authorization token in any input\r\n // Bit 0x04 in commitment indicates fee manager closure 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((tx.inputs[inputIndex].nftCommitment & bytes(0x04)) == 0x04) {\r\n // Found authorization token - user is authorized to close\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 // Verify all fee tokens are burned (not present in any output)\r\n int outputIndex = 0;\r\n do {\r\n // Prevent any output from receiving fee tokens (forces burn)\r\n // Prevent from sending to this contract\r\n require(tx.outputs[outputIndex].tokenCategory != feeToken);\r\n require(tx.outputs[outputIndex].lockingBytecode != tx.inputs[this.activeInputIndex].lockingBytecode);\r\n outputIndex = outputIndex + 1;\r\n } while(outputIndex < tx.outputs.length);\r\n }\r\n\r\n /**\r\n * pay(): Validate fee payment according to encoding\r\n * \r\n * Routes fee to destination encoded in fee token NFT or default destination.\r\n * Supports two fee types: Bitcoin satoshis or CashTokens.\r\n * \r\n * Validates:\r\n * - This FeeManager UTXO returns to itself (no state change)\r\n * - Fee amount matches encoding (satoshis or token amount)\r\n * - Fee routes to correct destination (from encoding or default)\r\n * - Token category (if token fee) matches encoding\r\n * \r\n * Transaction Structure:\r\n * Input[activeInputIndex]: This FeeManager with fee token\r\n * Output[activeInputIndex]: This FeeManager (self-return)\r\n * Output[activeInputIndex+1]: Fee payment to destination\r\n * \r\n * Fee Encoding Logic:\r\n * - If fee_category == 0x00: Pay satoshis = fee_amount\r\n * - If fee_category != 0x00: Pay tokens = fee_amount of fee_category\r\n * - If fee_destination present: Pay to fee_destination\r\n * - Else: Pay to default destination\r\n */\r\n function pay() {\r\n // This FeeManager must return to itself (no state change)\r\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\r\n // Preserve fee token category for continuation\r\n require(tx.inputs[this.activeInputIndex].tokenCategory == tx.outputs[this.activeInputIndex].tokenCategory);\r\n // Preserve fee token NFT commitment (maintains encoding)\r\n require(tx.inputs[this.activeInputIndex].nftCommitment == tx.outputs[this.activeInputIndex].nftCommitment);\r\n\r\n // Extract fee parameters from NFT commitment if this is fee token\r\n if(tx.inputs[this.activeInputIndex].tokenCategory == feeToken) {\r\n // Decode fee commitment: [category | amount | destination]\r\n bytes fee_category, bytes fee_next1 = tx.inputs[this.activeInputIndex].nftCommitment.split(32);\r\n bytes fee_amount, bytes fee_destination = fee_next1.split(8);\r\n\r\n // Determine fee destination (override or default)\r\n if(fee_destination.length > 0) {\r\n // Fee override destination specified in encoding\r\n require(tx.outputs[this.activeInputIndex + 1].lockingBytecode == fee_destination);\r\n } else {\r\n // No override - use default destination\r\n require(tx.outputs[this.activeInputIndex + 1].lockingBytecode == destination);\r\n }\r\n \r\n // Validate fee amount and type\r\n if(int(fee_category) == 0) {\r\n // Category 0 = Bitcoin satoshis (not tokens)\r\n // Output must have exact satoshi amount as encoded\r\n require(tx.outputs[this.activeInputIndex + 1].value == int(fee_amount));\r\n } else {\r\n // Non-zero category = CashToken fee type\r\n // Output must have correct token category and amount\r\n require(tx.outputs[this.activeInputIndex + 1].tokenCategory == fee_category);\r\n require(tx.outputs[this.activeInputIndex + 1].tokenAmount == int(fee_amount));\r\n }\r\n } else {\r\n // No fee token encoding - use default values\r\n // Pay default satoshi amount to default destination\r\n require(tx.outputs[this.activeInputIndex + 1].lockingBytecode == destination);\r\n require(tx.outputs[this.activeInputIndex + 1].value == defaultValue);\r\n }\r\n }\r\n}\r\n",
|
|
33
|
+
"debug": {
|
|
34
|
+
"bytecode": "5479009c6300006576ce5379876376cf5484548763517b757c6868768b7776c39f5279919a91667c69006576d1557987916976cdc0c7879169768b7776c4a2666d6d6d755167547a519dc0c7c0cd88c0cec0d188c0cfc0d288c0ce537a8763c0cf01207f76587f76827700a063c08bcd788867c08bcd56798868537981009c63c08bcc5279819d67c08bd1547988c08bd35279819d686d6d67c08bcd527988c08bcc53799d686d755168",
|
|
35
|
+
"sourceMap": "42:4:67:5;;;;;45:26:45:31;46:25:46:26;47:8:55:62;48:25:48:35;:15::50:1;:54::63:0;;:15:::1;:65:53:13:0;49:30:49:40;:20::55:1;:64::68:0;:20::69:1;:74::78:0;:19:::1;:80:52:17:0;51:33:51:37;:20::38:1;;;49:80:52:17;48:65:53:13;54:25:54:35:0;:::39:1;:12::40;55:16:55:26:0;:29::45;:16:::1;:50::60:0;;:49:::1;:16;47:8::62;;56:16:56:26:0;:8::49:1;59:26:59:27:0;60:8:66:49;63:31:63:42;:20::57:1;:61::69:0;;:20:::1;;:12::71;64:31:64:42:0;:20::59:1;:73::94:0;:63::111:1;:20;;:12::113;65:26:65:37:0;:::41:1;:12::42;66:16:66:27:0;:30::47;60:8::49:1;;42:4:67:5;;;;;;92::132::0;;;;94:26:94:47;:16::64:1;:79::100:0;:68::117:1;:8::119;96:26:96:47:0;:16::62:1;:77::98:0;:66::113:1;:8::115;98:26:98:47:0;:16::62:1;:77::98:0;:66::113:1;:8::115;101:21:101:42:0;:11::57:1;:61::69:0;;:11:::1;:71:126:9:0;103:60:103:81;:50::96:1;:103::105:0;:50::106:1;104:54:104:63:0;:70::71;:54::72:1;107:15:107:30:0;:::37:1;;:40::41:0;:15:::1;:43:110:13:0;109:35:109:56;:::60:1;:24::77;:81::96:0;:16::98:1;110:19:113:13:0;112:35:112:56;:::60:1;:24::77;:81::92:0;;:16::94:1;110:19:113:13;116::116:31:0;;:15::32:1;:36::37:0;:15:::1;:39:120:13:0;119:35:119:56;:::60:1;:24::67;:75::85:0;;:71::86:1;:16::88;120:19:125:13:0;123:35:123:56;:::60:1;:24::75;:79::91:0;;:16::93:1;124:35:124:56:0;:::60:1;:24::73;:81::91:0;;:77::92:1;:16::94;120:19:125:13;101:71:126:9;;126:15:131::0;129:31:129:52;:::56:1;:20::73;:77::88:0;;:12::90:1;130:31:130:52:0;:::56:1;:20::63;:67::79:0;;:12::81:1;126:15:131:9;92:4:132:5;;;28:0:133:1",
|
|
36
|
+
"logs": [],
|
|
37
|
+
"requires": [
|
|
38
|
+
{
|
|
39
|
+
"ip": 44,
|
|
40
|
+
"line": 56,
|
|
41
|
+
"message": "unauthorized user"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"ip": 53,
|
|
45
|
+
"line": 63
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"ip": 60,
|
|
49
|
+
"line": 64
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"ip": 82,
|
|
53
|
+
"line": 94
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"ip": 87,
|
|
57
|
+
"line": 96
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"ip": 92,
|
|
61
|
+
"line": 98
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"ip": 116,
|
|
65
|
+
"line": 109
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"ip": 123,
|
|
69
|
+
"line": 112
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"ip": 137,
|
|
73
|
+
"line": 119
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"ip": 144,
|
|
77
|
+
"line": 123
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"ip": 151,
|
|
81
|
+
"line": 124
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"ip": 161,
|
|
85
|
+
"line": 129
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"ip": 167,
|
|
89
|
+
"line": 130
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
"compiler": {
|
|
94
|
+
"name": "cashc",
|
|
95
|
+
"version": "0.13.0-next.6",
|
|
96
|
+
"options": {
|
|
97
|
+
"enforceFunctionParameterTypes": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"updatedAt": "2026-05-06T19:09:27.269Z"
|
|
101
|
+
}
|