@column-org/wallet-sdk 1.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.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # @column-org/wallet-sdk
2
+
3
+ The official SDK for integrating Column Wallet's deep-linking protocol into your Web3 applications on the Movement/Aptos ecosystem.
4
+
5
+ ## Features
6
+
7
+ - **Direct Deep-Linking**: Low-latency communication between dApps and Column Wallet.
8
+ - **End-to-End Encryption**: Secure handshakes using X25519 and TweetNaCl.
9
+ - **Environment Agnostic**: Works seamlessly for both mobile websites and native applications.
10
+ - **Aptos Compatible**: Built-in support for BCS transaction payloads and message signing.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @column-org/wallet-sdk
16
+ # or
17
+ yarn add @column-org/wallet-sdk
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Initialize the SDK
23
+
24
+ ```typescript
25
+ import { ColumnWalletSDK } from '@column-org/wallet-sdk';
26
+
27
+ const sdk = new ColumnWalletSDK({
28
+ appName: 'My Awesome DApp',
29
+ appUrl: 'https://mydapp.io',
30
+ redirectLink: 'https://mydapp.io/callback', // Use custom scheme for native apps
31
+ appIcon: 'https://mydapp.io/logo.png'
32
+ });
33
+ ```
34
+
35
+ ### 2. Connect Wallet
36
+
37
+ You can either generate a link manually or use the **built-in premium modal**:
38
+
39
+ #### Method A: Using the built-in Modal (Recommended)
40
+ ```typescript
41
+ // This will open a glassmorphic selection modal
42
+ sdk.openConnectModal();
43
+ ```
44
+
45
+ #### Method B: Manual Redirect
46
+ ```typescript
47
+ // Generate the connection URL
48
+ const connectUrl = sdk.connect();
49
+
50
+ // Redirect the user to Column Wallet
51
+ window.location.href = connectUrl;
52
+ ```
53
+
54
+ ### 3. Handle the Response
55
+
56
+ After approval, Column redirects back to your `redirectLink`.
57
+
58
+ ```typescript
59
+ // On your callback page
60
+ const response = sdk.handleResponse(window.location.href);
61
+
62
+ if (response.address) {
63
+ console.log('Connected to:', response.address);
64
+ // Store the wallet's encryption public key for future signing requests
65
+ localStorage.setItem('wallet_pk', response.column_encryption_public_key);
66
+ }
67
+ ```
68
+
69
+ ### 4. Sign and Submit Transaction
70
+
71
+ ```typescript
72
+ sdk.setWalletPublicKey(localStorage.getItem('wallet_pk'));
73
+
74
+ const txUrl = sdk.signAndSubmitTransaction(base58TransactionPayload);
75
+ window.location.href = txUrl;
76
+ ```
77
+
78
+ ## Security
79
+
80
+ Column SDK uses the **Nacl protocol** for authenticated encryption. Every request generates an ephemeral session key pair, ensuring that your user's transactions are private and secure from transit-level interceptions.
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,59 @@
1
+ import * as nacl from 'tweetnacl';
2
+
3
+ interface ColumnSDKConfig {
4
+ appUrl?: string;
5
+ appName?: string;
6
+ redirectLink?: string;
7
+ appIcon?: string;
8
+ appDescription?: string;
9
+ walletScheme?: string;
10
+ network?: string;
11
+ }
12
+ declare class ColumnCrypto {
13
+ static generateKeyPair(): nacl.BoxKeyPair;
14
+ static encryptPayload(payload: any, targetPublicKeyB58: string, mySecretKey: Uint8Array): {
15
+ data: string;
16
+ nonce: string;
17
+ };
18
+ static decryptPayload(encryptedDataB58: string, nonceB58: string, sourcePublicKeyB58: string, mySecretKey: Uint8Array): any;
19
+ }
20
+
21
+ declare class ColumnWalletSDK {
22
+ private config;
23
+ private sessionKeyPair;
24
+ private walletEncryptionPublicKey;
25
+ private modal;
26
+ constructor(config: ColumnSDKConfig);
27
+ private autoDetectMetadata;
28
+ /**
29
+ * Opens the premium Column selection modal
30
+ */
31
+ openConnectModal(): void;
32
+ setWalletEncryptionPublicKey(key: string): void;
33
+ /**
34
+ * Alias for setWalletEncryptionPublicKey to match docs
35
+ */
36
+ importWalletKey(key: string): void;
37
+ /**
38
+ * Step 1: Connect to the wallet
39
+ */
40
+ connect(): string;
41
+ /**
42
+ * Step 2: Sign and Submit a transaction
43
+ */
44
+ signAndSubmitTransaction(transaction: any): string;
45
+ /**
46
+ * Step 3: Sign a simple message
47
+ */
48
+ signMessage(message: string): string;
49
+ /**
50
+ * Utility: Parse the response from Column
51
+ */
52
+ handleResponse(url: string): any;
53
+ /**
54
+ * Sets the wallet's public key if known from a previous session
55
+ */
56
+ setWalletPublicKey(publicKeyB58: string): void;
57
+ }
58
+
59
+ export { ColumnCrypto, type ColumnSDKConfig, ColumnWalletSDK };
@@ -0,0 +1,59 @@
1
+ import * as nacl from 'tweetnacl';
2
+
3
+ interface ColumnSDKConfig {
4
+ appUrl?: string;
5
+ appName?: string;
6
+ redirectLink?: string;
7
+ appIcon?: string;
8
+ appDescription?: string;
9
+ walletScheme?: string;
10
+ network?: string;
11
+ }
12
+ declare class ColumnCrypto {
13
+ static generateKeyPair(): nacl.BoxKeyPair;
14
+ static encryptPayload(payload: any, targetPublicKeyB58: string, mySecretKey: Uint8Array): {
15
+ data: string;
16
+ nonce: string;
17
+ };
18
+ static decryptPayload(encryptedDataB58: string, nonceB58: string, sourcePublicKeyB58: string, mySecretKey: Uint8Array): any;
19
+ }
20
+
21
+ declare class ColumnWalletSDK {
22
+ private config;
23
+ private sessionKeyPair;
24
+ private walletEncryptionPublicKey;
25
+ private modal;
26
+ constructor(config: ColumnSDKConfig);
27
+ private autoDetectMetadata;
28
+ /**
29
+ * Opens the premium Column selection modal
30
+ */
31
+ openConnectModal(): void;
32
+ setWalletEncryptionPublicKey(key: string): void;
33
+ /**
34
+ * Alias for setWalletEncryptionPublicKey to match docs
35
+ */
36
+ importWalletKey(key: string): void;
37
+ /**
38
+ * Step 1: Connect to the wallet
39
+ */
40
+ connect(): string;
41
+ /**
42
+ * Step 2: Sign and Submit a transaction
43
+ */
44
+ signAndSubmitTransaction(transaction: any): string;
45
+ /**
46
+ * Step 3: Sign a simple message
47
+ */
48
+ signMessage(message: string): string;
49
+ /**
50
+ * Utility: Parse the response from Column
51
+ */
52
+ handleResponse(url: string): any;
53
+ /**
54
+ * Sets the wallet's public key if known from a previous session
55
+ */
56
+ setWalletPublicKey(publicKeyB58: string): void;
57
+ }
58
+
59
+ export { ColumnCrypto, type ColumnSDKConfig, ColumnWalletSDK };
package/dist/index.js ADDED
@@ -0,0 +1,474 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ColumnCrypto: () => ColumnCrypto,
34
+ ColumnWalletSDK: () => ColumnWalletSDK
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/ColumnWalletSDK.ts
39
+ var import_bs582 = __toESM(require("bs58"));
40
+
41
+ // src/ColumnShared.ts
42
+ var nacl = __toESM(require("tweetnacl"));
43
+ var import_bs58 = __toESM(require("bs58"));
44
+ var import_buffer = require("buffer");
45
+ var ColumnCrypto = class {
46
+ static generateKeyPair() {
47
+ return nacl.box.keyPair();
48
+ }
49
+ static encryptPayload(payload, targetPublicKeyB58, mySecretKey) {
50
+ const sharedSecret = nacl.box.before(
51
+ import_bs58.default.decode(targetPublicKeyB58),
52
+ mySecretKey
53
+ );
54
+ const nonce = nacl.randomBytes(nacl.box.nonceLength);
55
+ const message = import_buffer.Buffer.from(JSON.stringify(payload), "utf8");
56
+ const encrypted = nacl.box.after(message, nonce, sharedSecret);
57
+ return {
58
+ data: import_bs58.default.encode(encrypted),
59
+ nonce: import_bs58.default.encode(nonce)
60
+ };
61
+ }
62
+ static decryptPayload(encryptedDataB58, nonceB58, sourcePublicKeyB58, mySecretKey) {
63
+ const sharedSecret = nacl.box.before(
64
+ import_bs58.default.decode(sourcePublicKeyB58),
65
+ mySecretKey
66
+ );
67
+ const decrypted = nacl.box.open.after(
68
+ import_bs58.default.decode(encryptedDataB58),
69
+ import_bs58.default.decode(nonceB58),
70
+ sharedSecret
71
+ );
72
+ if (!decrypted) return null;
73
+ const jsonString = import_buffer.Buffer.from(decrypted).toString("utf8");
74
+ return JSON.parse(jsonString);
75
+ }
76
+ };
77
+
78
+ // src/ui/ColumnConnectModal.ts
79
+ var ColumnModalStyles = `
80
+ .column-modal-overlay {
81
+ position: fixed;
82
+ top: 0;
83
+ left: 0;
84
+ width: 100%;
85
+ height: 100%;
86
+ background: rgba(0, 0, 0, 0.75);
87
+ backdrop-filter: blur(8px);
88
+ -webkit-backdrop-filter: blur(8px);
89
+ display: flex;
90
+ justify-content: center;
91
+ align-items: center;
92
+ z-index: 999999;
93
+ opacity: 0;
94
+ transition: opacity 0.3s ease;
95
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
96
+ }
97
+
98
+ .column-modal-overlay.active {
99
+ opacity: 1;
100
+ }
101
+
102
+ .column-modal-card {
103
+ background: #1C1F2E;
104
+ width: 100%;
105
+ max-width: 420px;
106
+ border-radius: 20px;
107
+ padding: 28px;
108
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
109
+ border: 1px solid rgba(255, 255, 255, 0.1);
110
+ transform: scale(0.95);
111
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
112
+ position: relative;
113
+ }
114
+
115
+ .column-modal-overlay.active .column-modal-card {
116
+ transform: scale(1);
117
+ }
118
+
119
+ .column-modal-close {
120
+ position: absolute;
121
+ top: 16px;
122
+ right: 16px;
123
+ background: transparent;
124
+ border: none;
125
+ color: #8B98A5;
126
+ cursor: pointer;
127
+ font-size: 28px;
128
+ padding: 4px;
129
+ line-height: 1;
130
+ transition: color 0.2s ease;
131
+ }
132
+
133
+ .column-modal-close:hover {
134
+ color: #fff;
135
+ }
136
+
137
+ .column-modal-title {
138
+ color: #fff;
139
+ font-size: 20px;
140
+ font-weight: 600;
141
+ margin: 0 0 24px;
142
+ text-align: center;
143
+ line-height: 1.4;
144
+ }
145
+
146
+ .column-modal-wallet-option {
147
+ background: #252938;
148
+ border: 1px solid rgba(255, 255, 255, 0.08);
149
+ border-radius: 14px;
150
+ padding: 16px 20px;
151
+ width: 100%;
152
+ display: flex;
153
+ justify-content: space-between;
154
+ align-items: center;
155
+ cursor: pointer;
156
+ transition: all 0.2s ease;
157
+ }
158
+
159
+ .column-modal-wallet-option:hover {
160
+ background: #2D3142;
161
+ border-color: rgba(255, 218, 52, 0.3);
162
+ transform: translateY(-2px);
163
+ }
164
+
165
+ .column-modal-wallet-left {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 14px;
169
+ }
170
+
171
+ .column-modal-wallet-icon {
172
+ width: 40px;
173
+ height: 40px;
174
+ background: rgba(255, 218, 52, 0.12);
175
+ border-radius: 10px;
176
+ display: flex;
177
+ justify-content: center;
178
+ align-items: center;
179
+ flex-shrink: 0;
180
+ }
181
+
182
+ .column-modal-wallet-name {
183
+ color: #fff;
184
+ font-size: 16px;
185
+ font-weight: 600;
186
+ }
187
+
188
+ .column-modal-wallet-badge {
189
+ background: rgba(76, 175, 80, 0.15);
190
+ color: #4CAF50;
191
+ font-size: 13px;
192
+ font-weight: 600;
193
+ padding: 6px 12px;
194
+ border-radius: 8px;
195
+ }
196
+ `;
197
+ var ColumnConnectModal = class {
198
+ overlay = null;
199
+ onConnect;
200
+ constructor(onConnect) {
201
+ this.onConnect = onConnect;
202
+ this.injectStyles();
203
+ }
204
+ injectStyles() {
205
+ if (typeof document === "undefined") return;
206
+ if (document.getElementById("column-modal-styles")) return;
207
+ const styleTag = document.createElement("style");
208
+ styleTag.id = "column-modal-styles";
209
+ styleTag.innerHTML = ColumnModalStyles;
210
+ document.head.appendChild(styleTag);
211
+ }
212
+ open() {
213
+ if (typeof document === "undefined") return;
214
+ this.overlay = document.createElement("div");
215
+ this.overlay.className = "column-modal-overlay";
216
+ this.overlay.innerHTML = `
217
+ <div class="column-modal-card">
218
+ <button class="column-modal-close" id="column-modal-close">&times;</button>
219
+ <h2 class="column-modal-title">Connect a wallet on<br>Movement to continue</h2>
220
+
221
+ <button class="column-modal-wallet-option" id="column-modal-connect-btn">
222
+ <div class="column-modal-wallet-left">
223
+ <div class="column-modal-wallet-icon">
224
+ <svg width="28" height="28" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
225
+ <path d="M16 4L28 28H4L16 4Z" stroke="#ffda34" stroke-width="2.5" stroke-linejoin="round"/>
226
+ <circle cx="16" cy="18" r="4" fill="#ffda34"/>
227
+ </svg>
228
+ </div>
229
+ <span class="column-modal-wallet-name">Column Wallet</span>
230
+ </div>
231
+ <span class="column-modal-wallet-badge">Mobile</span>
232
+ </button>
233
+ </div>
234
+ `;
235
+ document.body.appendChild(this.overlay);
236
+ setTimeout(() => {
237
+ this.overlay?.classList.add("active");
238
+ }, 10);
239
+ document.getElementById("column-modal-close")?.addEventListener("click", () => this.close());
240
+ document.getElementById("column-modal-connect-btn")?.addEventListener("click", () => {
241
+ this.onConnect();
242
+ this.close();
243
+ });
244
+ this.overlay.addEventListener("click", (e) => {
245
+ if (e.target === this.overlay) this.close();
246
+ });
247
+ }
248
+ close() {
249
+ if (!this.overlay) return;
250
+ this.overlay.classList.remove("active");
251
+ setTimeout(() => {
252
+ if (this.overlay && this.overlay.parentNode) {
253
+ this.overlay.parentNode.removeChild(this.overlay);
254
+ }
255
+ this.overlay = null;
256
+ }, 300);
257
+ }
258
+ };
259
+
260
+ // src/ColumnWalletSDK.ts
261
+ var ColumnWalletSDK = class {
262
+ config;
263
+ sessionKeyPair;
264
+ walletEncryptionPublicKey = null;
265
+ modal;
266
+ constructor(config) {
267
+ this.config = this.autoDetectMetadata(config);
268
+ this.sessionKeyPair = ColumnCrypto.generateKeyPair();
269
+ this.modal = new ColumnConnectModal(() => {
270
+ window.location.href = this.connect();
271
+ });
272
+ }
273
+ autoDetectMetadata(config) {
274
+ if (typeof document === "undefined") return config;
275
+ const detectedConfig = { ...config };
276
+ if (!detectedConfig.appUrl) {
277
+ detectedConfig.appUrl = window.location.origin;
278
+ }
279
+ if (!detectedConfig.appName) {
280
+ detectedConfig.appName = document.title || "Unknown dApp";
281
+ }
282
+ if (!detectedConfig.appIcon) {
283
+ const iconLink = document.querySelector('link[rel~="icon"]') || document.querySelector('link[rel="apple-touch-icon"]');
284
+ if (iconLink) {
285
+ try {
286
+ detectedConfig.appIcon = new URL(iconLink.getAttribute("href") || "", window.location.origin).href;
287
+ } catch (e) {
288
+ detectedConfig.appIcon = iconLink.href;
289
+ }
290
+ }
291
+ }
292
+ if (!detectedConfig.appDescription) {
293
+ const metaDescription = document.querySelector('meta[name="description"]') || document.querySelector('meta[property="og:description"]');
294
+ if (metaDescription?.content) {
295
+ detectedConfig.appDescription = metaDescription.content;
296
+ }
297
+ }
298
+ if (!detectedConfig.redirectLink) {
299
+ detectedConfig.redirectLink = window.location.href.split("?")[0];
300
+ }
301
+ return detectedConfig;
302
+ }
303
+ /**
304
+ * Opens the premium Column selection modal
305
+ */
306
+ openConnectModal() {
307
+ this.modal.open();
308
+ }
309
+ setWalletEncryptionPublicKey(key) {
310
+ this.walletEncryptionPublicKey = key;
311
+ }
312
+ /**
313
+ * Alias for setWalletEncryptionPublicKey to match docs
314
+ */
315
+ importWalletKey(key) {
316
+ this.setWalletEncryptionPublicKey(key);
317
+ }
318
+ /**
319
+ * Step 1: Connect to the wallet
320
+ */
321
+ connect() {
322
+ const params = new URLSearchParams({
323
+ request: "connect",
324
+ app_url: this.config.appUrl || "",
325
+ app_name: this.config.appName || "",
326
+ dapp_encryption_public_key: import_bs582.default.encode(this.sessionKeyPair.publicKey),
327
+ redirect_link: this.config.redirectLink || ""
328
+ });
329
+ if (this.config.appIcon) {
330
+ params.append("app_icon", this.config.appIcon);
331
+ }
332
+ if (this.config.network) {
333
+ params.append("network", this.config.network);
334
+ }
335
+ if (this.config.appDescription) {
336
+ params.append("app_description", this.config.appDescription);
337
+ }
338
+ const scheme = this.config.walletScheme || "column";
339
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
340
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
341
+ }
342
+ /**
343
+ * Step 2: Sign and Submit a transaction
344
+ */
345
+ signAndSubmitTransaction(transaction) {
346
+ if (!this.walletEncryptionPublicKey) {
347
+ throw new Error("Wallet not connected. Call connect() first and store the wallet_encryption_public_key.");
348
+ }
349
+ const payload = {
350
+ transaction
351
+ };
352
+ const encrypted = ColumnCrypto.encryptPayload(
353
+ payload,
354
+ this.walletEncryptionPublicKey,
355
+ this.sessionKeyPair.secretKey
356
+ );
357
+ const params = new URLSearchParams({
358
+ request: "signAndSubmitTransaction",
359
+ app_url: this.config.appUrl || "",
360
+ app_name: this.config.appName || "",
361
+ dapp_encryption_public_key: import_bs582.default.encode(this.sessionKeyPair.publicKey),
362
+ data: encrypted.data,
363
+ nonce: encrypted.nonce,
364
+ redirect_link: this.config.redirectLink || ""
365
+ });
366
+ if (this.config.appIcon) {
367
+ params.append("app_icon", this.config.appIcon);
368
+ }
369
+ if (this.config.network) {
370
+ params.append("network", this.config.network);
371
+ }
372
+ if (this.config.appDescription) {
373
+ params.append("app_description", this.config.appDescription);
374
+ }
375
+ const scheme = this.config.walletScheme || "column";
376
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
377
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
378
+ }
379
+ /**
380
+ * Step 3: Sign a simple message
381
+ */
382
+ signMessage(message) {
383
+ if (!this.walletEncryptionPublicKey) {
384
+ throw new Error("Wallet not connected.");
385
+ }
386
+ const payload = {
387
+ message
388
+ };
389
+ const encrypted = ColumnCrypto.encryptPayload(
390
+ payload,
391
+ this.walletEncryptionPublicKey,
392
+ this.sessionKeyPair.secretKey
393
+ );
394
+ const params = new URLSearchParams({
395
+ request: "signMessage",
396
+ app_url: this.config.appUrl || "",
397
+ app_name: this.config.appName || "",
398
+ dapp_encryption_public_key: import_bs582.default.encode(this.sessionKeyPair.publicKey),
399
+ data: encrypted.data,
400
+ nonce: encrypted.nonce,
401
+ redirect_link: this.config.redirectLink || ""
402
+ });
403
+ if (this.config.appIcon) {
404
+ params.append("app_icon", this.config.appIcon);
405
+ }
406
+ if (this.config.network) {
407
+ params.append("network", this.config.network);
408
+ }
409
+ if (this.config.appDescription) {
410
+ params.append("app_description", this.config.appDescription);
411
+ }
412
+ const scheme = this.config.walletScheme || "column";
413
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
414
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
415
+ }
416
+ /**
417
+ * Utility: Parse the response from Column
418
+ */
419
+ handleResponse(url) {
420
+ let params;
421
+ try {
422
+ const urlObj = new URL(url.includes("://") ? url : `http://localhost/${url}`);
423
+ const hashStr = urlObj.hash.substring(1);
424
+ const searchStr = urlObj.search.substring(1);
425
+ if (hashStr) {
426
+ params = new URLSearchParams(hashStr);
427
+ } else {
428
+ params = new URLSearchParams(searchStr);
429
+ }
430
+ } catch (e) {
431
+ const hashMatch = url.match(/#(.*)$/);
432
+ const queryMatch = url.match(/\?(.*)$/);
433
+ params = new URLSearchParams(hashMatch ? hashMatch[1] : queryMatch ? queryMatch[1] : url);
434
+ }
435
+ const data = params.get("data");
436
+ const nonce = params.get("nonce");
437
+ const error = params.get("error");
438
+ if (error) {
439
+ throw new Error(`Column Wallet Error: ${error}`);
440
+ }
441
+ if (data && nonce && this.walletEncryptionPublicKey) {
442
+ const decrypted = ColumnCrypto.decryptPayload(
443
+ data,
444
+ nonce,
445
+ this.walletEncryptionPublicKey,
446
+ this.sessionKeyPair.secretKey
447
+ );
448
+ return {
449
+ ...decrypted,
450
+ network: params.get("network")
451
+ // Always include network from URL
452
+ };
453
+ }
454
+ const response = {};
455
+ params.forEach((value, key) => {
456
+ response[key] = value;
457
+ });
458
+ if (response.column_encryption_public_key) {
459
+ this.walletEncryptionPublicKey = response.column_encryption_public_key;
460
+ }
461
+ return response;
462
+ }
463
+ /**
464
+ * Sets the wallet's public key if known from a previous session
465
+ */
466
+ setWalletPublicKey(publicKeyB58) {
467
+ this.walletEncryptionPublicKey = publicKeyB58;
468
+ }
469
+ };
470
+ // Annotate the CommonJS export names for ESM import in node:
471
+ 0 && (module.exports = {
472
+ ColumnCrypto,
473
+ ColumnWalletSDK
474
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,436 @@
1
+ // src/ColumnWalletSDK.ts
2
+ import bs582 from "bs58";
3
+
4
+ // src/ColumnShared.ts
5
+ import * as nacl from "tweetnacl";
6
+ import bs58 from "bs58";
7
+ import { Buffer } from "buffer";
8
+ var ColumnCrypto = class {
9
+ static generateKeyPair() {
10
+ return nacl.box.keyPair();
11
+ }
12
+ static encryptPayload(payload, targetPublicKeyB58, mySecretKey) {
13
+ const sharedSecret = nacl.box.before(
14
+ bs58.decode(targetPublicKeyB58),
15
+ mySecretKey
16
+ );
17
+ const nonce = nacl.randomBytes(nacl.box.nonceLength);
18
+ const message = Buffer.from(JSON.stringify(payload), "utf8");
19
+ const encrypted = nacl.box.after(message, nonce, sharedSecret);
20
+ return {
21
+ data: bs58.encode(encrypted),
22
+ nonce: bs58.encode(nonce)
23
+ };
24
+ }
25
+ static decryptPayload(encryptedDataB58, nonceB58, sourcePublicKeyB58, mySecretKey) {
26
+ const sharedSecret = nacl.box.before(
27
+ bs58.decode(sourcePublicKeyB58),
28
+ mySecretKey
29
+ );
30
+ const decrypted = nacl.box.open.after(
31
+ bs58.decode(encryptedDataB58),
32
+ bs58.decode(nonceB58),
33
+ sharedSecret
34
+ );
35
+ if (!decrypted) return null;
36
+ const jsonString = Buffer.from(decrypted).toString("utf8");
37
+ return JSON.parse(jsonString);
38
+ }
39
+ };
40
+
41
+ // src/ui/ColumnConnectModal.ts
42
+ var ColumnModalStyles = `
43
+ .column-modal-overlay {
44
+ position: fixed;
45
+ top: 0;
46
+ left: 0;
47
+ width: 100%;
48
+ height: 100%;
49
+ background: rgba(0, 0, 0, 0.75);
50
+ backdrop-filter: blur(8px);
51
+ -webkit-backdrop-filter: blur(8px);
52
+ display: flex;
53
+ justify-content: center;
54
+ align-items: center;
55
+ z-index: 999999;
56
+ opacity: 0;
57
+ transition: opacity 0.3s ease;
58
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
59
+ }
60
+
61
+ .column-modal-overlay.active {
62
+ opacity: 1;
63
+ }
64
+
65
+ .column-modal-card {
66
+ background: #1C1F2E;
67
+ width: 100%;
68
+ max-width: 420px;
69
+ border-radius: 20px;
70
+ padding: 28px;
71
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
72
+ border: 1px solid rgba(255, 255, 255, 0.1);
73
+ transform: scale(0.95);
74
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
75
+ position: relative;
76
+ }
77
+
78
+ .column-modal-overlay.active .column-modal-card {
79
+ transform: scale(1);
80
+ }
81
+
82
+ .column-modal-close {
83
+ position: absolute;
84
+ top: 16px;
85
+ right: 16px;
86
+ background: transparent;
87
+ border: none;
88
+ color: #8B98A5;
89
+ cursor: pointer;
90
+ font-size: 28px;
91
+ padding: 4px;
92
+ line-height: 1;
93
+ transition: color 0.2s ease;
94
+ }
95
+
96
+ .column-modal-close:hover {
97
+ color: #fff;
98
+ }
99
+
100
+ .column-modal-title {
101
+ color: #fff;
102
+ font-size: 20px;
103
+ font-weight: 600;
104
+ margin: 0 0 24px;
105
+ text-align: center;
106
+ line-height: 1.4;
107
+ }
108
+
109
+ .column-modal-wallet-option {
110
+ background: #252938;
111
+ border: 1px solid rgba(255, 255, 255, 0.08);
112
+ border-radius: 14px;
113
+ padding: 16px 20px;
114
+ width: 100%;
115
+ display: flex;
116
+ justify-content: space-between;
117
+ align-items: center;
118
+ cursor: pointer;
119
+ transition: all 0.2s ease;
120
+ }
121
+
122
+ .column-modal-wallet-option:hover {
123
+ background: #2D3142;
124
+ border-color: rgba(255, 218, 52, 0.3);
125
+ transform: translateY(-2px);
126
+ }
127
+
128
+ .column-modal-wallet-left {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 14px;
132
+ }
133
+
134
+ .column-modal-wallet-icon {
135
+ width: 40px;
136
+ height: 40px;
137
+ background: rgba(255, 218, 52, 0.12);
138
+ border-radius: 10px;
139
+ display: flex;
140
+ justify-content: center;
141
+ align-items: center;
142
+ flex-shrink: 0;
143
+ }
144
+
145
+ .column-modal-wallet-name {
146
+ color: #fff;
147
+ font-size: 16px;
148
+ font-weight: 600;
149
+ }
150
+
151
+ .column-modal-wallet-badge {
152
+ background: rgba(76, 175, 80, 0.15);
153
+ color: #4CAF50;
154
+ font-size: 13px;
155
+ font-weight: 600;
156
+ padding: 6px 12px;
157
+ border-radius: 8px;
158
+ }
159
+ `;
160
+ var ColumnConnectModal = class {
161
+ overlay = null;
162
+ onConnect;
163
+ constructor(onConnect) {
164
+ this.onConnect = onConnect;
165
+ this.injectStyles();
166
+ }
167
+ injectStyles() {
168
+ if (typeof document === "undefined") return;
169
+ if (document.getElementById("column-modal-styles")) return;
170
+ const styleTag = document.createElement("style");
171
+ styleTag.id = "column-modal-styles";
172
+ styleTag.innerHTML = ColumnModalStyles;
173
+ document.head.appendChild(styleTag);
174
+ }
175
+ open() {
176
+ if (typeof document === "undefined") return;
177
+ this.overlay = document.createElement("div");
178
+ this.overlay.className = "column-modal-overlay";
179
+ this.overlay.innerHTML = `
180
+ <div class="column-modal-card">
181
+ <button class="column-modal-close" id="column-modal-close">&times;</button>
182
+ <h2 class="column-modal-title">Connect a wallet on<br>Movement to continue</h2>
183
+
184
+ <button class="column-modal-wallet-option" id="column-modal-connect-btn">
185
+ <div class="column-modal-wallet-left">
186
+ <div class="column-modal-wallet-icon">
187
+ <svg width="28" height="28" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
188
+ <path d="M16 4L28 28H4L16 4Z" stroke="#ffda34" stroke-width="2.5" stroke-linejoin="round"/>
189
+ <circle cx="16" cy="18" r="4" fill="#ffda34"/>
190
+ </svg>
191
+ </div>
192
+ <span class="column-modal-wallet-name">Column Wallet</span>
193
+ </div>
194
+ <span class="column-modal-wallet-badge">Mobile</span>
195
+ </button>
196
+ </div>
197
+ `;
198
+ document.body.appendChild(this.overlay);
199
+ setTimeout(() => {
200
+ this.overlay?.classList.add("active");
201
+ }, 10);
202
+ document.getElementById("column-modal-close")?.addEventListener("click", () => this.close());
203
+ document.getElementById("column-modal-connect-btn")?.addEventListener("click", () => {
204
+ this.onConnect();
205
+ this.close();
206
+ });
207
+ this.overlay.addEventListener("click", (e) => {
208
+ if (e.target === this.overlay) this.close();
209
+ });
210
+ }
211
+ close() {
212
+ if (!this.overlay) return;
213
+ this.overlay.classList.remove("active");
214
+ setTimeout(() => {
215
+ if (this.overlay && this.overlay.parentNode) {
216
+ this.overlay.parentNode.removeChild(this.overlay);
217
+ }
218
+ this.overlay = null;
219
+ }, 300);
220
+ }
221
+ };
222
+
223
+ // src/ColumnWalletSDK.ts
224
+ var ColumnWalletSDK = class {
225
+ config;
226
+ sessionKeyPair;
227
+ walletEncryptionPublicKey = null;
228
+ modal;
229
+ constructor(config) {
230
+ this.config = this.autoDetectMetadata(config);
231
+ this.sessionKeyPair = ColumnCrypto.generateKeyPair();
232
+ this.modal = new ColumnConnectModal(() => {
233
+ window.location.href = this.connect();
234
+ });
235
+ }
236
+ autoDetectMetadata(config) {
237
+ if (typeof document === "undefined") return config;
238
+ const detectedConfig = { ...config };
239
+ if (!detectedConfig.appUrl) {
240
+ detectedConfig.appUrl = window.location.origin;
241
+ }
242
+ if (!detectedConfig.appName) {
243
+ detectedConfig.appName = document.title || "Unknown dApp";
244
+ }
245
+ if (!detectedConfig.appIcon) {
246
+ const iconLink = document.querySelector('link[rel~="icon"]') || document.querySelector('link[rel="apple-touch-icon"]');
247
+ if (iconLink) {
248
+ try {
249
+ detectedConfig.appIcon = new URL(iconLink.getAttribute("href") || "", window.location.origin).href;
250
+ } catch (e) {
251
+ detectedConfig.appIcon = iconLink.href;
252
+ }
253
+ }
254
+ }
255
+ if (!detectedConfig.appDescription) {
256
+ const metaDescription = document.querySelector('meta[name="description"]') || document.querySelector('meta[property="og:description"]');
257
+ if (metaDescription?.content) {
258
+ detectedConfig.appDescription = metaDescription.content;
259
+ }
260
+ }
261
+ if (!detectedConfig.redirectLink) {
262
+ detectedConfig.redirectLink = window.location.href.split("?")[0];
263
+ }
264
+ return detectedConfig;
265
+ }
266
+ /**
267
+ * Opens the premium Column selection modal
268
+ */
269
+ openConnectModal() {
270
+ this.modal.open();
271
+ }
272
+ setWalletEncryptionPublicKey(key) {
273
+ this.walletEncryptionPublicKey = key;
274
+ }
275
+ /**
276
+ * Alias for setWalletEncryptionPublicKey to match docs
277
+ */
278
+ importWalletKey(key) {
279
+ this.setWalletEncryptionPublicKey(key);
280
+ }
281
+ /**
282
+ * Step 1: Connect to the wallet
283
+ */
284
+ connect() {
285
+ const params = new URLSearchParams({
286
+ request: "connect",
287
+ app_url: this.config.appUrl || "",
288
+ app_name: this.config.appName || "",
289
+ dapp_encryption_public_key: bs582.encode(this.sessionKeyPair.publicKey),
290
+ redirect_link: this.config.redirectLink || ""
291
+ });
292
+ if (this.config.appIcon) {
293
+ params.append("app_icon", this.config.appIcon);
294
+ }
295
+ if (this.config.network) {
296
+ params.append("network", this.config.network);
297
+ }
298
+ if (this.config.appDescription) {
299
+ params.append("app_description", this.config.appDescription);
300
+ }
301
+ const scheme = this.config.walletScheme || "column";
302
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
303
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
304
+ }
305
+ /**
306
+ * Step 2: Sign and Submit a transaction
307
+ */
308
+ signAndSubmitTransaction(transaction) {
309
+ if (!this.walletEncryptionPublicKey) {
310
+ throw new Error("Wallet not connected. Call connect() first and store the wallet_encryption_public_key.");
311
+ }
312
+ const payload = {
313
+ transaction
314
+ };
315
+ const encrypted = ColumnCrypto.encryptPayload(
316
+ payload,
317
+ this.walletEncryptionPublicKey,
318
+ this.sessionKeyPair.secretKey
319
+ );
320
+ const params = new URLSearchParams({
321
+ request: "signAndSubmitTransaction",
322
+ app_url: this.config.appUrl || "",
323
+ app_name: this.config.appName || "",
324
+ dapp_encryption_public_key: bs582.encode(this.sessionKeyPair.publicKey),
325
+ data: encrypted.data,
326
+ nonce: encrypted.nonce,
327
+ redirect_link: this.config.redirectLink || ""
328
+ });
329
+ if (this.config.appIcon) {
330
+ params.append("app_icon", this.config.appIcon);
331
+ }
332
+ if (this.config.network) {
333
+ params.append("network", this.config.network);
334
+ }
335
+ if (this.config.appDescription) {
336
+ params.append("app_description", this.config.appDescription);
337
+ }
338
+ const scheme = this.config.walletScheme || "column";
339
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
340
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
341
+ }
342
+ /**
343
+ * Step 3: Sign a simple message
344
+ */
345
+ signMessage(message) {
346
+ if (!this.walletEncryptionPublicKey) {
347
+ throw new Error("Wallet not connected.");
348
+ }
349
+ const payload = {
350
+ message
351
+ };
352
+ const encrypted = ColumnCrypto.encryptPayload(
353
+ payload,
354
+ this.walletEncryptionPublicKey,
355
+ this.sessionKeyPair.secretKey
356
+ );
357
+ const params = new URLSearchParams({
358
+ request: "signMessage",
359
+ app_url: this.config.appUrl || "",
360
+ app_name: this.config.appName || "",
361
+ dapp_encryption_public_key: bs582.encode(this.sessionKeyPair.publicKey),
362
+ data: encrypted.data,
363
+ nonce: encrypted.nonce,
364
+ redirect_link: this.config.redirectLink || ""
365
+ });
366
+ if (this.config.appIcon) {
367
+ params.append("app_icon", this.config.appIcon);
368
+ }
369
+ if (this.config.network) {
370
+ params.append("network", this.config.network);
371
+ }
372
+ if (this.config.appDescription) {
373
+ params.append("app_description", this.config.appDescription);
374
+ }
375
+ const scheme = this.config.walletScheme || "column";
376
+ const base = scheme.includes("://") ? scheme : `${scheme}://`;
377
+ return `${base}${base.endsWith("/") ? "" : "/"}?${params.toString()}`;
378
+ }
379
+ /**
380
+ * Utility: Parse the response from Column
381
+ */
382
+ handleResponse(url) {
383
+ let params;
384
+ try {
385
+ const urlObj = new URL(url.includes("://") ? url : `http://localhost/${url}`);
386
+ const hashStr = urlObj.hash.substring(1);
387
+ const searchStr = urlObj.search.substring(1);
388
+ if (hashStr) {
389
+ params = new URLSearchParams(hashStr);
390
+ } else {
391
+ params = new URLSearchParams(searchStr);
392
+ }
393
+ } catch (e) {
394
+ const hashMatch = url.match(/#(.*)$/);
395
+ const queryMatch = url.match(/\?(.*)$/);
396
+ params = new URLSearchParams(hashMatch ? hashMatch[1] : queryMatch ? queryMatch[1] : url);
397
+ }
398
+ const data = params.get("data");
399
+ const nonce = params.get("nonce");
400
+ const error = params.get("error");
401
+ if (error) {
402
+ throw new Error(`Column Wallet Error: ${error}`);
403
+ }
404
+ if (data && nonce && this.walletEncryptionPublicKey) {
405
+ const decrypted = ColumnCrypto.decryptPayload(
406
+ data,
407
+ nonce,
408
+ this.walletEncryptionPublicKey,
409
+ this.sessionKeyPair.secretKey
410
+ );
411
+ return {
412
+ ...decrypted,
413
+ network: params.get("network")
414
+ // Always include network from URL
415
+ };
416
+ }
417
+ const response = {};
418
+ params.forEach((value, key) => {
419
+ response[key] = value;
420
+ });
421
+ if (response.column_encryption_public_key) {
422
+ this.walletEncryptionPublicKey = response.column_encryption_public_key;
423
+ }
424
+ return response;
425
+ }
426
+ /**
427
+ * Sets the wallet's public key if known from a previous session
428
+ */
429
+ setWalletPublicKey(publicKeyB58) {
430
+ this.walletEncryptionPublicKey = publicKeyB58;
431
+ }
432
+ };
433
+ export {
434
+ ColumnCrypto,
435
+ ColumnWalletSDK
436
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@column-org/wallet-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Column Wallet Mobile Deep-Link SDK for Movement/Aptos ecosystem",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
16
+ "lint": "eslint src/**/*.ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "column",
21
+ "wallet",
22
+ "aptos",
23
+ "movement",
24
+ "deeplink",
25
+ "sdk",
26
+ "web3"
27
+ ],
28
+ "author": "Column Labs",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "bs58": "^6.0.0",
32
+ "buffer": "^6.0.3",
33
+ "tweetnacl": "^1.0.3"
34
+ },
35
+ "devDependencies": {
36
+ "tsup": "^8.0.2",
37
+ "typescript": "^5.3.3"
38
+ }
39
+ }