Skip to content

Commit 53c7ffd

Browse files
refactor(utxo-core): split Output and descriptor Output
Issue: BTC-1826
1 parent 6733dd6 commit 53c7ffd

File tree

7 files changed

+124
-122
lines changed

7 files changed

+124
-122
lines changed

modules/utxo-core/src/Output.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
export type Output<TValue = bigint> = {
2+
script: Buffer;
3+
value: TValue;
4+
};
5+
export type MaxOutput = Output<'max'>;
6+
type ValueBigInt = { value: bigint };
7+
type ValueMax = { value: 'max' };
8+
9+
/**
10+
* @return true if the output is a max output
11+
*/
12+
export function isMaxOutput<A extends ValueBigInt, B extends ValueMax>(output: A | B): output is B {
13+
return output.value === 'max';
14+
}
15+
16+
/**
17+
* @return the max output if there is one
18+
* @throws if there are multiple max outputs
19+
*/
20+
export function getMaxOutput<A extends ValueBigInt, B extends ValueMax>(outputs: (A | B)[]): B | undefined {
21+
const max = outputs.filter(isMaxOutput<A, B>);
22+
if (max.length === 0) {
23+
return undefined;
24+
}
25+
if (max.length > 1) {
26+
throw new Error('Multiple max outputs');
27+
}
28+
return max[0];
29+
}
30+
31+
/**
32+
* @return the sum of the outputs
33+
*/
34+
export function getOutputSum(outputs: ValueBigInt[]): bigint {
35+
return outputs.reduce((sum, output) => sum + output.value, 0n);
36+
}
37+
38+
/**
39+
* @return the sum of the outputs that are not 'max'
40+
*/
41+
export function getFixedOutputSum(outputs: (ValueBigInt | ValueMax)[]): bigint {
42+
return getOutputSum(outputs.filter((o): o is Output => !isMaxOutput(o)));
43+
}
44+
45+
/**
46+
* @param outputs
47+
* @param params
48+
* @return the outputs with the 'max' output replaced with the max amount
49+
*/
50+
export function toFixedOutputs<A extends ValueBigInt, B extends ValueMax>(
51+
outputs: (A | B)[],
52+
params: { maxAmount: bigint }
53+
): A[] {
54+
// assert that there is at most one max output
55+
const maxOutput = getMaxOutput<A, B>(outputs);
56+
return outputs.map((output): A => {
57+
if (isMaxOutput(output)) {
58+
if (output !== maxOutput) {
59+
throw new Error('illegal state');
60+
}
61+
return { ...output, value: params.maxAmount };
62+
} else {
63+
return output;
64+
}
65+
});
66+
}
67+
68+
export type PrevOutput = {
69+
hash: string;
70+
index: number;
71+
witnessUtxo: Output;
72+
};

modules/utxo-core/src/descriptor/Output.ts

Lines changed: 2 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,10 @@
11
import { Descriptor } from '@bitgo/wasm-miniscript';
22

3+
import { getFixedOutputSum, MaxOutput, Output, PrevOutput } from '../Output';
4+
35
import { DescriptorMap } from './DescriptorMap';
46
import { createScriptPubKeyFromDescriptor } from './address';
57

