@utxopia/sdk 0.1.0-alpha.2 → 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.
Files changed (142) hide show
  1. package/README.md +214 -108
  2. package/{packages/sdk/dist → dist}/client.d.ts +25 -1
  3. package/{packages/sdk/dist → dist}/client.js +36 -1
  4. package/{packages/sdk/dist → dist}/crypto-ed25519.d.ts +14 -0
  5. package/{packages/sdk/dist → dist}/crypto-ed25519.js +14 -0
  6. package/{packages/sdk/dist → dist}/index.d.ts +3 -3
  7. package/{packages/sdk/dist → dist}/index.js +3 -3
  8. package/{packages/sdk/dist → dist}/instructions.d.ts +28 -11
  9. package/{packages/sdk/dist → dist}/instructions.js +45 -15
  10. package/{packages/sdk/dist → dist}/psbt.d.ts +12 -2
  11. package/{packages/sdk/dist → dist}/psbt.js +17 -12
  12. package/{packages/sdk/dist → dist}/stealth.d.ts +104 -9
  13. package/{packages/sdk/dist → dist}/stealth.js +146 -14
  14. package/{packages/sdk/dist → dist}/taproot.d.ts +39 -2
  15. package/{packages/sdk/dist → dist}/taproot.js +54 -2
  16. package/package.json +86 -63
  17. package/src/announcement-client.ts +457 -0
  18. package/src/auditor-ciphertext.ts +181 -0
  19. package/src/auditor.ts +409 -0
  20. package/src/bitcoin/ika.ts +103 -0
  21. package/src/bitcoin/index.ts +5 -0
  22. package/src/bound-params.ts +322 -0
  23. package/src/chadbuffer.ts +603 -0
  24. package/src/circomlibjs.d.ts +51 -0
  25. package/src/claim-link.ts +53 -0
  26. package/src/client.ts +638 -0
  27. package/src/commitment-tree.ts +736 -0
  28. package/src/config.ts +772 -0
  29. package/src/core/esplora.ts +332 -0
  30. package/src/core/mempool.ts +159 -0
  31. package/src/crypto-babyjub.ts +385 -0
  32. package/src/crypto-ed25519.ts +297 -0
  33. package/src/crypto.ts +199 -0
  34. package/src/event-client.ts +231 -0
  35. package/src/events.ts +384 -0
  36. package/src/explorer.ts +300 -0
  37. package/src/index.ts +902 -0
  38. package/src/instructions.ts +2820 -0
  39. package/src/keys.ts +1228 -0
  40. package/src/logger.ts +41 -0
  41. package/src/magicblock.ts +278 -0
  42. package/src/merkle.ts +197 -0
  43. package/src/note.ts +754 -0
  44. package/src/pda.ts +516 -0
  45. package/src/pool-state.ts +176 -0
  46. package/src/poseidon.ts +175 -0
  47. package/src/prover/index.ts +19 -0
  48. package/src/prover/mobile.ts +303 -0
  49. package/src/prover/web.ts +771 -0
  50. package/src/psbt.ts +345 -0
  51. package/src/selective-disclosure.ts +284 -0
  52. package/src/sender-memo.ts +343 -0
  53. package/src/snarkjs.d.ts +19 -0
  54. package/src/sns-resolver.ts +333 -0
  55. package/src/solana/connection.ts +189 -0
  56. package/src/solana/priority-fee.ts +201 -0
  57. package/src/spend-doc.ts +163 -0
  58. package/src/stealth.ts +1477 -0
  59. package/src/taproot.ts +707 -0
  60. package/src/token-registry.ts +207 -0
  61. package/src/utils/encoding.ts +33 -0
  62. package/src/vk-registry.ts +295 -0
  63. package/LICENSE +0 -21
  64. package/packages/btc-client/src/esplora-client.ts +0 -153
  65. package/packages/btc-client/src/index.ts +0 -3
  66. package/packages/btc-client/src/op-return.ts +0 -93
  67. package/packages/btc-client/src/types.ts +0 -112
  68. package/packages/sdk/README.md +0 -277
  69. /package/{packages/sdk/dist → dist}/announcement-client.d.ts +0 -0
  70. /package/{packages/sdk/dist → dist}/announcement-client.js +0 -0
  71. /package/{packages/sdk/dist → dist}/auditor-ciphertext.d.ts +0 -0
  72. /package/{packages/sdk/dist → dist}/auditor-ciphertext.js +0 -0
  73. /package/{packages/sdk/dist → dist}/auditor.d.ts +0 -0
  74. /package/{packages/sdk/dist → dist}/auditor.js +0 -0
  75. /package/{packages/sdk/dist → dist}/bitcoin/ika.d.ts +0 -0
  76. /package/{packages/sdk/dist → dist}/bitcoin/ika.js +0 -0
  77. /package/{packages/sdk/dist → dist}/bitcoin/index.d.ts +0 -0
  78. /package/{packages/sdk/dist → dist}/bitcoin/index.js +0 -0
  79. /package/{packages/sdk/dist → dist}/bound-params.d.ts +0 -0
  80. /package/{packages/sdk/dist → dist}/bound-params.js +0 -0
  81. /package/{packages/sdk/dist → dist}/chadbuffer.d.ts +0 -0
  82. /package/{packages/sdk/dist → dist}/chadbuffer.js +0 -0
  83. /package/{packages/sdk/dist → dist}/claim-link.d.ts +0 -0
  84. /package/{packages/sdk/dist → dist}/claim-link.js +0 -0
  85. /package/{packages/sdk/dist → dist}/commitment-tree.d.ts +0 -0
  86. /package/{packages/sdk/dist → dist}/commitment-tree.js +0 -0
  87. /package/{packages/sdk/dist → dist}/config.d.ts +0 -0
  88. /package/{packages/sdk/dist → dist}/config.js +0 -0
  89. /package/{packages/sdk/dist → dist}/core/esplora.d.ts +0 -0
  90. /package/{packages/sdk/dist → dist}/core/esplora.js +0 -0
  91. /package/{packages/sdk/dist → dist}/core/mempool.d.ts +0 -0
  92. /package/{packages/sdk/dist → dist}/core/mempool.js +0 -0
  93. /package/{packages/sdk/dist → dist}/crypto-babyjub.d.ts +0 -0
  94. /package/{packages/sdk/dist → dist}/crypto-babyjub.js +0 -0
  95. /package/{packages/sdk/dist → dist}/crypto.d.ts +0 -0
  96. /package/{packages/sdk/dist → dist}/crypto.js +0 -0
  97. /package/{packages/sdk/dist → dist}/event-client.d.ts +0 -0
  98. /package/{packages/sdk/dist → dist}/event-client.js +0 -0
  99. /package/{packages/sdk/dist → dist}/events.d.ts +0 -0
  100. /package/{packages/sdk/dist → dist}/events.js +0 -0
  101. /package/{packages/sdk/dist → dist}/explorer.d.ts +0 -0
  102. /package/{packages/sdk/dist → dist}/explorer.js +0 -0
  103. /package/{packages/sdk/dist → dist}/keys.d.ts +0 -0
  104. /package/{packages/sdk/dist → dist}/keys.js +0 -0
  105. /package/{packages/sdk/dist → dist}/logger.d.ts +0 -0
  106. /package/{packages/sdk/dist → dist}/logger.js +0 -0
  107. /package/{packages/sdk/dist → dist}/magicblock.d.ts +0 -0
  108. /package/{packages/sdk/dist → dist}/magicblock.js +0 -0
  109. /package/{packages/sdk/dist → dist}/merkle.d.ts +0 -0
  110. /package/{packages/sdk/dist → dist}/merkle.js +0 -0
  111. /package/{packages/sdk/dist → dist}/note.d.ts +0 -0
  112. /package/{packages/sdk/dist → dist}/note.js +0 -0
  113. /package/{packages/sdk/dist → dist}/pda.d.ts +0 -0
  114. /package/{packages/sdk/dist → dist}/pda.js +0 -0
  115. /package/{packages/sdk/dist → dist}/pool-state.d.ts +0 -0
  116. /package/{packages/sdk/dist → dist}/pool-state.js +0 -0
  117. /package/{packages/sdk/dist → dist}/poseidon.d.ts +0 -0
  118. /package/{packages/sdk/dist → dist}/poseidon.js +0 -0
  119. /package/{packages/sdk/dist → dist}/prover/index.d.ts +0 -0
  120. /package/{packages/sdk/dist → dist}/prover/index.js +0 -0
  121. /package/{packages/sdk/dist → dist}/prover/mobile.d.ts +0 -0
  122. /package/{packages/sdk/dist → dist}/prover/mobile.js +0 -0
  123. /package/{packages/sdk/dist → dist}/prover/web.d.ts +0 -0
  124. /package/{packages/sdk/dist → dist}/prover/web.js +0 -0
  125. /package/{packages/sdk/dist → dist}/selective-disclosure.d.ts +0 -0
  126. /package/{packages/sdk/dist → dist}/selective-disclosure.js +0 -0
  127. /package/{packages/sdk/dist → dist}/sender-memo.d.ts +0 -0
  128. /package/{packages/sdk/dist → dist}/sender-memo.js +0 -0
  129. /package/{packages/sdk/dist → dist}/sns-resolver.d.ts +0 -0
  130. /package/{packages/sdk/dist → dist}/sns-resolver.js +0 -0
  131. /package/{packages/sdk/dist → dist}/solana/connection.d.ts +0 -0
  132. /package/{packages/sdk/dist → dist}/solana/connection.js +0 -0
  133. /package/{packages/sdk/dist → dist}/solana/priority-fee.d.ts +0 -0
  134. /package/{packages/sdk/dist → dist}/solana/priority-fee.js +0 -0
  135. /package/{packages/sdk/dist → dist}/spend-doc.d.ts +0 -0
  136. /package/{packages/sdk/dist → dist}/spend-doc.js +0 -0
  137. /package/{packages/sdk/dist → dist}/token-registry.d.ts +0 -0
  138. /package/{packages/sdk/dist → dist}/token-registry.js +0 -0
  139. /package/{packages/sdk/dist → dist}/utils/encoding.d.ts +0 -0
  140. /package/{packages/sdk/dist → dist}/utils/encoding.js +0 -0
  141. /package/{packages/sdk/dist → dist}/vk-registry.d.ts +0 -0
  142. /package/{packages/sdk/dist → dist}/vk-registry.js +0 -0
