@column-org/wallet-sdk 1.1.0 → 1.1.2

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/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@column-org/wallet-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Column Wallet Mobile Deep-Link SDK for Movement/Aptos ecosystem",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
+ "react-native": "src/index.native.ts",
7
8
  "types": "dist/index.d.ts",
8
9
  "files": [
9
- "dist"
10
+ "dist",
11
+ "src"
10
12
  ],
11
13
  "publishConfig": {
12
14
  "access": "public"
@@ -16,6 +18,10 @@
16
18
  "lint": "eslint src/**/*.ts",
17
19
  "prepublishOnly": "npm run build"
18
20
  },
21
+ "peerDependencies": {
22
+ "react": ">=16.8.0",
23
+ "react-native": ">=0.60.0"
24
+ },
19
25
  "keywords": [
20
26
  "column",
21
27
  "wallet",
@@ -30,9 +36,12 @@
30
36
  "dependencies": {
31
37
  "bs58": "^6.0.0",
32
38
  "buffer": "^6.0.3",
39
+ "react-native-get-random-values": "^1.11.0",
33
40
  "tweetnacl": "^1.0.3"
34
41
  },
35
42
  "devDependencies": {
43
+ "react": "^18.2.0",
44
+ "react-native": "^0.73.0",
36
45
  "tsup": "^8.0.2",
37
46
  "typescript": "^5.3.3"
38
47
  }
@@ -0,0 +1,69 @@
1
+ import * as nacl from 'tweetnacl'
2
+ import bs58 from 'bs58'
3
+ import { Buffer } from 'buffer'
4
+
5
+ export interface ColumnSDKConfig {
6
+ appUrl?: string
7
+ appName?: string
8
+ redirectLink?: string
9
+ appIcon?: string
10
+ appDescription?: string
11
+ walletScheme?: string
12
+ network?: string
13
+ sessionSecretKey?: string
14
+ }
15
+
16
+ export class ColumnCrypto {
17
+ static generateKeyPair(): nacl.BoxKeyPair {
18
+ return nacl.box.keyPair()
19
+ }
20
+
21
+ static encryptPayload(
22
+ payload: any,
23
+ targetPublicKeyB58: string,
24
+ mySecretKey: Uint8Array
25
+ ): { data: string; nonce: string } {
26
+ const sharedSecret = nacl.box.before(
27
+ bs58.decode(targetPublicKeyB58),
28
+ mySecretKey
29
+ )
30
+
31
+ const nonce = nacl.randomBytes(nacl.box.nonceLength)
32
+ const message = Buffer.from(JSON.stringify(payload), 'utf8')
33
+
34
+ const encrypted = nacl.box.after(message, nonce, sharedSecret)
35
+
36
+ return {
37
+ data: bs58.encode(encrypted),
38
+ nonce: bs58.encode(nonce)
39
+ }
40
+ }
41
+
42
+ static decryptPayload(
43
+ encryptedDataB58: string,
44
+ nonceB58: string,
45
+ sourcePublicKeyB58: string,
46
+ mySecretKey: Uint8Array
47
+ ): any {
48
+ const sharedSecret = nacl.box.before(
49
+ bs58.decode(sourcePublicKeyB58),
50
+ mySecretKey
51
+ )
52
+
53
+ const decrypted = nacl.box.open.after(
54
+ bs58.decode(encryptedDataB58),
55
+ bs58.decode(nonceB58),
56
+ sharedSecret
57
+ )
58
+
59
+ if (!decrypted) return null
60
+
61
+ try {
62
+ const jsonString = Buffer.from(decrypted).toString('utf8')
63
+ return JSON.parse(jsonString)
64
+ } catch (e) {
65
+ console.error('ColumnCrypto: Failed to parse decrypted payload', e)
66
+ return null
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,236 @@
1
+ import bs58 from 'bs58'
2
+ import { ColumnCrypto, ColumnSDKConfig } from './ColumnShared'
3
+ import * as nacl from 'tweetnacl'
4
+ import { ColumnConnectModal } from './ui/ColumnConnectModal'
5
+
6
+ export class ColumnWalletSDK {
7
+ private config: ColumnSDKConfig
8
+ private sessionKeyPair: nacl.BoxKeyPair
9
+ private walletEncryptionPublicKey: string | null = null
10
+
11
+ constructor(config: ColumnSDKConfig) {
12
+ this.config = config
13
+
14
+ if (config.sessionSecretKey) {
15
+ try {
16
+ const secretKey = bs58.decode(config.sessionSecretKey)
17
+ this.sessionKeyPair = nacl.box.keyPair.fromSecretKey(secretKey)
18
+ } catch (e) {
19
+ console.warn('ColumnWalletSDK: Invalid sessionSecretKey provided, generating new one.')
20
+ this.sessionKeyPair = ColumnCrypto.generateKeyPair()
21
+ }
22
+ } else {
23
+ this.sessionKeyPair = ColumnCrypto.generateKeyPair()
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Returns the current session's secret key (bs58) so it can be persisted
29
+ */
30
+ public getSessionSecretKey(): string {
31
+ return bs58.encode(this.sessionKeyPair.secretKey)
32
+ }
33
+
34
+ /**
35
+ * Internal: Used by Web/Native adapters to get the config
36
+ */
37
+ public getConfig(): ColumnSDKConfig {
38
+ return this.config
39
+ }
40
+
41
+ public setWalletEncryptionPublicKey(key: string) {
42
+ this.walletEncryptionPublicKey = key
43
+ }
44
+
45
+ /**
46
+ * Alias for setWalletEncryptionPublicKey to match docs
47
+ */
48
+ public importWalletKey(key: string) {
49
+ this.setWalletEncryptionPublicKey(key)
50
+ }
51
+
52
+ /**
53
+ * Step 1: Connect to the wallet
54
+ */
55
+ public connect(): string {
56
+ const params = new URLSearchParams({
57
+ request: 'connect',
58
+ app_url: this.config.appUrl || '',
59
+ app_name: this.config.appName || '',
60
+ dapp_encryption_public_key: bs58.encode(this.sessionKeyPair.publicKey),
61
+ redirect_link: this.config.redirectLink || ''
62
+ } as Record<string, string>)
63
+
64
+ if (this.config.appIcon) {
65
+ params.append('app_icon', this.config.appIcon)
66
+ }
67
+
68
+ if (this.config.network) {
69
+ params.append('network', this.config.network)
70
+ }
71
+
72
+ if (this.config.appDescription) {
73
+ params.append('app_description', this.config.appDescription)
74
+ }
75
+
76
+ const scheme = this.config.walletScheme || 'column'
77
+ const base = scheme.includes('://') ? scheme : `${scheme}://`
78
+ return `${base}${base.endsWith('/') ? '' : '/'}?${params.toString()}`
79
+ }
80
+
81
+ /**
82
+ * Step 2: Sign and Submit a transaction
83
+ */
84
+ public signAndSubmitTransaction(transaction: any): string {
85
+ if (!this.walletEncryptionPublicKey) {
86
+ throw new Error('Wallet not connected. Call connect() first and store the wallet_encryption_public_key.')
87
+ }
88
+
89
+ const payload = {
90
+ transaction: transaction,
91
+ }
92
+
93
+ const encrypted = ColumnCrypto.encryptPayload(
94
+ payload,
95
+ this.walletEncryptionPublicKey,
96
+ this.sessionKeyPair.secretKey
97
+ )
98
+
99
+ const params = new URLSearchParams({
100
+ request: 'signAndSubmitTransaction',
101
+ app_url: this.config.appUrl || '',
102
+ app_name: this.config.appName || '',
103
+ dapp_encryption_public_key: bs58.encode(this.sessionKeyPair.publicKey),
104
+ data: encrypted.data,
105
+ nonce: encrypted.nonce,
106
+ redirect_link: this.config.redirectLink || ''
107
+ } as Record<string, string>)
108
+
109
+ if (this.config.appIcon) {
110
+ params.append('app_icon', this.config.appIcon)
111
+ }
112
+ if (this.config.network) {
113
+ params.append('network', this.config.network)
114
+ }
115
+ if (this.config.appDescription) {
116
+ params.append('app_description', this.config.appDescription)
117
+ }
118
+
119
+ const scheme = this.config.walletScheme || 'column'
120
+ const base = scheme.includes('://') ? scheme : `${scheme}://`
121
+ return `${base}${base.endsWith('/') ? '' : '/'}?${params.toString()}`
122
+ }
123
+
124
+ /**
125
+ * Step 3: Sign a simple message
126
+ */
127
+ public signMessage(message: string): string {
128
+ if (!this.walletEncryptionPublicKey) {
129
+ throw new Error('Wallet not connected.')
130
+ }
131
+
132
+ const payload = {
133
+ message: message,
134
+ }
135
+
136
+ const encrypted = ColumnCrypto.encryptPayload(
137
+ payload,
138
+ this.walletEncryptionPublicKey,
139
+ this.sessionKeyPair.secretKey
140
+ )
141
+
142
+ const params = new URLSearchParams({
143
+ request: 'signMessage',
144
+ app_url: this.config.appUrl || '',
145
+ app_name: this.config.appName || '',
146
+ dapp_encryption_public_key: bs58.encode(this.sessionKeyPair.publicKey),
147
+ data: encrypted.data,
148
+ nonce: encrypted.nonce,
149
+ redirect_link: this.config.redirectLink || ''
150
+ } as Record<string, string>)
151
+
152
+ if (this.config.appIcon) {
153
+ params.append('app_icon', this.config.appIcon)
154
+ }
155
+ if (this.config.network) {
156
+ params.append('network', this.config.network)
157
+ }
158
+ if (this.config.appDescription) {
159
+ params.append('app_description', this.config.appDescription)
160
+ }
161
+
162
+ const scheme = this.config.walletScheme || 'column'
163
+ const base = scheme.includes('://') ? scheme : `${scheme}://`
164
+ return `${base}${base.endsWith('/') ? '' : '/'}?${params.toString()}`
165
+ }
166
+
167
+ /**
168
+ * Utility: Parse the response from Column
169
+ */
170
+ public handleResponse(url: string): any {
171
+ let params: URLSearchParams;
172
+
173
+ try {
174
+ const urlObj = new URL(url.includes('://') ? url : `http://localhost/${url}`);
175
+ // Prioritize Hash params if they exist (used for tab burial redirection)
176
+ const hashStr = urlObj.hash.substring(1);
177
+ const searchStr = urlObj.search.substring(1);
178
+
179
+ if (hashStr) {
180
+ params = new URLSearchParams(hashStr);
181
+ } else {
182
+ params = new URLSearchParams(searchStr);
183
+ }
184
+ } catch (e) {
185
+ // Fallback for native schemes or malformed URLs
186
+ const hashMatch = url.match(/#(.*)$/);
187
+ const queryMatch = url.match(/\?(.*)$/);
188
+ params = new URLSearchParams(hashMatch ? hashMatch[1] : (queryMatch ? queryMatch[1] : url));
189
+ }
190
+
191
+ const data = params.get('data')
192
+ const nonce = params.get('nonce')
193
+ const error = params.get('error')
194
+
195
+ if (error) {
196
+ throw new Error(`Column Wallet Error: ${error}`)
197
+ }
198
+
199
+ if (data && nonce && this.walletEncryptionPublicKey) {
200
+ const decrypted = ColumnCrypto.decryptPayload(
201
+ data,
202
+ nonce,
203
+ this.walletEncryptionPublicKey,
204
+ this.sessionKeyPair.secretKey
205
+ )
206
+
207
+ if (!decrypted) {
208
+ throw new Error("Column Wallet Error: Decryption failed. The response may have been tampered with or keys do not match.")
209
+ }
210
+
211
+ return {
212
+ ...decrypted,
213
+ network: params.get('network') // Always include network from URL
214
+ }
215
+ }
216
+
217
+ // Plaintext fallback for initial connect
218
+ const response: Record<string, string> = {}
219
+ params.forEach((value, key) => {
220
+ response[key] = value
221
+ })
222
+
223
+ if (response.column_encryption_public_key) {
224
+ this.walletEncryptionPublicKey = response.column_encryption_public_key
225
+ }
226
+
227
+ return response
228
+ }
229
+
230
+ /**
231
+ * Sets the wallet's public key if known from a previous session
232
+ */
233
+ public setWalletPublicKey(publicKeyB58: string) {
234
+ this.walletEncryptionPublicKey = publicKeyB58
235
+ }
236
+ }
Binary file
@@ -0,0 +1,11 @@
1
+ import 'react-native-get-random-values';
2
+ import { Buffer } from 'buffer';
3
+
4
+ // Polyfill Buffer for the native environment
5
+ if (typeof global.Buffer === 'undefined') {
6
+ global.Buffer = Buffer;
7
+ }
8
+
9
+ export * from './ColumnWalletSDK';
10
+ export * from './ColumnShared';
11
+ export * from './ui/native/ColumnWalletModal';
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './ColumnWalletSDK'
2
+ export * from './ColumnShared'
3
+ export * from './ui/ColumnWalletWeb'
@@ -0,0 +1,199 @@
1
+ export const ColumnModalStyles = `
2
+ .column-modal-overlay {
3
+ position: fixed;
4
+ top: 0;
5
+ left: 0;
6
+ width: 100%;
7
+ height: 100%;
8
+ background: rgba(0, 0, 0, 0.75);
9
+ backdrop-filter: blur(8px);
10
+ -webkit-backdrop-filter: blur(8px);
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ z-index: 999999;
15
+ opacity: 0;
16
+ transition: opacity 0.3s ease;
17
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
18
+ }
19
+
20
+ .column-modal-overlay.active {
21
+ opacity: 1;
22
+ }
23
+
24
+ .column-modal-card {
25
+ background: #1C1F2E;
26
+ width: 100%;
27
+ max-width: 420px;
28
+ border-radius: 20px;
29
+ padding: 28px;
30
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
31
+ border: 1px solid rgba(255, 255, 255, 0.1);
32
+ transform: scale(0.95);
33
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
34
+ position: relative;
35
+ }
36
+
37
+ .column-modal-overlay.active .column-modal-card {
38
+ transform: scale(1);
39
+ }
40
+
41
+ .column-modal-close {
42
+ position: absolute;
43
+ top: 16px;
44
+ right: 16px;
45
+ background: transparent;
46
+ border: none;
47
+ color: #8B98A5;
48
+ cursor: pointer;
49
+ font-size: 28px;
50
+ padding: 4px;
51
+ line-height: 1;
52
+ transition: color 0.2s ease;
53
+ }
54
+
55
+ .column-modal-close:hover {
56
+ color: #fff;
57
+ }
58
+
59
+ .column-modal-title {
60
+ color: #fff;
61
+ font-size: 20px;
62
+ font-weight: 600;
63
+ margin: 0 0 24px;
64
+ text-align: center;
65
+ line-height: 1.4;
66
+ }
67
+
68
+ .column-modal-wallet-option {
69
+ background: #252938;
70
+ border: 1px solid rgba(255, 255, 255, 0.08);
71
+ border-radius: 14px;
72
+ padding: 16px 20px;
73
+ width: 100%;
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ cursor: pointer;
78
+ transition: all 0.2s ease;
79
+ }
80
+
81
+ .column-modal-wallet-option:hover {
82
+ background: #2D3142;
83
+ border-color: rgba(255, 218, 52, 0.3);
84
+ transform: translateY(-2px);
85
+ }
86
+
87
+ .column-modal-wallet-left {
88
+ display: flex;
89
+ align-items: center;
90
+ gap: 14px;
91
+ }
92
+
93
+ .column-modal-wallet-icon {
94
+ width: 40px;
95
+ height: 40px;
96
+ background: rgba(255, 218, 52, 0.12);
97
+ border-radius: 10px;
98
+ display: flex;
99
+ justify-content: center;
100
+ align-items: center;
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .column-modal-wallet-name {
105
+ color: #fff;
106
+ font-size: 16px;
107
+ font-weight: 600;
108
+ }
109
+
110
+ .column-modal-wallet-badge {
111
+ background: rgba(76, 175, 80, 0.15);
112
+ color: #4CAF50;
113
+ font-size: 13px;
114
+ font-weight: 600;
115
+ padding: 6px 12px;
116
+ border-radius: 8px;
117
+ }
118
+ `;
119
+
120
+ export class ColumnConnectModal {
121
+ private overlay: HTMLDivElement | null = null;
122
+ private onConnect: () => void;
123
+
124
+ constructor(onConnect: () => void) {
125
+ this.onConnect = onConnect;
126
+ this.injectStyles();
127
+ }
128
+
129
+ private injectStyles() {
130
+ if (typeof document === 'undefined') return;
131
+ if (document.getElementById('column-modal-styles')) return;
132
+
133
+ const styleTag = document.createElement('style');
134
+ styleTag.id = 'column-modal-styles';
135
+ styleTag.innerHTML = ColumnModalStyles;
136
+ document.head.appendChild(styleTag);
137
+ }
138
+
139
+ public open() {
140
+ if (typeof document === 'undefined' || this.overlay) return;
141
+
142
+ // Create overlay
143
+ this.overlay = document.createElement('div');
144
+ this.overlay.className = 'column-modal-overlay';
145
+
146
+ // Create card with new design
147
+ this.overlay.innerHTML = `
148
+ <div class="column-modal-card">
149
+ <button class="column-modal-close" id="column-modal-close">&times;</button>
150
+ <h2 class="column-modal-title">Connect a wallet on<br>Movement to continue</h2>
151
+
152
+ <button class="column-modal-wallet-option" id="column-modal-connect-btn">
153
+ <div class="column-modal-wallet-left">
154
+ <div class="column-modal-wallet-icon">
155
+ <svg width="28" height="28" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
156
+ <path d="M16 4L28 28H4L16 4Z" stroke="#ffda34" stroke-width="2.5" stroke-linejoin="round"/>
157
+ <circle cx="16" cy="18" r="4" fill="#ffda34"/>
158
+ </svg>
159
+ </div>
160
+ <span class="column-modal-wallet-name">Column Wallet</span>
161
+ </div>
162
+ <span class="column-modal-wallet-badge">Mobile</span>
163
+ </button>
164
+ </div>
165
+ `;
166
+
167
+ document.body.appendChild(this.overlay);
168
+
169
+ // Animate in
170
+ setTimeout(() => {
171
+ this.overlay?.classList.add('active');
172
+ }, 10);
173
+
174
+ // Bind events
175
+ document.getElementById('column-modal-close')?.addEventListener('click', () => this.close());
176
+
177
+ document.getElementById('column-modal-connect-btn')?.addEventListener('click', () => {
178
+ this.onConnect();
179
+ this.close();
180
+ });
181
+
182
+ // Close on click outside
183
+ this.overlay.addEventListener('click', (e) => {
184
+ if (e.target === this.overlay) this.close();
185
+ });
186
+ }
187
+
188
+ public close() {
189
+ if (!this.overlay) return;
190
+
191
+ this.overlay.classList.remove('active');
192
+ setTimeout(() => {
193
+ if (this.overlay && this.overlay.parentNode) {
194
+ this.overlay.parentNode.removeChild(this.overlay);
195
+ }
196
+ this.overlay = null;
197
+ }, 300);
198
+ }
199
+ }
@@ -0,0 +1,34 @@
1
+ import { ColumnWalletSDK } from '../ColumnWalletSDK';
2
+ import { ColumnConnectModal } from './ColumnConnectModal';
3
+ import { ColumnSDKConfig } from '../ColumnShared';
4
+
5
+ /**
6
+ * Enhanced SDK Class specifically for Web environments
7
+ */
8
+ export class ColumnWalletWeb extends ColumnWalletSDK {
9
+ private modal: ColumnConnectModal;
10
+
11
+ constructor(config: ColumnSDKConfig) {
12
+ const detectedConfig = ColumnWalletWeb.autoDetectMetadata(config);
13
+ super(detectedConfig);
14
+
15
+ this.modal = new ColumnConnectModal(() => {
16
+ window.location.href = this.connect();
17
+ });
18
+ }
19
+
20
+ private static autoDetectMetadata(config: ColumnSDKConfig): ColumnSDKConfig {
21
+ if (typeof document === 'undefined') return config;
22
+ const detectedConfig = { ...config };
23
+
24
+ if (!detectedConfig.appUrl) detectedConfig.appUrl = window.location.origin;
25
+ if (!detectedConfig.appName) detectedConfig.appName = document.title || 'Unknown dApp';
26
+ if (!detectedConfig.redirectLink) detectedConfig.redirectLink = window.location.href.split('?')[0];
27
+
28
+ return detectedConfig;
29
+ }
30
+
31
+ public openConnectModal() {
32
+ this.modal.open();
33
+ }
34
+ }
@@ -0,0 +1,157 @@
1
+ import React from 'react';
2
+ import { View, Text, Modal, StyleSheet, TouchableOpacity, Linking, Image } from 'react-native';
3
+ import { ColumnWalletSDK } from '../../ColumnWalletSDK';
4
+
5
+ interface Props {
6
+ visible: boolean;
7
+ onClose: () => void;
8
+ onConnect: () => void;
9
+ sdk: ColumnWalletSDK; // Pass the initialized SDK instance
10
+ }
11
+
12
+ export function ColumnWalletModal({ visible, onClose, onConnect, sdk }: Props) {
13
+ const handleConnect = async () => {
14
+ try {
15
+ const url = sdk.connect();
16
+
17
+ // Attempt to open the deep link
18
+ // Use relaxed check for Expo Go / Android 11+ visibility rules
19
+ try {
20
+ const supported = await Linking.canOpenURL(url);
21
+ if (supported) {
22
+ await Linking.openURL(url);
23
+ onConnect();
24
+ onClose();
25
+ return;
26
+ }
27
+ } catch (e) {
28
+ // Ignore check errors
29
+ }
30
+
31
+ // Fallback: Try to open anyway
32
+ await Linking.openURL(url).catch(() => {
33
+ // If failed, show alert or similar (handling logic can be refined)
34
+ });
35
+
36
+ onConnect();
37
+ onClose();
38
+ } catch (error) {
39
+ console.error("Deep link error:", error);
40
+ }
41
+ };
42
+
43
+ return (
44
+ <Modal
45
+ animationType="slide"
46
+ transparent={true}
47
+ visible={visible}
48
+ onRequestClose={onClose}
49
+ >
50
+ <View style={styles.centeredView}>
51
+ {/* Transparent overlay as requested */}
52
+ <TouchableOpacity style={styles.overlay} onPress={onClose} activeOpacity={1} />
53
+
54
+ <View style={styles.modalView}>
55
+ <Text style={styles.modalTitle}>Connect Wallet</Text>
56
+
57
+ <TouchableOpacity style={styles.walletOption} onPress={handleConnect}>
58
+ <View style={styles.walletIconContainer}>
59
+ {/* Embed default SVG or icon here if possible, for now using simple view */}
60
+ <View style={styles.walletIcon} />
61
+ </View>
62
+ <View style={styles.walletInfo}>
63
+ <Text style={styles.walletName}>Column Wallet</Text>
64
+ <Text style={styles.walletTag}>Recommended</Text>
65
+ </View>
66
+ </TouchableOpacity>
67
+
68
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
69
+ <Text style={styles.closeButtonText}>Cancel</Text>
70
+ </TouchableOpacity>
71
+ </View>
72
+ </View>
73
+ </Modal>
74
+ );
75
+ }
76
+
77
+ const styles = StyleSheet.create({
78
+ centeredView: {
79
+ flex: 1,
80
+ justifyContent: 'flex-end',
81
+ alignItems: 'center',
82
+ },
83
+ overlay: {
84
+ ...StyleSheet.absoluteFillObject,
85
+ // Setting background to transparent as requested
86
+ backgroundColor: 'transparent',
87
+ },
88
+ modalView: {
89
+ width: '100%',
90
+ backgroundColor: '#1C1F2E',
91
+ borderTopLeftRadius: 20,
92
+ borderTopRightRadius: 20,
93
+ padding: 24,
94
+ paddingBottom: 40,
95
+ shadowColor: '#000',
96
+ shadowOffset: {
97
+ width: 0,
98
+ height: -2,
99
+ },
100
+ shadowOpacity: 0.25,
101
+ shadowRadius: 4,
102
+ elevation: 5,
103
+ },
104
+ modalTitle: {
105
+ fontSize: 20,
106
+ fontWeight: '600',
107
+ color: 'white',
108
+ marginBottom: 20,
109
+ textAlign: 'center',
110
+ },
111
+ walletOption: {
112
+ flexDirection: 'row',
113
+ alignItems: 'center',
114
+ backgroundColor: '#252938',
115
+ padding: 16,
116
+ borderRadius: 16,
117
+ marginBottom: 16,
118
+ borderWidth: 1,
119
+ borderColor: 'rgba(255,255,255,0.1)',
120
+ },
121
+ walletIconContainer: {
122
+ width: 40,
123
+ height: 40,
124
+ borderRadius: 10,
125
+ backgroundColor: '#ffda34',
126
+ justifyContent: 'center',
127
+ alignItems: 'center',
128
+ marginRight: 16,
129
+ },
130
+ walletIcon: {
131
+ width: 20,
132
+ height: 20,
133
+ backgroundColor: '#000',
134
+ borderRadius: 10,
135
+ },
136
+ walletInfo: {
137
+ flex: 1,
138
+ },
139
+ walletName: {
140
+ color: 'white',
141
+ fontSize: 16,
142
+ fontWeight: '600',
143
+ },
144
+ walletTag: {
145
+ color: '#8B98A5',
146
+ fontSize: 12,
147
+ },
148
+ closeButton: {
149
+ marginTop: 8,
150
+ alignItems: 'center',
151
+ padding: 12,
152
+ },
153
+ closeButtonText: {
154
+ color: '#8B98A5',
155
+ fontSize: 16,
156
+ },
157
+ });