@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 +12 -2
- package/dist/psbt.js +17 -12
- package/package.json +1 -1
- package/src/psbt.ts +24 -12
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
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
|
-
|
|
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
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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) {
|