@t402/btc 2.7.0
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/README.md +120 -0
- package/dist/cjs/exact/client/index.d.ts +89 -0
- package/dist/cjs/exact/client/index.js +145 -0
- package/dist/cjs/exact/client/index.js.map +1 -0
- package/dist/cjs/exact/facilitator/index.d.ts +114 -0
- package/dist/cjs/exact/facilitator/index.js +218 -0
- package/dist/cjs/exact/facilitator/index.js.map +1 -0
- package/dist/cjs/exact/server/index.d.ts +101 -0
- package/dist/cjs/exact/server/index.js +161 -0
- package/dist/cjs/exact/server/index.js.map +1 -0
- package/dist/cjs/index.d.ts +179 -0
- package/dist/cjs/index.js +849 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lightning/client/index.d.ts +82 -0
- package/dist/cjs/lightning/client/index.js +114 -0
- package/dist/cjs/lightning/client/index.js.map +1 -0
- package/dist/cjs/lightning/facilitator/index.d.ts +93 -0
- package/dist/cjs/lightning/facilitator/index.js +211 -0
- package/dist/cjs/lightning/facilitator/index.js.map +1 -0
- package/dist/cjs/lightning/server/index.d.ts +96 -0
- package/dist/cjs/lightning/server/index.js +157 -0
- package/dist/cjs/lightning/server/index.js.map +1 -0
- package/dist/cjs/signer-B_Z4WGLa.d.ts +64 -0
- package/dist/esm/chunk-2DEKJ7ER.mjs +123 -0
- package/dist/esm/chunk-2DEKJ7ER.mjs.map +1 -0
- package/dist/esm/chunk-3IOPLDQH.mjs +74 -0
- package/dist/esm/chunk-3IOPLDQH.mjs.map +1 -0
- package/dist/esm/chunk-7IU3Z36R.mjs +103 -0
- package/dist/esm/chunk-7IU3Z36R.mjs.map +1 -0
- package/dist/esm/chunk-HNFWDITA.mjs +170 -0
- package/dist/esm/chunk-HNFWDITA.mjs.map +1 -0
- package/dist/esm/chunk-MX3PAUPJ.mjs +65 -0
- package/dist/esm/chunk-MX3PAUPJ.mjs.map +1 -0
- package/dist/esm/chunk-YJYTK2QQ.mjs +127 -0
- package/dist/esm/chunk-YJYTK2QQ.mjs.map +1 -0
- package/dist/esm/chunk-YWZC2RR7.mjs +38 -0
- package/dist/esm/chunk-YWZC2RR7.mjs.map +1 -0
- package/dist/esm/chunk-ZOL5R3HZ.mjs +177 -0
- package/dist/esm/chunk-ZOL5R3HZ.mjs.map +1 -0
- package/dist/esm/exact/client/index.d.mts +89 -0
- package/dist/esm/exact/client/index.mjs +11 -0
- package/dist/esm/exact/client/index.mjs.map +1 -0
- package/dist/esm/exact/facilitator/index.d.mts +114 -0
- package/dist/esm/exact/facilitator/index.mjs +11 -0
- package/dist/esm/exact/facilitator/index.mjs.map +1 -0
- package/dist/esm/exact/server/index.d.mts +101 -0
- package/dist/esm/exact/server/index.mjs +10 -0
- package/dist/esm/exact/server/index.mjs.map +1 -0
- package/dist/esm/index.d.mts +179 -0
- package/dist/esm/index.mjs +77 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/lightning/client/index.d.mts +82 -0
- package/dist/esm/lightning/client/index.mjs +11 -0
- package/dist/esm/lightning/client/index.mjs.map +1 -0
- package/dist/esm/lightning/facilitator/index.d.mts +93 -0
- package/dist/esm/lightning/facilitator/index.mjs +11 -0
- package/dist/esm/lightning/facilitator/index.mjs.map +1 -0
- package/dist/esm/lightning/server/index.d.mts +96 -0
- package/dist/esm/lightning/server/index.mjs +10 -0
- package/dist/esm/lightning/server/index.mjs.map +1 -0
- package/dist/esm/signer-B_Z4WGLa.d.mts +64 -0
- package/package.json +142 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/exact/facilitator/index.ts
|
|
23
|
+
var facilitator_exports = {};
|
|
24
|
+
__export(facilitator_exports, {
|
|
25
|
+
ExactBtcScheme: () => ExactBtcScheme,
|
|
26
|
+
registerExactBtcScheme: () => registerExactBtcScheme
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(facilitator_exports);
|
|
29
|
+
|
|
30
|
+
// src/constants.ts
|
|
31
|
+
var BTC_MAINNET = "bip122:000000000019d6689c085ae165831e93";
|
|
32
|
+
var BTC_TESTNET = "bip122:000000000933ea01ad0ee984209779ba";
|
|
33
|
+
var LIGHTNING_MAINNET = "lightning:mainnet";
|
|
34
|
+
var LIGHTNING_TESTNET = "lightning:testnet";
|
|
35
|
+
var BTC_NETWORKS = [BTC_MAINNET, BTC_TESTNET];
|
|
36
|
+
var LIGHTNING_NETWORKS = [LIGHTNING_MAINNET, LIGHTNING_TESTNET];
|
|
37
|
+
var ALL_NETWORKS = [...BTC_NETWORKS, ...LIGHTNING_NETWORKS];
|
|
38
|
+
var DUST_LIMIT = 546;
|
|
39
|
+
var SCHEME_EXACT = "exact";
|
|
40
|
+
var MAINNET_ADDRESS_PREFIXES = ["bc1", "1", "3"];
|
|
41
|
+
var TESTNET_ADDRESS_PREFIXES = ["tb1", "m", "n", "2"];
|
|
42
|
+
|
|
43
|
+
// src/utils.ts
|
|
44
|
+
function validateBitcoinAddress(address) {
|
|
45
|
+
if (!address || address.length < 14 || address.length > 90) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const allPrefixes = [...MAINNET_ADDRESS_PREFIXES, ...TESTNET_ADDRESS_PREFIXES];
|
|
49
|
+
return allPrefixes.some((prefix) => address.startsWith(prefix));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/exact/facilitator/scheme.ts
|
|
53
|
+
var ExactBtcScheme = class {
|
|
54
|
+
constructor(signer) {
|
|
55
|
+
this.signer = signer;
|
|
56
|
+
__publicField(this, "scheme", SCHEME_EXACT);
|
|
57
|
+
__publicField(this, "caipFamily", "bip122:*");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get mechanism-specific extra data for the supported kinds endpoint.
|
|
61
|
+
* Bitcoin on-chain has no extra data.
|
|
62
|
+
*/
|
|
63
|
+
getExtra(_network) {
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get signer addresses used by this facilitator.
|
|
68
|
+
*/
|
|
69
|
+
getSigners(_network) {
|
|
70
|
+
return [...this.signer.getAddresses()];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Verifies a payment payload.
|
|
74
|
+
*
|
|
75
|
+
* Validates:
|
|
76
|
+
* 1. Scheme and network matching
|
|
77
|
+
* 2. PSBT structure and signatures
|
|
78
|
+
* 3. Output matches (payTo, amount)
|
|
79
|
+
* 4. Amount above dust limit
|
|
80
|
+
*/
|
|
81
|
+
async verify(payload, requirements) {
|
|
82
|
+
const btcPayload = payload.payload;
|
|
83
|
+
if (!btcPayload?.signedPsbt) {
|
|
84
|
+
return {
|
|
85
|
+
isValid: false,
|
|
86
|
+
invalidReason: "invalid_payload_structure",
|
|
87
|
+
payer: void 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (payload.accepted.scheme !== SCHEME_EXACT || requirements.scheme !== SCHEME_EXACT) {
|
|
91
|
+
return {
|
|
92
|
+
isValid: false,
|
|
93
|
+
invalidReason: "unsupported_scheme",
|
|
94
|
+
payer: void 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const validNetworks = BTC_NETWORKS;
|
|
98
|
+
if (!validNetworks.includes(requirements.network)) {
|
|
99
|
+
return {
|
|
100
|
+
isValid: false,
|
|
101
|
+
invalidReason: "unsupported_network",
|
|
102
|
+
payer: void 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (payload.accepted.network !== requirements.network) {
|
|
106
|
+
return {
|
|
107
|
+
isValid: false,
|
|
108
|
+
invalidReason: "network_mismatch",
|
|
109
|
+
payer: void 0
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!validateBitcoinAddress(requirements.payTo)) {
|
|
113
|
+
return {
|
|
114
|
+
isValid: false,
|
|
115
|
+
invalidReason: "invalid_pay_to_address",
|
|
116
|
+
payer: void 0
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (BigInt(requirements.amount) < BigInt(DUST_LIMIT)) {
|
|
120
|
+
return {
|
|
121
|
+
isValid: false,
|
|
122
|
+
invalidReason: "amount_below_dust_limit",
|
|
123
|
+
payer: void 0
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const result = await this.signer.verifyPsbt({
|
|
128
|
+
signedPsbt: btcPayload.signedPsbt,
|
|
129
|
+
expectedPayTo: requirements.payTo,
|
|
130
|
+
expectedAmount: requirements.amount
|
|
131
|
+
});
|
|
132
|
+
if (!result.valid) {
|
|
133
|
+
return {
|
|
134
|
+
isValid: false,
|
|
135
|
+
invalidReason: result.reason || "psbt_verification_failed",
|
|
136
|
+
payer: result.payer
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
isValid: true,
|
|
141
|
+
invalidReason: void 0,
|
|
142
|
+
payer: result.payer
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
146
|
+
return {
|
|
147
|
+
isValid: false,
|
|
148
|
+
invalidReason: `verification_error: ${errorMessage}`,
|
|
149
|
+
payer: void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Settles a payment by finalizing and broadcasting the PSBT.
|
|
155
|
+
*/
|
|
156
|
+
async settle(payload, requirements) {
|
|
157
|
+
const btcPayload = payload.payload;
|
|
158
|
+
if (!btcPayload?.signedPsbt) {
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
network: payload.accepted.network,
|
|
162
|
+
transaction: "",
|
|
163
|
+
errorReason: "invalid_payload_structure",
|
|
164
|
+
payer: void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const verifyResult = await this.verify(payload, requirements);
|
|
168
|
+
if (!verifyResult.isValid) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
network: payload.accepted.network,
|
|
172
|
+
transaction: "",
|
|
173
|
+
errorReason: verifyResult.invalidReason ?? "verification_failed",
|
|
174
|
+
payer: verifyResult.payer
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const txId = await this.signer.broadcastPsbt(btcPayload.signedPsbt);
|
|
179
|
+
const confirmation = await this.signer.waitForConfirmation(txId, 1);
|
|
180
|
+
if (!confirmation.confirmed) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
errorReason: "transaction_not_confirmed",
|
|
184
|
+
transaction: txId,
|
|
185
|
+
network: payload.accepted.network,
|
|
186
|
+
payer: verifyResult.payer
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
success: true,
|
|
191
|
+
transaction: txId,
|
|
192
|
+
network: payload.accepted.network,
|
|
193
|
+
payer: verifyResult.payer
|
|
194
|
+
};
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error("Failed to settle Bitcoin transaction:", error);
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
errorReason: "transaction_failed",
|
|
200
|
+
transaction: "",
|
|
201
|
+
network: payload.accepted.network,
|
|
202
|
+
payer: verifyResult.payer
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// src/exact/facilitator/register.ts
|
|
209
|
+
function registerExactBtcScheme(facilitator, config) {
|
|
210
|
+
facilitator.register(config.networks, new ExactBtcScheme(config.signer));
|
|
211
|
+
return facilitator;
|
|
212
|
+
}
|
|
213
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
214
|
+
0 && (module.exports = {
|
|
215
|
+
ExactBtcScheme,
|
|
216
|
+
registerExactBtcScheme
|
|
217
|
+
});
|
|
218
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/exact/facilitator/index.ts","../../../../src/constants.ts","../../../../src/utils.ts","../../../../src/exact/facilitator/scheme.ts","../../../../src/exact/facilitator/register.ts"],"sourcesContent":["export { ExactBtcScheme } from './scheme.js'\nexport type { FacilitatorBtcSigner } from './scheme.js'\nexport { registerExactBtcScheme } from './register.js'\nexport type { BtcFacilitatorConfig } from './register.js'\n","/**\n * Bitcoin & Lightning Network Constants\n *\n * CAIP-2 network identifiers, dust limits, and fee constants.\n */\n\n/**\n * CAIP-2 Network Identifiers for Bitcoin\n * Uses BIP-122 chain genesis block hashes\n */\nexport const BTC_MAINNET = 'bip122:000000000019d6689c085ae165831e93'\nexport const BTC_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba'\n\n/**\n * CAIP-2 Network Identifiers for Lightning Network\n */\nexport const LIGHTNING_MAINNET = 'lightning:mainnet'\nexport const LIGHTNING_TESTNET = 'lightning:testnet'\n\n/**\n * All supported BTC on-chain networks\n */\nexport const BTC_NETWORKS = [BTC_MAINNET, BTC_TESTNET] as const\n\n/**\n * All supported Lightning networks\n */\nexport const LIGHTNING_NETWORKS = [LIGHTNING_MAINNET, LIGHTNING_TESTNET] as const\n\n/**\n * All supported networks (on-chain + Lightning)\n */\nexport const ALL_NETWORKS = [...BTC_NETWORKS, ...LIGHTNING_NETWORKS] as const\n\nexport type BtcNetwork = (typeof BTC_NETWORKS)[number]\nexport type LightningNetwork = (typeof LIGHTNING_NETWORKS)[number]\n\n/**\n * Dust limit in satoshis - minimum viable output value\n * Outputs below this threshold are rejected by Bitcoin nodes\n */\nexport const DUST_LIMIT = 546\n\n/**\n * Minimum relay fee in satoshis\n * Transactions with fees below this are not relayed by default\n */\nexport const MIN_RELAY_FEE = 1000\n\n/**\n * Satoshis per BTC\n */\nexport const SATS_PER_BTC = 100_000_000\n\n/**\n * Scheme identifiers\n */\nexport const SCHEME_EXACT = 'exact'\n\n/**\n * Default timeout for payment validity (in seconds)\n */\nexport const DEFAULT_VALIDITY_DURATION = 3600 // 1 hour\n\n/**\n * Bitcoin address prefixes for basic validation\n */\nexport const MAINNET_ADDRESS_PREFIXES = ['bc1', '1', '3']\nexport const TESTNET_ADDRESS_PREFIXES = ['tb1', 'm', 'n', '2']\n","/**\n * Bitcoin & Lightning Utility Functions\n *\n * Helper functions for address validation, unit conversion,\n * and invoice validation.\n */\n\nimport { SATS_PER_BTC, MAINNET_ADDRESS_PREFIXES, TESTNET_ADDRESS_PREFIXES } from './constants.js'\n\n/**\n * Convert satoshis to BTC\n *\n * @param sats - Amount in satoshis\n * @returns Amount in BTC as string (to avoid floating point issues)\n */\nexport function satoshisToBtc(sats: bigint | number | string): string {\n const satsBigInt = BigInt(sats)\n const whole = satsBigInt / BigInt(SATS_PER_BTC)\n const frac = satsBigInt % BigInt(SATS_PER_BTC)\n\n if (frac === 0n) {\n return whole.toString()\n }\n\n const fracStr = frac.toString().padStart(8, '0')\n return `${whole}.${fracStr}`.replace(/\\.?0+$/, '')\n}\n\n/**\n * Convert BTC to satoshis\n *\n * @param btc - Amount in BTC (string or number)\n * @returns Amount in satoshis as bigint\n */\nexport function btcToSatoshis(btc: string | number): bigint {\n const btcStr = typeof btc === 'number' ? btc.toString() : btc\n const [wholePart, fracPart = ''] = btcStr.split('.')\n const paddedFrac = fracPart.padEnd(8, '0').slice(0, 8)\n const combined = wholePart + paddedFrac\n const result = BigInt(combined.replace(/^0+/, '') || '0')\n return result\n}\n\n/**\n * Validate a Bitcoin address (basic format validation)\n *\n * Checks address prefix against known formats:\n * - Mainnet: bc1 (bech32), 1 (P2PKH), 3 (P2SH)\n * - Testnet: tb1 (bech32), m/n (P2PKH), 2 (P2SH)\n *\n * @param address - Bitcoin address to validate\n * @returns true if the address has a valid format\n */\nexport function validateBitcoinAddress(address: string): boolean {\n if (!address || address.length < 14 || address.length > 90) {\n return false\n }\n\n const allPrefixes = [...MAINNET_ADDRESS_PREFIXES, ...TESTNET_ADDRESS_PREFIXES]\n return allPrefixes.some((prefix) => address.startsWith(prefix))\n}\n\n/**\n * Check if a Bitcoin address is for mainnet\n *\n * @param address - Bitcoin address\n * @returns true if mainnet address\n */\nexport function isMainnetAddress(address: string): boolean {\n return MAINNET_ADDRESS_PREFIXES.some((prefix) => address.startsWith(prefix))\n}\n\n/**\n * Check if a Bitcoin address is for testnet\n *\n * @param address - Bitcoin address\n * @returns true if testnet address\n */\nexport function isTestnetAddress(address: string): boolean {\n return TESTNET_ADDRESS_PREFIXES.some((prefix) => address.startsWith(prefix))\n}\n\n/**\n * Validate a BOLT11 Lightning invoice (basic format validation)\n *\n * BOLT11 invoices follow the format:\n * - lnbc... for mainnet\n * - lntb... for testnet\n * - lnbcrt... for regtest\n *\n * @param invoice - BOLT11 invoice string\n * @returns true if the invoice has a valid format\n */\nexport function validateBolt11Invoice(invoice: string): boolean {\n if (!invoice || invoice.length < 20) {\n return false\n }\n\n const lower = invoice.toLowerCase()\n return lower.startsWith('lnbc') || lower.startsWith('lntb') || lower.startsWith('lnbcrt')\n}\n\n/**\n * Validate a hex-encoded string\n *\n * @param hex - String to validate\n * @param expectedLength - Expected byte length (hex length / 2)\n * @returns true if valid hex of expected length\n */\nexport function isValidHex(hex: string, expectedLength?: number): boolean {\n if (!/^[0-9a-fA-F]+$/.test(hex)) {\n return false\n }\n if (expectedLength !== undefined && hex.length !== expectedLength * 2) {\n return false\n }\n return true\n}\n","/**\n * Bitcoin On-chain Facilitator Scheme Implementation\n *\n * Verifies and settles Bitcoin on-chain payments using the exact scheme.\n * Validates signed PSBTs and broadcasts transactions to the Bitcoin network.\n */\n\nimport type {\n PaymentPayload,\n PaymentRequirements,\n SchemeNetworkFacilitator,\n SettleResponse,\n VerifyResponse,\n} from '@t402/core/types'\nimport type { BtcOnchainPayload } from '../../types.js'\nimport { SCHEME_EXACT, DUST_LIMIT, BTC_NETWORKS } from '../../constants.js'\nimport { validateBitcoinAddress } from '../../utils.js'\n\n/**\n * Facilitator BTC signer interface for verify and settle operations\n */\nexport interface FacilitatorBtcSigner {\n /**\n * Get all addresses this facilitator can use\n */\n getAddresses(): readonly string[]\n\n /**\n * Verify a signed PSBT\n * Checks that outputs match expected values and signatures are valid\n *\n * @param params - Verification parameters\n * @returns Verification result\n */\n verifyPsbt(params: {\n signedPsbt: string\n expectedPayTo: string\n expectedAmount: string\n }): Promise<{\n valid: boolean\n reason?: string\n payer?: string\n }>\n\n /**\n * Finalize and broadcast a signed PSBT\n *\n * @param signedPsbt - Base64-encoded signed PSBT\n * @returns Transaction ID\n */\n broadcastPsbt(signedPsbt: string): Promise<string>\n\n /**\n * Wait for a transaction to be confirmed\n *\n * @param txId - Transaction ID\n * @param confirmations - Number of confirmations to wait for\n * @returns Confirmation result\n */\n waitForConfirmation(\n txId: string,\n confirmations?: number,\n ): Promise<{\n confirmed: boolean\n txId: string\n blockHash?: string\n confirmations: number\n }>\n}\n\n/**\n * Bitcoin facilitator implementation for the Exact payment scheme.\n *\n * Verifies signed PSBTs and broadcasts transactions to settle payments.\n */\nexport class ExactBtcScheme implements SchemeNetworkFacilitator {\n readonly scheme = SCHEME_EXACT\n readonly caipFamily = 'bip122:*'\n\n constructor(private readonly signer: FacilitatorBtcSigner) {}\n\n /**\n * Get mechanism-specific extra data for the supported kinds endpoint.\n * Bitcoin on-chain has no extra data.\n */\n getExtra(_network: string): Record<string, unknown> | undefined {\n return undefined\n }\n\n /**\n * Get signer addresses used by this facilitator.\n */\n getSigners(_network: string): string[] {\n return [...this.signer.getAddresses()]\n }\n\n /**\n * Verifies a payment payload.\n *\n * Validates:\n * 1. Scheme and network matching\n * 2. PSBT structure and signatures\n * 3. Output matches (payTo, amount)\n * 4. Amount above dust limit\n */\n async verify(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n ): Promise<VerifyResponse> {\n const btcPayload = payload.payload as BtcOnchainPayload | undefined\n\n // Validate payload structure\n if (!btcPayload?.signedPsbt) {\n return {\n isValid: false,\n invalidReason: 'invalid_payload_structure',\n payer: undefined,\n }\n }\n\n // Verify scheme matches\n if (payload.accepted.scheme !== SCHEME_EXACT || requirements.scheme !== SCHEME_EXACT) {\n return {\n isValid: false,\n invalidReason: 'unsupported_scheme',\n payer: undefined,\n }\n }\n\n // Verify network is a valid BTC network\n const validNetworks: readonly string[] = BTC_NETWORKS\n if (!validNetworks.includes(requirements.network)) {\n return {\n isValid: false,\n invalidReason: 'unsupported_network',\n payer: undefined,\n }\n }\n\n // Verify network matches\n if (payload.accepted.network !== requirements.network) {\n return {\n isValid: false,\n invalidReason: 'network_mismatch',\n payer: undefined,\n }\n }\n\n // Validate payTo address\n if (!validateBitcoinAddress(requirements.payTo)) {\n return {\n isValid: false,\n invalidReason: 'invalid_pay_to_address',\n payer: undefined,\n }\n }\n\n // Validate amount above dust limit\n if (BigInt(requirements.amount) < BigInt(DUST_LIMIT)) {\n return {\n isValid: false,\n invalidReason: 'amount_below_dust_limit',\n payer: undefined,\n }\n }\n\n // Verify the PSBT\n try {\n const result = await this.signer.verifyPsbt({\n signedPsbt: btcPayload.signedPsbt,\n expectedPayTo: requirements.payTo,\n expectedAmount: requirements.amount,\n })\n\n if (!result.valid) {\n return {\n isValid: false,\n invalidReason: result.reason || 'psbt_verification_failed',\n payer: result.payer,\n }\n }\n\n return {\n isValid: true,\n invalidReason: undefined,\n payer: result.payer,\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n return {\n isValid: false,\n invalidReason: `verification_error: ${errorMessage}`,\n payer: undefined,\n }\n }\n }\n\n /**\n * Settles a payment by finalizing and broadcasting the PSBT.\n */\n async settle(\n payload: PaymentPayload,\n requirements: PaymentRequirements,\n ): Promise<SettleResponse> {\n const btcPayload = payload.payload as BtcOnchainPayload | undefined\n\n if (!btcPayload?.signedPsbt) {\n return {\n success: false,\n network: payload.accepted.network,\n transaction: '',\n errorReason: 'invalid_payload_structure',\n payer: undefined,\n }\n }\n\n // Re-verify before settling\n const verifyResult = await this.verify(payload, requirements)\n if (!verifyResult.isValid) {\n return {\n success: false,\n network: payload.accepted.network,\n transaction: '',\n errorReason: verifyResult.invalidReason ?? 'verification_failed',\n payer: verifyResult.payer,\n }\n }\n\n try {\n // Broadcast the signed transaction\n const txId = await this.signer.broadcastPsbt(btcPayload.signedPsbt)\n\n // Wait for at least 1 confirmation\n const confirmation = await this.signer.waitForConfirmation(txId, 1)\n\n if (!confirmation.confirmed) {\n return {\n success: false,\n errorReason: 'transaction_not_confirmed',\n transaction: txId,\n network: payload.accepted.network,\n payer: verifyResult.payer,\n }\n }\n\n return {\n success: true,\n transaction: txId,\n network: payload.accepted.network,\n payer: verifyResult.payer,\n }\n } catch (error) {\n console.error('Failed to settle Bitcoin transaction:', error)\n return {\n success: false,\n errorReason: 'transaction_failed',\n transaction: '',\n network: payload.accepted.network,\n payer: verifyResult.payer,\n }\n }\n }\n}\n","import { t402Facilitator } from '@t402/core/facilitator'\nimport { Network } from '@t402/core/types'\nimport { FacilitatorBtcSigner, ExactBtcScheme } from './scheme.js'\n\n/**\n * Configuration options for registering BTC schemes to an t402Facilitator\n */\nexport interface BtcFacilitatorConfig {\n /**\n * The Bitcoin signer for facilitator operations (verify and settle)\n */\n signer: FacilitatorBtcSigner\n\n /**\n * Networks to register (single network or array of networks)\n * Examples: \"bip122:000000000019d6689c085ae165831e93\"\n */\n networks: Network | Network[]\n}\n\n/**\n * Registers Bitcoin exact payment schemes to an t402Facilitator instance.\n *\n * @param facilitator - The t402Facilitator instance to register schemes to\n * @param config - Configuration for BTC facilitator registration\n * @returns The facilitator instance for chaining\n */\nexport function registerExactBtcScheme(\n facilitator: t402Facilitator,\n config: BtcFacilitatorConfig,\n): t402Facilitator {\n facilitator.register(config.networks, new ExactBtcScheme(config.signer))\n return facilitator\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SchemeNetworkServer, MoneyParser, Price, Network, AssetAmount, PaymentRequirements } from '@t402/core/types';
|
|
2
|
+
import { t402ResourceServer } from '@t402/core/server';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bitcoin On-chain Server Scheme Implementation
|
|
6
|
+
*
|
|
7
|
+
* Handles price parsing and payment requirement enhancement for
|
|
8
|
+
* Bitcoin on-chain payments using the exact scheme.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for ExactBtcScheme server
|
|
13
|
+
*/
|
|
14
|
+
interface ExactBtcSchemeConfig {
|
|
15
|
+
/**
|
|
16
|
+
* The Bitcoin address to receive payments
|
|
17
|
+
*/
|
|
18
|
+
payTo: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bitcoin server implementation for the Exact payment scheme.
|
|
22
|
+
* Handles price parsing and converts user-friendly amounts to satoshis.
|
|
23
|
+
*
|
|
24
|
+
* For Bitcoin, the asset is always "BTC" and amounts are in satoshis.
|
|
25
|
+
*/
|
|
26
|
+
declare class ExactBtcScheme implements SchemeNetworkServer {
|
|
27
|
+
readonly scheme = "exact";
|
|
28
|
+
private moneyParsers;
|
|
29
|
+
private config;
|
|
30
|
+
constructor(config: ExactBtcSchemeConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Register a custom money parser in the parser chain.
|
|
33
|
+
*
|
|
34
|
+
* @param parser - Custom function to convert amount to AssetAmount (or null to skip)
|
|
35
|
+
* @returns The server instance for chaining
|
|
36
|
+
*/
|
|
37
|
+
registerMoneyParser(parser: MoneyParser): ExactBtcScheme;
|
|
38
|
+
/**
|
|
39
|
+
* Parses a price into an asset amount in satoshis.
|
|
40
|
+
*
|
|
41
|
+
* Accepts:
|
|
42
|
+
* - Number: treated as USD, converted to satoshis at a default rate
|
|
43
|
+
* - String with $: treated as USD
|
|
44
|
+
* - AssetAmount: returned directly
|
|
45
|
+
*
|
|
46
|
+
* For Bitcoin, amounts are always in satoshis and the asset is "BTC".
|
|
47
|
+
*
|
|
48
|
+
* @param price - The price to parse
|
|
49
|
+
* @param network - The network to use
|
|
50
|
+
* @returns Promise that resolves to the parsed asset amount
|
|
51
|
+
*/
|
|
52
|
+
parsePrice(price: Price, network: Network): Promise<AssetAmount>;
|
|
53
|
+
/**
|
|
54
|
+
* Build payment requirements for this scheme/network combination.
|
|
55
|
+
*
|
|
56
|
+
* @param paymentRequirements - Base payment requirements with amount/asset already set
|
|
57
|
+
* @param supportedKind - The supported kind from facilitator
|
|
58
|
+
* @param extensionKeys - Extensions supported by the facilitator
|
|
59
|
+
* @returns Enhanced payment requirements
|
|
60
|
+
*/
|
|
61
|
+
enhancePaymentRequirements(paymentRequirements: PaymentRequirements, supportedKind: {
|
|
62
|
+
t402Version: number;
|
|
63
|
+
scheme: string;
|
|
64
|
+
network: Network;
|
|
65
|
+
extra?: Record<string, unknown>;
|
|
66
|
+
}, extensionKeys: string[]): Promise<PaymentRequirements>;
|
|
67
|
+
/**
|
|
68
|
+
* Parse Money (string | number) to a decimal number.
|
|
69
|
+
*/
|
|
70
|
+
private parseMoneyToDecimal;
|
|
71
|
+
/**
|
|
72
|
+
* Default money conversion: treat amount as satoshis.
|
|
73
|
+
* For direct satoshi amounts, the amount is used as-is.
|
|
74
|
+
*/
|
|
75
|
+
private defaultMoneyConversion;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Configuration options for registering BTC schemes to an t402ResourceServer
|
|
80
|
+
*/
|
|
81
|
+
interface BtcResourceServerConfig {
|
|
82
|
+
/**
|
|
83
|
+
* Optional specific networks to register
|
|
84
|
+
* If not provided, registers wildcard support (bip122:*)
|
|
85
|
+
*/
|
|
86
|
+
networks?: Network[];
|
|
87
|
+
/**
|
|
88
|
+
* Scheme configuration (payTo address, etc.)
|
|
89
|
+
*/
|
|
90
|
+
schemeConfig: ExactBtcSchemeConfig;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Registers Bitcoin exact payment schemes to an t402ResourceServer instance.
|
|
94
|
+
*
|
|
95
|
+
* @param server - The t402ResourceServer instance to register schemes to
|
|
96
|
+
* @param config - Configuration for BTC resource server registration
|
|
97
|
+
* @returns The server instance for chaining
|
|
98
|
+
*/
|
|
99
|
+
declare function registerExactBtcScheme(server: t402ResourceServer, config: BtcResourceServerConfig): t402ResourceServer;
|
|
100
|
+
|
|
101
|
+
export { type BtcResourceServerConfig, ExactBtcScheme, type ExactBtcSchemeConfig, registerExactBtcScheme };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/exact/server/index.ts
|
|
23
|
+
var server_exports = {};
|
|
24
|
+
__export(server_exports, {
|
|
25
|
+
ExactBtcScheme: () => ExactBtcScheme,
|
|
26
|
+
registerExactBtcScheme: () => registerExactBtcScheme
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(server_exports);
|
|
29
|
+
|
|
30
|
+
// src/constants.ts
|
|
31
|
+
var BTC_MAINNET = "bip122:000000000019d6689c085ae165831e93";
|
|
32
|
+
var BTC_TESTNET = "bip122:000000000933ea01ad0ee984209779ba";
|
|
33
|
+
var LIGHTNING_MAINNET = "lightning:mainnet";
|
|
34
|
+
var LIGHTNING_TESTNET = "lightning:testnet";
|
|
35
|
+
var BTC_NETWORKS = [BTC_MAINNET, BTC_TESTNET];
|
|
36
|
+
var LIGHTNING_NETWORKS = [LIGHTNING_MAINNET, LIGHTNING_TESTNET];
|
|
37
|
+
var ALL_NETWORKS = [...BTC_NETWORKS, ...LIGHTNING_NETWORKS];
|
|
38
|
+
var SATS_PER_BTC = 1e8;
|
|
39
|
+
var SCHEME_EXACT = "exact";
|
|
40
|
+
|
|
41
|
+
// src/exact/server/scheme.ts
|
|
42
|
+
var ExactBtcScheme = class {
|
|
43
|
+
constructor(config) {
|
|
44
|
+
__publicField(this, "scheme", SCHEME_EXACT);
|
|
45
|
+
__publicField(this, "moneyParsers", []);
|
|
46
|
+
__publicField(this, "config");
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a custom money parser in the parser chain.
|
|
51
|
+
*
|
|
52
|
+
* @param parser - Custom function to convert amount to AssetAmount (or null to skip)
|
|
53
|
+
* @returns The server instance for chaining
|
|
54
|
+
*/
|
|
55
|
+
registerMoneyParser(parser) {
|
|
56
|
+
this.moneyParsers.push(parser);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parses a price into an asset amount in satoshis.
|
|
61
|
+
*
|
|
62
|
+
* Accepts:
|
|
63
|
+
* - Number: treated as USD, converted to satoshis at a default rate
|
|
64
|
+
* - String with $: treated as USD
|
|
65
|
+
* - AssetAmount: returned directly
|
|
66
|
+
*
|
|
67
|
+
* For Bitcoin, amounts are always in satoshis and the asset is "BTC".
|
|
68
|
+
*
|
|
69
|
+
* @param price - The price to parse
|
|
70
|
+
* @param network - The network to use
|
|
71
|
+
* @returns Promise that resolves to the parsed asset amount
|
|
72
|
+
*/
|
|
73
|
+
async parsePrice(price, network) {
|
|
74
|
+
if (typeof price === "object" && price !== null && "amount" in price) {
|
|
75
|
+
if (!price.asset) {
|
|
76
|
+
throw new Error(`Asset must be specified for AssetAmount on network ${network}`);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
amount: price.amount,
|
|
80
|
+
asset: price.asset,
|
|
81
|
+
extra: price.extra || {}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const amount = this.parseMoneyToDecimal(price);
|
|
85
|
+
for (const parser of this.moneyParsers) {
|
|
86
|
+
const result = await parser(amount, network);
|
|
87
|
+
if (result !== null) {
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return this.defaultMoneyConversion(amount);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build payment requirements for this scheme/network combination.
|
|
95
|
+
*
|
|
96
|
+
* @param paymentRequirements - Base payment requirements with amount/asset already set
|
|
97
|
+
* @param supportedKind - The supported kind from facilitator
|
|
98
|
+
* @param extensionKeys - Extensions supported by the facilitator
|
|
99
|
+
* @returns Enhanced payment requirements
|
|
100
|
+
*/
|
|
101
|
+
async enhancePaymentRequirements(paymentRequirements, supportedKind, extensionKeys) {
|
|
102
|
+
void supportedKind;
|
|
103
|
+
void extensionKeys;
|
|
104
|
+
return {
|
|
105
|
+
...paymentRequirements,
|
|
106
|
+
payTo: paymentRequirements.payTo || this.config.payTo,
|
|
107
|
+
asset: paymentRequirements.asset || "BTC"
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parse Money (string | number) to a decimal number.
|
|
112
|
+
*/
|
|
113
|
+
parseMoneyToDecimal(money) {
|
|
114
|
+
if (typeof money === "number") {
|
|
115
|
+
if (!Number.isFinite(money)) {
|
|
116
|
+
throw new Error(`Invalid money value: ${money}`);
|
|
117
|
+
}
|
|
118
|
+
return money;
|
|
119
|
+
}
|
|
120
|
+
const cleanMoney = money.replace(/^\$/, "").trim();
|
|
121
|
+
const amount = parseFloat(cleanMoney);
|
|
122
|
+
if (isNaN(amount)) {
|
|
123
|
+
throw new Error(`Invalid money format: ${money}`);
|
|
124
|
+
}
|
|
125
|
+
return amount;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Default money conversion: treat amount as satoshis.
|
|
129
|
+
* For direct satoshi amounts, the amount is used as-is.
|
|
130
|
+
*/
|
|
131
|
+
defaultMoneyConversion(amount) {
|
|
132
|
+
const sats = Math.floor(amount * SATS_PER_BTC);
|
|
133
|
+
return {
|
|
134
|
+
amount: sats.toString(),
|
|
135
|
+
asset: "BTC",
|
|
136
|
+
extra: {
|
|
137
|
+
symbol: "BTC",
|
|
138
|
+
decimals: 8
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/exact/server/register.ts
|
|
145
|
+
function registerExactBtcScheme(server, config) {
|
|
146
|
+
const scheme = new ExactBtcScheme(config.schemeConfig);
|
|
147
|
+
if (config.networks && config.networks.length > 0) {
|
|
148
|
+
config.networks.forEach((network) => {
|
|
149
|
+
server.register(network, scheme);
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
server.register("bip122:*", scheme);
|
|
153
|
+
}
|
|
154
|
+
return server;
|
|
155
|
+
}
|
|
156
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
157
|
+
0 && (module.exports = {
|
|
158
|
+
ExactBtcScheme,
|
|
159
|
+
registerExactBtcScheme
|
|
160
|
+
});
|
|
161
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/exact/server/index.ts","../../../../src/constants.ts","../../../../src/exact/server/scheme.ts","../../../../src/exact/server/register.ts"],"sourcesContent":["export { ExactBtcScheme } from './scheme.js'\nexport type { ExactBtcSchemeConfig } from './scheme.js'\nexport { registerExactBtcScheme } from './register.js'\nexport type { BtcResourceServerConfig } from './register.js'\n","/**\n * Bitcoin & Lightning Network Constants\n *\n * CAIP-2 network identifiers, dust limits, and fee constants.\n */\n\n/**\n * CAIP-2 Network Identifiers for Bitcoin\n * Uses BIP-122 chain genesis block hashes\n */\nexport const BTC_MAINNET = 'bip122:000000000019d6689c085ae165831e93'\nexport const BTC_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba'\n\n/**\n * CAIP-2 Network Identifiers for Lightning Network\n */\nexport const LIGHTNING_MAINNET = 'lightning:mainnet'\nexport const LIGHTNING_TESTNET = 'lightning:testnet'\n\n/**\n * All supported BTC on-chain networks\n */\nexport const BTC_NETWORKS = [BTC_MAINNET, BTC_TESTNET] as const\n\n/**\n * All supported Lightning networks\n */\nexport const LIGHTNING_NETWORKS = [LIGHTNING_MAINNET, LIGHTNING_TESTNET] as const\n\n/**\n * All supported networks (on-chain + Lightning)\n */\nexport const ALL_NETWORKS = [...BTC_NETWORKS, ...LIGHTNING_NETWORKS] as const\n\nexport type BtcNetwork = (typeof BTC_NETWORKS)[number]\nexport type LightningNetwork = (typeof LIGHTNING_NETWORKS)[number]\n\n/**\n * Dust limit in satoshis - minimum viable output value\n * Outputs below this threshold are rejected by Bitcoin nodes\n */\nexport const DUST_LIMIT = 546\n\n/**\n * Minimum relay fee in satoshis\n * Transactions with fees below this are not relayed by default\n */\nexport const MIN_RELAY_FEE = 1000\n\n/**\n * Satoshis per BTC\n */\nexport const SATS_PER_BTC = 100_000_000\n\n/**\n * Scheme identifiers\n */\nexport const SCHEME_EXACT = 'exact'\n\n/**\n * Default timeout for payment validity (in seconds)\n */\nexport const DEFAULT_VALIDITY_DURATION = 3600 // 1 hour\n\n/**\n * Bitcoin address prefixes for basic validation\n */\nexport const MAINNET_ADDRESS_PREFIXES = ['bc1', '1', '3']\nexport const TESTNET_ADDRESS_PREFIXES = ['tb1', 'm', 'n', '2']\n","/**\n * Bitcoin On-chain Server Scheme Implementation\n *\n * Handles price parsing and payment requirement enhancement for\n * Bitcoin on-chain payments using the exact scheme.\n */\n\nimport type {\n AssetAmount,\n Network,\n PaymentRequirements,\n Price,\n SchemeNetworkServer,\n MoneyParser,\n} from '@t402/core/types'\nimport { SCHEME_EXACT, SATS_PER_BTC } from '../../constants.js'\n\n/**\n * Configuration options for ExactBtcScheme server\n */\nexport interface ExactBtcSchemeConfig {\n /**\n * The Bitcoin address to receive payments\n */\n payTo: string\n}\n\n/**\n * Bitcoin server implementation for the Exact payment scheme.\n * Handles price parsing and converts user-friendly amounts to satoshis.\n *\n * For Bitcoin, the asset is always \"BTC\" and amounts are in satoshis.\n */\nexport class ExactBtcScheme implements SchemeNetworkServer {\n readonly scheme = SCHEME_EXACT\n private moneyParsers: MoneyParser[] = []\n private config: ExactBtcSchemeConfig\n\n constructor(config: ExactBtcSchemeConfig) {\n this.config = config\n }\n\n /**\n * Register a custom money parser in the parser chain.\n *\n * @param parser - Custom function to convert amount to AssetAmount (or null to skip)\n * @returns The server instance for chaining\n */\n registerMoneyParser(parser: MoneyParser): ExactBtcScheme {\n this.moneyParsers.push(parser)\n return this\n }\n\n /**\n * Parses a price into an asset amount in satoshis.\n *\n * Accepts:\n * - Number: treated as USD, converted to satoshis at a default rate\n * - String with $: treated as USD\n * - AssetAmount: returned directly\n *\n * For Bitcoin, amounts are always in satoshis and the asset is \"BTC\".\n *\n * @param price - The price to parse\n * @param network - The network to use\n * @returns Promise that resolves to the parsed asset amount\n */\n async parsePrice(price: Price, network: Network): Promise<AssetAmount> {\n // If already an AssetAmount, return it directly\n if (typeof price === 'object' && price !== null && 'amount' in price) {\n if (!price.asset) {\n throw new Error(`Asset must be specified for AssetAmount on network ${network}`)\n }\n return {\n amount: price.amount,\n asset: price.asset,\n extra: price.extra || {},\n }\n }\n\n // Parse Money to decimal number\n const amount = this.parseMoneyToDecimal(price)\n\n // Try each custom money parser in order\n for (const parser of this.moneyParsers) {\n const result = await parser(amount, network)\n if (result !== null) {\n return result\n }\n }\n\n // Default: treat amount as satoshis directly\n return this.defaultMoneyConversion(amount)\n }\n\n /**\n * Build payment requirements for this scheme/network combination.\n *\n * @param paymentRequirements - Base payment requirements with amount/asset already set\n * @param supportedKind - The supported kind from facilitator\n * @param extensionKeys - Extensions supported by the facilitator\n * @returns Enhanced payment requirements\n */\n async enhancePaymentRequirements(\n paymentRequirements: PaymentRequirements,\n supportedKind: {\n t402Version: number\n scheme: string\n network: Network\n extra?: Record<string, unknown>\n },\n extensionKeys: string[],\n ): Promise<PaymentRequirements> {\n void supportedKind\n void extensionKeys\n\n return {\n ...paymentRequirements,\n payTo: paymentRequirements.payTo || this.config.payTo,\n asset: paymentRequirements.asset || 'BTC',\n }\n }\n\n /**\n * Parse Money (string | number) to a decimal number.\n */\n private parseMoneyToDecimal(money: string | number): number {\n if (typeof money === 'number') {\n if (!Number.isFinite(money)) {\n throw new Error(`Invalid money value: ${money}`)\n }\n return money\n }\n\n const cleanMoney = money.replace(/^\\$/, '').trim()\n const amount = parseFloat(cleanMoney)\n\n if (isNaN(amount)) {\n throw new Error(`Invalid money format: ${money}`)\n }\n\n return amount\n }\n\n /**\n * Default money conversion: treat amount as satoshis.\n * For direct satoshi amounts, the amount is used as-is.\n */\n private defaultMoneyConversion(amount: number): AssetAmount {\n // Amount is treated as satoshis directly\n const sats = Math.floor(amount * SATS_PER_BTC)\n\n return {\n amount: sats.toString(),\n asset: 'BTC',\n extra: {\n symbol: 'BTC',\n decimals: 8,\n },\n }\n }\n}\n","import { t402ResourceServer } from '@t402/core/server'\nimport { Network } from '@t402/core/types'\nimport { ExactBtcScheme, ExactBtcSchemeConfig } from './scheme.js'\n\n/**\n * Configuration options for registering BTC schemes to an t402ResourceServer\n */\nexport interface BtcResourceServerConfig {\n /**\n * Optional specific networks to register\n * If not provided, registers wildcard support (bip122:*)\n */\n networks?: Network[]\n\n /**\n * Scheme configuration (payTo address, etc.)\n */\n schemeConfig: ExactBtcSchemeConfig\n}\n\n/**\n * Registers Bitcoin exact payment schemes to an t402ResourceServer instance.\n *\n * @param server - The t402ResourceServer instance to register schemes to\n * @param config - Configuration for BTC resource server registration\n * @returns The server instance for chaining\n */\nexport function registerExactBtcScheme(\n server: t402ResourceServer,\n config: BtcResourceServerConfig,\n): t402ResourceServer {\n const scheme = new ExactBtcScheme(config.schemeConfig)\n\n if (config.networks && config.networks.length > 0) {\n config.networks.forEach((network) => {\n server.register(network, scheme)\n })\n } else {\n server.register('bip122:*', scheme)\n }\n\n return server\n}\n"],"mappings":"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","names":[]}
|