@phantom/parsers 0.0.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/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # @phantom/parsers
2
+
3
+ A utility package for parsing and converting various message and transaction formats into base64url format for use with Phantom's API. This package provides a unified interface for handling different blockchain transaction formats and message types across multiple networks.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @phantom/parsers
9
+ # or
10
+ yarn add @phantom/parsers
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ The parsers package provides two main functions:
16
+
17
+ - **`parseMessage`** - Converts various message formats to base64url
18
+ - **`parseTransaction`** - Converts various transaction formats to base64url for different blockchain networks
19
+
20
+ ## Supported Networks
21
+
22
+ - **Solana** (`solana:*`)
23
+ - **Ethereum/EVM** (`ethereum:*`, `eip155:*`, `polygon:*`, `arbitrum:*`, `optimism:*`, `base:*`, `bsc:*`, `avalanche:*`)
24
+ - **Bitcoin** (`bitcoin:*`)
25
+ - **Sui** (`sui:*`)
26
+
27
+ ## API Reference
28
+
29
+ ### parseMessage(message)
30
+
31
+ Converts various message formats to base64url encoding.
32
+
33
+ **Parameters:**
34
+
35
+ - `message` (string | Uint8Array | Buffer) - The message to parse
36
+
37
+ **Returns:**
38
+
39
+ - `ParsedMessage` object with:
40
+ - `base64url` (string) - Base64url encoded message
41
+ - `originalFormat` (string) - The detected input format
42
+
43
+ **Example:**
44
+
45
+ ```typescript
46
+ import { parseMessage } from "@phantom/parsers";
47
+
48
+ // Plain text message
49
+ const result1 = parseMessage("Hello, Phantom!");
50
+ console.log(result1.base64url); // "SGVsbG8sIFBoYW50b20h"
51
+ console.log(result1.originalFormat); // "string"
52
+
53
+ // Uint8Array
54
+ const bytes = new TextEncoder().encode("Hello, Phantom!");
55
+ const result2 = parseMessage(bytes);
56
+ console.log(result2.base64url); // "SGVsbG8sIFBoYW50b20h"
57
+ console.log(result2.originalFormat); // "bytes"
58
+ ```
59
+
60
+ ### parseTransaction(transaction, networkId)
61
+
62
+ Converts various transaction formats to base64url encoding based on the target network.
63
+
64
+ **Parameters:**
65
+
66
+ - `transaction` (any) - The transaction object/data to parse
67
+ - `networkId` (NetworkId) - The target network identifier
68
+
69
+ **Returns:**
70
+
71
+ - `Promise<ParsedTransaction>` object with:
72
+ - `base64url` (string) - Base64url encoded transaction
73
+ - `originalFormat` (string) - The detected input format
74
+
75
+ ## Supported Transaction Formats
76
+
77
+ ### Solana
78
+
79
+ ```typescript
80
+ import { Transaction } from "@solana/web3.js";
81
+ import { parseTransaction } from "@phantom/parsers";
82
+ import { NetworkId } from "@phantom/client";
83
+
84
+ // Solana Web3.js Transaction
85
+ const transaction = new Transaction().add(/* instructions */);
86
+ const result = await parseTransaction(transaction, NetworkId.SOLANA_MAINNET);
87
+
88
+ // Raw bytes
89
+ const rawBytes = new Uint8Array([1, 2, 3, 4]);
90
+ const result2 = await parseTransaction(rawBytes, NetworkId.SOLANA_MAINNET);
91
+
92
+ // Hex string
93
+ const result3 = await parseTransaction("0x01020304", NetworkId.SOLANA_MAINNET);
94
+ ```
95
+
96
+ ### Ethereum/EVM
97
+
98
+ ```typescript
99
+ import { parseTransaction } from "@phantom/parsers";
100
+ import { NetworkId } from "@phantom/client";
101
+
102
+ // Viem/Ethers transaction object
103
+ const evmTransaction = {
104
+ to: "0x742d35Cc6634C0532925a3b8D4C8db86fB5C4A7E",
105
+ value: 1000000000000000000n, // 1 ETH in wei
106
+ data: "0x",
107
+ gasLimit: 21000n,
108
+ gasPrice: 20000000000n, // 20 gwei
109
+ };
110
+
111
+ const result = await parseTransaction(evmTransaction, NetworkId.ETHEREUM_MAINNET);
112
+
113
+ // Raw transaction bytes
114
+ const rawTx = new Uint8Array([
115
+ /* transaction bytes */
116
+ ]);
117
+ const result2 = await parseTransaction(rawTx, NetworkId.ETHEREUM_MAINNET);
118
+
119
+ // Hex-encoded transaction
120
+ const result3 = await parseTransaction("0xf86c...", NetworkId.ETHEREUM_MAINNET);
121
+ ```
122
+
123
+ ### Bitcoin
124
+
125
+ ```typescript
126
+ import { parseTransaction } from "@phantom/parsers";
127
+ import { NetworkId } from "@phantom/client";
128
+
129
+ // Raw transaction bytes
130
+ const bitcoinTx = new Uint8Array([
131
+ /* bitcoin transaction bytes */
132
+ ]);
133
+ const result = await parseTransaction(bitcoinTx, NetworkId.BITCOIN_MAINNET);
134
+
135
+ // Hex-encoded transaction
136
+ const result2 = await parseTransaction("0x0100000001...", NetworkId.BITCOIN_MAINNET);
137
+ ```
138
+
139
+ ### Sui
140
+
141
+ ```typescript
142
+ import { parseTransaction } from "@phantom/parsers";
143
+ import { NetworkId } from "@phantom/client";
144
+
145
+ // Sui transaction bytes
146
+ const suiTx = new Uint8Array([
147
+ /* sui transaction bytes */
148
+ ]);
149
+ const result = await parseTransaction(suiTx, NetworkId.SUI_MAINNET);
150
+ ```
151
+
152
+ ## Format Detection
153
+
154
+ The parsers automatically detect the input format and handle conversion appropriately:
155
+
156
+ ### Message Formats
157
+
158
+ - **String** - Plain text messages (UTF-8 encoded)
159
+ - **Uint8Array/Buffer** - Raw byte data
160
+ - **Base64/Base64url** - Already encoded data (re-encoded to base64url)
161
+
162
+ ### Transaction Formats
163
+
164
+ - **@solana/web3.js** - Solana Web3.js Transaction objects (calls `.serialize()`)
165
+ - **@solana/kit** - Solana Kit transaction objects
166
+ - **Viem** - Ethereum transaction objects with standard fields
167
+ - **Ethers** - Ethereum transaction objects (legacy and modern formats)
168
+ - **Raw bytes** - Uint8Array or Buffer containing transaction data
169
+ - **Hex strings** - "0x"-prefixed hex-encoded transaction data
170
+ - **Base64/Base64url** - Already encoded transaction data
171
+
172
+ ## Error Handling
173
+
174
+ The parsers will throw descriptive errors for:
175
+
176
+ - Unsupported network identifiers
177
+ - Invalid transaction formats for the target network
178
+ - Malformed input data
179
+ - Encoding/decoding failures
180
+
181
+ ```typescript
182
+ try {
183
+ const result = await parseTransaction(invalidData, "unsupported:network");
184
+ } catch (error) {
185
+ console.error("Parsing failed:", error.message);
186
+ // "Unsupported network: unsupported"
187
+ }
188
+ ```
189
+
190
+ ## Integration with Phantom SDKs
191
+
192
+ This package is used internally by:
193
+
194
+ - **@phantom/browser-sdk** - For client-side transaction parsing
195
+ - **@phantom/server-sdk** - For server-side transaction parsing
196
+ - **@phantom/react-sdk** - Through browser-sdk integration
197
+
198
+ The parsers enable these SDKs to accept native transaction objects from popular libraries like @solana/web3.js, viem, and ethers, providing a seamless developer experience.
199
+
200
+ ## Network ID Format
201
+
202
+ Network IDs follow the format `{chain}:{network}`:
203
+
204
+ - Solana: `solana:mainnet-beta`, `solana:devnet`, `solana:testnet`
205
+ - Ethereum: `ethereum:1` (mainnet), `ethereum:5` (goerli)
206
+ - EIP-155: `eip155:1` (ethereum), `eip155:137` (polygon)
207
+ - Bitcoin: `bitcoin:000000000019d6689c085ae165831e93`
208
+ - Sui: `sui:mainnet`, `sui:testnet`, `sui:devnet`
209
+
210
+ ## Development
211
+
212
+ This package is part of the Phantom Wallet SDK monorepo. For development:
213
+
214
+ ```bash
215
+ # Install dependencies
216
+ yarn install
217
+
218
+ # Build the package
219
+ yarn build
220
+
221
+ # Run tests
222
+ yarn test
223
+ ```
224
+
225
+ ## License
226
+
227
+ MIT License - see LICENSE file for details.
@@ -0,0 +1,19 @@
1
+ import { NetworkId } from '@phantom/client';
2
+
3
+ interface ParsedTransaction {
4
+ base64url: string;
5
+ originalFormat: string;
6
+ }
7
+ interface ParsedMessage {
8
+ base64url: string;
9
+ }
10
+ /**
11
+ * Parse a message to base64url format for the client
12
+ */
13
+ declare function parseMessage(message: string): ParsedMessage;
14
+ /**
15
+ * Parse a transaction to base64url format based on network type
16
+ */
17
+ declare function parseTransaction(transaction: any, networkId: NetworkId): Promise<ParsedTransaction>;
18
+
19
+ export { ParsedMessage, ParsedTransaction, parseMessage, parseTransaction };
package/dist/index.js ADDED
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ parseMessage: () => parseMessage,
24
+ parseTransaction: () => parseTransaction
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+ var import_base64url = require("@phantom/base64url");
28
+ var import_buffer = require("buffer");
29
+ function parseMessage(message) {
30
+ return {
31
+ base64url: (0, import_base64url.stringToBase64url)(message)
32
+ };
33
+ }
34
+ async function parseTransaction(transaction, networkId) {
35
+ const networkPrefix = networkId.split(":")[0].toLowerCase();
36
+ switch (networkPrefix) {
37
+ case "solana":
38
+ return parseSolanaTransaction(transaction);
39
+ case "ethereum":
40
+ case "eip155":
41
+ case "polygon":
42
+ case "optimism":
43
+ case "arbitrum":
44
+ case "base":
45
+ return parseEVMTransaction(transaction);
46
+ case "sui":
47
+ return await parseSuiTransaction(transaction);
48
+ case "bitcoin":
49
+ return parseBitcoinTransaction(transaction);
50
+ default:
51
+ throw new Error(`Unsupported network: ${networkPrefix}`);
52
+ }
53
+ }
54
+ function parseSolanaTransaction(transaction) {
55
+ if (transaction?.messageBytes != null) {
56
+ return {
57
+ base64url: (0, import_base64url.base64urlEncode)(transaction.messageBytes),
58
+ originalFormat: "@solana/kit"
59
+ };
60
+ }
61
+ if (typeof transaction?.serialize === "function") {
62
+ const serialized = transaction.serialize();
63
+ return {
64
+ base64url: (0, import_base64url.base64urlEncode)(serialized),
65
+ originalFormat: "@solana/web3.js"
66
+ };
67
+ }
68
+ if (transaction instanceof Uint8Array) {
69
+ return {
70
+ base64url: (0, import_base64url.base64urlEncode)(transaction),
71
+ originalFormat: "bytes"
72
+ };
73
+ }
74
+ if (typeof transaction === "string") {
75
+ try {
76
+ const bytes = import_buffer.Buffer.from(transaction, "base64");
77
+ return {
78
+ base64url: (0, import_base64url.base64urlEncode)(new Uint8Array(bytes)),
79
+ originalFormat: "base64"
80
+ };
81
+ } catch {
82
+ throw new Error("Unsupported Solana transaction format");
83
+ }
84
+ }
85
+ throw new Error("Unsupported Solana transaction format");
86
+ }
87
+ function parseEVMTransaction(transaction) {
88
+ if (transaction && typeof transaction === "object" && (transaction.to || transaction.data)) {
89
+ const bytes = new TextEncoder().encode(
90
+ JSON.stringify(transaction, (_key, value) => typeof value === "bigint" ? value.toString() : value)
91
+ );
92
+ return {
93
+ base64url: (0, import_base64url.base64urlEncode)(bytes),
94
+ originalFormat: "viem"
95
+ };
96
+ }
97
+ if (transaction?.serialize && typeof transaction.serialize === "function") {
98
+ const serialized = transaction.serialize();
99
+ const bytes = new Uint8Array(import_buffer.Buffer.from(serialized.slice(2), "hex"));
100
+ return {
101
+ base64url: (0, import_base64url.base64urlEncode)(bytes),
102
+ originalFormat: "ethers"
103
+ };
104
+ }
105
+ if (transaction instanceof Uint8Array) {
106
+ return {
107
+ base64url: (0, import_base64url.base64urlEncode)(transaction),
108
+ originalFormat: "bytes"
109
+ };
110
+ }
111
+ if (typeof transaction === "string" && transaction.startsWith("0x")) {
112
+ const bytes = new Uint8Array(import_buffer.Buffer.from(transaction.slice(2), "hex"));
113
+ return {
114
+ base64url: (0, import_base64url.base64urlEncode)(bytes),
115
+ originalFormat: "hex"
116
+ };
117
+ }
118
+ throw new Error("Unsupported EVM transaction format");
119
+ }
120
+ async function parseSuiTransaction(transaction) {
121
+ if (transaction?.serialize && typeof transaction.serialize === "function") {
122
+ const serialized = transaction.serialize();
123
+ return {
124
+ base64url: (0, import_base64url.base64urlEncode)(serialized),
125
+ originalFormat: "sui-sdk"
126
+ };
127
+ }
128
+ if (transaction instanceof Uint8Array) {
129
+ return {
130
+ base64url: (0, import_base64url.base64urlEncode)(transaction),
131
+ originalFormat: "bytes"
132
+ };
133
+ }
134
+ if (transaction?.build && typeof transaction.build === "function") {
135
+ const built = await transaction.build();
136
+ if (built?.serialize && typeof built.serialize === "function") {
137
+ const serialized = built.serialize();
138
+ return {
139
+ base64url: (0, import_base64url.base64urlEncode)(serialized),
140
+ originalFormat: "transaction-block"
141
+ };
142
+ }
143
+ }
144
+ throw new Error("Unsupported Sui transaction format");
145
+ }
146
+ function parseBitcoinTransaction(transaction) {
147
+ if (transaction?.toBuffer && typeof transaction.toBuffer === "function") {
148
+ const buffer = transaction.toBuffer();
149
+ return {
150
+ base64url: (0, import_base64url.base64urlEncode)(new Uint8Array(buffer)),
151
+ originalFormat: "bitcoinjs-lib"
152
+ };
153
+ }
154
+ if (transaction instanceof Uint8Array) {
155
+ return {
156
+ base64url: (0, import_base64url.base64urlEncode)(transaction),
157
+ originalFormat: "bytes"
158
+ };
159
+ }
160
+ if (typeof transaction === "string") {
161
+ const bytes = new Uint8Array(import_buffer.Buffer.from(transaction, "hex"));
162
+ return {
163
+ base64url: (0, import_base64url.base64urlEncode)(bytes),
164
+ originalFormat: "hex"
165
+ };
166
+ }
167
+ throw new Error("Unsupported Bitcoin transaction format");
168
+ }
169
+ // Annotate the CommonJS export names for ESM import in node:
170
+ 0 && (module.exports = {
171
+ parseMessage,
172
+ parseTransaction
173
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,147 @@
1
+ // src/index.ts
2
+ import { base64urlEncode, stringToBase64url } from "@phantom/base64url";
3
+ import { Buffer } from "buffer";
4
+ function parseMessage(message) {
5
+ return {
6
+ base64url: stringToBase64url(message)
7
+ };
8
+ }
9
+ async function parseTransaction(transaction, networkId) {
10
+ const networkPrefix = networkId.split(":")[0].toLowerCase();
11
+ switch (networkPrefix) {
12
+ case "solana":
13
+ return parseSolanaTransaction(transaction);
14
+ case "ethereum":
15
+ case "eip155":
16
+ case "polygon":
17
+ case "optimism":
18
+ case "arbitrum":
19
+ case "base":
20
+ return parseEVMTransaction(transaction);
21
+ case "sui":
22
+ return await parseSuiTransaction(transaction);
23
+ case "bitcoin":
24
+ return parseBitcoinTransaction(transaction);
25
+ default:
26
+ throw new Error(`Unsupported network: ${networkPrefix}`);
27
+ }
28
+ }
29
+ function parseSolanaTransaction(transaction) {
30
+ if (transaction?.messageBytes != null) {
31
+ return {
32
+ base64url: base64urlEncode(transaction.messageBytes),
33
+ originalFormat: "@solana/kit"
34
+ };
35
+ }
36
+ if (typeof transaction?.serialize === "function") {
37
+ const serialized = transaction.serialize();
38
+ return {
39
+ base64url: base64urlEncode(serialized),
40
+ originalFormat: "@solana/web3.js"
41
+ };
42
+ }
43
+ if (transaction instanceof Uint8Array) {
44
+ return {
45
+ base64url: base64urlEncode(transaction),
46
+ originalFormat: "bytes"
47
+ };
48
+ }
49
+ if (typeof transaction === "string") {
50
+ try {
51
+ const bytes = Buffer.from(transaction, "base64");
52
+ return {
53
+ base64url: base64urlEncode(new Uint8Array(bytes)),
54
+ originalFormat: "base64"
55
+ };
56
+ } catch {
57
+ throw new Error("Unsupported Solana transaction format");
58
+ }
59
+ }
60
+ throw new Error("Unsupported Solana transaction format");
61
+ }
62
+ function parseEVMTransaction(transaction) {
63
+ if (transaction && typeof transaction === "object" && (transaction.to || transaction.data)) {
64
+ const bytes = new TextEncoder().encode(
65
+ JSON.stringify(transaction, (_key, value) => typeof value === "bigint" ? value.toString() : value)
66
+ );
67
+ return {
68
+ base64url: base64urlEncode(bytes),
69
+ originalFormat: "viem"
70
+ };
71
+ }
72
+ if (transaction?.serialize && typeof transaction.serialize === "function") {
73
+ const serialized = transaction.serialize();
74
+ const bytes = new Uint8Array(Buffer.from(serialized.slice(2), "hex"));
75
+ return {
76
+ base64url: base64urlEncode(bytes),
77
+ originalFormat: "ethers"
78
+ };
79
+ }
80
+ if (transaction instanceof Uint8Array) {
81
+ return {
82
+ base64url: base64urlEncode(transaction),
83
+ originalFormat: "bytes"
84
+ };
85
+ }
86
+ if (typeof transaction === "string" && transaction.startsWith("0x")) {
87
+ const bytes = new Uint8Array(Buffer.from(transaction.slice(2), "hex"));
88
+ return {
89
+ base64url: base64urlEncode(bytes),
90
+ originalFormat: "hex"
91
+ };
92
+ }
93
+ throw new Error("Unsupported EVM transaction format");
94
+ }
95
+ async function parseSuiTransaction(transaction) {
96
+ if (transaction?.serialize && typeof transaction.serialize === "function") {
97
+ const serialized = transaction.serialize();
98
+ return {
99
+ base64url: base64urlEncode(serialized),
100
+ originalFormat: "sui-sdk"
101
+ };
102
+ }
103
+ if (transaction instanceof Uint8Array) {
104
+ return {
105
+ base64url: base64urlEncode(transaction),
106
+ originalFormat: "bytes"
107
+ };
108
+ }
109
+ if (transaction?.build && typeof transaction.build === "function") {
110
+ const built = await transaction.build();
111
+ if (built?.serialize && typeof built.serialize === "function") {
112
+ const serialized = built.serialize();
113
+ return {
114
+ base64url: base64urlEncode(serialized),
115
+ originalFormat: "transaction-block"
116
+ };
117
+ }
118
+ }
119
+ throw new Error("Unsupported Sui transaction format");
120
+ }
121
+ function parseBitcoinTransaction(transaction) {
122
+ if (transaction?.toBuffer && typeof transaction.toBuffer === "function") {
123
+ const buffer = transaction.toBuffer();
124
+ return {
125
+ base64url: base64urlEncode(new Uint8Array(buffer)),
126
+ originalFormat: "bitcoinjs-lib"
127
+ };
128
+ }
129
+ if (transaction instanceof Uint8Array) {
130
+ return {
131
+ base64url: base64urlEncode(transaction),
132
+ originalFormat: "bytes"
133
+ };
134
+ }
135
+ if (typeof transaction === "string") {
136
+ const bytes = new Uint8Array(Buffer.from(transaction, "hex"));
137
+ return {
138
+ base64url: base64urlEncode(bytes),
139
+ originalFormat: "hex"
140
+ };
141
+ }
142
+ throw new Error("Unsupported Bitcoin transaction format");
143
+ }
144
+ export {
145
+ parseMessage,
146
+ parseTransaction
147
+ };
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@phantom/parsers",
3
+ "version": "0.0.2",
4
+ "description": "Transaction and message parsers for Phantom Wallet SDK",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "?pack-release": "When https://github.com/changesets/changesets/issues/432 has a solution we can remove this trick",
20
+ "pack-release": "rimraf ./_release && yarn pack && mkdir ./_release && tar zxvf ./package.tgz --directory ./_release && rm ./package.tgz",
21
+ "build": "rimraf ./dist && tsup",
22
+ "dev": "rimraf ./dist && tsup --watch",
23
+ "clean": "rm -rf dist",
24
+ "test": "jest",
25
+ "test:watch": "jest --watch",
26
+ "lint": "tsc --noEmit && eslint --cache . --ext .ts",
27
+ "prettier": "prettier --write \"src/**/*.{ts}\""
28
+ },
29
+ "dependencies": {
30
+ "@phantom/base64url": "^0.1.0",
31
+ "@phantom/client": "^0.1.1",
32
+ "buffer": "^6.0.3"
33
+ },
34
+ "devDependencies": {
35
+ "@types/jest": "^29.5.12",
36
+ "@types/node": "^20.11.0",
37
+ "eslint": "8.53.0",
38
+ "jest": "^29.7.0",
39
+ "prettier": "^3.5.2",
40
+ "rimraf": "^6.0.1",
41
+ "ts-jest": "^29.1.2",
42
+ "tsup": "^6.7.0",
43
+ "typescript": "^5.0.4"
44
+ },
45
+ "keywords": [
46
+ "phantom",
47
+ "parsers",
48
+ "transaction",
49
+ "message",
50
+ "solana",
51
+ "ethereum",
52
+ "bitcoin",
53
+ "sui"
54
+ ],
55
+ "license": "MIT",
56
+ "publishConfig": {
57
+ "directory": "_release/package"
58
+ }
59
+ }