6-
export type Output<TValue = bigint> = {
7-
script: Buffer;
8-
value: TValue;
9-
};
10-
11-
export type MaxOutput = Output<'max'>;
12-
13-
type ValueBigInt = { value: bigint };
14-
type ValueMax = { value: 'max' };
15-
16-
/**
17-
* @return true if the output is a max output
18-
*/
19-
export function isMaxOutput<A extends ValueBigInt, B extends ValueMax>(output: A | B): output is B {
20-
return output.value === 'max';
21-
}
22-
23-
/**
24-
* @return the max output if there is one
25-
* @throws if there are multiple max outputs
26-
*/
27-
export function getMaxOutput<A extends ValueBigInt, B extends ValueMax>(outputs: (A | B)[]): B | undefined {
28-
const max = outputs.filter(isMaxOutput<A, B>);
29-
if (max.length === 0) {
30-
return undefined;
31-
}
32-
if (max.length > 1) {
33-
throw new Error('Multiple max outputs');
34-
}
35-
return max[0];
36-
}
37-
38-
/**
39-
* @return the sum of the outputs
40-
*/
41-
export function getOutputSum(outputs: ValueBigInt[]): bigint {
42-
return outputs.reduce((sum, output) => sum + output.value, 0n);
43-
}
44-
45-
/**
46-
* @return the sum of the outputs that are not 'max'
47-
*/
48-
export function getFixedOutputSum(outputs: (ValueBigInt | ValueMax)[]): bigint {
49-
return getOutputSum(outputs.filter((o): o is Output => !isMaxOutput(o)));
50-
}
51-
52-
/**
53-
* @param outputs
54-
* @param params
55-
* @return the outputs with the 'max' output replaced with the max amount
56-
*/
57-
export function toFixedOutputs<A extends ValueBigInt, B extends ValueMax>(
58-
outputs: (A | B)[],
59-
params: { maxAmount: bigint }
60-
): A[] {
61-
// assert that there is at most one max output
62-
const maxOutput = getMaxOutput<A, B>(outputs);
63-
return outputs.map((output): A => {
64-
if (isMaxOutput(output)) {
65-
if (output !== maxOutput) {
66-
throw new Error('illegal state');
67-
}
68-
return { ...output, value: params.maxAmount };
69-
} else {
70-
return output;
71-
}
72-
});
73-
}
74-
758
export type WithDescriptor<T> = T & {
769
descriptor: Descriptor;
7710
};
@@ -96,12 +29,6 @@ export function getExternalFixedAmount(outputs: WithOptDescriptor<Output | MaxOu
9629
return getFixedOutputSum(outputs.filter(isExternalOutput));
9730
}
9831

