@arkade-os/sdk 0.4.0-next.0 → 0.4.0-next.1
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 +127 -24
- package/dist/cjs/bip322/index.js +270 -0
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/intent/index.js +19 -9
- package/dist/cjs/repositories/indexedDB/contractRepository.js +1 -1
- package/dist/cjs/repositories/indexedDB/db.js +8 -46
- package/dist/cjs/repositories/indexedDB/walletRepository.js +1 -1
- package/dist/cjs/repositories/realm/contractRepository.js +120 -0
- package/dist/cjs/repositories/realm/index.js +9 -0
- package/dist/cjs/repositories/realm/schemas.js +108 -0
- package/dist/cjs/repositories/realm/types.js +7 -0
- package/dist/cjs/repositories/realm/walletRepository.js +273 -0
- package/dist/cjs/repositories/serialization.js +49 -0
- package/dist/cjs/repositories/sqlite/contractRepository.js +139 -0
- package/dist/cjs/repositories/sqlite/index.js +7 -0
- package/dist/cjs/repositories/sqlite/types.js +2 -0
- package/dist/cjs/repositories/sqlite/walletRepository.js +328 -0
- package/dist/cjs/wallet/vtxo-manager.js +7 -0
- package/dist/esm/bip322/index.js +267 -0
- package/dist/esm/index.js +4 -1
- package/dist/esm/intent/index.js +16 -7
- package/dist/esm/repositories/indexedDB/contractRepository.js +1 -1
- package/dist/esm/repositories/indexedDB/db.js +2 -40
- package/dist/esm/repositories/indexedDB/walletRepository.js +1 -1
- package/dist/esm/repositories/realm/contractRepository.js +116 -0
- package/dist/esm/repositories/realm/index.js +3 -0
- package/dist/esm/repositories/realm/schemas.js +105 -0
- package/dist/esm/repositories/realm/types.js +6 -0
- package/dist/esm/repositories/realm/walletRepository.js +269 -0
- package/dist/esm/repositories/serialization.js +40 -0
- package/dist/esm/repositories/sqlite/contractRepository.js +135 -0
- package/dist/esm/repositories/sqlite/index.js +2 -0
- package/dist/esm/repositories/sqlite/types.js +1 -0
- package/dist/esm/repositories/sqlite/walletRepository.js +324 -0
- package/dist/esm/wallet/vtxo-manager.js +7 -0
- package/dist/types/bip322/index.d.ts +55 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/intent/index.d.ts +13 -0
- package/dist/types/repositories/indexedDB/db.d.ts +2 -54
- package/dist/types/repositories/realm/contractRepository.d.ts +24 -0
- package/dist/types/repositories/realm/index.d.ts +4 -0
- package/dist/types/repositories/realm/schemas.d.ts +208 -0
- package/dist/types/repositories/realm/types.d.ts +16 -0
- package/dist/types/repositories/realm/walletRepository.d.ts +31 -0
- package/dist/types/repositories/serialization.d.ts +40 -0
- package/dist/types/repositories/sqlite/contractRepository.d.ts +33 -0
- package/dist/types/repositories/sqlite/index.d.ts +3 -0
- package/dist/types/repositories/sqlite/types.d.ts +18 -0
- package/dist/types/repositories/sqlite/walletRepository.d.ts +40 -0
- package/package.json +18 -14
- package/dist/cjs/adapters/expo-db.js +0 -35
- package/dist/esm/adapters/expo-db.js +0 -27
- package/dist/types/adapters/expo-db.d.ts +0 -7
- /package/dist/cjs/{db → repositories/indexedDB}/manager.js +0 -0
- /package/dist/esm/{db → repositories/indexedDB}/manager.js +0 -0
- /package/dist/types/{db → repositories/indexedDB}/manager.d.ts +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { p2tr, p2wpkh, Address, OutScript, RawWitness, SigHash, } from "@scure/btc-signer";
|
|
2
|
+
import { concatBytes, hash160, sha256x2, } from "@scure/btc-signer/utils.js";
|
|
3
|
+
import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
|
|
4
|
+
import { equalBytes } from "@noble/curves/utils.js";
|
|
5
|
+
import { base64 } from "@scure/base";
|
|
6
|
+
import { Transaction } from '../utils/transaction.js';
|
|
7
|
+
import { craftToSpendTx, OP_RETURN_EMPTY_PKSCRIPT } from '../intent/index.js';
|
|
8
|
+
const TAG_BIP322 = "BIP0322-signed-message";
|
|
9
|
+
/**
|
|
10
|
+
* BIP-322 simple message signing and verification.
|
|
11
|
+
*
|
|
12
|
+
* Supports P2TR (Taproot) signing and verification, P2WPKH verification,
|
|
13
|
+
* and legacy P2PKH verification (Bitcoin Core signmessage format).
|
|
14
|
+
*
|
|
15
|
+
* Reuses the same toSpend/toSign transaction construction as Intent proofs,
|
|
16
|
+
* but with the standard BIP-322 tagged hash ("BIP0322-signed-message")
|
|
17
|
+
* instead of the Ark-specific tag.
|
|
18
|
+
*
|
|
19
|
+
* @see https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Sign a message (P2TR)
|
|
24
|
+
* const signature = await BIP322.sign("Hello Bitcoin!", identity);
|
|
25
|
+
*
|
|
26
|
+
* // Verify a signature (P2TR or P2WPKH)
|
|
27
|
+
* const valid = BIP322.verify("Hello Bitcoin!", signature, "bc1p...");
|
|
28
|
+
* const valid2 = BIP322.verify("Hello Bitcoin!", signature, "bc1q...");
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export var BIP322;
|
|
32
|
+
(function (BIP322) {
|
|
33
|
+
/**
|
|
34
|
+
* Sign a message using the BIP-322 simple signature scheme.
|
|
35
|
+
*
|
|
36
|
+
* Constructs the standard BIP-322 toSpend and toSign transactions,
|
|
37
|
+
* signs via the Identity interface, and returns the base64-encoded
|
|
38
|
+
* witness stack.
|
|
39
|
+
*
|
|
40
|
+
* @param message - The message to sign
|
|
41
|
+
* @param identity - Identity instance (holds the private key internally)
|
|
42
|
+
* @param network - Optional Bitcoin network for P2TR address derivation
|
|
43
|
+
* @returns Base64-encoded BIP-322 simple signature (witness stack)
|
|
44
|
+
*/
|
|
45
|
+
async function sign(message, identity, network) {
|
|
46
|
+
const xOnlyPubKey = await identity.xOnlyPublicKey();
|
|
47
|
+
const payment = p2tr(xOnlyPubKey, undefined, network);
|
|
48
|
+
// Build BIP-322 toSpend using shared construction with BIP-322 tag
|
|
49
|
+
const toSpend = craftToSpendTx(message, payment.script, TAG_BIP322);
|
|
50
|
+
// Build BIP-322 toSign: version 0, single input spending toSpend, OP_RETURN output
|
|
51
|
+
const toSign = craftBIP322ToSignP2TR(toSpend, payment.script, xOnlyPubKey);
|
|
52
|
+
// Sign with identity (handles P2TR key-spend internally)
|
|
53
|
+
const signed = await identity.sign(toSign, [0]);
|
|
54
|
+
// Finalize and extract witness
|
|
55
|
+
signed.finalizeIdx(0);
|
|
56
|
+
const input = signed.getInput(0);
|
|
57
|
+
if (!input.finalScriptWitness) {
|
|
58
|
+
throw new Error("BIP-322: failed to produce witness after signing");
|
|
59
|
+
}
|
|
60
|
+
return base64.encode(RawWitness.encode(input.finalScriptWitness));
|
|
61
|
+
}
|
|
62
|
+
BIP322.sign = sign;
|
|
63
|
+
/**
|
|
64
|
+
* Verify a BIP-322 signature for a P2TR, P2WPKH, or legacy P2PKH address.
|
|
65
|
+
*
|
|
66
|
+
* For segwit addresses (P2TR, P2WPKH), reconstructs the BIP-322
|
|
67
|
+
* toSpend/toSign transactions and verifies the witness signature.
|
|
68
|
+
*
|
|
69
|
+
* For P2PKH addresses, verifies using the Bitcoin Core legacy
|
|
70
|
+
* `signmessage` format (compact recoverable ECDSA signature).
|
|
71
|
+
*
|
|
72
|
+
* @param message - The original message that was signed
|
|
73
|
+
* @param signature - Base64-encoded signature (BIP-322 witness or legacy compact)
|
|
74
|
+
* @param address - P2TR, P2WPKH, or P2PKH address of the signer
|
|
75
|
+
* @param network - Optional Bitcoin network for address decoding
|
|
76
|
+
* @returns true if the signature is valid
|
|
77
|
+
*/
|
|
78
|
+
function verify(message, signature, address, network) {
|
|
79
|
+
let decoded;
|
|
80
|
+
try {
|
|
81
|
+
decoded = Address(network).decode(address);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
// Legacy P2PKH: signature is base64 of 65 raw bytes, not a witness
|
|
87
|
+
if (decoded.type === "pkh") {
|
|
88
|
+
try {
|
|
89
|
+
return verifyLegacy(message, base64.decode(signature), decoded.hash);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// BIP-322 simple: signature is base64 of RawWitness
|
|
96
|
+
let pkScript;
|
|
97
|
+
let witnessItems;
|
|
98
|
+
try {
|
|
99
|
+
pkScript = OutScript.encode(decoded);
|
|
100
|
+
witnessItems = RawWitness.decode(base64.decode(signature));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (witnessItems.length === 0) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (decoded.type === "tr") {
|
|
109
|
+
return verifyP2TR(message, witnessItems, pkScript, decoded.pubkey);
|
|
110
|
+
}
|
|
111
|
+
if (decoded.type === "wpkh") {
|
|
112
|
+
return verifyP2WPKH(message, witnessItems, pkScript, decoded.hash);
|
|
113
|
+
}
|
|
114
|
+
throw new Error(`BIP-322 verify: unsupported address type '${decoded.type}'`);
|
|
115
|
+
}
|
|
116
|
+
BIP322.verify = verify;
|
|
117
|
+
})(BIP322 || (BIP322 = {}));
|
|
118
|
+
function verifyP2TR(message, witnessItems, pkScript, pubkey) {
|
|
119
|
+
// P2TR key-spend witness is exactly [schnorr_signature].
|
|
120
|
+
// Multiple items indicates a script-path spend, which BIP-322 simple doesn't cover.
|
|
121
|
+
if (witnessItems.length !== 1) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const sig = witnessItems[0];
|
|
125
|
+
if (sig.length !== 64 && sig.length !== 65) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// BIP-322 simple only allows SIGHASH_DEFAULT (64-byte sig) or SIGHASH_ALL (0x01).
|
|
129
|
+
const sighashType = sig.length === 65 ? sig[64] : SigHash.DEFAULT;
|
|
130
|
+
if (sighashType !== SigHash.DEFAULT && sighashType !== SigHash.ALL) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const toSpend = craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
134
|
+
const toSign = craftBIP322ToSignP2TR(toSpend, pkScript, pubkey);
|
|
135
|
+
const sighash = toSign.preimageWitnessV1(0, [pkScript], sighashType, [0n]);
|
|
136
|
+
const rawSig = sig.length === 65 ? sig.subarray(0, 64) : sig;
|
|
137
|
+
return schnorr.verify(rawSig, sighash, pubkey);
|
|
138
|
+
}
|
|
139
|
+
function verifyP2WPKH(message, witnessItems, pkScript, addressHash) {
|
|
140
|
+
// P2WPKH witness: [der_signature || sighash_byte, compressed_pubkey]
|
|
141
|
+
if (witnessItems.length !== 2) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const sigWithHash = witnessItems[0];
|
|
145
|
+
const pubkey = witnessItems[1];
|
|
146
|
+
if (pubkey.length !== 33 || sigWithHash.length < 2) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
// Verify the pubkey matches the address hash
|
|
150
|
+
const derived = p2wpkh(pubkey);
|
|
151
|
+
if (!equalBytes(derived.hash, addressHash)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
// Extract sighash type (last byte) and DER signature
|
|
155
|
+
const sighashType = sigWithHash[sigWithHash.length - 1];
|
|
156
|
+
const derSig = sigWithHash.subarray(0, sigWithHash.length - 1);
|
|
157
|
+
// Build toSpend and toSign
|
|
158
|
+
const toSpend = craftToSpendTx(message, pkScript, TAG_BIP322);
|
|
159
|
+
const toSign = craftBIP322ToSignSimple(toSpend, pkScript);
|
|
160
|
+
// BIP-143 scriptCode for P2WPKH: equivalent P2PKH script
|
|
161
|
+
const scriptCode = OutScript.encode({ type: "pkh", hash: addressHash });
|
|
162
|
+
const sighash = toSign.preimageWitnessV0(0, scriptCode, sighashType, 0n);
|
|
163
|
+
return secp256k1.verify(derSig, sighash, pubkey, {
|
|
164
|
+
prehash: false,
|
|
165
|
+
format: "der",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Verify a legacy Bitcoin Core signmessage signature for a P2PKH address.
|
|
170
|
+
*
|
|
171
|
+
* The signature is 65 bytes: [recovery_flag, r(32), s(32)].
|
|
172
|
+
* The recovery flag encodes both the recovery ID and whether the key is
|
|
173
|
+
* compressed: flag = 27 + recovery_id (+ 4 if compressed).
|
|
174
|
+
*/
|
|
175
|
+
function verifyLegacy(message, sigBytes, addressHash) {
|
|
176
|
+
if (sigBytes.length !== 65) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const flag = sigBytes[0];
|
|
180
|
+
if (flag < 27 || flag > 34) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
const compressed = flag >= 31;
|
|
184
|
+
const recoveryId = compressed ? flag - 31 : flag - 27;
|
|
185
|
+
const compactSig = sigBytes.subarray(1, 65);
|
|
186
|
+
const msgHash = bitcoinMessageHash(message);
|
|
187
|
+
try {
|
|
188
|
+
const sig = secp256k1.Signature.fromBytes(compactSig, "compact").addRecoveryBit(recoveryId);
|
|
189
|
+
const point = sig.recoverPublicKey(msgHash);
|
|
190
|
+
const pubkeyBytes = point.toBytes(compressed);
|
|
191
|
+
return equalBytes(hash160(pubkeyBytes), addressHash);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Compute the Bitcoin message hash: SHA256d(magic_prefix + CompactSize(len) + message).
|
|
199
|
+
*/
|
|
200
|
+
function bitcoinMessageHash(message) {
|
|
201
|
+
const MAGIC = new TextEncoder().encode("\x18Bitcoin Signed Message:\n");
|
|
202
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
203
|
+
return sha256x2(concatBytes(MAGIC, encodeCompactSize(msgBytes.length), msgBytes));
|
|
204
|
+
}
|
|
205
|
+
function encodeCompactSize(n) {
|
|
206
|
+
if (n < 253)
|
|
207
|
+
return new Uint8Array([n]);
|
|
208
|
+
if (n <= 0xffff) {
|
|
209
|
+
const buf = new Uint8Array(3);
|
|
210
|
+
buf[0] = 253;
|
|
211
|
+
buf[1] = n & 0xff;
|
|
212
|
+
buf[2] = (n >> 8) & 0xff;
|
|
213
|
+
return buf;
|
|
214
|
+
}
|
|
215
|
+
const buf = new Uint8Array(5);
|
|
216
|
+
buf[0] = 254;
|
|
217
|
+
buf[1] = n & 0xff;
|
|
218
|
+
buf[2] = (n >> 8) & 0xff;
|
|
219
|
+
buf[3] = (n >> 16) & 0xff;
|
|
220
|
+
buf[4] = (n >> 24) & 0xff;
|
|
221
|
+
return buf;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Build the BIP-322 "toSign" transaction for P2TR key-spend.
|
|
225
|
+
*/
|
|
226
|
+
function craftBIP322ToSignP2TR(toSpend, pkScript, tapInternalKey) {
|
|
227
|
+
const tx = new Transaction({ version: 0 });
|
|
228
|
+
tx.addInput({
|
|
229
|
+
txid: toSpend.id,
|
|
230
|
+
index: 0,
|
|
231
|
+
sequence: 0,
|
|
232
|
+
witnessUtxo: {
|
|
233
|
+
script: pkScript,
|
|
234
|
+
amount: 0n,
|
|
235
|
+
},
|
|
236
|
+
tapInternalKey,
|
|
237
|
+
sighashType: SigHash.DEFAULT,
|
|
238
|
+
});
|
|
239
|
+
tx.addOutput({
|
|
240
|
+
amount: 0n,
|
|
241
|
+
script: OP_RETURN_EMPTY_PKSCRIPT,
|
|
242
|
+
});
|
|
243
|
+
return tx;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Build the BIP-322 "toSign" transaction (generic, no key metadata).
|
|
247
|
+
*
|
|
248
|
+
* Used for P2WPKH verification where the toSign only needs
|
|
249
|
+
* the witnessUtxo, not tapInternalKey.
|
|
250
|
+
*/
|
|
251
|
+
function craftBIP322ToSignSimple(toSpend, pkScript) {
|
|
252
|
+
const tx = new Transaction({ version: 0 });
|
|
253
|
+
tx.addInput({
|
|
254
|
+
txid: toSpend.id,
|
|
255
|
+
index: 0,
|
|
256
|
+
sequence: 0,
|
|
257
|
+
witnessUtxo: {
|
|
258
|
+
script: pkScript,
|
|
259
|
+
amount: 0n,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
tx.addOutput({
|
|
263
|
+
amount: 0n,
|
|
264
|
+
script: OP_RETURN_EMPTY_PKSCRIPT,
|
|
265
|
+
});
|
|
266
|
+
return tx;
|
|
267
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -23,6 +23,7 @@ import { CLTVMultisigTapscript, ConditionCSVMultisigTapscript, ConditionMultisig
|
|
|
23
23
|
import { hasBoardingTxExpired, buildOffchainTx, verifyTapscriptSignatures, combineTapscriptSigs, isValidArkAddress, } from './utils/arkTransaction.js';
|
|
24
24
|
import { VtxoTaprootTree, ConditionWitness, getArkPsbtFields, setArkPsbtField, ArkPsbtFieldKey, ArkPsbtFieldKeyType, CosignerPublicKey, VtxoTreeExpiry, } from './utils/unknownFields.js';
|
|
25
25
|
import { Intent } from './intent/index.js';
|
|
26
|
+
import { BIP322 } from './bip322/index.js';
|
|
26
27
|
import { ArkNote } from './arknote/index.js';
|
|
27
28
|
import { networks } from './networks.js';
|
|
28
29
|
import { RestIndexerProvider, IndexerTxType, ChainTxType, } from './providers/indexer.js';
|
|
@@ -37,7 +38,7 @@ export * from './arkfee/index.js';
|
|
|
37
38
|
export * as asset from './asset/index.js';
|
|
38
39
|
// Contracts
|
|
39
40
|
import { ContractManager, ContractWatcher, contractHandlers, DefaultContractHandler, DelegateContractHandler, VHTLCContractHandler, encodeArkContract, decodeArkContract, contractFromArkContract, contractFromArkContractWithAddress, isArkContract, } from './contracts/index.js';
|
|
40
|
-
import { closeDatabase, openDatabase } from './
|
|
41
|
+
import { closeDatabase, openDatabase } from './repositories/indexedDB/manager.js';
|
|
41
42
|
import { WalletMessageHandler } from './wallet/serviceWorker/wallet-message-handler.js';
|
|
42
43
|
export {
|
|
43
44
|
// Wallets
|
|
@@ -66,6 +67,8 @@ closeDatabase, openDatabase,
|
|
|
66
67
|
IndexedDBWalletRepository, IndexedDBContractRepository, InMemoryWalletRepository, InMemoryContractRepository, MIGRATION_KEY, migrateWalletRepository, requiresMigration, getMigrationStatus, rollbackMigration, WalletRepositoryImpl, ContractRepositoryImpl,
|
|
67
68
|
// Intent proof
|
|
68
69
|
Intent,
|
|
70
|
+
// BIP-322 message signing
|
|
71
|
+
BIP322,
|
|
69
72
|
// TxTree
|
|
70
73
|
TxTree,
|
|
71
74
|
// Anchor
|
package/dist/esm/intent/index.js
CHANGED
|
@@ -103,10 +103,10 @@ export var Intent;
|
|
|
103
103
|
}
|
|
104
104
|
Intent.encodeMessage = encodeMessage;
|
|
105
105
|
})(Intent || (Intent = {}));
|
|
106
|
-
const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([OP.RETURN]);
|
|
106
|
+
export const OP_RETURN_EMPTY_PKSCRIPT = new Uint8Array([OP.RETURN]);
|
|
107
107
|
const ZERO_32 = new Uint8Array(32).fill(0);
|
|
108
108
|
const MAX_INDEX = 0xffffffff;
|
|
109
|
-
const TAG_INTENT_PROOF = "ark-intent-proof-message";
|
|
109
|
+
export const TAG_INTENT_PROOF = "ark-intent-proof-message";
|
|
110
110
|
function validateInput(input) {
|
|
111
111
|
if (input.index === undefined)
|
|
112
112
|
throw new Error("intent proof input requires index");
|
|
@@ -131,9 +131,18 @@ function validateOutputs(outputs) {
|
|
|
131
131
|
outputs.forEach(validateOutput);
|
|
132
132
|
return true;
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Creates the "to_spend" transaction used by both intent proofs and BIP-322.
|
|
136
|
+
*
|
|
137
|
+
* The message is hashed with the given tagged-hash tag before being placed
|
|
138
|
+
* into the scriptSig as `OP_0 <hash>`.
|
|
139
|
+
*
|
|
140
|
+
* @param message - The message to embed
|
|
141
|
+
* @param pkScript - The scriptPubKey of the signer's address
|
|
142
|
+
* @param tag - Tagged-hash tag (defaults to the Ark intent proof tag)
|
|
143
|
+
*/
|
|
144
|
+
export function craftToSpendTx(message, pkScript, tag = TAG_INTENT_PROOF) {
|
|
145
|
+
const messageHash = hashMessage(message, tag);
|
|
137
146
|
const tx = new Transaction({
|
|
138
147
|
version: 0,
|
|
139
148
|
});
|
|
@@ -203,8 +212,8 @@ function craftToSignTx(toSpend, inputs, outputs) {
|
|
|
203
212
|
}
|
|
204
213
|
return tx;
|
|
205
214
|
}
|
|
206
|
-
function hashMessage(message) {
|
|
207
|
-
return schnorr.utils.taggedHash(
|
|
215
|
+
function hashMessage(message, tag = TAG_INTENT_PROOF) {
|
|
216
|
+
return schnorr.utils.taggedHash(tag, new TextEncoder().encode(message));
|
|
208
217
|
}
|
|
209
218
|
function prepareCoinAsIntentProofInput(coin) {
|
|
210
219
|
if (!("tapTree" in coin)) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DB_VERSION, STORE_CONTRACTS } from './db.js';
|
|
2
|
-
import { closeDatabase, openDatabase } from '
|
|
2
|
+
import { closeDatabase, openDatabase } from './manager.js';
|
|
3
3
|
import { initDatabase } from './schema.js';
|
|
4
4
|
import { DEFAULT_DB_NAME } from '../../worker/browser/utils.js';
|
|
5
5
|
/**
|
|
@@ -1,42 +1,4 @@
|
|
|
1
|
-
import { hex } from "@scure/base";
|
|
2
|
-
import { TaprootControlBlock } from "@scure/btc-signer";
|
|
3
1
|
import { DB_VERSION, STORE_CONTRACTS, LEGACY_STORE_CONTRACT_COLLECTIONS, STORE_TRANSACTIONS, STORE_UTXOS, STORE_VTXOS, STORE_WALLET_STATE, } from './schema.js';
|
|
4
2
|
export { STORE_VTXOS, STORE_UTXOS, STORE_TRANSACTIONS, STORE_WALLET_STATE, STORE_CONTRACTS, LEGACY_STORE_CONTRACT_COLLECTIONS, DB_VERSION, };
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
s: hex.encode(s),
|
|
8
|
-
});
|
|
9
|
-
export const serializeVtxo = (v) => ({
|
|
10
|
-
...v,
|
|
11
|
-
tapTree: hex.encode(v.tapTree),
|
|
12
|
-
forfeitTapLeafScript: serializeTapLeaf(v.forfeitTapLeafScript),
|
|
13
|
-
intentTapLeafScript: serializeTapLeaf(v.intentTapLeafScript),
|
|
14
|
-
extraWitness: v.extraWitness?.map(hex.encode),
|
|
15
|
-
});
|
|
16
|
-
export const serializeUtxo = (u) => ({
|
|
17
|
-
...u,
|
|
18
|
-
tapTree: hex.encode(u.tapTree),
|
|
19
|
-
forfeitTapLeafScript: serializeTapLeaf(u.forfeitTapLeafScript),
|
|
20
|
-
intentTapLeafScript: serializeTapLeaf(u.intentTapLeafScript),
|
|
21
|
-
extraWitness: u.extraWitness?.map(hex.encode),
|
|
22
|
-
});
|
|
23
|
-
export const deserializeTapLeaf = (t) => {
|
|
24
|
-
const cb = TaprootControlBlock.decode(hex.decode(t.cb));
|
|
25
|
-
const s = hex.decode(t.s);
|
|
26
|
-
return [cb, s];
|
|
27
|
-
};
|
|
28
|
-
export const deserializeVtxo = (o) => ({
|
|
29
|
-
...o,
|
|
30
|
-
createdAt: new Date(o.createdAt),
|
|
31
|
-
tapTree: hex.decode(o.tapTree),
|
|
32
|
-
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
33
|
-
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
34
|
-
extraWitness: o.extraWitness?.map(hex.decode),
|
|
35
|
-
});
|
|
36
|
-
export const deserializeUtxo = (o) => ({
|
|
37
|
-
...o,
|
|
38
|
-
tapTree: hex.decode(o.tapTree),
|
|
39
|
-
forfeitTapLeafScript: deserializeTapLeaf(o.forfeitTapLeafScript),
|
|
40
|
-
intentTapLeafScript: deserializeTapLeaf(o.intentTapLeafScript),
|
|
41
|
-
extraWitness: o.extraWitness?.map(hex.decode),
|
|
42
|
-
});
|
|
3
|
+
// Serialization helpers (re-exported from shared module)
|
|
4
|
+
export { serializeTapLeaf, serializeVtxo, serializeUtxo, deserializeTapLeaf, deserializeVtxo, deserializeUtxo, } from '../serialization.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STORE_VTXOS, STORE_UTXOS, STORE_TRANSACTIONS, STORE_WALLET_STATE, serializeVtxo, serializeUtxo, deserializeVtxo, deserializeUtxo, DB_VERSION, } from './db.js';
|
|
2
|
-
import { closeDatabase, openDatabase } from '
|
|
2
|
+
import { closeDatabase, openDatabase } from './manager.js';
|
|
3
3
|
import { initDatabase } from './schema.js';
|
|
4
4
|
import { DEFAULT_DB_NAME } from '../../worker/browser/utils.js';
|
|
5
5
|
/**
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realm-based implementation of ContractRepository.
|
|
3
|
+
*
|
|
4
|
+
* Consumers must open Realm with the schemas from `./schemas.ts` and pass
|
|
5
|
+
* the instance to the constructor.
|
|
6
|
+
*
|
|
7
|
+
* Realm handles schema creation on open, so `ensureInit()` is a no-op.
|
|
8
|
+
* The consumer owns the Realm lifecycle — `[Symbol.asyncDispose]` is a no-op.
|
|
9
|
+
*/
|
|
10
|
+
export class RealmContractRepository {
|
|
11
|
+
constructor(realm) {
|
|
12
|
+
this.realm = realm;
|
|
13
|
+
this.version = 1;
|
|
14
|
+
}
|
|
15
|
+
// ── Lifecycle ──────────────────────────────────────────────────────
|
|
16
|
+
async ensureInit() {
|
|
17
|
+
// Realm handles schema on open — nothing to initialise.
|
|
18
|
+
}
|
|
19
|
+
async [Symbol.asyncDispose]() {
|
|
20
|
+
// no-op — consumer owns the Realm lifecycle
|
|
21
|
+
}
|
|
22
|
+
// ── Clear ──────────────────────────────────────────────────────────
|
|
23
|
+
async clear() {
|
|
24
|
+
await this.ensureInit();
|
|
25
|
+
this.realm.write(() => {
|
|
26
|
+
this.realm.delete(this.realm.objects("ArkContract"));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// ── Contract management ────────────────────────────────────────────
|
|
30
|
+
async getContracts(filter) {
|
|
31
|
+
await this.ensureInit();
|
|
32
|
+
let results = this.realm.objects("ArkContract");
|
|
33
|
+
if (filter) {
|
|
34
|
+
const filterParts = [];
|
|
35
|
+
const filterArgs = [];
|
|
36
|
+
let argIndex = 0;
|
|
37
|
+
argIndex = this.addFilterCondition(filterParts, filterArgs, "script", filter.script, argIndex);
|
|
38
|
+
argIndex = this.addFilterCondition(filterParts, filterArgs, "state", filter.state, argIndex);
|
|
39
|
+
argIndex = this.addFilterCondition(filterParts, filterArgs, "type", filter.type, argIndex);
|
|
40
|
+
if (filterParts.length > 0) {
|
|
41
|
+
const query = filterParts.join(" AND ");
|
|
42
|
+
results = results.filtered(query, ...filterArgs);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return [...results].map(contractObjectToDomain);
|
|
46
|
+
}
|
|
47
|
+
async saveContract(contract) {
|
|
48
|
+
await this.ensureInit();
|
|
49
|
+
this.realm.write(() => {
|
|
50
|
+
this.realm.create("ArkContract", {
|
|
51
|
+
script: contract.script,
|
|
52
|
+
address: contract.address,
|
|
53
|
+
type: contract.type,
|
|
54
|
+
state: contract.state,
|
|
55
|
+
paramsJson: JSON.stringify(contract.params),
|
|
56
|
+
createdAt: contract.createdAt,
|
|
57
|
+
expiresAt: contract.expiresAt ?? null,
|
|
58
|
+
label: contract.label ?? null,
|
|
59
|
+
metadataJson: contract.metadata
|
|
60
|
+
? JSON.stringify(contract.metadata)
|
|
61
|
+
: null,
|
|
62
|
+
}, "modified");
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async deleteContract(script) {
|
|
66
|
+
await this.ensureInit();
|
|
67
|
+
this.realm.write(() => {
|
|
68
|
+
const toDelete = this.realm
|
|
69
|
+
.objects("ArkContract")
|
|
70
|
+
.filtered("script == $0", script);
|
|
71
|
+
this.realm.delete(toDelete);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
75
|
+
addFilterCondition(parts, args, column, value, argIndex) {
|
|
76
|
+
if (value === undefined)
|
|
77
|
+
return argIndex;
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
if (value.length === 0)
|
|
80
|
+
return argIndex;
|
|
81
|
+
const conditions = value.map((_, i) => {
|
|
82
|
+
return `${column} == $${argIndex + i}`;
|
|
83
|
+
});
|
|
84
|
+
parts.push(`(${conditions.join(" OR ")})`);
|
|
85
|
+
args.push(...value);
|
|
86
|
+
return argIndex + value.length;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
parts.push(`${column} == $${argIndex}`);
|
|
90
|
+
args.push(value);
|
|
91
|
+
return argIndex + 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ── Realm object → Domain converter ──────────────────────────────────────
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
function contractObjectToDomain(obj) {
|
|
98
|
+
const contract = {
|
|
99
|
+
script: obj.script,
|
|
100
|
+
address: obj.address,
|
|
101
|
+
type: obj.type,
|
|
102
|
+
state: obj.state,
|
|
103
|
+
params: JSON.parse(obj.paramsJson),
|
|
104
|
+
createdAt: obj.createdAt,
|
|
105
|
+
};
|
|
106
|
+
if (obj.expiresAt !== null && obj.expiresAt !== undefined) {
|
|
107
|
+
contract.expiresAt = obj.expiresAt;
|
|
108
|
+
}
|
|
109
|
+
if (obj.label !== null && obj.label !== undefined) {
|
|
110
|
+
contract.label = obj.label;
|
|
111
|
+
}
|
|
112
|
+
if (obj.metadataJson !== null && obj.metadataJson !== undefined) {
|
|
113
|
+
contract.metadata = JSON.parse(obj.metadataJson);
|
|
114
|
+
}
|
|
115
|
+
return contract;
|
|
116
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Realm object schemas for the Ark wallet.
|
|
3
|
+
*
|
|
4
|
+
* All schema names are prefixed with "Ark" to avoid collisions with
|
|
5
|
+
* other Realm schemas in the consuming application.
|
|
6
|
+
*
|
|
7
|
+
* Since `realm` is a peer dependency (not installed in this package),
|
|
8
|
+
* schemas are defined as plain JS objects conforming to Realm's
|
|
9
|
+
* ObjectSchema shape.
|
|
10
|
+
*/
|
|
11
|
+
export const ArkVtxoSchema = {
|
|
12
|
+
name: "ArkVtxo",
|
|
13
|
+
primaryKey: "pk",
|
|
14
|
+
properties: {
|
|
15
|
+
pk: "string", // composite: `${txid}:${vout}`
|
|
16
|
+
address: { type: "string", indexed: true },
|
|
17
|
+
txid: "string",
|
|
18
|
+
vout: "int",
|
|
19
|
+
value: "int",
|
|
20
|
+
tapTree: "string", // hex-encoded
|
|
21
|
+
forfeitCb: "string",
|
|
22
|
+
forfeitS: "string",
|
|
23
|
+
intentCb: "string",
|
|
24
|
+
intentS: "string",
|
|
25
|
+
extraWitnessJson: "string?",
|
|
26
|
+
statusJson: "string",
|
|
27
|
+
virtualStatusJson: "string",
|
|
28
|
+
spentBy: "string?",
|
|
29
|
+
settledBy: "string?",
|
|
30
|
+
arkTxId: "string?",
|
|
31
|
+
createdAt: "string", // ISO 8601
|
|
32
|
+
isUnrolled: "bool",
|
|
33
|
+
isSpent: "bool?",
|
|
34
|
+
assetsJson: "string?",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export const ArkUtxoSchema = {
|
|
38
|
+
name: "ArkUtxo",
|
|
39
|
+
primaryKey: "pk",
|
|
40
|
+
properties: {
|
|
41
|
+
pk: "string", // composite: `${txid}:${vout}`
|
|
42
|
+
address: { type: "string", indexed: true },
|
|
43
|
+
txid: "string",
|
|
44
|
+
vout: "int",
|
|
45
|
+
value: "int",
|
|
46
|
+
tapTree: "string", // hex-encoded
|
|
47
|
+
forfeitCb: "string",
|
|
48
|
+
forfeitS: "string",
|
|
49
|
+
intentCb: "string",
|
|
50
|
+
intentS: "string",
|
|
51
|
+
extraWitnessJson: "string?",
|
|
52
|
+
statusJson: "string",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
export const ArkTransactionSchema = {
|
|
56
|
+
name: "ArkTransaction",
|
|
57
|
+
primaryKey: "pk",
|
|
58
|
+
properties: {
|
|
59
|
+
pk: "string", // composite: `${address}:${boardingTxid}:${commitmentTxid}:${arkTxid}`
|
|
60
|
+
address: { type: "string", indexed: true },
|
|
61
|
+
boardingTxid: "string",
|
|
62
|
+
commitmentTxid: "string",
|
|
63
|
+
arkTxid: "string",
|
|
64
|
+
type: "string",
|
|
65
|
+
amount: "int",
|
|
66
|
+
settled: "bool",
|
|
67
|
+
createdAt: "int",
|
|
68
|
+
assetsJson: "string?",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
export const ArkWalletStateSchema = {
|
|
72
|
+
name: "ArkWalletState",
|
|
73
|
+
primaryKey: "key",
|
|
74
|
+
properties: {
|
|
75
|
+
key: "string",
|
|
76
|
+
lastSyncTime: "int?",
|
|
77
|
+
settingsJson: "string?",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
export const ArkContractSchema = {
|
|
81
|
+
name: "ArkContract",
|
|
82
|
+
primaryKey: "script",
|
|
83
|
+
properties: {
|
|
84
|
+
script: "string",
|
|
85
|
+
address: "string",
|
|
86
|
+
type: { type: "string", indexed: true },
|
|
87
|
+
state: { type: "string", indexed: true },
|
|
88
|
+
paramsJson: "string",
|
|
89
|
+
createdAt: "int",
|
|
90
|
+
expiresAt: "int?",
|
|
91
|
+
label: "string?",
|
|
92
|
+
metadataJson: "string?",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* All Realm schemas needed by the Ark wallet repositories.
|
|
97
|
+
* Pass this array to your Realm configuration's `schema` property.
|
|
98
|
+
*/
|
|
99
|
+
export const ArkRealmSchemas = [
|
|
100
|
+
ArkVtxoSchema,
|
|
101
|
+
ArkUtxoSchema,
|
|
102
|
+
ArkTransactionSchema,
|
|
103
|
+
ArkWalletStateSchema,
|
|
104
|
+
ArkContractSchema,
|
|
105
|
+
];
|