> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Embedded Wallet Differences

> How Bitcoin functions behave differently with embedded wallets

# Embedded Wallet Differences

The Bitcoin functions work with both external wallets (browser extensions such as Xverse, Unisat, OKX, and Phantom) and Dynamic embedded wallets. Most behave identically. A few accept options that an embedded wallet cannot act on, because an embedded wallet is a single MPC key rather than a wallet application managing several addresses.

This page lists every difference. If a function is not mentioned here, it behaves the same for both wallet types.

## Why the differences exist

An embedded Bitcoin wallet is **one key at one fixed derivation path**. Three consequences follow, and they explain every entry below.

**One address type per wallet.** The address type is chosen when the wallet is created, either Native SegWit or Taproot, and cannot change afterwards. An external wallet can expose a payment address and a separate ordinals address at the same time, so its functions accept an `addressType` to select between them. An embedded wallet has only one address type, so there is nothing to select.

**No derived addresses.** The derivation path is fixed per address type, with no account or index component, and a user has one Bitcoin embedded wallet. You cannot derive additional receiving addresses from it.

**One address, encoded per network.** The same key produces a different address string on each network, because only the prefix and checksum differ:

```
bc1qw66rqtzzzfew70rjz7gtpehwwunl2wjzums3sz   mainnet
tb1qw66rqtzzzfew70rjz7gtpehwwunl2wjzkatzt3   testnet
```

These are the same wallet, not two addresses. Where a function takes an address, pass the encoding for the network you are signing on.

## signPsbt and signPsbts

