Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion packages/wasm-utxo/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default tseslint.config(
{
languageOptions: {
parserOptions: {
projectService: true,
project: ["./tsconfig.json", "./tsconfig.test.json"],
tsconfigRootDir: import.meta.dirname,
},
},
Expand All @@ -25,4 +25,29 @@ export default tseslint.config(
"*.config.js",
],
},
// Ban Node.js globals in production code
{
files: ["js/**/*.ts"],
rules: {
"no-restricted-globals": [
"error",
{
name: "Buffer",
message: "Use Uint8Array instead of Buffer for ESM compatibility.",
},
{
name: "process",
message: "Avoid Node.js process global for ESM compatibility.",
},
{
name: "__dirname",
message: "Use import.meta.url instead of __dirname for ESM.",
},
{
name: "__filename",
message: "Use import.meta.url instead of __filename for ESM.",
},
],
},
},
);
20 changes: 20 additions & 0 deletions packages/wasm-utxo/js/descriptorWallet/DescriptorMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* DescriptorMap type and utilities.
* Moved from @bitgo/utxo-core.
*/
import { Descriptor } from "../index.js";

/** Map from descriptor name to descriptor (TypeScript Map) */
export type DescriptorMap = Map<string, Descriptor>;

/** Convert an array of descriptor name-value pairs to a descriptor map */
export function toDescriptorMap(
descriptors: { name: string; value: Descriptor | string }[],
): DescriptorMap {
return new Map(
descriptors.map((d) => [
d.name,
d.value instanceof Descriptor ? d.value : Descriptor.fromStringDetectType(d.value),
]),
);
}
67 changes: 67 additions & 0 deletions packages/wasm-utxo/js/descriptorWallet/DescriptorOutput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Descriptor output types and utilities.
* Moved from @bitgo/utxo-core.
*/
import { Descriptor } from "../index.js";

import { getFixedOutputSum, MaxOutput, Output, PrevOutput } from "./Output.js";
import { DescriptorMap } from "./DescriptorMap.js";
import { getDescriptorAtIndexCheckScript } from "./derive.js";

export type WithDescriptor<T> = T & {
descriptor: Descriptor;
};

export type WithOptDescriptor<T> = T & {
descriptor?: Descriptor;
};

export function isInternalOutput<T extends object>(
output: T | WithDescriptor<T>,
): output is WithDescriptor<T> {
return "descriptor" in output && output.descriptor !== undefined;
}

export function isExternalOutput<T extends object>(output: T | WithDescriptor<T>): output is T {
return !isInternalOutput(output);
}

/**
* @return the sum of the external outputs that are not 'max'
* @param outputs
*/
export function getExternalFixedAmount(outputs: WithOptDescriptor<Output | MaxOutput>[]): bigint {
return getFixedOutputSum(outputs.filter(isExternalOutput));
}

export type DescriptorWalletOutput = PrevOutput & {
descriptorName: string;
descriptorIndex: number | undefined;
};

export type DerivedDescriptorWalletOutput = WithDescriptor<PrevOutput>;

export function toDerivedDescriptorWalletOutput(
output: DescriptorWalletOutput,
descriptorMap: DescriptorMap,
): DerivedDescriptorWalletOutput {
const descriptor = descriptorMap.get(output.descriptorName);
if (!descriptor) {
throw new Error(`Descriptor not found: ${output.descriptorName}`);
}
if (!(descriptor instanceof Descriptor)) {
throw new Error(`Expected Descriptor instance for ${output.descriptorName}`);
}
const descriptorAtIndex = getDescriptorAtIndexCheckScript(
descriptor,
output.descriptorIndex,
output.witnessUtxo.script,
output.descriptorName,
);
return {
hash: output.hash,
index: output.index,
witnessUtxo: output.witnessUtxo,
descriptor: descriptorAtIndex,
};
}
85 changes: 85 additions & 0 deletions packages/wasm-utxo/js/descriptorWallet/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Migration Guide: utxo-core/descriptor to wasm-utxo/descriptorWallet

This module provides descriptor wallet functionality that was previously in `@bitgo/utxo-core`.