99-
export type PrevOutput = {
100-
hash: string;
101-
index: number;
102-
witnessUtxo: Output;
103-
};
104-
10532
export type DescriptorWalletOutput = PrevOutput & {
10633
descriptorName: string;
10734
descriptorIndex: number;

modules/utxo-core/src/descriptor/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ export * from './address';
33
export * from './DescriptorMap';
44
export * from './Output';
55
export * from './VirtualSize';
6+
7+
/** @deprecated - import from @bitgo/utxo-core directly instead */
8+
export * from '../Output';

modules/utxo-core/src/descriptor/psbt/createPsbt.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as utxolib from '@bitgo/utxo-lib';
22
import { Descriptor } from '@bitgo/wasm-miniscript';
33

4-
import { DerivedDescriptorWalletOutput, Output, WithOptDescriptor } from '../Output';
4+
import { DerivedDescriptorWalletOutput, WithOptDescriptor } from '../Output';
5+
import { Output } from '../../Output';
56

67
import { toUtxoPsbt, toWrappedPsbt } from './wrap';
78
import { assertSatisfiable } from './assertSatisfiable';

modules/utxo-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * as bip65 from './bip65';
22
export * as descriptor from './descriptor';
33
export * as testutil from './testutil';
44
export * from './dustThreshold';
5+
export * from './Output';

modules/utxo-core/test/Output.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as assert from 'assert';
2+
3+
import { getFixedOutputSum, getMaxOutput, getOutputSum, isMaxOutput, toFixedOutputs } from '../src';
4+
5+
describe('Output', function () {
6+
const oBigInt = { value: 1n };
7+
const oBigInt2 = { value: 2n };
8+
const oMax = { value: 'max' } as const;
9+
10+
it('getMaxOutput returns expected values', function () {
11+
assert.strictEqual(getMaxOutput([oBigInt]), undefined);
12+
assert.strictEqual(getMaxOutput([oBigInt, oBigInt]), undefined);
13+
assert.strictEqual(getMaxOutput([oBigInt, oMax]), oMax);
14+
assert.throws(() => getMaxOutput([oMax, oMax]), /Multiple max outputs/);
15+
});
16+
17+
it('isMaxOutput correctly identifies max outputs', function () {
18+
assert.strictEqual(isMaxOutput(oBigInt), false);
19+
assert.strictEqual(isMaxOutput(oMax), true);
20+
});
21+
22+
it('getOutputSum calculates sum correctly', function () {
23+
assert.strictEqual(getOutputSum([]), 0n);
24+
assert.strictEqual(getOutputSum([oBigInt]), 1n);
25+
assert.strictEqual(getOutputSum([oBigInt, oBigInt2]), 3n);
26+
});
27+
28+
it('getFixedOutputSum handles mixed outputs', function () {
29+
assert.strictEqual(getFixedOutputSum([]), 0n);
30+
assert.strictEqual(getFixedOutputSum([oBigInt]), 1n);
31+
assert.strictEqual(getFixedOutputSum([oBigInt, oMax]), 1n);
32+
assert.strictEqual(getFixedOutputSum([oBigInt, oBigInt2, oMax]), 3n);
33+
});
34+
35+
it('toFixedOutputs converts max outputs correctly', function () {
36+
const maxAmount = 10n;
37+
assert.deepStrictEqual(toFixedOutputs([oBigInt], { maxAmount }), [oBigInt]);
38+
assert.deepStrictEqual(toFixedOutputs([oMax], { maxAmount }), [{ ...oMax, value: maxAmount }]);
39+
assert.throws(() => toFixedOutputs([oMax, oMax], { maxAmount }), /Multiple max outputs/);
40+
});
41+
});

modules/utxo-core/test/descriptor/Output.ts

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,12 @@
1-
import * as assert from 'assert';
1+
import assert from 'assert';
22

33
import { Descriptor } from '@bitgo/wasm-miniscript';
44

5-
import {
6-
getMaxOutput,
7-
isMaxOutput,
8-
getOutputSum,
9-
getFixedOutputSum,
10-
toFixedOutputs,
11-
isInternalOutput,
12-
isExternalOutput,
13-
} from '../../src/descriptor';
5+
import { isExternalOutput, isInternalOutput } from '../../src/descriptor';
146

15-
describe('Output', function () {
16-
const oBigInt = { value: 1n };
17-
const oBigInt2 = { value: 2n };
18-
const oMax = { value: 'max' } as const;
7+
describe('decscriptor.Output', function () {
198
const mockDescriptor = {} as Descriptor;
209

21-
it('getMaxOutput returns expected values', function () {
22-
assert.strictEqual(getMaxOutput([oBigInt]), undefined);
23-
assert.strictEqual(getMaxOutput([oBigInt, oBigInt]), undefined);
24-
assert.strictEqual(getMaxOutput([oBigInt, oMax]), oMax);
25-
assert.throws(() => getMaxOutput([oMax, oMax]), /Multiple max outputs/);
26-
});
27-
28-
it('isMaxOutput correctly identifies max outputs', function () {
29-
assert.strictEqual(isMaxOutput(oBigInt), false);
30-
assert.strictEqual(isMaxOutput(oMax), true);
31-
});
32-
33-
it('getOutputSum calculates sum correctly', function () {
34-
assert.strictEqual(getOutputSum([]), 0n);
35-
assert.strictEqual(getOutputSum([oBigInt]), 1n);
36-
assert.strictEqual(getOutputSum([oBigInt, oBigInt2]), 3n);
37-
});
38-
39-
it('getFixedOutputSum handles mixed outputs', function () {
40-
assert.strictEqual(getFixedOutputSum([]), 0n);
41-
assert.strictEqual(getFixedOutputSum([oBigInt]), 1n);
42-
assert.strictEqual(getFixedOutputSum([oBigInt, oMax]), 1n);
43-
assert.strictEqual(getFixedOutputSum([oBigInt, oBigInt2, oMax]), 3n);
44-
});
45-
46-
it('toFixedOutputs converts max outputs correctly', function () {
47-
const maxAmount = 10n;
48-
assert.deepStrictEqual(toFixedOutputs([oBigInt], { maxAmount }), [oBigInt]);
49-
assert.deepStrictEqual(toFixedOutputs([oMax], { maxAmount }), [{ ...oMax, value: maxAmount }]);
50-
assert.throws(() => toFixedOutputs([oMax, oMax], { maxAmount }), /Multiple max outputs/);
51-
});
52-
5310
it('isInternalOutput correctly identifies internal outputs', function () {
5411
const internalOutput = { value: 1n, descriptor: mockDescriptor };
5512
const externalOutput = { value: 1n };

0 commit comments

Comments
 (0)