> [!NOTE]
>
> An embedded wallet signs only the inputs that belong to its own address. Inputs owned by other addresses in the PSBT are left untouched, so a PSBT can still be co-signed by another party afterwards. The one exception is a P2WSH multisig input that you explicitly allow with [`allowedWitnessScripts`](#co-signing-p2wsh-multisig-inputs).

**`signature[].address`** must be your wallet's own address for the active network. On testnet, use the `tb1...` encoding. An address the wallet does not own is rejected rather than ignored, so a mistyped or mainnet-encoded address returns an error instead of a PSBT with nothing signed.

**`signature[].signingIndexes`** limits signing to a subset of your wallet's inputs. Requesting an index the wallet does not own throws an error, and no input is signed.

**`signature[].disableAddressValidation`** has no effect. On an external wallet the extension verifies key ownership itself before signing, so skipping Dynamic's check still leaves a check in place. On an embedded wallet the ownership check is the only one, so it always runs.

**`allowedSighash`** restricts which signature hash (sighash) types the wallet will sign. The type itself comes from the PSBT (BIP-174 `PSBT_IN_SIGHASH_TYPE`), so this option can only reject a type, never select one.

### Defaults

Two separate settings determine what is signed: which inputs, and which sighash type is accepted. Omitting either one never extends signing beyond the inputs the wallet owns.

| Condition | Result |
| - | - |
| `signingIndexes` omitted | Every input the wallet owns is signed. Inputs owned by anyone else are skipped. |
| `signingIndexes` provided | Only the listed inputs are signed, and each must be one the wallet owns. |
| `allowedSighash` omitted or `[]` | No restriction. Whatever type the PSBT declares is accepted. |
| `allowedSighash` provided | An input declaring a type outside the list throws an error, and no input is signed. |
| `allowedWitnessScripts` omitted or `[]` | P2WSH inputs are never signed. |
| `allowedWitnessScripts` provided | A P2WSH input is also signed when its witness script is in the list and passes the checks in [Co-signing P2WSH multisig inputs](#co-signing-p2wsh-multisig-inputs). |
| PSBT input declares no sighash type | `SIGHASH_ALL` (`0x01`), or `SIGHASH_DEFAULT` (`0x00`) on a Taproot wallet. |

> [!NOTE]
>
> An empty `allowedSighash` means "accept any type", not "use `SIGHASH_ALL`". The type always comes from the PSBT. `SIGHASH_ALL` is the fallback only when the PSBT itself declares nothing, which is the common case for a PSBT built by [sendBitcoin](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/send-bitcoin.md) or a standard wallet.

> [!WARNING]
>
> `SIGHASH_NONE` produces a signature that does not commit to the transaction outputs, and `SIGHASH_SINGLE` commits to only one output index. Anyone holding the signed PSBT can change the remaining outputs before broadcasting. Use `allowedSighash` to restrict signing to the types your application expects, for example `[0x01, 0x81]` for `SIGHASH_ALL` and `SIGHASH_ALL | ANYONECANPAY`.

### Co-signing P2WSH multisig inputs

By default an embedded wallet signs only inputs that spend from its own address. To co-sign a P2WSH multisig, for example a 2-of-3 that holds the embedded wallet's key next to keys held elsewhere, pass the multisig's witness script in `allowedWitnessScripts`. This option is available for embedded wallets only, and requires `@dynamic-labs-sdk/bitcoin` 1.48.0 or later.

`allowedWitnessScripts` is an array of hex-encoded witness scripts. The wallet signs a P2WSH input only when all of these hold:

* The input's witness script is in `allowedWitnessScripts`.
* The SHA-256 of the witness script matches the input's `scriptPubKey`.
* The script is a standard `m`-of-`n` multisig.
* The wallet's own compressed public key is one of the keys in the script.

An input that fails any check is not signed. If you also pass `signingIndexes` naming that input, the call throws `signingIndexes contains indexes that do not belong to the signing address`.

```javascript theme={"system"}
import { signPsbt } from "@dynamic-labs-sdk/bitcoin";

const { signedPsbt } = await signPsbt({
  walletAccount,
  request: {
    unsignedPsbtBase64: psbt.toBase64(),
    allowedSighash: [1], // SIGHASH_ALL
    allowedWitnessScripts: [witnessScriptHex],
  },
});
```

> [!NOTE]
>
> The returned PSBT is not finalized. It carries this wallet's partial signature, and you merge the signatures from the other co-signers and finalize it yourself before broadcasting.

> [!WARNING]
>
> `allowedWitnessScripts` is your application's consent to co-sign that script. Build the list from scripts you constructed and trust, never from scripts taken from the PSBT or from user input, and keep `allowedSighash` set to the types you expect.

Only a wallet whose address type is Native SegWit can co-sign P2WSH inputs. A Taproot wallet never signs a P2WSH input, even when its script is listed. The multisig script must contain the wallet's compressed public key, which you can read from the `publicKey` of the Native SegWit entry in the wallet account's `addressesWithTypes`.

## signMessageWithCustomOptions

> [!NOTE]
>
> Both options are ignored for embedded wallets. The call succeeds and returns a valid signature over the message, using the wallet's single address and BIP-322.

**`addressType`** has no effect. An embedded wallet has one address type, fixed at creation, so there is nothing to select.

**`protocol`** is not supported. Embedded wallets sign messages with BIP-322 only, and Dynamic verifies the BIP-322 hash on the server. Requesting `ecdsa` does not change the signature that is produced.

To sign a message with an embedded wallet, use [signMessage](https://docs.dynamic.xyz/docs/javascript/reference/wallets/sign-message.md), which needs neither option.

## Functions that behave the same

| Function | Notes |
| - | - |
| [addBitcoinExtension](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/adding-bitcoin-extension.md) | Required for both wallet types |
| [isBitcoinWalletAccount](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/checking-bitcoin-wallet-account-type.md) | Works for both wallet types |
| [sendBitcoin](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/send-bitcoin.md) | Builds, signs, and broadcasts using the wallet's address for the active network |
| [sendRawTransaction](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/send-raw-transaction.md) | Broadcasts a transaction that is already signed, so the wallet does no signing |

## Related functions

* [signPsbt](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/sign-psbt.md) - Sign a PSBT
* [signPsbts](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/sign-psbts.md) - Sign multiple PSBTs
* [signMessageWithCustomOptions](https://docs.dynamic.xyz/docs/javascript/reference/bitcoin/sign-message-with-custom-options.md) - Sign a message with custom options


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.