@utxopia/sdk 0.1.0-alpha.3 → 0.1.0-alpha.4

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/dist/psbt.d.ts CHANGED
@@ -31,7 +31,14 @@ export interface BuildDepositPsbtParams {
31
31
  /** Deposit amount in satoshis */
32
32
  depositAmountSats: number;
33
33
  /** Compact deposit OP_RETURN payload (from buildDepositOpReturn) */
34
- opReturnPayload: Uint8Array;
34
+ /**
35
+ * 73-byte deposit metadata, for the OP_RETURN flow.
36
+ *
37
+ * Omit it for an OP_RETURN-free deposit (`verify_deposit`), where the address
38
+ * itself binds the note keys through its tapleaf. The transaction is then a
39
+ * plain payment, which is what lets an exchange withdrawal fund it.
40
+ */
41
+ opReturnPayload?: Uint8Array;
35
42
  /** Change address (same type as sender) */
36
43
  changeAddress: string;
37
44
  /** Fee rate in sats/vbyte */
@@ -55,7 +62,10 @@ export interface BuildDepositPsbtResult {
55
62
  /**
56
63
  * Estimate the transaction fee for a deposit PSBT.
57
64
  */
58
- export declare function estimateDepositFee(numInputs: number, feeRate: number, inputType?: "p2tr" | "p2wpkh", hasChange?: boolean): number;
65
+ export declare function estimateDepositFee(numInputs: number, feeRate: number, inputType?: "p2tr" | "p2wpkh", hasChange?: boolean,
66
+ /** An OP_RETURN-free deposit has one output fewer; defaults true for callers
67
+ * written before that flow existed. */
68
+ hasOpReturn?: boolean): number;
59
69
  /**
60
70
  * Build a deposit PSBT with OP_RETURN for non-interactive stealth deposits.
61
71
  *
package/dist/psbt.js CHANGED
@@ -33,13 +33,15 @@ const TX_OVERHEAD_VBYTES = 11;
33
33
  /**
34
34
  * Estimate the transaction fee for a deposit PSBT.
35
35
  */
36
- export function estimateDepositFee(numInputs, feeRate, inputType = "p2tr", hasChange = true) {
36
+ export function estimateDepositFee(numInputs, feeRate, inputType = "p2tr", hasChange = true,
37
+ /** An OP_RETURN-free deposit has one output fewer; defaults true for callers
38
+ * written before that flow existed. */
39
+ hasOpReturn = true) {
37
40
  const inputVbytes = inputType === "p2tr" ? P2TR_INPUT_VBYTES : P2WPKH_INPUT_VBYTES;
38
- const outputCount = hasChange ? 3 : 2; // deposit + OP_RETURN + optional change
39
41
  const vsize = TX_OVERHEAD_VBYTES +
40
42
  numInputs * inputVbytes +
41
43
  P2TR_OUTPUT_VBYTES + // deposit output
42
- OP_RETURN_OUTPUT_VBYTES + // OP_RETURN output
44
+ (hasOpReturn ? OP_RETURN_OUTPUT_VBYTES : 0) +
43
45
  (hasChange ? P2TR_OUTPUT_VBYTES : 0); // change output
44
46
  return Math.ceil(vsize * feeRate);
45
47
  }
@@ -59,7 +61,7 @@ export function buildDepositPsbt(params) {
59
61
  if (depositAmountSats < DUST_LIMIT) {
60
62
  throw new Error(`Deposit amount ${depositAmountSats} is below dust limit ${DUST_LIMIT}`);
61
63
  }
62
- if (opReturnPayload.length !== DEPOSIT_OP_RETURN_SIZE) {
64
+ if (opReturnPayload && opReturnPayload.length !== DEPOSIT_OP_RETURN_SIZE) {
63
65
  throw new Error(`OP_RETURN payload must be ${DEPOSIT_OP_RETURN_SIZE} bytes, got ${opReturnPayload.length}`);
64
66
  }
65
67
  const btcNetwork = network === "mainnet" ? btc.NETWORK : btc.TEST_NETWORK;
@@ -69,10 +71,10 @@ export function buildDepositPsbt(params) {
69
71
  const firstScript = hex.decode(senderUtxos[0].scriptPubkeyHex);
70
72
  const inputType = firstScript[0] === 0x51 ? "p2tr" : "p2wpkh";
71
73
  // Estimate fee with change
72
- const feeWithChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, true);
74
+ const feeWithChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, true, Boolean(opReturnPayload));
73
75
  const changeAmount = totalInput - depositAmountSats - feeWithChange;
74
76
  // Check if we have enough funds
75
- const feeWithoutChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, false);
77
+ const feeWithoutChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, false, Boolean(opReturnPayload));
76
78
  if (totalInput < depositAmountSats + feeWithoutChange) {
77
79
  throw new Error(`Insufficient funds: have ${totalInput} sats, need ${depositAmountSats + feeWithoutChange} sats (including fee)`);
78
80
  }
@@ -113,12 +115,15 @@ export function buildDepositPsbt(params) {
113
115
  }
114
116
  // Output 1: P2TR deposit
115
117
  tx.addOutputAddress(depositAddress, BigInt(depositAmountSats), btcNetwork);
116
- // Output 2: OP_RETURN with compact deposit payload.
117
- const opReturnScript = createOpReturnScriptFromPayload(opReturnPayload);
118
- tx.addOutput({
119
- script: opReturnScript,
120
- amount: 0n,
121
- });
118
+ // Output 2: OP_RETURN with compact deposit payload — only for that flow. A
119
+ // tweak-bound deposit adds nothing here, so the transaction is indistinguishable
120
+ // from an ordinary payment.
121
+ if (opReturnPayload) {
122
+ tx.addOutput({
123
+ script: createOpReturnScriptFromPayload(opReturnPayload),
124
+ amount: 0n,
125
+ });
126
+ }
122
127
  // Output 3: Change (if above dust)
123
128
  if (hasChange) {
124
129
  tx.addOutputAddress(changeAddress, BigInt(changeAmount), btcNetwork);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utxopia/sdk",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.4",
4
4
  "description": "UTXOpia SDK - Private Bitcoin across Solana and Sui",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/psbt.ts CHANGED
@@ -41,7 +41,14 @@ export interface BuildDepositPsbtParams {
41
41
  /** Deposit amount in satoshis */
42
42
  depositAmountSats: number;
43
43
  /** Compact deposit OP_RETURN payload (from buildDepositOpReturn) */
44
- opReturnPayload: Uint8Array;
44
+ /**
45
+ * 73-byte deposit metadata, for the OP_RETURN flow.
46
+ *
47
+ * Omit it for an OP_RETURN-free deposit (`verify_deposit`), where the address
48
+ * itself binds the note keys through its tapleaf. The transaction is then a
49
+ * plain payment, which is what lets an exchange withdrawal fund it.
50
+ */
51
+ opReturnPayload?: Uint8Array;
45
52
  /** Change address (same type as sender) */
46
53
  changeAddress: string;
47
54
  /** Fee rate in sats/vbyte */
@@ -98,15 +105,17 @@ export function estimateDepositFee(
98
105
  feeRate: number,
99
106
  inputType: "p2tr" | "p2wpkh" = "p2tr",
100
107
  hasChange: boolean = true,
108
+ /** An OP_RETURN-free deposit has one output fewer; defaults true for callers
109
+ * written before that flow existed. */
110
+ hasOpReturn: boolean = true,
101
111
  ): number {
102
112
  const inputVbytes = inputType === "p2tr" ? P2TR_INPUT_VBYTES : P2WPKH_INPUT_VBYTES;
103
- const outputCount = hasChange ? 3 : 2; // deposit + OP_RETURN + optional change
104
113
 
105
114
  const vsize =
106
115
  TX_OVERHEAD_VBYTES +
107
116
  numInputs * inputVbytes +
108
117
  P2TR_OUTPUT_VBYTES + // deposit output
109
- OP_RETURN_OUTPUT_VBYTES + // OP_RETURN output
118
+ (hasOpReturn ? OP_RETURN_OUTPUT_VBYTES : 0) +
110
119
  (hasChange ? P2TR_OUTPUT_VBYTES : 0); // change output
111
120
 
112
121
  return Math.ceil(vsize * feeRate);
@@ -138,7 +147,7 @@ export function buildDepositPsbt(params: BuildDepositPsbtParams): BuildDepositPs
138
147
  if (depositAmountSats < DUST_LIMIT) {
139
148
  throw new Error(`Deposit amount ${depositAmountSats} is below dust limit ${DUST_LIMIT}`);
140
149
  }
141
- if (opReturnPayload.length !== DEPOSIT_OP_RETURN_SIZE) {
150
+ if (opReturnPayload && opReturnPayload.length !== DEPOSIT_OP_RETURN_SIZE) {
142
151
  throw new Error(`OP_RETURN payload must be ${DEPOSIT_OP_RETURN_SIZE} bytes, got ${opReturnPayload.length}`);
143
152
  }
144
153
 
@@ -152,11 +161,11 @@ export function buildDepositPsbt(params: BuildDepositPsbtParams): BuildDepositPs
152
161
  const inputType = firstScript[0] === 0x51 ? "p2tr" : "p2wpkh";
153
162
 
154
163
  // Estimate fee with change
155
- const feeWithChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, true);
164
+ const feeWithChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, true, Boolean(opReturnPayload));
156
165
  const changeAmount = totalInput - depositAmountSats - feeWithChange;
157
166
 
158
167
  // Check if we have enough funds
159
- const feeWithoutChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, false);
168
+ const feeWithoutChange = estimateDepositFee(senderUtxos.length, feeRate, inputType, false, Boolean(opReturnPayload));
160
169
  if (totalInput < depositAmountSats + feeWithoutChange) {
161
170
  throw new Error(
162
171
  `Insufficient funds: have ${totalInput} sats, need ${depositAmountSats + feeWithoutChange} sats (including fee)`,
@@ -203,12 +212,15 @@ export function buildDepositPsbt(params: BuildDepositPsbtParams): BuildDepositPs
203
212
  // Output 1: P2TR deposit
204
213
  tx.addOutputAddress(depositAddress, BigInt(depositAmountSats), btcNetwork);
205
214
 
206
- // Output 2: OP_RETURN with compact deposit payload.
207
- const opReturnScript = createOpReturnScriptFromPayload(opReturnPayload);
208
- tx.addOutput({
209
- script: opReturnScript,
210
- amount: 0n,
211
- });
215
+ // Output 2: OP_RETURN with compact deposit payload — only for that flow. A
216
+ // tweak-bound deposit adds nothing here, so the transaction is indistinguishable
217
+ // from an ordinary payment.
218
+ if (opReturnPayload) {
219
+ tx.addOutput({
220
+ script: createOpReturnScriptFromPayload(opReturnPayload),
221
+ amount: 0n,
222
+ });
223
+ }
212
224
 
213
225
  // Output 3: Change (if above dust)
214
226
  if (hasChange) {