## Import Changes

### Before (utxo-core)

```typescript
import {
DescriptorMap,
toDescriptorMap,
findDescriptorForInput,
createPsbt,
parse,
getDescriptorAtIndex,
createScriptPubKeyFromDescriptor,
getVirtualSize,
} from "@bitgo/utxo-core/descriptor";
```

### After (wasm-utxo)

```typescript
import { descriptorWallet } from "@bitgo/wasm-utxo";

const {
toDescriptorMap,
findDescriptorForInput,
createPsbt,
parse,
getDescriptorAtIndex,
createScriptPubKeyFromDescriptor,
getVirtualSize,
} = descriptorWallet;
```

## API Changes

### PSBT Creation

The `createPsbt` function returns a `wasm-utxo.Psbt` instead of `utxolib.bitgo.UtxoPsbt`.

```typescript
// Before: Returns utxolib.bitgo.UtxoPsbt
const psbt = createPsbt(params, inputs, outputs);

// After: Returns wasm-utxo Psbt
const psbt = descriptorWallet.createPsbt(params, inputs, outputs);
```

### Address Creation

The `createAddressFromDescriptor` function takes a `CoinName` instead of `utxolib.Network`:

```typescript
// Before
createAddressFromDescriptor(descriptor, index, utxolib.networks.bitcoin);

// After
descriptorWallet.createAddressFromDescriptor(descriptor, index, "Bitcoin");
```

### Signing

Use `signWithKey` from the descriptorWallet module:

```typescript
// Before
tx.signInputHD(vin, signerKeychain);

// After
descriptorWallet.signWithKey(psbt, signerKeychain);
```

## Not Ported

The following are intentionally **not** included in this migration:

- `fromFixedScriptWallet` - Converting fixed-script wallets to descriptors should remain in utxo-core or abstract-utxo

## Network Support

Descriptor wallets are currently only supported for Bitcoin mainnet and testnet.
Altcoin descriptor wallets should continue using the fixed-script wallet approach.
79 changes: 79 additions & 0 deletions packages/wasm-utxo/js/descriptorWallet/Output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Output types and utilities for descriptor wallets.
* Moved from @bitgo/utxo-core.
*/

export type Output<TValue = bigint> = {
script: Uint8Array;
value: TValue;
};
export type MaxOutput = Output<"max">;
type ValueBigInt = { value: bigint };
type ValueMax = { value: "max" };

/**
* @return true if the output is a max output
*/
export function isMaxOutput<A extends ValueBigInt, B extends ValueMax>(output: A | B): output is B {
return output.value === "max";
}

/**
* @return the max output if there is one
* @throws if there are multiple max outputs
*/
export function getMaxOutput<A extends ValueBigInt, B extends ValueMax>(
outputs: (A | B)[],
): B | undefined {
const max = outputs.filter(isMaxOutput<A, B>);
if (max.length === 0) {
return undefined;
}
if (max.length > 1) {
throw new Error("Multiple max outputs");
}
return max[0];
}

/**
* @return the sum of the outputs
*/
export function getOutputSum(outputs: ValueBigInt[]): bigint {
return outputs.reduce((sum, output) => sum + output.value, 0n);
}

/**
* @return the sum of the outputs that are not 'max'
*/
export function getFixedOutputSum(outputs: (ValueBigInt | ValueMax)[]): bigint {
return getOutputSum(outputs.filter((o): o is Output => !isMaxOutput(o)));
}

/**
* @param outputs
* @param params
* @return the outputs with the 'max' output replaced with the max amount
*/
export function toFixedOutputs<A extends ValueBigInt, B extends ValueMax>(
outputs: (A | B)[],
params: { maxAmount: bigint },
): A[] {
// assert that there is at most one max output
const maxOutput = getMaxOutput<A, B>(outputs);
return outputs.map((output): A => {
if (isMaxOutput(output)) {
if (output !== maxOutput) {
throw new Error("illegal state");
}
return { ...output, value: params.maxAmount };
} else {
return output;
}
});
}

export type PrevOutput = {
hash: string;
index: number;
witnessUtxo: Output;
};
Loading