@@ -1,153 +0,0 @@
1
- import type {
2
- BitcoinAddressInfo,
3
- BitcoinClient,
4
- BitcoinDepositCandidate,
5
- BitcoinMerkleProof,
6
- BitcoinNetwork,
7
- BitcoinOutspend,
8
- BitcoinTransaction,
9
- BitcoinTxStatus,
10
- BitcoinUtxo,
11
- } from "./types";
12
- import { extractUtxopiaDepositOpReturn } from "./op-return";
13
-
14
- const NETWORK_URLS: Record<BitcoinNetwork, string> = {
15
- mainnet: "https://mempool.space/api",
16
- testnet: "https://mempool.space/testnet/api",
17
- testnet4: "https://mempool.space/testnet4/api",
18
- signet: "https://mempool.space/signet/api",
19
- regtest: "http://localhost:2140",
20
- };
21
-
22
- export interface EsploraClientOptions {
23
- network?: BitcoinNetwork;
24
- baseUrl?: string;
25
- fetchImpl?: typeof fetch;
26
- }
27
-
28
- export class EsploraBitcoinClient implements BitcoinClient {
29
- readonly network: BitcoinNetwork;
30
- private readonly baseUrl: string;
31
- private readonly fetchImpl: typeof fetch;
32
-
33
- constructor(options: EsploraClientOptions = {}) {
34
- this.network = options.network ?? "testnet";
35
- this.baseUrl = (options.baseUrl ?? NETWORK_URLS[this.network]).replace(/\/$/, "");
36
- this.fetchImpl = options.fetchImpl ?? fetch;
37
- }
38
-
39
- async getAddress(address: string): Promise<BitcoinAddressInfo> {
40
- return this.getJson(`/address/${address}`);
41
- }
42
-
43
- async getAddressTxs(address: string, lastSeenTxid?: string): Promise<BitcoinTransaction[]> {
44
- return this.getJson(lastSeenTxid ? `/address/${address}/txs/chain/${lastSeenTxid}` : `/address/${address}/txs`);
45
- }
46
-
47
- async getAddressTxsMempool(address: string): Promise<BitcoinTransaction[]> {
48
- return this.getJson(`/address/${address}/txs/mempool`);
49
- }
50
-
51
- async getAddressUtxos(address: string): Promise<BitcoinUtxo[]> {
52
- return this.getJson(`/address/${address}/utxo`);
53
- }
54
-
55
- async getTransaction(txid: string): Promise<BitcoinTransaction> {
56
- return this.getJson(`/tx/${txid}`);
57
- }
58
-
59
- async getTxStatus(txid: string): Promise<BitcoinTxStatus> {
60
- return this.getJson(`/tx/${txid}/status`);
61
- }
62
-
63
- async getTxHex(txid: string): Promise<string> {
64
- return this.getText(`/tx/${txid}/hex`);
65
- }
66
-
67
- async getTxMerkleProof(txid: string): Promise<BitcoinMerkleProof> {
68
- return this.getJson(`/tx/${txid}/merkle-proof`);
69
- }
70
-
71
- async getTxOutspend(txid: string, vout: number): Promise<BitcoinOutspend> {
72
- return this.getJson(`/tx/${txid}/outspend/${vout}`);
73
- }
74
-
75
- async getBlockHeight(): Promise<number> {
76
- return Number.parseInt(await this.getText("/blocks/tip/height"), 10);
77
- }
78
-
79
- async getBlockHash(height: number): Promise<string> {
80
- return this.getText(`/block-height/${height}`);
81
- }
82
-
83
- async getBlockHeader(hash: string): Promise<string> {
84
- return this.getText(`/block/${hash}/header`);
85
- }
86
-
87
- async broadcastTx(txHex: string): Promise<string> {
88
- const res = await this.fetchImpl(`${this.baseUrl}/tx`, {
89
- method: "POST",
90
- headers: { "content-type": "text/plain" },
91
- body: txHex,
92
- });
93
- if (!res.ok) {
94
- throw new Error(`Esplora broadcast failed: ${res.status} ${res.statusText}: ${await res.text()}`);
95
- }
96
- return res.text();
97
- }
98
-
99
- async getConfirmations(txid: string): Promise<number> {
100
- const status = await this.getTxStatus(txid);
101
- if (!status.confirmed || status.block_height === undefined) {
102
- return 0;
103
- }
104
-
105
- const tipHeight = await this.getBlockHeight();
106
- return tipHeight - status.block_height + 1;
107
- }
108
-
109
- async findDepositCandidates(address: string, minConfirmations = 1): Promise<BitcoinDepositCandidate[]> {
110
- const [utxos, tipHeight, txs] = await Promise.all([
111
- this.getAddressUtxos(address),
112
- this.getBlockHeight(),
113
- this.getAddressTxs(address),
114
- ]);
115
- const txById = new Map(txs.map((tx) => [tx.txid, tx]));
116
-
117
- return utxos
118
- .map((utxo) => ({
119
- address,
120
- txid: utxo.txid,
121
- vout: utxo.vout,
122
- value: utxo.value,
123
- confirmations: confirmationsForStatus(utxo.status, tipHeight),
124
- status: utxo.status,
125
- opReturn: extractUtxopiaDepositOpReturn(txById.get(utxo.txid)),
126
- }))
127
- .filter((candidate) => candidate.confirmations >= minConfirmations);
128
- }
129
-
130
- private async getJson<T>(endpoint: string): Promise<T> {
131
- const res = await this.fetchImpl(`${this.baseUrl}${endpoint}`);
132
- if (!res.ok) {
133
- throw new Error(`Esplora API failed: ${res.status} ${res.statusText}`);
134
- }
135
- return res.json() as Promise<T>;
136
- }
137
-
138
- private async getText(endpoint: string): Promise<string> {
139
- const res = await this.fetchImpl(`${this.baseUrl}${endpoint}`);
140
- if (!res.ok) {
141
- throw new Error(`Esplora API failed: ${res.status} ${res.statusText}`);
142
- }
143
- return res.text();
144
- }
145
- }
146
-
147
- function confirmationsForStatus(status: BitcoinTxStatus, tipHeight: number): number {
148
- if (!status.confirmed || status.block_height === undefined) {
149
- return 0;
150
- }
151
-
152
- return tipHeight - status.block_height + 1;
153
- }
@@ -1,3 +0,0 @@
1
- export * from "./esplora-client";
2
- export * from "./op-return";
3
- export * from "./types";
@@ -1,93 +0,0 @@
1
- import type { BitcoinTransaction, UtxopiaDepositOpReturn } from "./types";
2
-
3
- export const UTXOPIA_DEPOSIT_OP_RETURN_SIZE = 73;
4
- export const UTXOPIA_DEPOSIT_OP_RETURN_VERSION = 1;
5
- export const DESTINATION_CHAIN_SOLANA = 1;
6
- export const BITCOIN_NETWORK_MAINNET = 0;
7
- export const BITCOIN_NETWORK_TESTNET4 = 2;
8
- export const BITCOIN_NETWORK_REGTEST = 3;
9
-
10
- export function extractUtxopiaDepositOpReturn(
11
- tx: BitcoinTransaction | undefined,
12
- ): UtxopiaDepositOpReturn | undefined {
13
- if (!tx) {
14
- return undefined;
15
- }
16
-
17
- for (const output of tx.vout) {
18
- const payload = parseOpReturnPayload(output.scriptpubkey);
19
- if (payload?.length !== UTXOPIA_DEPOSIT_OP_RETURN_SIZE) {
20
- continue;
21
- }
22
-
23
- const header = decodeDepositHeader(payload[0]);
24
- if (!header) {
25
- continue;
26
- }
27
-
28
- const ephemeralPubkey = payload.slice(9, 41);
29
- const notePublicKey = payload.slice(41, 73);
30
- return {
31
- ...header,
32
- poolTag: payload.slice(1, 9),
33
- ephemeralPubkey,
34
- notePublicKey,
35
- rawPayload: payload,
36
- };
37
- }
38
-
39
- return undefined;
40
- }
41
-
42
- export function decodeDepositHeader(header: number): {
43
- version: number;
44
- destinationChain: number;
45
- bitcoinNetwork: number;
46
- } | undefined {
47
- const version = header >> 6;
48
- const destinationChain = (header >> 4) & 0x03;
49
- const bitcoinNetwork = header & 0x0f;
50
- if (version !== UTXOPIA_DEPOSIT_OP_RETURN_VERSION) return undefined;
51
- if (destinationChain !== DESTINATION_CHAIN_SOLANA) return undefined;
52
- if (
53
- bitcoinNetwork !== BITCOIN_NETWORK_MAINNET
54
- && bitcoinNetwork !== BITCOIN_NETWORK_TESTNET4
55
- && bitcoinNetwork !== BITCOIN_NETWORK_REGTEST
56
- ) {
57
- return undefined;
58
- }
59
- return { version, destinationChain, bitcoinNetwork };
60
- }
61
-
62
- export function parseOpReturnPayload(scriptPubkeyHex: string): Uint8Array | undefined {
63
- const script = hexToBytes(scriptPubkeyHex);
64
- if (script.length < 2 || script[0] !== 0x6a) {
65
- return undefined;
66
- }
67
-
68
- const pushOpcode = script[1];
69
- if (pushOpcode > 0 && pushOpcode <= 75) {
70
- const end = 2 + pushOpcode;
71
- return end === script.length ? script.slice(2, end) : undefined;
72
- }
73
-
74
- if (pushOpcode === 0x4c && script.length >= 3) {
75
- const len = script[2];
76
- const end = 3 + len;
77
- return end === script.length ? script.slice(3, end) : undefined;
78
- }
79
-
80
- return undefined;
81
- }
82
-
83
- export function hexToBytes(hex: string): Uint8Array {
84
- if (hex.length % 2 !== 0) {
85
- throw new Error("Invalid hex string length");
86
- }
87
-
88
- const bytes = new Uint8Array(hex.length / 2);
89
- for (let i = 0; i < bytes.length; i += 1) {
90
- bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
91
- }
92
- return bytes;
93
- }
@@ -1,112 +0,0 @@
1
- export type BitcoinNetwork = "mainnet" | "testnet" | "testnet4" | "signet" | "regtest";
2
-
3
- export interface BitcoinTxStatus {
4
- confirmed: boolean;
5
- block_height?: number;
6
- block_hash?: string;
7
- block_time?: number;
8
- }
9
-
10
- export interface BitcoinTxInput {
11
- txid: string;
12
- vout: number;
13
- prevout: BitcoinTxOutput | null;
14
- scriptsig: string;
15
- scriptsig_asm: string;
16
- witness?: string[];
17
- is_coinbase: boolean;
18
- sequence: number;
19
- }
20
-
21
- export interface BitcoinTxOutput {
22
- scriptpubkey: string;
23
- scriptpubkey_asm: string;
24
- scriptpubkey_type: string;
25
- scriptpubkey_address?: string;
26
- value: number;
27
- }
28
-
29
- export interface BitcoinTransaction {
30
- txid: string;
31
- version: number;
32
- locktime: number;
33
- vin: BitcoinTxInput[];
34
- vout: BitcoinTxOutput[];
35
- size: number;
36
- weight: number;
37
- fee: number;
38
- status: BitcoinTxStatus;
39
- }
40
-
41
- export interface BitcoinAddressInfo {
42
- address: string;
43
- chain_stats: {
44
- funded_txo_count: number;
45
- funded_txo_sum: number;
46
- spent_txo_count: number;
47
- spent_txo_sum: number;
48
- tx_count: number;
49
- };
50
- mempool_stats: {
51
- funded_txo_count: number;
52
- funded_txo_sum: number;
53
- spent_txo_count: number;
54
- spent_txo_sum: number;
55
- tx_count: number;
56
- };
57
- }
58
-
59
- export interface BitcoinUtxo {
60
- txid: string;
61
- vout: number;
62
- status: BitcoinTxStatus;
63
- value: number;
64
- }
65
-
66
- export interface BitcoinMerkleProof {
67
- block_height: number;
68
- merkle: string[];
69
- pos: number;
70
- }
71
-
72
- export interface BitcoinOutspend {
73
- spent: boolean;
74
- txid?: string;
75
- vin?: number;
76
- status?: BitcoinTxStatus;
77
- }
78
-
79
- export interface BitcoinDepositCandidate {
80
- address: string;
81
- txid: string;
82
- vout: number;
83
- value: number;
84
- confirmations: number;
85
- status: BitcoinTxStatus;
86
- opReturn?: UtxopiaDepositOpReturn;
87
- }
88
-
89
- export interface UtxopiaDepositOpReturn {
90
- version: number;
91
- destinationChain: number;
92
- bitcoinNetwork: number;
93
- poolTag: Uint8Array;
94
- ephemeralPubkey: Uint8Array;
95
- notePublicKey: Uint8Array;
96
- rawPayload: Uint8Array;
97
- }
98
-
99
- export interface BitcoinClient {
100
- getAddress(address: string): Promise<BitcoinAddressInfo>;
101
- getAddressTxs(address: string, lastSeenTxid?: string): Promise<BitcoinTransaction[]>;
102
- getAddressUtxos(address: string): Promise<BitcoinUtxo[]>;
103
- getTransaction(txid: string): Promise<BitcoinTransaction>;
104
- getTxStatus(txid: string): Promise<BitcoinTxStatus>;
105
- getTxHex(txid: string): Promise<string>;
106
- getTxMerkleProof(txid: string): Promise<BitcoinMerkleProof>;
107
- getTxOutspend(txid: string, vout: number): Promise<BitcoinOutspend>;
108
- getBlockHeight(): Promise<number>;
109
- getBlockHash(height: number): Promise<string>;
110
- getBlockHeader(hash: string): Promise<string>;
111
- broadcastTx(txHex: string): Promise<string>;
112
- }
@@ -1,277 +0,0 @@
1
- # @utxopia/sdk
2
-
3
- TypeScript SDK for interacting with the UTXOpia protocol - a privacy-preserving Bitcoin-to-Solana bridge using Zero-Knowledge Proofs.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- bun add @utxopia/sdk
9
- # or
10
- npm install @utxopia/sdk
11
- ```
12
-
13
- ## Quick Start
14
-
15
- ```typescript
16
- import {
17
- deriveKeysFromWallet,
18
- createStealthDeposit,
19
- scanAnnouncements,
20
- resolveSnsName,
21
- } from '@utxopia/sdk';
22
-
23
- // 1. Derive keys from wallet
24
- const keys = await deriveKeysFromWallet(walletAdapter);
25
-
26
- // 2. Look up recipient by .utxopia.sol name
27
- const recipient = await resolveSnsName(connection, 'alice');
28
-
29
- // 3. Create stealth deposit
30
- const deposit = await createStealthDeposit(recipient, 100000n);
31
-
32
- // 4. Scan for incoming deposits
33
- const notes = await scanAnnouncements(keys, announcements);
34
- ```
35
-
36
- ## Core Features
37
-
38
- ### Key Derivation
39
-
40
- Derive spending and viewing keys from a Solana wallet signature (RAILGUN-style):
41
-
42
- ```typescript
43
- import { deriveKeysFromWallet, type UTXOpiaKeys } from '@utxopia/sdk';
44
-
45
- const keys: UTXOpiaKeys = await deriveKeysFromWallet(walletAdapter);
46
- // keys.spendingPubKey - for receiving funds
47
- // keys.viewingPubKey - for scanning deposits
48
- // keys.spendingPrivKey - for claiming (keep secret!)
49
- // keys.viewingPrivKey - for scanning (can delegate)
50
- ```
51
-
52
- ### Stealth Addresses (EIP-5564/DKSAP Pattern)
53
-
54
- Create private deposits that only the recipient can detect and claim:
55
-
56
- ```typescript
57
- import {
58
- createStealthDeposit,
59
- scanAnnouncements,
60
- prepareClaimInputs,
61
- } from '@utxopia/sdk';
62
-
63
- // Sender: Create stealth deposit
64
- const deposit = await createStealthDeposit(recipientMeta, amountSats);
65
- // deposit.ephemeralPub - publish on-chain
66
- // deposit.commitment - add to Merkle tree
67
- // deposit.amountSats - verified BTC amount
68
-
69
- // Recipient: Scan for deposits
70
- const notes = await scanAnnouncements(keys, onChainAnnouncements);
71
-
72
- // Recipient: Prepare claim inputs for ZK proof
73
- const claimInputs = await prepareClaimInputs(keys, note, merkleProof);
74
- ```
75
-
76
- ### Note Generation
77
-
78
- Create and manage shielded notes:
79
-
80
- ```typescript
81
- import {
82
- generateNote,
83
- deriveNote,
84
- createClaimLink,
85
- parseClaimLink,
86
- } from '@utxopia/sdk';
87
-
88
- // Generate random note
89
- const note = generateNote(100000n);
90
-
91
- // Derive deterministic note from seed
92
- const note = deriveNote('my-secret-phrase', 0, 100000n);
93
-
94
- // Create shareable claim link
95
- const link = createClaimLink(note);
96
-
97
- // Parse claim link
98
- const parsed = parseClaimLink(link);
99
- ```
100
-
101
- ### Taproot Address Derivation
102
-
103
- Generate BTC deposit addresses:
104
-
105
- ```typescript
106
- import { deriveTaprootAddress, verifyTaprootAddress } from '@utxopia/sdk';
107
-
108
- // Use the configured FROST/Ika custody public key; there is no safe default.
109
- const custodyInternalKey = getConfiguredCustodyInternalKey();
110
- const { address } = deriveTaprootAddress(
111
- commitment,
112
- 'testnet',
113
- custodyInternalKey,
114
- );
115
-
116
- // Verify address matches commitment
117
- const isValid = verifyTaprootAddress(address, commitment, custodyInternalKey);
118
- ```
119
-
120
- ### Merkle Proofs
121
-
122
- Work with the on-chain commitment tree:
123
-
124
- ```typescript
125
- import {
126
- createMerkleProof,
127
- proofToNoirFormat,
128
- TREE_DEPTH,
129
- } from '@utxopia/sdk';
130
-
131
- const proof = createMerkleProof(leaves, leafIndex);
132
- const noirProof = proofToNoirFormat(proof);
133
- ```
134
-
135
- ## API Reference
136
-
137
- ### Stealth Module
138
-
139
- | Function | Description |
140
- |----------|-------------|
141
- | `createStealthDeposit(recipient, amount)` | Create stealth deposit for recipient |
142
- | `scanAnnouncements(keys, announcements)` | Scan for deposits using viewing key |
143
- | `prepareClaimInputs(keys, note, proof)` | Prepare inputs for ZK claim proof |
144
- | `scanUnifiedNotes(keys, announcements)` | Scan announcement events for owned notes |
145
- | `resolveSnsName(conn, name)` | Look up .utxopia.sol name to stealth address |
146
-
147
- ### Key Derivation Module
148
-
149
- | Function | Description |
150
- |----------|-------------|
151
- | `deriveKeysFromWallet(wallet)` | Derive keys from wallet signature |
152
- | `deriveKeysFromSignature(sig)` | Derive keys from raw signature |
153
- | `deriveKeysFromSeed(seed)` | Derive keys from seed bytes |
154
- | `createStealthMetaAddress(keys)` | Create stealth meta-address |
155
- | `createDelegatedViewKey(keys, perms, expiry)` | Create delegated view key |
156
-
157
- ### Constants
158
-
159
- ```typescript
160
- // Program IDs
161
- UTXOPIA_PROGRAM_ID // Main UTXOpia program (devnet)
162
- CHADBUFFER_PROGRAM_ID // ChadBuffer for SPV proofs
163
-
164
- // Merkle Tree
165
- TREE_DEPTH // 20
166
- MAX_LEAVES // 2^20
167
- ZERO_VALUE // Empty leaf value
168
-
169
- ```
170
-
171
- ## Types
172
-
173
- ### UTXOpiaKeys
174
-
175
- ```typescript
176
- interface UTXOpiaKeys {
177
- spendingPubKey: BabyJubPoint;
178
- spendingPrivKey: bigint;
179
- viewingPubKey: Uint8Array;
180
- viewingPrivKey: Uint8Array;
181
- nullifyingKey: bigint;
182
- }
183
- ```
184
-
185
- ### StealthDeposit
186
-
187
- ```typescript
188
- interface StealthDeposit {
189
- ephemeralPub: Uint8Array; // 32 bytes (Ed25519)
190
- amountSats: bigint;
191
- commitment: Uint8Array; // 32 bytes
192
- createdAt: number;
193
- }
194
- ```
195
-
196
- ### ScannedNote
197
-
198
- ```typescript
199
- interface ScannedNote {
200
- amount: bigint;
201
- ephemeralPub: Uint8Array;
202
- stealthPub: BabyJubPoint;
203
- leafIndex: number;
204
- commitment: Uint8Array;
205
- }
206
- ```
207
-
208
- ### ConnectionAdapter
209
-
210
- ```typescript
211
- interface ConnectionAdapter {
212
- getAccountInfo: (
213
- pubkey: { toBytes(): Uint8Array }
214
- ) => Promise<{ data: Uint8Array } | null>;
215
- }
216
- ```
217
-
218
- ## Security Considerations
219
-
220
- 1. **Never expose spending private key** - Only needed for claiming
221
- 2. **Viewing key can be delegated** - For balance monitoring without spend capability
222
- 3. **Nullifiers prevent double-spending** - Derived from spending key + leaf index
223
- 4. **Commitments hide amounts** - Poseidon hash of NPK, token, and amount
224
-
225
- ## Development
226
-
227
- ```bash
228
- # Install dependencies
229
- bun install
230
-
231
- # Build
232
- bun run build
233
-
234
- # Run tests
235
- bun test
236
- ```
237
-
238
- ## Verify it yourself: rebuild the tree from chain
239
-
240
- Spending a note needs a Merkle proof, and a Merkle proof needs the whole leaf
241
- set — which normally comes from our indexer. If the leaves weren't recoverable
242
- from the chain itself, "you can exit without the operator" would be a promise
243
- rather than a property.
244
-
245
- This script rebuilds a pool's entire leaf set from Solana transaction logs and
246
- checks the result against the root the program is verifying against. It talks to
247
- a public RPC endpoint and nothing else — no backend, no indexer, no API key:
248
-
249
- ```bash
250
- TREE=<commitment-tree-pda> bun run scripts/rebuild-tree-from-chain.ts
251
- ```
252
-
253
- ```
254
- on-chain: 123 leaves, root 2994f7d670d12cd8dcbd89af708ff55f9e877bbb387aac4cf09e688a615650ce
255
- 134/134 scanned, 123 leaves
256
- recovered 123/123 leaves from logs
257
- rebuilt: 123 leaves, root 2994f7d670d12cd8dcbd89af708ff55f9e877bbb387aac4cf09e688a615650ce
258
-
259
- MATCH — the leaf set is recoverable from chain alone
260
- ```
261
-
262
- It exits non-zero on a mismatch or on missing leaves, so it works as a check in
263
- CI as well as by hand.
264
-
265
- | Env | Default | |
266
- |---|---|---|
267
- | `RPC` | devnet | Any Solana RPC endpoint |
268
- | `TREE` | `DEVNET_CONFIG.commitmentTreePda` | Commitment tree PDA to rebuild |
269
- | `EPOCH_SIG` | — | Tree's `INITIALIZE` signature. Optional; set it to skip a dead epoch if the PDA was closed and recreated |
270
- | `PACE_MS` | `120` | Delay between requests. Raise it if your endpoint throttles you |
271
-
272
- Public endpoints rate-limit this scan aggressively — the script paces itself and
273
- backs off, so expect it to take a minute or two rather than to fail.
274
-
275
- ## License
276
-
277
- MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes