@velumdotcash/sdk 2.0.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist/__tests__/paylink.test.d.ts +9 -0
  4. package/dist/__tests__/paylink.test.js +254 -0
  5. package/dist/config.d.ts +9 -0
  6. package/dist/config.js +12 -0
  7. package/dist/deposit.d.ts +22 -0
  8. package/dist/deposit.js +445 -0
  9. package/dist/depositSPL.d.ts +24 -0
  10. package/dist/depositSPL.js +499 -0
  11. package/dist/errors.d.ts +78 -0
  12. package/dist/errors.js +127 -0
  13. package/dist/exportUtils.d.ts +10 -0
  14. package/dist/exportUtils.js +10 -0
  15. package/dist/getUtxos.d.ts +30 -0
  16. package/dist/getUtxos.js +335 -0
  17. package/dist/getUtxosSPL.d.ts +34 -0
  18. package/dist/getUtxosSPL.js +442 -0
  19. package/dist/index.d.ts +183 -0
  20. package/dist/index.js +436 -0
  21. package/dist/models/keypair.d.ts +26 -0
  22. package/dist/models/keypair.js +43 -0
  23. package/dist/models/utxo.d.ts +51 -0
  24. package/dist/models/utxo.js +99 -0
  25. package/dist/test_paylink_logic.test.d.ts +1 -0
  26. package/dist/test_paylink_logic.test.js +114 -0
  27. package/dist/utils/address_lookup_table.d.ts +9 -0
  28. package/dist/utils/address_lookup_table.js +45 -0
  29. package/dist/utils/constants.d.ts +27 -0
  30. package/dist/utils/constants.js +56 -0
  31. package/dist/utils/debug-logger.d.ts +250 -0
  32. package/dist/utils/debug-logger.js +688 -0
  33. package/dist/utils/encryption.d.ts +152 -0
  34. package/dist/utils/encryption.js +700 -0
  35. package/dist/utils/logger.d.ts +9 -0
  36. package/dist/utils/logger.js +35 -0
  37. package/dist/utils/merkle_tree.d.ts +92 -0
  38. package/dist/utils/merkle_tree.js +186 -0
  39. package/dist/utils/node-shim.d.ts +14 -0
  40. package/dist/utils/node-shim.js +21 -0
  41. package/dist/utils/prover.d.ts +36 -0
  42. package/dist/utils/prover.js +169 -0
  43. package/dist/utils/utils.d.ts +64 -0
  44. package/dist/utils/utils.js +165 -0
  45. package/dist/withdraw.d.ts +22 -0
  46. package/dist/withdraw.js +290 -0
  47. package/dist/withdrawSPL.d.ts +24 -0
  48. package/dist/withdrawSPL.js +329 -0
  49. package/package.json +59 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Velum
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @velumdotcash/sdk
2
+
3
+ TypeScript SDK for private payments on Solana using Zero-Knowledge proofs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @velumdotcash/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { PrivacyCash } from "@velumdotcash/sdk";
15
+
16
+ // Initialize with wallet signature (browser)
17
+ const sdk = new PrivacyCash({
18
+ RPC_url: "https://api.mainnet-beta.solana.com",
19
+ publicKey: walletPublicKey,
20
+ signature: walletSignature,
21
+ transactionSigner: async (tx) => wallet.signTransaction(tx),
22
+ });
23
+
24
+ // Or with keypair (Node.js / scripts)
25
+ const sdk = new PrivacyCash({
26
+ RPC_url: "https://api.mainnet-beta.solana.com",
27
+ owner: keypair,
28
+ });
29
+ ```
30
+
31
+ ## API Reference
32
+
33
+ ### Deposits
34
+
35
+ ```typescript
36
+ // Deposit SOL
37
+ await sdk.deposit({ lamports: 10_000_000 });
38
+
39
+ // Deposit USDC
40
+ await sdk.depositUSDC({ base_units: 1_000_000 });
41
+
42
+ // Deposit any SPL token
43
+ await sdk.depositSPL({ base_units: 1_000_000, mintAddress: "..." });
44
+
45
+ // Deposit to a third-party recipient
46
+ await sdk.deposit({
47
+ lamports: 10_000_000,
48
+ recipientUtxoPublicKey: "...",
49
+ recipientEncryptionKey: encryptionKeyBytes,
50
+ });
51
+ ```
52
+
53
+ ### Withdrawals
54
+
55
+ ```typescript
56
+ // Withdraw SOL
57
+ await sdk.withdraw({ lamports: 10_000_000, recipientAddress: "..." });
58
+
59
+ // Withdraw USDC
60
+ await sdk.withdrawUSDC({ base_units: 1_000_000, recipientAddress: "..." });
61
+
62
+ // Withdraw any SPL token
63
+ await sdk.withdrawSPL({
64
+ base_units: 1_000_000,
65
+ mintAddress: "...",
66
+ recipientAddress: "...",
67
+ });
68
+ ```
69
+
70
+ ### Balance
71
+
72
+ ```typescript
73
+ const sol = await sdk.getPrivateBalance();
74
+ console.log(sol.lamports);
75
+
76
+ const usdc = await sdk.getPrivateBalanceUSDC();
77
+ console.log(usdc.base_units);
78
+
79
+ const spl = await sdk.getPrivateBalanceSpl(mintAddress);
80
+ console.log(spl.base_units);
81
+ ```
82
+
83
+ ### Key Derivation
84
+
85
+ ```typescript
86
+ // Get public keys for receiving payments
87
+ const encryptionKey = sdk.getAsymmetricPublicKey(); // Uint8Array
88
+ const utxoPubkey = await sdk.getShieldedPublicKey(); // string
89
+ ```
90
+
91
+ ## Browser Usage
92
+
93
+ The SDK works in browsers with wallet adapter integration. Circuit files (WASM + zkey) must be served from your application's public directory.
94
+
95
+ ```typescript
96
+ const sdk = new PrivacyCash({
97
+ RPC_url: rpcEndpoint,
98
+ publicKey: walletPublicKey,
99
+ signature: walletSignature,
100
+ transactionSigner: async (tx) => wallet.signTransaction(tx),
101
+ circuitPath: "/circuit", // path to circuit files in public/
102
+ });
103
+ ```
104
+
105
+ ## Node.js Usage
106
+
107
+ For server-side scripts and testing:
108
+
109
+ ```typescript
110
+ import { Keypair } from "@solana/web3.js";
111
+
112
+ const keypair = Keypair.fromSecretKey(secretKey);
113
+ const sdk = new PrivacyCash({
114
+ RPC_url: process.env.SOLANA_RPC_URL!,
115
+ owner: keypair,
116
+ storage: new LocalStorage("./cache"),
117
+ circuitPath: "./circuits",
118
+ enableDebug: true,
119
+ });
120
+ ```
121
+
122
+ ## Error Handling
123
+
124
+ ```typescript
125
+ import {
126
+ InsufficientBalanceError,
127
+ ZKProofError,
128
+ NetworkError,
129
+ TransactionTimeoutError,
130
+ } from "@velumdotcash/sdk";
131
+
132
+ try {
133
+ await sdk.deposit({ lamports: amount });
134
+ } catch (err) {
135
+ if (err instanceof InsufficientBalanceError) { /* ... */ }
136
+ if (err instanceof ZKProofError) { /* ... */ }
137
+ }
138
+ ```
139
+
140
+ ## License
141
+
142
+ ISC
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Integration tests for Privacy Paylink third-party deposit functionality
3
+ *
4
+ * These tests verify that:
5
+ * 1. Utxo can be created with only a public key (no private key)
6
+ * 2. Asymmetric encryption works correctly
7
+ * 3. Third-party deposits work end-to-end
8
+ */
9
+ export {};
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Integration tests for Privacy Paylink third-party deposit functionality
3
+ *
4
+ * These tests verify that:
5
+ * 1. Utxo can be created with only a public key (no private key)
6
+ * 2. Asymmetric encryption works correctly
7
+ * 3. Third-party deposits work end-to-end
8
+ */
9
+ import { describe, it, expect, beforeAll } from "vitest";
10
+ import { Utxo } from "../models/utxo";
11
+ import { Keypair } from "../models/keypair";
12
+ import { EncryptionService } from "../utils/encryption";
13
+ import { WasmFactory } from "@lightprotocol/hasher.rs";
14
+ import BN from "bn.js";
15
+ import nacl from "tweetnacl";
16
+ describe("Paylink Core Functionality", () => {
17
+ let lightWasm;
18
+ beforeAll(async () => {
19
+ // Initialize WASM - required for all crypto operations
20
+ lightWasm = await WasmFactory.getInstance();
21
+ });
22
+ describe("Utxo pubkey-only mode", () => {
23
+ it("should create Utxo with only publicKey", async () => {
24
+ // Generate a keypair to get a valid public key
25
+ const keypair = await Keypair.generateNew(lightWasm);
26
+ const pubkey = keypair.pubkey;
27
+ // Create Utxo with only the public key
28
+ const utxo = new Utxo({
29
+ lightWasm,
30
+ amount: new BN(1000000),
31
+ publicKey: pubkey,
32
+ });
33
+ expect(utxo.pubkey.toString()).toBe(pubkey.toString());
34
+ expect(utxo.amount.toNumber()).toBe(1000000);
35
+ });
36
+ it("should allow getCommitment() with pubkey-only Utxo", async () => {
37
+ const keypair = await Keypair.generateNew(lightWasm);
38
+ const utxo = new Utxo({
39
+ lightWasm,
40
+ amount: new BN(500000),
41
+ publicKey: keypair.pubkey,
42
+ });
43
+ // getCommitment should work without private key (async)
44
+ const commitment = await utxo.getCommitment();
45
+ expect(commitment).toBeDefined();
46
+ expect(typeof commitment).toBe("string");
47
+ });
48
+ it("should throw on getNullifier() with pubkey-only Utxo", async () => {
49
+ const keypair = await Keypair.generateNew(lightWasm);
50
+ const utxo = new Utxo({
51
+ lightWasm,
52
+ amount: new BN(500000),
53
+ publicKey: keypair.pubkey,
54
+ });
55
+ // getNullifier requires private key, should throw
56
+ await expect(utxo.getNullifier()).rejects.toThrow();
57
+ });
58
+ it("should work normally with full keypair", async () => {
59
+ const keypair = await Keypair.generateNew(lightWasm);
60
+ const utxo = new Utxo({
61
+ lightWasm,
62
+ amount: new BN(1000000),
63
+ keypair: keypair,
64
+ });
65
+ // Both should work with full keypair
66
+ const commitment = await utxo.getCommitment();
67
+ const nullifier = await utxo.getNullifier();
68
+ expect(commitment).toBeDefined();
69
+ expect(nullifier).toBeDefined();
70
+ });
71
+ });
72
+ describe("Asymmetric Encryption", () => {
73
+ let aliceEncryption;
74
+ let bobEncryption;
75
+ beforeAll(async () => {
76
+ // Simulate two different wallets by deriving from different signatures
77
+ const aliceSignature = nacl.randomBytes(64);
78
+ const bobSignature = nacl.randomBytes(64);
79
+ aliceEncryption = new EncryptionService();
80
+ aliceEncryption.deriveEncryptionKeyFromSignature(aliceSignature);
81
+ bobEncryption = new EncryptionService();
82
+ bobEncryption.deriveEncryptionKeyFromSignature(bobSignature);
83
+ });
84
+ it("should get asymmetric public key", () => {
85
+ const alicePubKey = aliceEncryption.getAsymmetricPublicKey();
86
+ expect(alicePubKey).toBeDefined();
87
+ expect(alicePubKey.length).toBe(32); // X25519 public key is 32 bytes
88
+ });
89
+ it("should encrypt for recipient using asymmetric encryption", () => {
90
+ const alicePubKey = aliceEncryption.getAsymmetricPublicKey();
91
+ const testData = Buffer.from("Hello Alice, this is a secret message!");
92
+ // Bob encrypts for Alice
93
+ const encrypted = bobEncryption.encryptAsymmetric(testData, alicePubKey);
94
+ expect(encrypted).toBeDefined();
95
+ expect(encrypted.length).toBeGreaterThan(testData.length); // Should have nonce + ephemeral key overhead
96
+ });
97
+ it("should decrypt via decryptUtxo for V3 encrypted data", async () => {
98
+ const alicePubKey = aliceEncryption.getAsymmetricPublicKey();
99
+ // Create a simple UTXO to encrypt
100
+ const keypair = await Keypair.generateNew(lightWasm);
101
+ const utxo = new Utxo({
102
+ lightWasm,
103
+ amount: new BN(1_000_000_000),
104
+ publicKey: keypair.pubkey,
105
+ });
106
+ // Bob encrypts UTXO for Alice using asymmetric encryption
107
+ const encrypted = bobEncryption.encryptUtxo(utxo, alicePubKey);
108
+ // Verify it's V3 format (first byte should be 0x03 is at the end of the 8-byte buffer)
109
+ expect(encrypted[7]).toBe(0x03);
110
+ // Alice can decrypt via decryptUtxo (handles V3 internally)
111
+ const decrypted = await aliceEncryption.decryptUtxo(encrypted, lightWasm);
112
+ expect(decrypted).toBeDefined();
113
+ expect(decrypted.amount.toString()).toBe(utxo.amount.toString());
114
+ });
115
+ it("should fail decryption with wrong key", async () => {
116
+ const alicePubKey = aliceEncryption.getAsymmetricPublicKey();
117
+ const keypair = await Keypair.generateNew(lightWasm);
118
+ const utxo = new Utxo({
119
+ lightWasm,
120
+ amount: new BN(1_000_000_000),
121
+ publicKey: keypair.pubkey,
122
+ });
123
+ // Bob encrypts for Alice
124
+ const encrypted = bobEncryption.encryptUtxo(utxo, alicePubKey);
125
+ // Bob tries to decrypt his own encrypted data for Alice - should fail
126
+ try {
127
+ await bobEncryption.decryptUtxo(encrypted, lightWasm);
128
+ expect(true).toBe(false); // Should not reach here
129
+ }
130
+ catch (e) {
131
+ expect(e).toBeDefined();
132
+ }
133
+ });
134
+ });
135
+ describe("UTXO Encryption for Third-Party", () => {
136
+ let senderEncryption;
137
+ let recipientEncryption;
138
+ let recipientKeypair;
139
+ beforeAll(async () => {
140
+ const senderSignature = nacl.randomBytes(64);
141
+ const recipientSignature = nacl.randomBytes(64);
142
+ senderEncryption = new EncryptionService();
143
+ senderEncryption.deriveEncryptionKeyFromSignature(senderSignature);
144
+ recipientEncryption = new EncryptionService();
145
+ recipientEncryption.deriveEncryptionKeyFromSignature(recipientSignature);
146
+ recipientKeypair = await Keypair.generateNew(lightWasm);
147
+ });
148
+ it("should encrypt UTXO for recipient", () => {
149
+ const recipientEncKey = recipientEncryption.getAsymmetricPublicKey();
150
+ // Create UTXO owned by recipient
151
+ const utxo = new Utxo({
152
+ lightWasm,
153
+ amount: new BN(1_000_000_000), // 1 SOL
154
+ publicKey: recipientKeypair.pubkey,
155
+ });
156
+ // Sender encrypts UTXO data for recipient
157
+ const encrypted = senderEncryption.encryptUtxo(utxo, recipientEncKey);
158
+ expect(encrypted).toBeDefined();
159
+ expect(encrypted.length).toBeGreaterThan(0);
160
+ // First byte should be V3 version marker (0x03 is at the end of the 8-byte buffer)
161
+ expect(encrypted[7]).toBe(0x03);
162
+ });
163
+ it("should allow recipient to decrypt UTXO", async () => {
164
+ // For this test to work, recipientKeypair must match what EncryptionService derives
165
+ const recipientPrivKey = recipientEncryption.getUtxoPrivateKeyV2();
166
+ // Re-instantiate the keypair that matches the encryption service
167
+ const derivedRecipientKeypair = new Keypair(recipientPrivKey, lightWasm);
168
+ const recipientEncKey = recipientEncryption.getAsymmetricPublicKey();
169
+ // Create UTXO owned by recipient
170
+ const originalAmount = new BN(2_500_000_000); // 2.5 SOL
171
+ const utxo = new Utxo({
172
+ lightWasm,
173
+ amount: originalAmount,
174
+ publicKey: derivedRecipientKeypair.pubkey,
175
+ });
176
+ // Sender encrypts for recipient
177
+ const encrypted = senderEncryption.encryptUtxo(utxo, recipientEncKey);
178
+ // Recipient decrypts
179
+ const decryptedUtxo = await recipientEncryption.decryptUtxo(encrypted, lightWasm);
180
+ expect(decryptedUtxo).toBeDefined();
181
+ expect(decryptedUtxo.amount.toString()).toBe(originalAmount.toString());
182
+ expect(decryptedUtxo.pubkey.toString()).toBe(derivedRecipientKeypair.pubkey.toString());
183
+ });
184
+ it("should not allow sender to decrypt their own encrypted UTXO for recipient", async () => {
185
+ const recipientEncKey = recipientEncryption.getAsymmetricPublicKey();
186
+ const utxo = new Utxo({
187
+ lightWasm,
188
+ amount: new BN(1_000_000_000),
189
+ publicKey: recipientKeypair.pubkey,
190
+ });
191
+ // Sender encrypts for recipient
192
+ const encrypted = senderEncryption.encryptUtxo(utxo, recipientEncKey);
193
+ // Sender tries to decrypt - should fail
194
+ try {
195
+ await senderEncryption.decryptUtxo(encrypted, lightWasm);
196
+ // If we get here, it failed to throw
197
+ expect(true).toBe(false);
198
+ }
199
+ catch (error) {
200
+ expect(error).toBeDefined();
201
+ }
202
+ });
203
+ });
204
+ describe("End-to-End Paylink Flow (Unit)", () => {
205
+ it("should support complete paylink flow", async () => {
206
+ // === RECIPIENT GENERATES PAYLINK ===
207
+ const recipientSignature = nacl.randomBytes(64);
208
+ const recipientEncryption = new EncryptionService();
209
+ recipientEncryption.deriveEncryptionKeyFromSignature(recipientSignature);
210
+ const recipientUtxoKeypair = await Keypair.generateNew(lightWasm);
211
+ // These would be encoded in the paylink URL
212
+ const paylinkData = {
213
+ recipientUtxoPubkey: recipientUtxoKeypair.pubkey.toString(),
214
+ recipientEncryptionKey: Buffer.from(recipientEncryption.getAsymmetricPublicKey()).toString("base64"),
215
+ token: "SOL",
216
+ amount: null, // Open amount
217
+ };
218
+ // === SENDER PAYS VIA PAYLINK ===
219
+ const senderSignature = nacl.randomBytes(64);
220
+ const senderEncryption = new EncryptionService();
221
+ senderEncryption.deriveEncryptionKeyFromSignature(senderSignature);
222
+ // Sender decodes paylink data
223
+ const recipientPubkey = new BN(paylinkData.recipientUtxoPubkey);
224
+ const recipientEncKey = new Uint8Array(Buffer.from(paylinkData.recipientEncryptionKey, "base64"));
225
+ // Sender creates UTXO for recipient
226
+ const paymentAmount = new BN(5_000_000_000); // 5 SOL
227
+ const outputUtxo = new Utxo({
228
+ lightWasm,
229
+ amount: paymentAmount,
230
+ publicKey: recipientPubkey,
231
+ });
232
+ // Sender encrypts UTXO for recipient
233
+ const encryptedOutput = senderEncryption.encryptUtxo(outputUtxo, recipientEncKey);
234
+ // Verify commitment can be computed (needed for on-chain)
235
+ const commitment = await outputUtxo.getCommitment();
236
+ expect(commitment).toBeDefined();
237
+ // === RECIPIENT CLAIMS ===
238
+ // Recipient scans encrypted outputs and tries to decrypt
239
+ const decryptedUtxo = await recipientEncryption.decryptUtxo(encryptedOutput, lightWasm);
240
+ expect(decryptedUtxo).toBeDefined();
241
+ expect(decryptedUtxo.amount.toString()).toBe(paymentAmount.toString());
242
+ // Recipient can now use their keypair to spend (would need full keypair for nullifier)
243
+ // In real flow, recipient would reconstruct UTXO with their full keypair
244
+ const spendableUtxo = new Utxo({
245
+ lightWasm,
246
+ amount: decryptedUtxo.amount,
247
+ keypair: recipientUtxoKeypair, // Full keypair for spending
248
+ });
249
+ // Now recipient can compute nullifier for spending
250
+ const nullifier = await spendableUtxo.getNullifier();
251
+ expect(nullifier).toBeDefined();
252
+ });
253
+ });
254
+ });
@@ -0,0 +1,9 @@
1
+ type Config = {
2
+ withdraw_fee_rate: number;
3
+ withdraw_rent_fee: number;
4
+ deposit_fee_rate: number;
5
+ usdc_withdraw_rent_fee: number;
6
+ rent_fees: any;
7
+ };
8
+ export declare function getConfig<K extends keyof Config>(key: K): Promise<Config[K]>;
9
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,12 @@
1
+ import { RELAYER_API_URL } from "./utils/constants.js";
2
+ let config;
3
+ export async function getConfig(key) {
4
+ if (!config) {
5
+ const res = await fetch(RELAYER_API_URL + '/config');
6
+ config = await res.json();
7
+ }
8
+ if (typeof config[key] == 'undefined') {
9
+ throw new Error(`can not get ${key} from ${RELAYER_API_URL}/config`);
10
+ }
11
+ return config[key];
12
+ }
@@ -0,0 +1,22 @@
1
+ import { Connection, PublicKey, VersionedTransaction } from "@solana/web3.js";
2
+ import BN from "bn.js";
3
+ import * as hasher from "@lightprotocol/hasher.rs";
4
+ import { EncryptionService } from "./utils/encryption.js";
5
+ type DepositParams = {
6
+ publicKey: PublicKey;
7
+ connection: Connection;
8
+ amount_in_lamports: number;
9
+ storage: Storage;
10
+ encryptionService: EncryptionService;
11
+ keyBasePath: string;
12
+ lightWasm: hasher.LightWasm;
13
+ referrer?: string;
14
+ signer?: PublicKey;
15
+ transactionSigner: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
16
+ recipientUtxoPublicKey?: BN | string;
17
+ recipientEncryptionKey?: Uint8Array;
18
+ };
19
+ export declare function deposit({ lightWasm, storage, keyBasePath, publicKey, connection, amount_in_lamports, encryptionService, transactionSigner, referrer, signer, recipientUtxoPublicKey, recipientEncryptionKey, }: DepositParams): Promise<{
20
+ tx: string;
21
+ }>;
22
+ export {};