Showing preview only (731K chars total). Download the full file or copy to clipboard to get everything.
Repository: toncenter/ton-wallet
Branch: master
Commit: a62f67cb079b
Files: 77
Total size: 701.8 KB
Directory structure:
gitextract_8l00d9tb/
├── .github/
│ └── ISSUE_TEMPLATE/
│ └── bug_report.yaml
├── .gitignore
├── build/
│ ├── gulp/
│ │ ├── config.js
│ │ ├── copy.js
│ │ ├── css.js
│ │ ├── env.js
│ │ ├── html.js
│ │ ├── manifest.js
│ │ ├── pack.js
│ │ ├── remove.js
│ │ ├── script.js
│ │ └── start.js
│ ├── gulpfile.js
│ ├── readme.md
│ └── safari/
│ ├── TON Wallet/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ ├── Main.html
│ │ │ └── Main.storyboard
│ │ ├── Resources/
│ │ │ ├── Script.js
│ │ │ └── Style.css
│ │ ├── TON_Wallet.entitlements
│ │ └── ViewController.swift
│ ├── TON Wallet Extension/
│ │ ├── Info.plist
│ │ ├── SafariWebExtensionHandler.swift
│ │ └── TON_Wallet_Extension.entitlements
│ └── TON Wallet.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata/
│ │ └── sergei.xcuserdatad/
│ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata/
│ └── sergei.xcuserdatad/
│ └── xcschemes/
│ └── xcschememanagement.plist
├── docs/
│ ├── assets/
│ │ ├── favicon/
│ │ │ ├── browserconfig.xml
│ │ │ └── site.webmanifest
│ │ └── lottie/
│ │ ├── confirm.tgs
│ │ ├── created.tgs
│ │ ├── diamond.tgs
│ │ ├── done.tgs
│ │ ├── empty.tgs
│ │ ├── intro.tgs
│ │ ├── lock.tgs
│ │ ├── money.tgs
│ │ └── paper.tgs
│ ├── index.html
│ ├── js/
│ │ ├── Controller.js
│ │ └── View.js
│ ├── libs/
│ │ ├── aes-js-3.1.2.js
│ │ └── noble-ed25519-1.7.3.js
│ └── main.css
├── license
├── package.json
├── readme.md
└── src/
├── assets/
│ ├── favicon/
│ │ ├── browserconfig.xml
│ │ └── site.webmanifest
│ └── lottie/
│ ├── confirm.tgs
│ ├── created.tgs
│ ├── diamond.tgs
│ ├── done.tgs
│ ├── empty.tgs
│ ├── intro.tgs
│ ├── lock.tgs
│ ├── money.tgs
│ └── paper.tgs
├── css/
│ └── main.css
├── index.html
├── js/
│ ├── Controller.js
│ ├── extension/
│ │ ├── background.js
│ │ ├── content.js
│ │ └── provider.js
│ ├── util/
│ │ ├── encryption.js
│ │ └── storage.js
│ └── view/
│ ├── DropDown.js
│ ├── Lottie.js
│ ├── Utils.js
│ └── View.js
└── libs/
├── aes-js-3.1.2.js
└── noble-ed25519-1.7.3.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Bug Report
description: Report of critical bug such as UX inconsistencies, logical contradictions and functional shortcomings.
labels: 'Bug Report'
body:
- type: dropdown
id: bug_type
attributes:
label: Bug Type
description: Select the bug type
options:
- Functional
- UX
- UI
- Performance
- Security
- Localization
- Other
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Reproduction steps
description: Steps to reproduce a bug (you are welcome to attach screenshots here)
placeholder: 1. Click diamond 2. HODL
validations:
required: true
- type: textarea
id: actual-result
attributes:
label: Actual result
description: What was seen
placeholder: When action is done, something happens
validations:
required: true
- type: textarea
id: expected-result
attributes:
label: Expected result
description: What was expected
placeholder: When action is done, nothing happens
validations:
required: true
- type: dropdown
id: suggested-severity
attributes:
label: Suggested Severity
description: Severity of the bug
options:
- Vulnerability
- Critical
- High
- Medium
- Low
validations:
required: true
# - type: dropdown
# id: browsers
# attributes:
# label: "Browsers"
# description: What browsers are you seeing the problem on ?
# multiple: true
# options:
# - Chrome
# - Safari
# - Firefox
# - Microsoft Edge
# - Brave
# validations:
# required: false
# - type: dropdown
# id: os
# attributes:
# label: "OS"
# description: What is the impacted environment ?
# multiple: true
# options:
# - Windows
# - Linux
# - Mac
# validations:
# required: false
- type: textarea
id: device
attributes:
label: Device
description: Information about device
value: |
Desktop (please complete the following information):
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: Additional Context
description: Add any other context about the problem here. You can also attach any extra screenshots or screencasts here.
placeholder: e.g. device was rooted
validations:
required: false
================================================
FILE: .gitignore
================================================
.idea
.vscode
artifacts
node_modules
.DS_Store
.env
================================================
FILE: build/gulp/config.js
================================================
/**
* Path to ".env" file relative to project root directory
*/
const DOTENV_PATH = '.env';
/**
* Required for any task environment variables (see .env.example file in project root directory)
* keys - required environment variable name
* values - task and target mask kind "{task_name}.{target_name}", any part of it or full mask
* may be replaced by "*" symbol which mean any of task, target or its combination
*/
const REQUIRED_ENV_VARS = {
'TONCENTER_API_KEY_WEB_MAIN': '*',
'TONCENTER_API_KEY_WEB_TEST': '*',
'TONCENTER_API_KEY_EXT_MAIN': '*',
'TONCENTER_API_KEY_EXT_TEST': '*',
'MOZILLA_ADDONS_API_KEY': 'publish.firefox',
'MOZILLA_ADDONS_API_SECRET': 'publish.firefox',
'MOZILLA_EXTENSION_ID': 'publish.firefox'
};
/**
* Possible build targets identifiers to names map
*/
const TARGETS = {
WEB: 'web',
CHROMIUM: 'chromium',
FIREFOX: 'firefox',
SAFARI: 'safari'
};
/**
* Possible build destinations identifiers to paths map
* Different targets can match same destinations, for example,
* FIREFOX and SAFARI require V2 build destination
*/
const BUILD_DESTS = {
WEB: 'docs',
V3: 'artifacts/v3',
V2: 'artifacts/v2'
};
/**
* Targets and build destinations conformity
*/
const TARGETS_BUILD_DESTS = {
[TARGETS.WEB]: BUILD_DESTS.WEB,
[TARGETS.CHROMIUM]: BUILD_DESTS.V3,
[TARGETS.FIREFOX]: BUILD_DESTS.V2,
[TARGETS.SAFARI]: BUILD_DESTS.V2
};
/**
* Globs for watch task
*/
const WATCH_GLOBS = ['src/**/*'];
/**
* Port for web wallet start task HTTP server
*/
const START_WEB_PORT = 8080;
module.exports = {
DOTENV_PATH,
REQUIRED_ENV_VARS,
TARGETS,
BUILD_DESTS,
TARGETS_BUILD_DESTS,
WATCH_GLOBS,
START_WEB_PORT
};
================================================
FILE: build/gulp/copy.js
================================================
const { dest, src } = require('gulp');
const { BUILD_DESTS } = require('./config');
/**
* Build destinations and required files conformity
* keys - build destination or "*" for all
* value - list of required files globs
*/
const BUILD_DESTS_GLOBS = {
'*': [
'src/assets/fonts/**/*',
'src/assets/lottie/**/*',
'src/assets/ui/**/*',
'src/libs/**/*'
],
[BUILD_DESTS.WEB]: [
'src/assets/favicon/**/*'
],
[BUILD_DESTS.V3]: [
'src/assets/extension/**/*',
'src/js/extension/**/*',
// Favicons need only for Chromium-based browsers (it show favicon as window icon),
// if other browser full migrate to manifest v3, need create separate destination
// for Chromium with favicons, now v3 used only for Chromium and its ok
// to add favicons to v3 destination
'src/assets/favicon/favicon.ico',
'src/assets/favicon/favicon-32x32.png',
'src/assets/favicon/favicon-16x16.png',
'src/assets/favicon/192x192.png'
],
[BUILD_DESTS.V2]: [
'src/assets/extension/**/*',
'src/js/extension/**/*'
]
};
const copy = buildDest => {
return src([...BUILD_DESTS_GLOBS['*'], ...BUILD_DESTS_GLOBS[buildDest]], { base: 'src' })
.pipe(dest(buildDest));
};
module.exports = copy;
================================================
FILE: build/gulp/css.js
================================================
const { dest, src } = require('gulp');
const css = buildDest => {
return src('src/css/**/*.css').pipe(dest(buildDest));
};
module.exports = css;
================================================
FILE: build/gulp/env.js
================================================
const { existsSync, readFileSync } = require('fs');
const { DOTENV_PATH, REQUIRED_ENV_VARS } = require('./config');
/**
* RegExp for matching rows in ".env" file
*
* @example
* # Comment
* KEY1 = VALUE1 # Trim spaces around key and value
* # Replace "\n" combination by newline symbol in value
* KEY2 = First line\nSecond line
* # Newline symbols inside quoted value (by any of ', " or ` quotes types)
* KEY3 = "Multiline
* quoted
* value"
*/
const ROW_REGEXP =
/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(('|"|`)(?:\\\3|(?!\3)[\s\S])*\3|[^#\n]+)?\s*(?:#.*)?$/gm;
/**
* Read ".env" file in project root directory and set parsed rows to environment variables
*/
const loadEnvFile = () => {
if (!existsSync(DOTENV_PATH)) return;
const source = readFileSync(DOTENV_PATH, 'utf8').replace(/\r\n?/g, '\n');
[...source.matchAll(ROW_REGEXP)].forEach(match => {
let value = match[2];
const maybeQuote = value[0];
// If value was quoted, replace escaped quotes inside
if (maybeQuote === "'" || maybeQuote === '"' || maybeQuote === '`') {
value = value.replace(new RegExp('\\\\' + maybeQuote, 'g'), maybeQuote);
}
// Unquote value
value = value.replace(/^(['"`])([\s\S]*)\1$/g, '$2');
// Replace "\n" in value by newline symbol
value = value.replace(/\\n/g, '\n');
process.env[match[1]] = value;
});
};
/**
* Check passed environment variables names exists in shell environment
*
* @param {string} taskName
* @param {string} targetName
*/
const checkRequiredEnvVars = (taskName, targetName) => {
const requiredEnvVarsNames = [];
// Filter required environment variables by passed task and target names
Object.keys(REQUIRED_ENV_VARS).forEach(envVarName => {
let mask = REQUIRED_ENV_VARS[envVarName];
if (mask === '*') mask = '*.*';
const [taskMask, targetMask] = mask.split('.');
const taskMatch = taskMask === '*' || taskMask === taskName;
const targetMatch = targetMask === '*' || targetName === 'all' || targetMask === targetName;
if (!taskMatch || !targetMatch) return;
requiredEnvVarsNames.push(envVarName);
});
let haveUnspecifiedEnvVars = false;
requiredEnvVarsNames.forEach(envVarName => {
if (process.env[envVarName]) return;
console.error(`Specify "${envVarName}" environment variable`);
haveUnspecifiedEnvVars = true;
});
if (haveUnspecifiedEnvVars) {
console.error('See ".env.example" file in project root directory for more');
process.exit(1);
}
};
module.exports = {
loadEnvFile,
checkRequiredEnvVars
};
================================================
FILE: build/gulp/html.js
================================================
const { dest, src } = require('gulp');
const replace = require('gulp-replace');
const { BUILD_DESTS } = require('./config');
const { version } = require('../../package.json');
const html = buildDest => {
let stream = src('src/index.html').pipe(replace('{{VERSION}}', version));
// For extensions add body class and remove Controller.js including
if (buildDest !== BUILD_DESTS.WEB) {
stream = stream.pipe(replace('<body>', '<body class="plugin">'))
.pipe(replace(/^.*<script.*src=".*Controller.js.*".*$(\r\n|\r|\n)/gm, ''));
}
return stream.pipe(dest(buildDest));
};
module.exports = html;
================================================
FILE: build/gulp/manifest.js
================================================
const { writeFileSync } = require('fs');
const { BUILD_DESTS } = require('./config');
const { version } = require('../../package.json');
const matches = [
'file://*/*',
'http://*/*',
'https://*/*'
];
/**
* Manifest V2 content
*/
const base = {
manifest_version: 2,
version,
name: 'TON Wallet',
description: 'Set up your own TON Wallet on The Open Network',
icons: {
16: 'assets/extension/icon-16.png',
19: 'assets/extension/icon-19.png',
24: 'assets/extension/icon-24.png',
32: 'assets/extension/icon-32.png',
38: 'assets/extension/icon-38.png',
48: 'assets/extension/icon-48.png',
64: 'assets/extension/icon-64.png',
96: 'assets/extension/icon-96.png',
128: 'assets/extension/icon-128.png',
256: 'assets/extension/icon-256.png',
512: 'assets/extension/icon-512.png'
},
permissions: ['webRequest'],
browser_action: {
default_title: 'TON Wallet'
},
background: {
scripts: ['js/extension/background.js'],
persistent: true
},
content_scripts: [{
matches,
js: ['js/extension/content.js'],
run_at: 'document_start',
all_frames: true
}],
web_accessible_resources: ['js/extension/provider.js'],
content_security_policy: "default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data: https://nft.ton.diamonds/; connect-src https://toncenter.com/api/ https://testnet.toncenter.com/api/ https://stage.toncenter.com/api/ https://stage-testnet.toncenter.com/api/ https://ton.diamonds/api/wallet/diamond_nfts"
};
const manifest = async buildDest => {
if (buildDest === BUILD_DESTS.WEB) return;
const content = JSON.parse(JSON.stringify(base));
// Changes to manifest V3
if (buildDest === BUILD_DESTS.V3) {
// Change manifest version
content.manifest_version = 3;
// New storage API permissions
content.permissions.push('storage');
// Permissions for content script and provider update
// content.permissions.push('tabs', 'scripting');
// Matches for content script and provider update
content.host_permissions = matches;
// Browser action field name change
content.action = content.browser_action;
delete(content.browser_action);
// Background script field change
const backgroundScript = content.background.scripts[0];
content.background = { service_worker: backgroundScript };
// Web accessible resources field change
const webAccessibleResources = content.web_accessible_resources;
content.web_accessible_resources = [{
resources: webAccessibleResources,
matches: matches
}];
// Extension page content security policy field change
content.content_security_policy = { extension_pages: content.content_security_policy };
}
writeFileSync(`${buildDest}/manifest.json`, JSON.stringify(content, null, 4));
};
module.exports = manifest;
================================================
FILE: build/gulp/pack.js
================================================
const { spawn } = require('child_process');
const { dest, src } = require('gulp');
const zip = require('gulp-zip');
const { TARGETS, TARGETS_BUILD_DESTS } = require('./config');
const { version } = require('../../package.json');
const pack = async targetName => {
if (targetName === TARGETS.WEB) {
console.log('Pack target "web" not available now');
return;
}
if (targetName === TARGETS.SAFARI) {
if (process.platform !== 'darwin') {
console.log('Pack target "safari" available only on MacOS');
return;
}
return new Promise((resolve, reject) => {
const child = spawn(
'xcodebuild',
[
'-project', 'build/safari/TON Wallet.xcodeproj',
'BUILD_DIR=../../artifacts/safari',
'CONFIGURATION_BUILD_DIR=../../artifacts/safari',
'SYMROOT=../../artifacts/safari'
],
{ stdio: 'inherit' }
);
child.on('close', code => {
if (code === 0) resolve();
else reject(new Error(`Child process fail with code ${code}`));
});
});
}
return src(`${TARGETS_BUILD_DESTS[targetName]}/**/*`)
.pipe(zip(`${targetName}-ton-wallet-${version}.zip`))
.pipe(dest('artifacts'));
};
module.exports = pack;
================================================
FILE: build/gulp/remove.js
================================================
const { existsSync, rmSync, rmdirSync } = require('fs');
const remove = async buildDest => {
if (!existsSync(buildDest)) return;
// fs.rm api was added in Node.js v14, in v16 recursive fs.rmdir is deprecated
(rmSync ? rmSync : rmdirSync)(buildDest, { recursive: true });
};
module.exports = remove;
================================================
FILE: build/gulp/script.js
================================================
const { resolve } = require('path');
const webpack = require('webpack');
const script = (buildDest, done) => {
webpack({
mode: 'none',
entry: {
Controller: './src/js/Controller.js',
View: './src/js/view/View.js'
},
plugins: [new webpack.DefinePlugin({
TONCENTER_API_KEY_WEB_MAIN: `'${process.env.TONCENTER_API_KEY_WEB_MAIN}'`,
TONCENTER_API_KEY_WEB_TEST: `'${process.env.TONCENTER_API_KEY_WEB_TEST}'`,
TONCENTER_API_KEY_EXT_MAIN: `'${process.env.TONCENTER_API_KEY_EXT_MAIN}'`,
TONCENTER_API_KEY_EXT_TEST: `'${process.env.TONCENTER_API_KEY_EXT_TEST}'`
})],
optimization: {
concatenateModules: true,
minimize: false
},
output: {
filename: '[name].js',
path: resolve(process.cwd(), `./${buildDest}/js`)
},
}, (err, stats) => {
if (err) return done(err);
if (stats.hasErrors()) return done(new Error(stats));
done();
});
};
module.exports = script;
================================================
FILE: build/gulp/start.js
================================================
const { access, readFile } = require('fs');
const { createServer } = require('http');
const open = require('open');
const { join, normalize, parse } = require('path');
const { TARGETS, START_WEB_PORT } = require('./config');
const cwd = process.cwd();
const mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.ico': 'image/x-icon',
'.png': 'image/png',
'.svg': 'image/svg+xml',
'.xml': 'text/xml',
'.webmanifest': 'application/manifest+json'
};
const startServer = (port, basePath) => {
const server = createServer();
server.on('request', (req, res) => {
const url = new URL(req.url, 'http://localhost');
if(url.pathname === '/') url.pathname = '/index.html';
const path = join(cwd, basePath, normalize(url.pathname).replace(/^(\.\.[\/\\])+/, ''));
access(path, err => {
if (err) {
res.statusCode = 404;
res.end(`File ${path} not found`);
return;
}
readFile(path, (err, data) => {
if(err){
res.statusCode = 500;
res.end(`Error in reading file ${path}`);
return;
}
res.setHeader(
'Content-type', mimeTypes[parse(path).ext] || 'application/octet-stream'
);
res.end(data);
});
});
});
return new Promise((resolve, reject) => {
server.on('error', reject);
server.listen(port, resolve);
});
};
const start = async targetName => {
if (targetName !== TARGETS.WEB) {
console.log(`Start target "${targetName}" not available`);
return;
}
const port = +process.env.START_WEB_PORT || START_WEB_PORT;
await startServer(port, 'docs');
const address = `http://localhost:${port}`;
console.log(`App available on ${address}`);
await open(address);
};
module.exports = start;
================================================
FILE: build/gulpfile.js
================================================
const { series, task, watch } = require('gulp');
const { TARGETS, BUILD_DESTS, TARGETS_BUILD_DESTS, WATCH_GLOBS } = require('./gulp/config');
const { checkRequiredEnvVars, loadEnvFile } = require('./gulp/env');
const copy = require('./gulp/copy');
const css = require('./gulp/css');
const html = require('./gulp/html');
const manifest = require('./gulp/manifest');
const pack = require('./gulp/pack');
const remove = require('./gulp/remove');
const script = require('./gulp/script');
const start = require('./gulp/start');
const taskName = process.argv[2];
const targetName = process.argv.pop();
const targetNames = ['all', ...Object.values(TARGETS)];
if (!targetName || !targetNames.includes(targetName)) {
console.error(`Pass one of possible target names: "${targetNames.join('", "')}"`);
process.exit(1);
}
loadEnvFile();
checkRequiredEnvVars(taskName, targetName);
const createBuildDestSeries = buildDest => {
return series(
remove.bind(null, buildDest),
copy.bind(null, buildDest),
css.bind(null, buildDest),
script.bind(null, buildDest),
html.bind(null, buildDest),
manifest.bind(null, buildDest)
);
};
let buildTasks;
let startTasks;
let packTasks;
if (targetName === 'all') {
buildTasks = series(
...Object.values(BUILD_DESTS).map(buildDest => createBuildDestSeries(buildDest))
);
startTasks = series(...Object.values(TARGETS).map(targetName => start.bind(null, targetName)));
packTasks = series(...Object.values(TARGETS).map(targetName => pack.bind(null, targetName)));
} else {
buildTasks = createBuildDestSeries(TARGETS_BUILD_DESTS[targetName]);
startTasks = start.bind(null, targetName);
packTasks = pack.bind(null, targetName);
}
task('dev', buildTasks);
task('watch', watch.bind(null, WATCH_GLOBS, { ignoreInitial: false }, buildTasks));
task('start', series(buildTasks, startTasks, watch.bind(null, WATCH_GLOBS, buildTasks)));
task('build', buildTasks);
task('pack', series(buildTasks, packTasks));
================================================
FILE: build/readme.md
================================================
# Source code
We deliberately use plain js and do not use frameworks, due to the direct access to the user's private keys. We also try to use the minimum number of third party libraries and consciously include them as static files rather than NPM packages to prevent accidental upgrade to a potentially malicious versions.
# Preparation
Set required for build environment variables in shell or `.env` file in project root directory. See `.env.example` with required variables names and their description.
If need, update `version` field in `package.json` to increase version in output manifest.json files, source files and update anti-cache parameters values.
Install development dependencies:
```
npm install
```
# Tasks
To run build task use next template:
```
npm run {task} {target}
```
Possible tasks:
- `build` - create bundles in targets destination directories
- `watch` - autorebuild bundles on sources change
- `start` - open target in web-browser and autorebuild bundles on sources change (only for `web` target to test website wallet local)
- `pack` - create bundles and prepare it to publuish-ready form
Possible targets and bundle files destinations folder:
- `web` - docs
- `chromium` - artifacts/v3
- `firefox` - artifacts/v2
- `safari` - artifacts/v2
- `all` - run all targets
Possible targets for pack task and output files destinations:
- `chromium` - artifacts/chromium-ton-wallet-{version}.zip
- `firefox` - artifacts/firefox-ton-wallet-{version}.zip
- `safari` - build xcode project to ton-wallet/artifacts/safari
- `all` - run all targets
Where {version} - value from package.json "version" field
# Chromium-based browsers Extension Developer Mode
- Open web browser
- Go to `chrome://extensions/`
- Enable "Developer Mode" in top right corner
- Click "Load unpacked extension" and specify `artifacts/v3` folder
# Mozilla Firefox Add-on Developer Mode
- Open Mozilla Firefox
- Go to `about:debugging#/runtime/this-firefox`
- Click "Load Temporary Add-on" and select `artifacts/v2/manifest.json` file
# Safari Extension Developer Mode
- Install Xcode Command Line Tools
- Open Safari and choose Safari > Preferences
- Select the Advanced tab, then select the "Show Develop menu in menu bar" checkbox
- Choose Develop > Allow Unsigned Extensions (the Allow Unsigned Extensions setting is reset when a user quits Safari, you must set it again the next time Safari is launched)
- Pack extension for Safari by command `npm run pack safari`
- Extension will automatically added to Safari
## Switch between clear console and debug mode
- Support from 1.1.36 version
- Open menu in right-top corner and select `About` item
- Click on `Version:` label with Alt key pressed
================================================
FILE: build/safari/TON Wallet/AppDelegate.swift
================================================
//
// AppDelegate.swift
// TON Wallet
//
// Created by Сергей Иваньков on 15.03.2022.
//
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
// Override point for customization after application launch.
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
================================================
FILE: build/safari/TON Wallet/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.799",
"green" : "0.533",
"red" : "0.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: build/safari/TON Wallet/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16",
"filename": "16.png"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16",
"filename": "32.png"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32",
"filename": "32.png"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32",
"filename": "64.png"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128",
"filename": "128.png"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128",
"filename": "256.png"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256",
"filename": "256.png"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256",
"filename": "512.png"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512",
"filename": "512.png"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512",
"filename": "1024.png"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: build/safari/TON Wallet/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: build/safari/TON Wallet/Base.lproj/Main.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="stylesheet" href="../Style.css">
<script src="../Script.js" defer></script>
</head>
<body>
<img src="../Icon.png" width="128" height="128" alt="TON Wallet Icon">
<p class="state-unknown">You can turn on TON Wallet’s extension in Safari Extensions preferences.</p>
<p class="state-on">TON Wallet’s extension is currently on. You can turn it off in Safari Extensions preferences.</p>
<p class="state-off">TON Wallet’s extension is currently off. You can turn it on in Safari Extensions preferences.</p>
<button class="open-preferences">Quit and Open Safari Extensions Preferences…</button>
</body>
</html>
================================================
FILE: build/safari/TON Wallet/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<plugIn identifier="com.apple.WebKit2IBPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="TON Wallet" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="TON Wallet" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About TON Wallet" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Hide TON Wallet" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="Ady-hI-5gd" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="Ady-hI-5gd" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="Ady-hI-5gd" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit TON Wallet" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="TON Wallet Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="TON_Wallet" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="76" y="-134"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController showSeguePresentationStyle="single" id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="TON Wallet" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" animationBehavior="default" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenNone="YES"/>
<rect key="contentRect" x="196" y="240" width="425" height="325"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<connections>
<outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
</connections>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="TON_Wallet" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<wkWebView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eOr-cG-IQY">
<rect key="frame" x="0.0" y="0.0" width="425" height="325"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
</subviews>
</view>
<connections>
<outlet property="webView" destination="eOr-cG-IQY" id="GFe-mU-dBY"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="655"/>
</scene>
</scenes>
</document>
================================================
FILE: build/safari/TON Wallet/Resources/Script.js
================================================
function show(enabled) {
if (typeof enabled === "boolean") {
document.body.classList.toggle(`state-on`, enabled);
document.body.classList.toggle(`state-off`, !enabled);
} else {
document.body.classList.remove(`state-on`);
document.body.classList.remove(`state-off`);
}
}
function openPreferences() {
webkit.messageHandlers.controller.postMessage("open-preferences");
}
document.querySelector("button.open-preferences").addEventListener("click", openPreferences);
================================================
FILE: build/safari/TON Wallet/Resources/Style.css
================================================
* {
-webkit-user-select: none;
-webkit-user-drag: none;
cursor: default;
}
:root {
color-scheme: light dark;
--spacing: 20px;
}
html {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing);
margin: 0 calc(var(--spacing) * 2);
height: 100%;
font: -apple-system-short-body;
text-align: center;
}
body:not(.state-on, .state-off) :is(.state-on, .state-off) {
display: none;
}
body.state-on :is(.state-off, .state-unknown) {
display: none;
}
body.state-off :is(.state-on, .state-unknown) {
display: none;
}
button {
font-size: 1em;
}
================================================
FILE: build/safari/TON Wallet/TON_Wallet.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
================================================
FILE: build/safari/TON Wallet/ViewController.swift
================================================
//
// ViewController.swift
// TON Wallet
//
// Created by Сергей Иваньков on 15.03.2022.
//
import Cocoa
import SafariServices
import WebKit
let extensionBundleIdentifier = "org.ton.wallet.extension"
class ViewController: NSViewController, WKNavigationDelegate, WKScriptMessageHandler {
@IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
self.webView.navigationDelegate = self
self.webView.configuration.userContentController.add(self, name: "controller")
self.webView.loadFileURL(Bundle.main.url(forResource: "Main", withExtension: "html")!, allowingReadAccessTo: Bundle.main.resourceURL!)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: extensionBundleIdentifier) { (state, error) in
guard let state = state, error == nil else {
// Insert code to inform the user that something went wrong.
return
}
DispatchQueue.main.async {
webView.evaluateJavaScript("show(\(state.isEnabled))")
}
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if (message.body as! String != "open-preferences") {
return;
}
SFSafariApplication.showPreferencesForExtension(withIdentifier: extensionBundleIdentifier) { error in
DispatchQueue.main.async {
NSApplication.shared.terminate(nil)
}
}
}
}
================================================
FILE: build/safari/TON Wallet Extension/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.Safari.web-extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string>
</dict>
</dict>
</plist>
================================================
FILE: build/safari/TON Wallet Extension/SafariWebExtensionHandler.swift
================================================
//
// SafariWebExtensionHandler.swift
// TON Wallet Extension
//
// Created by Сергей Иваньков on 15.03.2022.
//
import SafariServices
import os.log
let SFExtensionMessageKey = "message"
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let item = context.inputItems[0] as! NSExtensionItem
let message = item.userInfo?[SFExtensionMessageKey]
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
let response = NSExtensionItem()
response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
context.completeRequest(returningItems: [response], completionHandler: nil)
}
}
================================================
FILE: build/safari/TON Wallet Extension/TON_Wallet_Extension.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>
================================================
FILE: build/safari/TON Wallet.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
850824D227E0F882005B1F54 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850824D127E0F882005B1F54 /* AppDelegate.swift */; };
850824D627E0F882005B1F54 /* Main.html in Resources */ = {isa = PBXBuildFile; fileRef = 850824D427E0F882005B1F54 /* Main.html */; };
850824D827E0F882005B1F54 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 850824D727E0F882005B1F54 /* Icon.png */; };
850824DA27E0F882005B1F54 /* Style.css in Resources */ = {isa = PBXBuildFile; fileRef = 850824D927E0F882005B1F54 /* Style.css */; };
850824DC27E0F882005B1F54 /* Script.js in Resources */ = {isa = PBXBuildFile; fileRef = 850824DB27E0F882005B1F54 /* Script.js */; };
850824DE27E0F882005B1F54 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850824DD27E0F882005B1F54 /* ViewController.swift */; };
850824E127E0F882005B1F54 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 850824DF27E0F882005B1F54 /* Main.storyboard */; };
850824E327E0F885005B1F54 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 850824E227E0F885005B1F54 /* Assets.xcassets */; };
850824EB27E0F885005B1F54 /* TON Wallet Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 850824EA27E0F885005B1F54 /* TON Wallet Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
850824F027E0F885005B1F54 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850824EF27E0F885005B1F54 /* SafariWebExtensionHandler.swift */; };
858F762C27E794CB0020E363 /* js in Resources */ = {isa = PBXBuildFile; fileRef = 858F762627E794CB0020E363 /* js */; };
858F762D27E794CB0020E363 /* main.css in Resources */ = {isa = PBXBuildFile; fileRef = 858F762727E794CB0020E363 /* main.css */; };
858F762E27E794CB0020E363 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = 858F762827E794CB0020E363 /* index.html */; };
858F762F27E794CB0020E363 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 858F762927E794CB0020E363 /* assets */; };
858F763027E794CB0020E363 /* libs in Resources */ = {isa = PBXBuildFile; fileRef = 858F762A27E794CB0020E363 /* libs */; };
858F763127E794CB0020E363 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 858F762B27E794CB0020E363 /* manifest.json */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
850824EC27E0F885005B1F54 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 850824C627E0F882005B1F54 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 850824E927E0F885005B1F54;
remoteInfo = "TON Wallet Extension";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
8508250927E0F885005B1F54 /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 13;
files = (
850824EB27E0F885005B1F54 /* TON Wallet Extension.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
850824CE27E0F882005B1F54 /* TON Wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TON Wallet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
850824D127E0F882005B1F54 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
850824D527E0F882005B1F54 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = ../Base.lproj/Main.html; sourceTree = "<group>"; };
850824D727E0F882005B1F54 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Icon.png; sourceTree = "<group>"; };
850824D927E0F882005B1F54 /* Style.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = Style.css; sourceTree = "<group>"; };
850824DB27E0F882005B1F54 /* Script.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Script.js; sourceTree = "<group>"; };
850824DD27E0F882005B1F54 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
850824E027E0F882005B1F54 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
850824E227E0F885005B1F54 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
850824E427E0F885005B1F54 /* TON_Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TON_Wallet.entitlements; sourceTree = "<group>"; };
850824EA27E0F885005B1F54 /* TON Wallet Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "TON Wallet Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
850824EF27E0F885005B1F54 /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; };
8508250227E0F885005B1F54 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8508250327E0F885005B1F54 /* TON_Wallet_Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TON_Wallet_Extension.entitlements; sourceTree = "<group>"; };
858F762627E794CB0020E363 /* js */ = {isa = PBXFileReference; lastKnownFileType = folder; name = js; path = ../../../artifacts/v2/js; sourceTree = "<group>"; };
858F762727E794CB0020E363 /* main.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = main.css; path = ../../../artifacts/v2/main.css; sourceTree = "<group>"; };
858F762827E794CB0020E363 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = index.html; path = ../../../artifacts/v2/index.html; sourceTree = "<group>"; };
858F762927E794CB0020E363 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = ../../../artifacts/v2/assets; sourceTree = "<group>"; };
858F762A27E794CB0020E363 /* libs */ = {isa = PBXFileReference; lastKnownFileType = folder; name = libs; path = ../../../artifacts/v2/libs; sourceTree = "<group>"; };
858F762B27E794CB0020E363 /* manifest.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = manifest.json; path = ../../../artifacts/v2/manifest.json; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
850824CB27E0F882005B1F54 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
850824E727E0F885005B1F54 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
850824C527E0F882005B1F54 = {
isa = PBXGroup;
children = (
850824D027E0F882005B1F54 /* TON Wallet */,
850824EE27E0F885005B1F54 /* TON Wallet Extension */,
850824CF27E0F882005B1F54 /* Products */,
);
sourceTree = "<group>";
};
850824CF27E0F882005B1F54 /* Products */ = {
isa = PBXGroup;
children = (
850824CE27E0F882005B1F54 /* TON Wallet.app */,
850824EA27E0F885005B1F54 /* TON Wallet Extension.appex */,
);
name = Products;
sourceTree = "<group>";
};
850824D027E0F882005B1F54 /* TON Wallet */ = {
isa = PBXGroup;
children = (
850824D127E0F882005B1F54 /* AppDelegate.swift */,
850824DD27E0F882005B1F54 /* ViewController.swift */,
850824DF27E0F882005B1F54 /* Main.storyboard */,
850824E227E0F885005B1F54 /* Assets.xcassets */,
850824E427E0F885005B1F54 /* TON_Wallet.entitlements */,
850824D327E0F882005B1F54 /* Resources */,
);
path = "TON Wallet";
sourceTree = "<group>";
};
850824D327E0F882005B1F54 /* Resources */ = {
isa = PBXGroup;
children = (
850824D427E0F882005B1F54 /* Main.html */,
850824D727E0F882005B1F54 /* Icon.png */,
850824D927E0F882005B1F54 /* Style.css */,
850824DB27E0F882005B1F54 /* Script.js */,
);
path = Resources;
sourceTree = "<group>";
};
850824EE27E0F885005B1F54 /* TON Wallet Extension */ = {
isa = PBXGroup;
children = (
858E09B927E1038E00500609 /* Resources */,
850824EF27E0F885005B1F54 /* SafariWebExtensionHandler.swift */,
8508250227E0F885005B1F54 /* Info.plist */,
8508250327E0F885005B1F54 /* TON_Wallet_Extension.entitlements */,
);
path = "TON Wallet Extension";
sourceTree = "<group>";
};
858E09B927E1038E00500609 /* Resources */ = {
isa = PBXGroup;
children = (
858F762927E794CB0020E363 /* assets */,
858F762827E794CB0020E363 /* index.html */,
858F762627E794CB0020E363 /* js */,
858F762A27E794CB0020E363 /* libs */,
858F762727E794CB0020E363 /* main.css */,
858F762B27E794CB0020E363 /* manifest.json */,
);
name = Resources;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
850824CD27E0F882005B1F54 /* TON Wallet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8508250A27E0F885005B1F54 /* Build configuration list for PBXNativeTarget "TON Wallet" */;
buildPhases = (
850824CA27E0F882005B1F54 /* Sources */,
850824CB27E0F882005B1F54 /* Frameworks */,
850824CC27E0F882005B1F54 /* Resources */,
8508250927E0F885005B1F54 /* Embed App Extensions */,
);
buildRules = (
);
dependencies = (
850824ED27E0F885005B1F54 /* PBXTargetDependency */,
);
name = "TON Wallet";
productName = "TON Wallet";
productReference = 850824CE27E0F882005B1F54 /* TON Wallet.app */;
productType = "com.apple.product-type.application";
};
850824E927E0F885005B1F54 /* TON Wallet Extension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 8508250627E0F885005B1F54 /* Build configuration list for PBXNativeTarget "TON Wallet Extension" */;
buildPhases = (
850824E627E0F885005B1F54 /* Sources */,
850824E727E0F885005B1F54 /* Frameworks */,
850824E827E0F885005B1F54 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "TON Wallet Extension";
productName = "TON Wallet Extension";
productReference = 850824EA27E0F885005B1F54 /* TON Wallet Extension.appex */;
productType = "com.apple.product-type.app-extension";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
850824C627E0F882005B1F54 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
850824CD27E0F882005B1F54 = {
CreatedOnToolsVersion = 13.2.1;
};
850824E927E0F885005B1F54 = {
CreatedOnToolsVersion = 13.2.1;
};
};
};
buildConfigurationList = 850824C927E0F882005B1F54 /* Build configuration list for PBXProject "TON Wallet" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 850824C527E0F882005B1F54;
productRefGroup = 850824CF27E0F882005B1F54 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
850824CD27E0F882005B1F54 /* TON Wallet */,
850824E927E0F885005B1F54 /* TON Wallet Extension */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
850824CC27E0F882005B1F54 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
850824D827E0F882005B1F54 /* Icon.png in Resources */,
850824E127E0F882005B1F54 /* Main.storyboard in Resources */,
850824DC27E0F882005B1F54 /* Script.js in Resources */,
850824D627E0F882005B1F54 /* Main.html in Resources */,
850824E327E0F885005B1F54 /* Assets.xcassets in Resources */,
850824DA27E0F882005B1F54 /* Style.css in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
850824E827E0F885005B1F54 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
858F763027E794CB0020E363 /* libs in Resources */,
858F762E27E794CB0020E363 /* index.html in Resources */,
858F762F27E794CB0020E363 /* assets in Resources */,
858F762D27E794CB0020E363 /* main.css in Resources */,
858F763127E794CB0020E363 /* manifest.json in Resources */,
858F762C27E794CB0020E363 /* js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
850824CA27E0F882005B1F54 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
850824DE27E0F882005B1F54 /* ViewController.swift in Sources */,
850824D227E0F882005B1F54 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
850824E627E0F885005B1F54 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
850824F027E0F885005B1F54 /* SafariWebExtensionHandler.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
850824ED27E0F885005B1F54 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 850824E927E0F885005B1F54 /* TON Wallet Extension */;
targetProxy = 850824EC27E0F885005B1F54 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
850824D427E0F882005B1F54 /* Main.html */ = {
isa = PBXVariantGroup;
children = (
850824D527E0F882005B1F54 /* Base */,
);
name = Main.html;
sourceTree = "<group>";
};
850824DF27E0F882005B1F54 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
850824E027E0F882005B1F54 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
8508250427E0F885005B1F54 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
8508250527E0F885005B1F54 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 12.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
8508250727E0F885005B1F54 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "TON Wallet Extension/TON_Wallet_Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "TON Wallet Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "TON Wallet";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.1.35;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = org.ton.wallet.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
8508250827E0F885005B1F54 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "TON Wallet Extension/TON_Wallet_Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "TON Wallet Extension/Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "TON Wallet";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.1.35;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
);
PRODUCT_BUNDLE_IDENTIFIER = org.ton.wallet.extension;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
8508250B27E0F885005B1F54 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "TON Wallet/TON Wallet.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "TON Wallet";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.1.35;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = org.ton.wallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
8508250C27E0F885005B1F54 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "TON Wallet/TON Wallet.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "TON Wallet";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_KEY_NSMainStoryboardFile = Main;
INFOPLIST_KEY_NSPrincipalClass = NSApplication;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 1.1.35;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
"-framework",
WebKit,
);
PRODUCT_BUNDLE_IDENTIFIER = org.ton.wallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
850824C927E0F882005B1F54 /* Build configuration list for PBXProject "TON Wallet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8508250427E0F885005B1F54 /* Debug */,
8508250527E0F885005B1F54 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8508250627E0F885005B1F54 /* Build configuration list for PBXNativeTarget "TON Wallet Extension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8508250727E0F885005B1F54 /* Debug */,
8508250827E0F885005B1F54 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
8508250A27E0F885005B1F54 /* Build configuration list for PBXNativeTarget "TON Wallet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
8508250B27E0F885005B1F54 /* Debug */,
8508250C27E0F885005B1F54 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 850824C627E0F882005B1F54 /* Project object */;
}
================================================
FILE: build/safari/TON Wallet.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: build/safari/TON Wallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: build/safari/TON Wallet.xcodeproj/xcuserdata/sergei.xcuserdatad/xcschemes/xcschememanagement.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>TON Wallet.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
================================================
FILE: docs/assets/favicon/browserconfig.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="assets/favicon/mstile-70x70.png"/>
<square150x150logo src="assets/favicon/mstile-150x150.png"/>
<square310x310logo src="assets/favicon/mstile-310x310.png"/>
<wide310x150logo src="assets/favicon/mstile-310x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
================================================
FILE: docs/assets/favicon/site.webmanifest
================================================
{
"name": "TON Wallet",
"short_name": "TON Wallet",
"icons": [
{
"src": "36x36.png",
"sizes": "36x36",
"type": "image/png"
},
{
"src": "48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
================================================
FILE: docs/index.html
================================================
<!DOCTYPE html>
<html lang="en" translate="no">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data: https://nft.ton.diamonds/; manifest-src 'self'; connect-src 'self' https://toncenter.com/api/ https://testnet.toncenter.com/api/ https://stage.toncenter.com/api/ https://stage-testnet.toncenter.com/api/ https://ton.diamonds/api/wallet/diamond_nfts">
<meta name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
<title>TON Wallet</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<meta name="google" content="notranslate">
<meta name="description" content="Set up your own TON Wallet on The Open Network">
<meta name="image" content="assets/favicon/512x512.png">
<meta itemprop="name" content="TON Wallet">
<meta itemprop="description" content="Set up your own TON Wallet on The Open Network">
<meta itemprop="image" content="assets/favicon/512x512.png">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="TON Wallet">
<meta name="twitter:description" content="Set up your own TON Wallet on The Open Network">
<meta name="og:title" content="TON Wallet">
<meta name="og:description" content="Set up your own TON Wallet on The Open Network">
<meta name="og:type" content="website">
<link rel="apple-touch-icon" sizes="180x180" href="assets/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="192x192" href="assets/favicon/192x192.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon/favicon-16x16.png">
<link rel="manifest" href="assets/favicon/site.webmanifest">
<link rel="mask-icon" href="assets/favicon/safari-pinned-tab.svg" color="#0088cc">
<link rel="shortcut icon" href="assets/favicon/favicon.ico">
<meta name="apple-mobile-web-app-title" content="TON Wallet">
<meta name="application-name" content="TON Wallet">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="assets/favicon/mstile-144x144.png">
<meta name="msapplication-config" content="assets/favicon/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="main.css?v=1.1.50">
</head>
<body>
<div class="body-container">
<!-- Start Screen -->
<div id="start" class="screen visibility-hidden">
<div class="middle">
<div class="middle-content">
<tgs-player data-name="start" src="assets/lottie/intro.tgs"
width="120" height="120"
class="screen-lottie">
</tgs-player>
<div class="screen-title">TON Wallet</div>
<div class="screen-text">
TON wallet allows you to make fast and<br>
secure blockchain-based payments<br>
without intermediaries.
</div>
<div class="mt-95">
<button id="start_createBtn" class="btn-blue screen-btn">Create My Wallet</button>
</div>
<div class="mt-20">
<button id="start_importBtn" class="btn-lite font-weight-normal">Import existing wallet
</button>
</div>
<div class="mt-10">
<button id="start_importLedgerHidBtn" class="btn-lite font-weight-normal">Connect Ledger
</button>
</div>
<!-- <div class="mt-10">-->
<!-- <button id="start_importLedgerBleBtn" class="btn-lite font-weight-normal">Connect Ledger via Bluetooth-->
<!-- </button>-->
<!-- </div>-->
</div>
</div>
</div>
<!-- Import Screen -->
<div id="import" class="screen text-align-center visibility-hidden">
<button id="import_backBtn" class="btn-back"></button>
<div class="middle">
<div class="middle-content">
<div class="screen-title mt-80">24 Secret Words</div>
<div class="screen-text mb-10">
Please restore access to your <b>non-hardware</b> wallet by<br>
entering the 24 secret words you wrote<br>
down when creating the wallet.
</div>
<button id="import_alertBtn" class="btn-lite">I don't have them</button>
<div id="importWords">
</div>
<div id="wordsPopup" class="display-none">
</div>
<div class="clear-both">
<button id="import_continueBtn" class="btn-blue screen-btn mt-30 mb-20">
Continue
</button>
</div>
</div>
</div>
</div>
<!-- Created Screen -->
<div id="created" class="screen visibility-hidden">
<div class="middle">
<div class="middle-content">
<tgs-player data-name="created" src="assets/lottie/created.tgs"
width="120" height="120"
class="screen-lottie">
</tgs-player>
<div class="screen-title">Congratulations</div>
<div class="screen-text">
Your TON wallet has just been created.<br>
Only you control it.
</div>
<div class="screen-text">
To be able to always have access to it,<br>
please set up a secure password and write<br>
down secret words.
</div>
<div>
<button id="createdContinueButton" class="btn-blue screen-btn mt-18 mb-20">
Continue
</button>
</div>
</div>
</div>
</div>
<!-- Backup Screen -->
<div id="backup" class="screen text-align-center visibility-hidden">
<div class="middle">
<div class="middle-content">
<tgs-player data-name="backup" src="assets/lottie/paper.tgs"
width="120" height="120"
class="screen-lottie mt-30">
</tgs-player>
<div class="screen-title">24 secret words</div>
<div class="screen-text">
Write down these 24 words in the correct<br>
order and store them in secret place.
</div>
<div class="screen-text">
Use these secret words to restore access to<br>
your wallet if you lose your password or<br>
access to this device.
</div>
<div id="createWords" class="mt-10">
</div>
<div class="clear-both">
<button id="backup_continueBtn" class="btn-blue screen-btn mt-26 mb-20">
Continue
</button>
</div>
</div>
</div>
</div>
<!-- Words Confirm Screen -->
<div id="wordsConfirm" class="screen text-align-center visibility-hidden">
<button id="wordsConfirm_backBtn" class="btn-back"></button>
<div class="middle">
<div class="middle-content">
<tgs-player data-name="wordsConfirm" src="assets/lottie/confirm.tgs"
width="120" height="120"
class="screen-lottie mt-30">
</tgs-player>
<div class="screen-title">Test Time!</div>
<div class="screen-text">
Now let's check that you wrote your secret<br/>words correctly.<br/>
</div>
<div id="confirmWordsNums" class="screen-text">
Please enter the words <span></span>, <span></span> and <span></span> below:
</div>
<div id="confirmWords">
</div>
<div id="wordsConfirmPopup" class="display-none">
</div>
<div class="clear-both">
<button id="wordsConfirm_continueBtn" class="btn-blue screen-btn mt-26 mb-20">
Continue
</button>
</div>
</div>
</div>
</div>
<!-- Create Password Screen -->
<div id="createPassword" class="screen visibility-hidden">
<div class="middle">
<div class="middle-content">
<tgs-player data-name="createPassword" src="assets/lottie/lock.tgs"
width="120" height="120"
class="screen-lottie">
</tgs-player>
<div class="screen-title">Secure Password</div>
<div class="screen-text">
Please choose a secure password<br>
for confirming your payments
</div>
<div class="mt-54">
<input id="createPassword_input" placeholder="Enter your password" type="password">
</div>
<div>
<input id="createPassword_repeatInput" placeholder="Repeat your password" type="password">
</div>
<div>
<button id="createPassword_continueBtn" class="btn-blue screen-btn mt-38 mb-20">
Continue
</button>
</div>
</div>
</div>
</div>
<!-- Ready To Go Screen -->
<div id="readyToGo" class="screen visibility-hidden">
<div class="middle">
<div class="middle-content">
<tgs-player data-name="readyToGo" src="assets/lottie/done.tgs"
width="120" height="120"
class="screen-lottie">
</tgs-player>
<div class="screen-title">Ready to go!</div>
<div class="screen-text">
You're all set. Now you have a wallet that<br>
only you control - directly, without<br>
middlemen or bankers.
</div>
<div>
<button id="readyToGo_continueBtn" class="btn-blue screen-btn mt-170 mb-20">
View My Wallet
</button>
</div>
</div>
</div>
</div>
<!-- Main Screen -->
<div id="main" class="screen visibility-hidden">
<div class="head">
<div class="head-row">
<button id="main_refreshBtn" class="btn-round bg-refresh"></button>
<div id="updateLabel"></div>
<button id="main_settingsButton" class="btn-round bg-menu"></button>
</div>
<div class="balance-container">
<div id="balance"></div>
<tgs-player data-name="symbol" src="assets/lottie/diamond.tgs"
width="32" height="32"
class="balance-symbol">
</tgs-player>
<div class="balance-diamond-container">
<div id="diamond"></div>
</div>
</div>
<div class="your-balance">Your balance</div>
<!-- <button id="main_buyBtn" class="btn-blue">-->
<!-- <div class="btn-icon bg-down-left"></div>-->
<!-- Buy-->
<!-- </button>-->
<button id="main_receiveBtn" class="btn-blue">
<div class="btn-icon bg-down-left"></div>
Receive
</button>
<button id="sendButton" class="btn-blue display-none">
<div class="btn-icon bg-down-left rotate-180"></div>
Send
</button>
</div>
<div id="transactionsContainer">
<div id="transactionsList">
</div>
<div id="walletCreated" class="display-none">
<tgs-player data-name="main"
width="150" height="150"
src="assets/lottie/empty.tgs">
</tgs-player>
<div>Wallet Created</div>
</div>
</div>
</div>
<!-- Modal -->
<div id="modal" class="visibility-hidden"></div>
<!-- Menu Dropdown -->
<div id="menuDropdown" class="visibility-hidden">
<div id="menu_extension_chrome" class="dropdown-item">Chrome Extension</div>
<div id="menu_extension_firefox" class="dropdown-item">Firefox Add-on</div>
<div id="menu_about" class="dropdown-item">About</div>
<div id="menu_magic" class="dropdown-item">TON Magic <div class="dropdown-toggle"></div></div>
<div id="menu_telegram" class="dropdown-item">Open Telegram Web »</div>
<div id="menu_proxy" class="dropdown-item">TON Proxy <div class="dropdown-toggle"></div></div>
<div id="menu_changePassword" class="dropdown-item">Change password</div>
<div id="menu_backupWallet" class="dropdown-item">Back up wallet</div>
<div id="menu_delete" class="dropdown-item">Log Out</div>
</div>
<!-- Alert Popup-->
<div id="alert" class="popup visibility-hidden">
<div class="popup-title"></div>
<div class="popup-black-text"></div>
<div class="popup-footer"></div>
</div>
<!-- Receive Popup-->
<div id="receive" class="popup visibility-hidden">
<div class="popup-title">Receive TON</div>
<div class="popup-text">Share this address to receive TON.
</div>
<div class="qr-container">
<div id="qr"></div>
</div>
<div class="my-addr addr"></div>
<button id="receive_showAddressOnDeviceBtn" class="btn-lite btn-lite-first">Show Address on Device</button>
<button id="receive_invoiceBtn" class="btn-lite">Create Invoice</button>
<button id="receive_shareBtn" class="btn-blue">Share Wallet Address</button>
<button id="receive_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Invoice Popup -->
<div id="invoice" class="popup visibility-hidden">
<div class="popup-title">Create Invoice</div>
<div>
<div class="input-label">Amount</div>
</div>
<input id="invoice_amountInput" type="number" placeholder="Amount in TON you expect to receive">
<input id="invoice_commentInput" type="text" placeholder="Comment (optional)">
<div class="popup-grey-text">
You can specify the amount and purpose of<br>
the payment to save the sender some time.
</div>
<div class="input-label mt-24 mb-18">Invoice URL</div>
<div id="invoice_link" class="popup-black-text">
ton://transfer/
</div>
<div class="popup-grey-text mt-24">
Share this address to receive TON.
</div>
<button id="invoice_qrBtn" class="btn-lite">Generate QR Code</button>
<button id="invoice_shareBtn" class="btn-blue">Share Invoice URL</button>
<button id="invoice_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Invoice QR Popup-->
<div id="invoiceQr" class="popup visibility-hidden">
<div class="popup-title">Invoice QR</div>
<div class="qr-container">
<div id="invoiceQrImg"></div>
</div>
<div class="input-label">Expected Amount</div>
<div id="invoiceQrAmount" class="popup-black-text">
1
</div>
<button id="invoiceQr_shareBtn" class="btn-blue">Share QR Code</button>
<button id="invoiceQr_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Transaction Popup -->
<div id="transaction" class="popup visibility-hidden">
<div class="popup-title">Transaction</div>
<div id="transactionAmount">+0.1 TON</div>
<div id="transactionFee">0.1 transaction fee</div>
<div id="transactionSenderLabel" class="input-label mt-20">Sender</div>
<div id="transactionSender" class="addr">
</div>
<div class="input-label">Date</div>
<div id="transactionDate" class="popup-black-text">
</div>
<div id="transactionCommentLabel" class="input-label">Comment</div>
<div id="transactionComment" class="popup-black-text"></div>
<div id="transactionDecryptCommentButton" class="btn-lite">Enter password to view comment</div>
<button id="transaction_sendBtn" class="btn-blue mt-20">Send TON to this address</button>
<button id="transaction_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Send Popup -->
<div id="send" class="popup visibility-hidden">
<div class="popup-title">Send TON</div>
<div class="input-label">Recipient wallet address</div>
<input id="toWalletInput" type="text" placeholder="Enter wallet address">
<div class="popup-grey-text">
Copy the 48-letter wallet address of the
recipient here or ask them to send you a
ton:// link
</div>
<div class="position-relative w-100">
<div class="input-label">Amount</div>
<div id="sendBalance">Balance:</div>
</div>
<input id="amountInput" type="number" placeholder="0.0">
<input id="commentInput" type="text" placeholder="Comment (optional)">
<label id="encryptCommentCheckboxContainer" class="checkbox-container"> Encrypt Comment
<input type="checkbox" id="encryptCommentCheckbox">
<span class="checkmark"></span>
</label>
<button id="send_btn" class="btn-blue">Send TON</button>
<button id="send_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Send Confirm Popup -->
<div id="sendConfirm" class="popup pb-10 visibility-hidden">
<div class="popup-title">Confirmation</div>
<div class="popup-black-text">Do you want to send <b id="sendConfirmAmount">x TON</b> to:</div>
<div id="sendConfirmAddr" class="addr"></div>
<div id="sendConfirmFee" class="popup-grey-text text-align-center">Fee: ~x TON</div>
<div id="sendConfirmNotEncryptedNote" class="popup-grey-text text-align-center">Note: Your comment will <b>not</b> be encrypted</div>
<button id="sendConfirm_closeBtn" class="popup-close-btn"></button>
<div class="popup-footer">
<button id="sendConfirm_cancelBtn" class="btn-lite">CANCEL</button>
<button id="sendConfirm_okBtn" class="btn-lite">SEND TON</button>
</div>
</div>
<!-- Sign Confirm Popup -->
<div id="signConfirm" class="popup pb-10 visibility-hidden">
<div class="popup-title">Confirmation</div>
<div id="signConfirmText" class="popup-black-text">Do you want to sign:</div>
<div id="signConfirmData" class="addr"></div>
<div id="signConfirmAlert" class="popup-grey-text text-align-center font-weight-bold text-danger">Signing custom data is very dangerous. Use only if you know what you are doing.</div>
<button id="signConfirm_closeBtn" class="popup-close-btn"></button>
<div class="popup-footer">
<button id="signConfirm_cancelBtn" class="btn-lite">NO</button>
<button id="signConfirm_okBtn" class="btn-lite">YES</button>
</div>
</div>
<!-- Connect Confirm Popup -->
<div id="connectConfirm" class="popup pb-10 visibility-hidden">
<div class="popup-title">Confirmation</div>
<div class="popup-black-text">Do you want to connect your wallet to this page?</div>
<button id="connectConfirm_closeBtn" class="popup-close-btn"></button>
<div class="popup-footer">
<button id="connectConfirm_cancelBtn" class="btn-lite">NO</button>
<button id="connectConfirm_okBtn" class="btn-lite">YES</button>
</div>
</div>
<!-- Processing Popup -->
<div id="processing" class="popup text-align-center visibility-hidden">
<tgs-player data-name="processing" src="assets/lottie/money.tgs" width="150" height="150"></tgs-player>
<div class="popup-title">Sending TON</div>
<div class="popup-grey-text">Please wait a few seconds for your<br>transaction to be processed..</div>
<button id="processing_closeBtn" class="popup-close-btn"></button>
</div>
<!-- Done Popup -->
<div id="done" class="popup text-align-center pb-10 visibility-hidden">
<tgs-player data-name="done" src="assets/lottie/done.tgs" width="150" height="150"></tgs-player>
<div class="popup-title">Done!</div>
<div class="popup-grey-text">1 TON have been send</div>
<div class="popup-footer">
<button id="done_closeBtn" class="btn-lite">CLOSE</button>
</div>
</div>
<!-- About Popup -->
<div id="about" class="popup text-align-center pb-10 visibility-hidden">
<div class="popup-title">TON Wallet</div>
<div id="about_version" class="popup-grey-text">
Version: 1.1.50
</div>
<tgs-player data-name="about" src="assets/lottie/intro.tgs" width="150" height="150"></tgs-player>
<div class="popup-grey-text line-height-24">
API provider: <a href="https://toncenter.com" target="_blank">toncenter.com</a><br>
<a href="https://github.com/toncenter/ton-wallet" target="_blank">GitHub</a>,
<a href="https://github.com/toncenter/ton-wallet/issues" target="_blank">Issue Tracker</a>
</div>
<div class="popup-black-text about-magic">
<h4>What is TON Magic?</h4>
<p>
TON Magic provides native <b>Telegram integration</b> by patching the official Telegram web app (Z version).
</p>
<p>
Turn it on to send and receive Toncoins from any Telegram user. <a href="https://telegra.ph/Telegram--TON-11-10" id="about-magic-video" target="_blank">More info and demo</a>.
</p>
</div>
<div class="popup-footer">
<button id="about_closeBtn" class="btn-lite">CLOSE</button>
</div>
</div>
<!-- Change Password Popup -->
<div id="changePassword" class="popup pb-10 visibility-hidden">
<div class="popup-title">Change Password</div>
<tgs-player data-name="changePassword" src="assets/lottie/lock.tgs" width="150" height="150"></tgs-player>
<input id="changePassword_oldInput" placeholder="Enter your old password" type="password"
class="input-password">
<input id="changePassword_newInput" placeholder="Enter a new password" type="password"
class="input-password mt-20">
<input id="changePassword_repeatInput" placeholder="Repeat the new password" type="password"
class="input-password">
<div class="popup-footer">
<button id="changePassword_cancelBtn" class="btn-lite">CANCEL</button>
<button id="changePassword_okBtn" class="btn-lite">SAVE</button>
</div>
</div>
<!-- Enter Password Popup -->
<div id="enterPassword" class="popup pb-10 visibility-hidden">
<div class="popup-title">Password</div>
<tgs-player data-name="enterPassword" src="assets/lottie/lock.tgs" width="150" height="150"></tgs-player>
<input id="enterPassword_input" placeholder="Enter your password" type="password"
class="input-password">
<div class="popup-footer">
<button id="enterPassword_cancelBtn" class="btn-lite">CANCEL</button>
<button id="enterPassword_okBtn" class="btn-lite">NEXT</button>
</div>
</div>
<!-- Log Out Popup -->
<div id="delete" class="popup pb-10 visibility-hidden">
<div class="popup-title">Log Out</div>
<div class="popup-black-text">
This will disconnect the wallet from this<br>
app. You will be able to restore your<br>
wallet using <b>24 secret words</b> - or import<br>
another wallet.
</div>
<div class="popup-black-text mt-20">
Wallets are located in the decentralized<br>
TON Blockchain. If you want the wallet to<br>
be deleted simply transfer all the TON<br>
from it and leave it empty.
</div>
<div class="popup-footer">
<button id="delete_cancelBtn" class="btn-lite">CANCEL</button>
<button id="delete_okBtn" class="btn-lite btn-lite-red">DISCONNECT</button>
</div>
</div>
<!-- Connect Ledger Popup -->
<div id="connectLedger" class="popup pb-10 visibility-hidden">
<div class="popup-title">Connect Ledger</div>
<div class="popup-black-text">
Please use Edge/Google Chrome v89 or later.
</div>
<div class="popup-black-text mt-20">
Turn off Ledger Live.
</div>
<div class="popup-black-text mt-20">
If it does not connect, then try reconnecting the device.
</div>
<div class="popup-footer">
<button id="connectLedger_cancelBtn" class="btn-lite">OK</button>
</div>
</div>
<!-- Notify-->
<div id="notify" class="visibility-hidden"></div>
<!-- Loader Popup -->
<div id="loader" class="popup text-align-center pb-10 visibility-hidden">
<tgs-player data-name="loader" src="assets/lottie/intro.tgs" width="150" height="150"></tgs-player>
</div>
</div>
<!-- Scripts -->
<script type="text/javascript" src="libs/pako.min.js?v=1.1.50"></script>
<script type="text/javascript" src="libs/lottie.min.js?v=1.1.50"></script>
<script type="text/javascript" src="libs/easy.qrcode.min.js?v=1.1.50"></script>
<script type="text/javascript" src="libs/aes-js-3.1.2.js"></script>
<script type="text/javascript" src="libs/noble-ed25519-1.7.3.js"></script>
<script type="text/javascript" src="libs/tonweb.min.js?v=1.1.50"></script>
<script type="text/javascript" src="libs/tonweb-mnemonic.min.js?v=1.1.50"></script>
<script src="js/View.js?v=1.1.50" type="module"></script>
<script src="js/Controller.js?v=1.1.50" type="module"></script>
</body>
</html>
================================================
FILE: docs/js/Controller.js
================================================
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
;// CONCATENATED MODULE: ./src/js/util/storage.js
/**
* `localStorage` polyfill for Chrome Extension environment
*/
/* harmony default export */ const storage = (self.localStorage || {
/**
* @param key {string}
* @param value {string}
* @return {Promise<void>}
*/
setItem(key, value) {
return chrome.storage.local.set({[key]: value});
},
/**
* @param key {string}
* @return {Promise<string | null>}
*/
getItem(key) {
return chrome.storage.local.get(key)
.then(({[key]: value}) => value);
},
/**
* @param key {string}
* @return {Promise<void>}
*/
removeItem(key) {
return chrome.storage.local.remove(key);
},
/**
* @return {Promise<void>}
*/
clear() {
return chrome.storage.local.clear();
},
});
;// CONCATENATED MODULE: ./src/js/util/encryption.js
// This JS library implements TON message comment encryption and decryption for Web
// Reference C++ code - SimpleEncryptionV2 - https://github.com/ton-blockchain/ton/blob/cc0eb453cb3bf69f92693160103d33112856c056/tonlib/tonlib/keys/SimpleEncryption.cpp#L110
// Dependencies:
// - TonWeb 0.0.60
// - aes-js - 3.1.2 - https://github.com/ricmoo/aes-js/releases/tag/v3.1.2 - for aes-cbc without padding
// - noble-ed25519 - 1.7.3 - // https://github.com/paulmillr/noble-ed25519/releases/tag/1.7.3 - for getSharedKey
const ed25519 = self.nobleEd25519;
/**
* @param key {Uint8Array}
* @param data {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const hmac_sha512 = async (key, data) => {
const hmacAlgo = {name: "HMAC", hash: "SHA-512"};
/** @type {CryptoKey} */
const hmacKey = await self.crypto.subtle.importKey("raw", key, hmacAlgo, false, ["sign"]);
/** @type {ArrayBuffer} */
const signature = await self.crypto.subtle.sign(hmacAlgo, hmacKey, data);
const result = new Uint8Array(signature);
if (result.length !== 512 / 8) throw new Error();
return result;
}
/**
* @param hash {Uint8Array}
* @return {Promise<any>} aesjs.ModeOfOperation.cbc
*/
const getAesCbcState = async (hash) => {
if (hash.length < 48) throw new Error();
const key = hash.slice(0, 32);
const iv = hash.slice(32, 32 + 16);
// Note that native crypto.subtle AES-CBC not suitable here because
// even if the data IS a multiple of 16 bytes, padding will still be added
// So we use aes-js
return new aesjs.ModeOfOperation.cbc(key, iv);
}
/**
* @param dataLength {number}
* @param minPadding {number}
* @return {Uint8Array}
*/
const getRandomPrefix = (dataLength, minPadding) => {
const prefixLength = ((minPadding + 15 + dataLength) & -16) - dataLength;
/** @type {Uint8Array} */
const prefix = self.crypto.getRandomValues(new Uint8Array(prefixLength));
prefix[0] = prefixLength;
if ((prefixLength + dataLength) % 16 !== 0) throw new Error();
return prefix;
}
/**
* @param a {Uint8Array}
* @param b {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const combineSecrets = async (a, b) => {
return hmac_sha512(a, b);
}
/**
* @param data {Uint8Array}
* @param sharedSecret {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const encryptDataWithPrefix = async (data, sharedSecret, salt) => {
if (data.length % 16 !== 0) throw new Error();
/** @type {Uint8Array} */
const dataHash = await combineSecrets(salt, data);
/** @type {Uint8Array} */
const msgKey = dataHash.slice(0, 16);
const res = new Uint8Array(data.length + 16);
res.set(msgKey, 0);
/** @type {Uint8Array} */
const cbcStateSecret = await combineSecrets(sharedSecret, msgKey);
/** @type {Uint8Array} */
const encrypted = (await getAesCbcState(cbcStateSecret)).encrypt(data);
res.set(encrypted, 16);
return res;
}
/**
* @param data {Uint8Array}
* @param sharedSecret {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const encryptDataImpl = async (data, sharedSecret, salt) => {
/** @type {Uint8Array} */
const prefix = await getRandomPrefix(data.length, 16);
const combined = new Uint8Array(prefix.length + data.length);
combined.set(prefix, 0);
combined.set(data, prefix.length);
return encryptDataWithPrefix(combined, sharedSecret, salt);
}
/**
* @param data {Uint8Array}
* @param myPublicKey {Uint8Array}
* @param theirPublicKey {Uint8Array}
* @param privateKey {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const encryptData = async (data, myPublicKey, theirPublicKey, privateKey, salt) => {
/** @type {Uint8Array} */
const sharedSecret = await ed25519.getSharedSecret(privateKey, theirPublicKey);
/** @type {Uint8Array} */
const encrypted = await encryptDataImpl(data, sharedSecret, salt);
const prefixedEncrypted = new Uint8Array(myPublicKey.length + encrypted.length);
for (let i = 0; i < myPublicKey.length; i++) {
prefixedEncrypted[i] = theirPublicKey[i] ^ myPublicKey[i];
}
prefixedEncrypted.set(encrypted, myPublicKey.length);
return prefixedEncrypted;
}
/**
* @param bytes {Uint8Array}
* @return {Cell}
*/
const makeSnakeCells = (bytes) => {
const ROOT_CELL_BYTE_LENGTH = 35 + 4;
const CELL_BYTE_LENGTH = 127;
/** @type {Cell} */
const root = new TonWeb.boc.Cell();
root.bits.writeBytes(bytes.slice(0, Math.min(bytes.length, ROOT_CELL_BYTE_LENGTH)));
const cellCount = Math.ceil((bytes.length - ROOT_CELL_BYTE_LENGTH) / CELL_BYTE_LENGTH);
if (cellCount > 16) {
throw new Error('Text too long');
}
/** @type {Cell} */
let cell = root;
for (let i = 0; i < cellCount; i++) {
/** @type {Cell} */
const prevCell = cell;
cell = new TonWeb.boc.Cell();
const cursor = ROOT_CELL_BYTE_LENGTH + i * CELL_BYTE_LENGTH;
cell.bits.writeBytes(bytes.slice(cursor, Math.min(bytes.length, cursor + CELL_BYTE_LENGTH)));
prevCell.refs[0] = cell;
}
return root;
}
/**
* @param cell {Cell}
* @return {Uint8Array}
*/
const parseSnakeCells = (cell) => {
/** @type {Cell} */
let c = cell;
/** @type {Uint8Array} */
let result = new Uint8Array(0);
while (c) {
/** @type {Uint8Array} */
const newResult = new Uint8Array(result.length + c.bits.array.length);
newResult.set(result);
newResult.set(c.bits.array, result.length);
result = newResult;
c = c.refs[0];
}
return result;
}
/**
* @param comment {string}
* @param myPublicKey {Uint8Array}
* @param theirPublicKey {Uint8Array}
* @param myPrivateKey {Uint8Array}
* @param senderAddress {string | Address}
* @return {Promise<Cell>} full message binary payload with 0x2167da4b prefix
*/
const encryptMessageComment = async (comment, myPublicKey, theirPublicKey, myPrivateKey, senderAddress) => {
if (!comment || !comment.length) throw new Error('empty comment');
if (myPrivateKey.length === 64) {
myPrivateKey = myPrivateKey.slice(0, 32); // convert nacl private key
}
/** @type {Uint8Array} */
const commentBytes = new TextEncoder().encode(comment);
/** @type {Uint8Array} */
const salt = new TextEncoder().encode(new TonWeb.utils.Address(senderAddress).toString(true, true, true, false));
/** @type {Uint8Array} */
const encryptedBytes = await encryptData(commentBytes, myPublicKey, theirPublicKey, myPrivateKey, salt);
const payload = new Uint8Array(encryptedBytes.length + 4);
payload[0] = 0x21; // encrypted text prefix
payload[1] = 0x67;
payload[2] = 0xda;
payload[3] = 0x4b;
payload.set(encryptedBytes, 4);
return makeSnakeCells(payload);
}
/**
* @param cbcStateSecret {Uint8Array}
* @param msgKey {Uint8Array}
* @param encryptedData {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const doDecrypt = async (cbcStateSecret, msgKey, encryptedData, salt) => {
/** @type {Uint8Array} */
const decryptedData = (await getAesCbcState(cbcStateSecret)).decrypt(encryptedData);
/** @type {Uint8Array} */
const dataHash = await combineSecrets(salt, decryptedData);
/** @type {Uint8Array} */
const gotMsgKey = dataHash.slice(0, 16);
if (msgKey.join(',') !== gotMsgKey.join(',')) {
throw new Error('Failed to decrypt: hash mismatch')
}
const prefixLength = decryptedData[0];
if (prefixLength > decryptedData.length || prefixLength < 16) {
throw new Error('Failed to decrypt: invalid prefix size');
}
return decryptedData.slice(prefixLength);
}
/**
* @param encryptedData {Uint8Array}
* @param sharedSecret {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const decryptDataImpl = async (encryptedData, sharedSecret, salt) => {
if (encryptedData.length < 16) throw new Error('Failed to decrypt: data is too small');
if (encryptedData.length % 16 !== 0) throw new Error('Failed to decrypt: data size is not divisible by 16');
/** @type {Uint8Array} */
const msgKey = encryptedData.slice(0, 16);
/** @type {Uint8Array} */
const data = encryptedData.slice(16);
/** @type {Uint8Array} */
const cbcStateSecret = await combineSecrets(sharedSecret, msgKey);
/** @type {Uint8Array} */
const res = await doDecrypt(cbcStateSecret, msgKey, data, salt);
return res;
}
/**
* @param data {Uint8Array}
* @param publicKey {Uint8Array}
* @param privateKey {Uint8Array}
* @param salt {Uint8Array}
* @return {Promise<Uint8Array>}
*/
const decryptData = async (data, publicKey, privateKey, salt) => {
if (data.length < publicKey.length) {
throw new Error('Failed to decrypt: data is too small');
}
const theirPublicKey = new Uint8Array(publicKey.length);
for (let i = 0; i < publicKey.length; i++) {
theirPublicKey[i] = data[i] ^ publicKey[i];
}
/** @type {Uint8Array} */
const sharedSecret = await ed25519.getSharedSecret(privateKey, theirPublicKey);
/** @type {Uint8Array} */
const decrypted = await decryptDataImpl(data.slice(publicKey.length), sharedSecret, salt);
return decrypted;
}
/**
* @param encryptedData {Uint8Array} encrypted data without 0x2167da4b prefix
* @param myPublicKey {Uint8Array}
* @param myPrivateKey {Uint8Array}
* @param senderAddress {string | Address}
* @return {Promise<string>} decrypted text comment
*/
const decryptMessageComment = async (encryptedData, myPublicKey, myPrivateKey, senderAddress) => {
if (myPrivateKey.length === 64) {
myPrivateKey = myPrivateKey.slice(0, 32); // convert nacl private key
}
/** @type {Uint8Array} */
const salt = new TextEncoder().encode(new TonWeb.utils.Address(senderAddress).toString(true, true, true, false));
/** @type {Uint8Array} */
const decryptedBytes = await decryptData(encryptedData, myPublicKey, myPrivateKey, salt);
return new TextDecoder().decode(decryptedBytes);
}
;// CONCATENATED MODULE: ./src/js/Controller.js
const TONCONNECT_MAINNET = '-239';
const TONCONNECT_TESTNET = '-3';
let extensionWindowId = -1;
let contentScriptPorts = new Set();
let popupPort = null;
const queueToPopup = [];
/**
* @type {Promise<void> | null}
*/
let dAppPromise = null;
const createDappPromise = () => {
if (dAppPromise) dAppPromise.resolve(false);
let resolve;
let reject;
dAppPromise = new Promise((localResolve, localReject) => {
resolve = localResolve;
reject = localReject;
});
dAppPromise.resolve = (...args) => {
resolve(...args);
dAppPromise = null;
};
dAppPromise.reject = (...args) => {
reject(...args);
dAppPromise = null;
};
};
const showExtensionWindow = () => {
return new Promise(async resolve => {
if (extensionWindowId > -1) {
chrome.windows.update(extensionWindowId, {focused: true});
return resolve();
}
const windowState = (await storage.getItem('windowState')) || {};
windowState.top = windowState.top || 0;
windowState.left = windowState.left || 0;
windowState.height = windowState.height || 800;
windowState.width = windowState.width || 480;
chrome.windows.create(Object.assign(windowState, {
url: 'index.html',
type: 'popup',
focused: true
}), window => {
extensionWindowId = window.id;
resolve();
});
});
};
const BN = TonWeb.utils.BN;
const nacl = TonWeb.utils.nacl;
const Address = TonWeb.utils.Address;
const formatNanograms = TonWeb.utils.fromNano;
// ENCRYPTION
/**
* @param plaintext {string}
* @param password {string}
* @return {Promise<string>}
*/
async function encrypt(plaintext, password) {
const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password
const iv = crypto.getRandomValues(new Uint8Array(12)); // get 96-bit random iv
const alg = {name: 'AES-GCM', iv: iv}; // specify algorithm to use
const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['encrypt']); // generate key from pw
const ptUint8 = new TextEncoder().encode(plaintext); // encode plaintext as UTF-8
const ctBuffer = await crypto.subtle.encrypt(alg, key, ptUint8); // encrypt plaintext using key
const ctArray = Array.from(new Uint8Array(ctBuffer)); // ciphertext as byte array
const ctStr = ctArray.map(byte => String.fromCharCode(byte)).join(''); // ciphertext as string
const ctBase64 = btoa(ctStr); // encode ciphertext as base64
const ivHex = Array.from(iv).map(b => ('00' + b.toString(16)).slice(-2)).join(''); // iv as hex string
return ivHex + ctBase64; // return iv+ciphertext
}
/**
* @param ciphertext {string}
* @param password {string}
* @return {Promise<string>}
*/
async function decrypt(ciphertext, password) {
const pwUtf8 = new TextEncoder().encode(password); // encode password as UTF-8
const pwHash = await crypto.subtle.digest('SHA-256', pwUtf8); // hash the password
const iv = ciphertext.slice(0, 24).match(/.{2}/g).map(byte => parseInt(byte, 16)); // get iv from ciphertext
const alg = {name: 'AES-GCM', iv: new Uint8Array(iv)}; // specify algorithm to use
const key = await crypto.subtle.importKey('raw', pwHash, alg, false, ['decrypt']); // use pw to generate key
const ctStr = atob(ciphertext.slice(24)); // decode base64 ciphertext
const ctUint8 = new Uint8Array(ctStr.match(/[\s\S]/g).map(ch => ch.charCodeAt(0))); // ciphertext as Uint8Array
// note: why doesn't ctUint8 = new TextEncoder().encode(ctStr) work?
const plainBuffer = await crypto.subtle.decrypt(alg, key, ctUint8); // decrypt ciphertext using key
const plaintext = new TextDecoder().decode(plainBuffer); // decode password from UTF-8
return plaintext; // return the plaintext
}
// CONTROLLER
const IS_EXTENSION = !!(self.chrome && chrome.runtime && chrome.runtime.onConnect);
const ACCOUNT_NUMBER = 0;
const DEFAULT_WALLET_VERSION = 'v3R2';
const DEFAULT_LEDGER_WALLET_VERSION = 'v3R1';
class Controller {
constructor() {
/** @type {boolean} */
this.isTestnet = false;
/** @type {boolean} */
this.isDebug = false;
/** @type {string} */
this.myAddress = null;
/** @type {string} */
this.publicKeyHex = null;
/** @type {string[]} */
this.myMnemonicWords = null;
/** @type {BN | null} */
this.balance = null;
/** @type {WalletContract} */
this.walletContract = null;
this.transactions = [];
/** @type {number} */
this.updateIntervalId = 0;
/** @type {null | {totalAmount: BN, bodyHashBase64: string }} */
this.sendingData = null;
/** @type {boolean} */
this.processingVisible = false;
this.ledgerApp = null;
/** @type {boolean} */
this.isLedger = false;
/** @type {(words: string[]) => Promise<void> | null} */
this.afterEnterPassword = null;
if (self.view) {
self.view.controller = this;
}
this.pendingMessageResolvers = new Map();
this._lastMsgId = 1;
if (IS_EXTENSION) {
setInterval(() => storage.setItem('__time', Date.now()), 5 * 1000);
}
this.whenReady = this._init();
}
debug(...args) {
if (!this.isDebug) return;
console.log(...args);
}
/**
* @param words {string[]}
* @return {Promise<string>} base64
*/
static async wordsToPrivateKey(words) {
const keyPair = await TonWeb.mnemonic.mnemonicToKeyPair(words);
return TonWeb.utils.bytesToBase64(keyPair.secretKey.slice(0, 32));
}
/**
* @param words {string[]}
* @param password {string}
* @return {Promise<void>}
*/
static async saveWords(words, password) {
await storage.setItem('words', await encrypt(words.join(','), password));
}
/**
* @param password {string}
* @return {Promise<string[]>}
*/
static async loadWords(password) {
return (await decrypt(await storage.getItem('words'), password)).split(',');
}
/**
* @param isTestnet {boolean}
* @return {string}
*/
getApiKey(isTestnet) {
const webApiKey = isTestnet
? '4f96a149e04e0821d20f9e99ee716e20ff52db7238f38663226b1c0f303003e0'
: '4f96a149e04e0821d20f9e99ee716e20ff52db7238f38663226b1c0f303003e0';
const extensionApiKey = isTestnet
? '503af517296765c3f1729fcb301b063a00650a50a881eeaddb6307d5d45e21aa'
: '503af517296765c3f1729fcb301b063a00650a50a881eeaddb6307d5d45e21aa';
return IS_EXTENSION ? extensionApiKey : webApiKey;
}
async _init() {
return new Promise(async (resolve) => {
await storage.removeItem('pwdHash');
this.isTestnet = IS_EXTENSION ? (await storage.getItem('isTestnet')) : (self.location.href.indexOf('testnet') > -1);
this.isDebug = IS_EXTENSION ? (await storage.getItem('isDebug')) : (self.location.href.indexOf('debug') > -1);
const mainnetRpc = 'https://toncenter.com/api/v2/jsonRPC';
const testnetRpc = 'https://testnet.toncenter.com/api/v2/jsonRPC';
if (IS_EXTENSION && !(await storage.getItem('address'))) {
await this._restoreDeprecatedStorage();
}
this.ton = new TonWeb(new TonWeb.HttpProvider(this.isTestnet ? testnetRpc : mainnetRpc, {apiKey: this.getApiKey(this.isTestnet)}));
this.myAddress = await storage.getItem('address');
if (this.myAddress) {
this.myAddress = new TonWeb.utils.Address(this.myAddress).toString(true, true, false, this.isTestnet);
}
this.publicKeyHex = await storage.getItem('publicKey');
if (!this.myAddress || !(await storage.getItem('words'))) {
await storage.clear();
this.sendToView('showScreen', {name: 'start', noAnimation: true});
} else {
if ((await storage.getItem('isLedger')) === 'true') {
this.isLedger = true;
this.sendToView('setIsLedger', this.isLedger);
}
await this.showMain();
}
this.sendToView('setIsTestnet', this.isTestnet);
resolve();
});
}
async _restoreDeprecatedStorage() {
const {
address, words, walletVersion, magic, proxy,
} = await this.sendToView('restoreDeprecatedStorage', undefined, true, true);
if (!address || !words) {
return;
}
await Promise.all([
storage.setItem('address', address),
storage.setItem('words', words),
storage.setItem('walletVersion', walletVersion),
storage.setItem('magic', magic),
storage.setItem('proxy', proxy),
]);
}
async toggleTestnet() {
this.isTestnet = !this.isTestnet;
if (this.isTestnet) {
await storage.setItem('isTestnet', 'true');
} else {
await storage.removeItem('isTestnet');
}
this.clearVars();
await this._init();
await this.sendToView('setIsTestnet', this.isTestnet);
}
async toggleDebug() {
this.isDebug = !this.isDebug;
if (this.isDebug) {
await storage.setItem('isDebug', 'true');
} else {
await storage.removeItem('isDebug');
}
}
// INDEXED API
/**
* @private
* @param method {string}
* @param params {any}
* @return {Promise<any>}
*/
async sendToIndex(method, params) {
const mainnetRpc = 'https://toncenter.com/api/v3/';
const testnetRpc = 'https://testnet.toncenter.com/api/v3/';
const rpc = this.isTestnet ? testnetRpc : mainnetRpc;
const headers = {
'Content-Type': 'application/json',
'X-API-Key': this.getApiKey(this.isTestnet)
};
const response = await fetch(rpc + method + '?' + new URLSearchParams(params), {
method: 'GET',
headers: headers,
});
return await response.json();
}
/**
* @private
* @param address {string}
* @return {Promise<{seqno: number | null}>}
*/
async getWalletInfoFromIndex(address) {
return this.sendToIndex('wallet', {
address: address
});
}
/**
* @private
* @param address {string}
* @return {Promise<{balance: string, status: string}>}
*/
async getAccountInfoFromIndex(address) {
return this.sendToIndex('account', {
address: address
});
}
/**
* @return {Promise<number>} seqno
*/
async getMySeqno() {
const walletInfo = await this.getWalletInfoFromIndex(this.myAddress);
return walletInfo.seqno || 0;
}
/**
* @param address {string}
* @return {Promise<BN>} in nanotons
*/
async getBalance(address) {
const accountInfo = await this.getAccountInfoFromIndex(address);
return new BN(accountInfo.balance);
}
/**
* @param address {string}
* @return {Promise<boolean>}
*/
async checkContractInitialized(address) {
const accountInfo = await this.getAccountInfoFromIndex(address);
return accountInfo.status === "active";
}
/**
* @private
* @param address {string}
* @param limit {number}
* @return {Promise<void>}
*/
async getTransactionsFromIndex(address, limit) {
return this.sendToIndex('transactions', {
account: address,
limit: limit
});
}
/**
* @param limit? {number}
* @return {Promise<any[]>} transactions
*/
async getTransactions(limit = 10) {
/**
* @param msg {any} raw.message
* @return {string}
*/
function getComment(msg) {
if (!msg.message_content) return '';
if (!msg.message_content.decoded) return '';
if (msg.message_content.decoded['type'] !== 'text_comment') return '';
return msg.message_content.decoded.comment;
}
/**
* @param msg {any} raw.message
* @return {string} '' or base64
*/
function getEncryptedComment(msg) {
if (!msg.message_content) return '';
if (!msg.message_content.body) return '';
if (msg.opcode !== "0x2167da4b") return '';
/** @type {string} */
const cellBase64 = msg.message_content.body;
/** @type {Cell} */
const cell = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(cellBase64));
return TonWeb.utils.bytesToBase64(parseSnakeCells(cell).slice(4)); // skip 4 bytes of prefix 0x2167da4b
}
const arr = [];
const transactionsResponse= await this.getTransactionsFromIndex(this.myAddress, limit);
const transactions = transactionsResponse.transactions; // index.transaction[]
const addressBook = transactionsResponse.address_book;
/**
* @param rawAddress {string}
* @return {string}
*/
const formatTxAddress = (rawAddress) => {
return addressBook[rawAddress].user_friendly;
}
for (const t of transactions) {
let amount = new BN(t.in_msg.value);
for (const outMsg of t.out_msgs) {
amount = amount.sub(new BN(outMsg.value));
}
let from_addr = "";
let to_addr = "";
let comment = "";
let encryptedComment = "";
let inbound = false;
if (t.in_msg.source) { // internal message with Toncoins, set source
inbound = true;
from_addr = formatTxAddress(t.in_msg.source);
to_addr = formatTxAddress(t.in_msg.destination);
comment = getComment(t.in_msg);
encryptedComment = getEncryptedComment(t.in_msg);
} else if (t.out_msgs.length) { // external message, we sending Toncoins
inbound = false;
from_addr = formatTxAddress(t.out_msgs[0].source);
to_addr = formatTxAddress(t.out_msgs[0].destination);
comment = getComment(t.out_msgs[0]);
encryptedComment = getEncryptedComment(t.out_msgs[0]);
//TODO support many out messages. We need to show separate outgoing payment for each? How to show fees?
} else {
// Deploying wallet contract onchain
}
/** @type {BN} */
let fee = new BN(t.total_fees);
for (let outMsg of t.out_msgs) {
fee = fee.add(new BN(outMsg.fwd_fee));
fee = fee.add(new BN(outMsg.ihr_fee));
}
if (to_addr) {
arr.push({
bodyHashBase64: t.in_msg.message_content.hash, // base64
inbound,
hash: t.hash, // base64
amount: amount.toString(),
from_addr: from_addr,
to_addr: to_addr,
fee: fee.toString(), // string BN
comment: comment,
encryptedComment: encryptedComment,
date: t.now * 1000
});
}
}
return arr;
}
/**
* @param request {{expireAt?: number, messages: [{amount: BN, toAddress: string, comment?: string | Uint8Array | Cell, needEncryptComment: boolean, stateInit?: Cell}]}}
* @param keyPair {nacl.KeyPair | null} null if estimates fee, keyPair if real sending
* @return Promise<{{send: () => Promise<*>, getQuery: () => Promise<Cell>, estimateFee: () => Promise<*>}}> transfer object
*/
async sign(request, keyPair) {
/** @type {number} */
const seqno = await this.getMySeqno();
/** @type {Uint8Array | null} */
const secretKey = keyPair ? keyPair.secretKey : null;
return this.walletContract.methods.transfers({
secretKey: secretKey,
seqno: seqno,
expireAt: request.expireAt,
messages: request.messages.map(message => {
return {
toAddress: message.toAddress,
amount: message.amount,
payload: message.comment,
sendMode: 3,
stateInit: message.stateInit
}
})
});
}
// CREATE WALLET
async showCreated() {
this.sendToView('showScreen', {name: 'created'});
this.sendToView('disableCreated', true);
this.myMnemonicWords = await TonWeb.mnemonic.generateMnemonic();
const privateKey = await Controller.wordsToPrivateKey(this.myMnemonicWords);
const keyPair = nacl.sign.keyPair.fromSeed(TonWeb.utils.base64ToBytes(privateKey));
const walletVersion = DEFAULT_WALLET_VERSION;
const WalletClass = this.ton.wallet.all[walletVersion];
this.walletContract = new WalletClass(this.ton.provider, {
publicKey: keyPair.publicKey,
wc: 0
});
this.myAddress = (await this.walletContract.getAddress()).toString(true, true, false, this.isTestnet);
this.publicKeyHex = TonWeb.utils.bytesToHex(keyPair.publicKey);
await storage.setItem('publicKey', this.publicKeyHex);
await storage.setItem('walletVersion', walletVersion);
this.sendToView('disableCreated', false);
}
async createPrivateKey() {
this.showBackup(this.myMnemonicWords, true);
}
// BACKUP WALLET
onBackupWalletClick() {
this.afterEnterPassword = async mnemonicWords => {
this.showBackup(mnemonicWords);
};
this.sendToView('showPopup', {name: 'enterPassword'});
}
showBackup(words, isFirst) {
this.sendToView('showScreen', {name: 'backup', words, isFirst});
}
async onBackupDone() {
if (await storage.getItem('words')) {
this.sendToView('showScreen', {name: 'main'});
} else {
this.sendToView('showScreen', {name: 'wordsConfirm', words: this.myMnemonicWords});
}
}
onConfirmDone(words) {
if (words) {
let isValid = true;
Object.keys(words).forEach(index => {
if (this.myMnemonicWords[index] !== words[index]) {
isValid = false;
}
});
if (!isValid) {
return;
}
this.showCreatePassword();
}
}
// IMPORT LEDGER
async createLedger(transportType) {
let transport;
switch (transportType) {
case 'hid':
transport = await TonWeb.ledger.TransportWebHID.create();
break;
case 'ble':
transport = await TonWeb.ledger.BluetoothTransport.create();
break;
default:
throw new Error('unknown transportType' + transportType);
}
transport.setDebugMode(true);
this.isLedger = true;
this.ledgerApp = new TonWeb.ledger.AppTon(transport, this.ton);
const ledgerVersion = (await this.ledgerApp.getAppConfiguration()).version;
this.debug('ledgerAppConfig=', ledgerVersion);
if (!ledgerVersion.startsWith('2')) {
alert('Please update your Ledger TON-app to v2.0.1 or upper or use old wallet version https://tonwallet.me/prev/');
throw new Error('outdated ledger ton-app version');
}
const {publicKey} = await this.ledgerApp.getPublicKey(ACCOUNT_NUMBER, false); // todo: можно сохранять publicKey и не запрашивать это
const WalletClass = this.ton.wallet.all[DEFAULT_LEDGER_WALLET_VERSION];
const wallet = new WalletClass(this.ton.provider, {
publicKey: publicKey,
wc: 0
});
this.walletContract = wallet;
const address = await wallet.getAddress();
this.myAddress = address.toString(true, true, false, this.isTestnet);
this.publicKeyHex = TonWeb.utils.bytesToHex(publicKey);
}
async importLedger(transportType) {
await this.createLedger(transportType);
await storage.setItem('walletVersion', this.walletContract.getName());
await storage.setItem('address', this.myAddress);
await storage.setItem('isLedger', 'true');
await storage.setItem('ledgerTransportType', transportType);
await storage.setItem('words', 'ledger');
await storage.setItem('publicKey', this.publicKeyHex);
this.sendToView('setIsLedger', this.isLedger);
this.sendToView('showScreen', {name: 'readyToGo'});
}
// IMPORT WALLET
showImport() {
this.sendToView('showScreen', {name: 'import'});
}
async import(words) {
this.myMnemonicWords = words;
if (this.myMnemonicWords) {
try {
const privateKey = await Controller.wordsToPrivateKey(this.myMnemonicWords);
const keyPair = nacl.sign.keyPair.fromSeed(TonWeb.utils.base64ToBytes(privateKey));
let hasBalance = [];
for (let WalletClass of this.ton.wallet.list) {
const wallet = new WalletClass(this.ton.provider, {
publicKey: keyPair.publicKey,
wc: 0
});
const walletAddress = (await wallet.getAddress()).toString(true, true, false, this.isTestnet);
const walletBalance = await this.getBalance(walletAddress);
if (walletBalance.gt(new BN(0))) {
hasBalance.push({balance: walletBalance, clazz: WalletClass});
}
this.debug(wallet.getName(), walletAddress, walletBalance.toString());
}
let walletClass = this.ton.wallet.all[DEFAULT_WALLET_VERSION];
if (hasBalance.length > 0) {
hasBalance.sort((a, b) => {
return a.balance.cmp(b.balance);
});
walletClass = hasBalance[hasBalance.length - 1].clazz;
}
await this.importImpl(keyPair, walletClass);
this.sendToView('importCompleted', {state: 'success'});
} catch (e) {
this.debug(e);
this.sendToView('importCompleted', {state: 'failure'});
}
} else {
this.sendToView('importCompleted', {state: 'failure'});
}
}
async importImpl(keyPair, WalletClass) {
this.walletContract = new WalletClass(this.ton.provider, {
publicKey: keyPair.publicKey,
wc: 0
});
this.myAddress = (await this.walletContract.getAddress()).toString(true, true, false, this.isTestnet);
this.publicKeyHex = TonWeb.utils.bytesToHex(keyPair.publicKey);
await storage.setItem('publicKey', this.publicKeyHex);
await storage.setItem('walletVersion', this.walletContract.getName());
this.showCreatePassword();
}
// PASSWORD
showCreatePassword() {
this.sendToView('showScreen', {name: 'createPassword'});
}
/**
* @param password {string}
* @return {Promise<void>}
*/
async savePrivateKey(password) {
this.isLedger = false;
await storage.setItem('isLedger', 'false');
await storage.setItem('address', this.myAddress);
await Controller.saveWords(this.myMnemonicWords, password);
this.myMnemonicWords = null;
this.sendToView('setIsLedger', this.isLedger);
this.sendToView('showScreen', {name: 'readyToGo'});
this.sendToView('privateKeySaved');
}
/**
* @param oldPassword {string}
* @param newPassword {string}
* @return {Promise<void>}
*/
async onChangePassword(oldPassword, newPassword) {
let words;
try {
words = await Controller.loadWords(oldPassword);
} catch (e) {
this.sendToView('showChangePasswordError');
return;
}
await Controller.saveWords(words, newPassword);
this.sendToView('closePopup');
this.sendToView('passwordChanged');
}
/**
* @param password {string}
* @return {Promise<void>}
*/
async onEnterPassword(password) {
let words;
try {
words = await Controller.loadWords(password);
} catch (e) {
this.sendToView('showEnterPasswordError');
return;
}
this.afterEnterPassword(words);
this.sendToView('passwordEntered');
}
// MAIN
/**
* @return {Promise<void>}
*/
async showMain() {
this.sendToView('showScreen', {name: 'main', myAddress: this.myAddress});
if (!this.walletContract) {
const walletVersion = await storage.getItem('walletVersion');
const walletClass = walletVersion ? this.ton.wallet.all[walletVersion] : this.ton.wallet.default;
this.walletContract = new walletClass(this.ton.provider, {
address: this.myAddress,
publicKey: this.publicKeyHex ? TonWeb.utils.hexToBytes(this.publicKeyHex) : undefined,
wc: 0
});
}
this.updateIntervalId = setInterval(() => this.update(false), 5000);
this.update(true);
this.sendToDapp('ton_accounts', [this.myAddress]);
}
/**
* @return {Promise<void>}
*/
async initDapp() {
this.sendToDapp('ton_accounts', this.myAddress ? [this.myAddress] : []);
this.doMagic((await storage.getItem('magic')) === 'true');
this.doProxy((await storage.getItem('proxy')) === 'true');
}
/**
* @return {Promise<void>}
*/
async initView() {
if (!this.myAddress || !(await storage.getItem('words'))) {
this.sendToView('showScreen', {name: 'start', noAnimation: true});
} else {
this.sendToView('showScreen', {name: 'main', myAddress: this.myAddress});
if (this.balance !== null) {
this.sendToView('setBalance', {balance: this.balance.toString(), txs: this.transactions});
}
}
this.sendToView('setIsMagic', (await storage.getItem('magic')) === 'true');
this.sendToView('setIsProxy', (await storage.getItem('proxy')) === 'true');
this.sendToView('setIsTestnet', this.isTestnet);
}
/**
* @return {Promise<boolean>} successfully updated
*/
async updateBalance() {
try {
this.balance = await this.getBalance(this.myAddress);
return true;
} catch (e) {
console.error(e);
return false;
}
}
/**
* @param force {boolean}
* @return {Promise<boolean>} successfully updated
*/
async update(force) {
try {
// if (!document.hasFocus()) {
// return true;
// }
const needUpdate = (this.processingVisible && this.sendingData) || (this.balance === null) || force;
if (!needUpdate) return true;
if (!(await this.updateBalance())) return false;
const txs = await this.getTransactions();
if (txs.length > 0) {
this.transactions = txs;
if (this.processingVisible && this.sendingData) {
for (let tx of txs) {
if (tx.bodyHashBase64 === this.sendingData.bodyHashBase64) {
this.sendToView('showPopup', {
name: 'done',
message: formatNanograms(this.sendingData.totalAmount) + ' TON have been sent'
});
this.processingVisible = false;
this.sendingData = null;
break;
}
}
}
}
this.sendToView('setBalance', {balance: this.balance.toString(), txs: this.transactions});
return true;
} catch (e) {
console.error(e);
return false;
}
}
async showAddressOnDevice() {
if (!this.ledgerApp) {
await this.createLedger((await storage.getItem('ledgerTransportType')) || 'hid');
}
const {address} = await this.ledgerApp.getAddress(ACCOUNT_NUMBER, true, this.ledgerApp.ADDRESS_FORMAT_USER_FRIENDLY + this.ledgerApp.ADDRESS_FORMAT_URL_SAFE + this.ledgerApp.ADDRESS_FORMAT_BOUNCEABLE);
this.debug(address.toString(true, true, true));
}
// DECRYPT MESSAGE COMMENT
/**
* @param hash {string}
* @param encryptedComment {string} base64
* @param senderAddress {string | Address}
*/
onDecryptComment(hash, encryptedComment, senderAddress) {
this.afterEnterPassword = async mnemonicWords => {
const keyPair = await TonWeb.mnemonic.mnemonicToKeyPair(mnemonicWords);
let decryptedComment = ''
try {
decryptedComment = await decryptMessageComment(TonWeb.utils.base64ToBytes(encryptedComment), keyPair.publicKey, keyPair.secretKey, senderAddress);
} catch (e) {
console.error(e);
}
this.sendToView('decryptedComment', {hash, decryptedComment});
};
this.sendToView('showPopup', {name: 'enterPassword'});
}
// SEND TONCOIN
/**
* @param request {{expireAt?: number, messages: [{amount: BN, toAddress: string, comment?: string | Uint8Array | Cell, needEncryptComment: boolean, stateInit?: Cell}]}}
* @return {Promise<BN>} total fees in nanotons
*/
async getFees(request) {
/** @type {{expireAt?: number, messages: [{amount: BN, toAddress: string, comment?: string | Uint8Array | Cell, needEncryptComment: boolean, stateInit?: Cell}]}} */
const tempRequest = {
expireAt: request.expireAt,
messages: []
};
for (const message of request.messages) {
let tempComment = message.comment
if (message.needEncryptComment) {
const tempKeyPair = TonWeb.utils.newKeyPair(); // encrypt with random key just to get estimage fees
const tempEncryptedCommentCell = await encryptMessageComment(message.comment, tempKeyPair.publicKey, tempKeyPair.publicKey, tempKeyPair.secretKey, this.myAddress);
tempComment = tempEncryptedCommentCell;
}
tempRequest.messages.push({
amount: message.amount,
toAddress: message.toAddress,
comment: tempComment,
needEncryptComment: message.needEncryptComment,
stateInit: message.stateInit
});
}
const query = await this.sign(tempRequest, null);
const all_fees = await query.estimateFee();
const fees = all_fees.source_fees;
const in_fwd_fee = new BN(fees.in_fwd_fee); // External processing fee
const storage_fee = new BN(fees.storage_fee);
const gas_fee = new BN(fees.gas_fee);
const fwd_fee = new BN(fees.fwd_fee);
return in_fwd_fee.add(storage_fee).add(gas_fee).add(fwd_fee);
};
/**
* @param request {{expireAt?: number, messages: [{amount: BN, toAddress: string, comment?: string | Uint8Array | Cell, needEncryptComment: boolean, stateInit?: Cell}]}}
* @param needQueue? {boolean}
* @return {Promise<Cell | null>} successfully sent BoC
*/
async showSendConfirm(request, needQueue) {
createDappPromise();
if (!request.messages) throw new Error('no messages');
if (!request.messages.length) throw new Error('no messages to send');
if (request.messages.length > 4) throw new Error('maximum 4 message at once');
/** @type {BN} */
let totalAmount = new BN(0);
for (const message of request.messages) {
// message.address
if (!message.amount) throw new Error('no amount');
if (!message.amount.gte(new BN(0))) {
this.sendToView('sendCheckFailed', {message: 'Amount must be positive'});
return null;
}
if (message.amount.eq(new BN(0)) && !message.comment) {
this.sendToView('sendCheckFailed', {message: 'You can send 0 TON only with comment'});
return null;
}
totalAmount = totalAmount.add(message.amount);
// message.toAddress
if (!message.toAddress) throw new Error('no toAddress');
if (!Address.isValid(message.toAddress)) {
try {
message.toAddress = message.toAddress.toLowerCase();
if (!message.toAddress.endsWith('.ton') && !message.toAddress.endsWith('.t.me')) {
throw new Error();
}
message.toAddress = await this.ton.dns.getWalletAddress(message.toAddress);
if (!message.toAddress) {
throw new Error();
}
if (!Address.isValid(message.toAddress)) {
throw new Error();
}
message.toAddress = message.toAddress.toString(true, true, true, this.isTestnet);
} catch (e) {
this.sendToView('sendCheckFailed', {message: 'Invalid address or domain'});
return null;
}
}
// make toAddress non-bounceable if destination contract uninitialized
if (!(await this.checkContractInitialized(message.toAddress))) {
message.toAddress = (new Address(message.toAddress)).toString(true, true, false);
}
// message.payload
if (!message.comment) {
message.needEncryptComment = false;
}
// serialize long text comment
if (!message.needEncryptComment && (typeof message.comment === 'string')) {
if (message.comment.length > 0) {
const commentBytes = new TextEncoder().encode(message.comment);
const payloadBytes = new Uint8Array(4 + commentBytes.length);
payloadBytes[0] = 0; // zero uint32 means simple text message
payloadBytes[1] = 0;
payloadBytes[2] = 0;
payloadBytes[3] = 0;
payloadBytes.set(commentBytes, 4);
message.comment = makeSnakeCells(payloadBytes);
}
}
// get destination public key for encryption
if (message.needEncryptComment) {
let toPublicKey = null;
try {
const toPublicKeyBN = await this.ton.provider.call2(message.toAddress, 'get_public_key');
let toPublicKeyHex = toPublicKeyBN.toString(16);
if (toPublicKeyHex.length % 2 !== 0) {
toPublicKeyHex = '0' + toPublicKeyHex;
}
toPublicKey = TonWeb.utils.hexToBytes(toPublicKeyHex);
} catch (e) {
console.error(e);
}
if (!toPublicKey) {
this.sendToView('sendCheckCantPublicKey', {});
return null;
}
message.toPublicKey = toPublicKey;
}
}
// check balance
if (!(await this.updateBalance())) {
this.sendToView('sendCheckFailed', {message: 'API request error'});
return null;
}
if (this.balance.lt(totalAmount)) {
this.sendToView('sendCheckFailed', {
message: 'Not enough balance'
});
return null;
}
let fee;
try {
fee = await this.getFees(request);
} catch (e) {
console.error(e);
this.sendToView('sendCheckFailed', {message: 'API request error'});
return null;
}
if (this.balance.sub(fee).lt(totalAmount)) {
this.sendToView('sendCheckCantPayFee', {fee});
return null;
}
// start
if (this.isLedger) {
const message = request.messages[0];
this.sendToView('showPopup', {
name: 'sendConfirm',
amount: message.amount.toString(),
toAddress: message.toAddress,
needEncryptComment: false,
fee: fee.toString()
}, needQueue);
const sentBoc = await this.send(request, null, totalAmount);
if (sentBoc) {
dAppPromise.resolve(sentBoc);
} else {
this.sendToView('sendCheckFailed', {message: 'API request error'});
dAppPromise.resolve(null);
}
} else {
this.afterEnterPassword = async words => {
this.processingVisible = true;
this.sendToView('showPopup', {name: 'processing'});
const keyPair = await TonWeb.mnemonic.mnemonicToKeyPair(words);
for (const message of request.messages) {
if (message.needEncryptComment) {
const encryptedCommentCell = await encryptMessageComment(message.comment, keyPair.publicKey, message.toPublicKey, keyPair.secretKey, this.myAddress);
message.comment = encryptedCommentCell;
}
}
const privateKeyBase64 = await Controller.wordsToPrivateKey(words);
const sentBoc = await this.send(request, privateKeyBase64, totalAmount);
this.onCancelAction = null;
if (sentBoc) {
dAppPromise.resolve(sentBoc);
} else {
this.sendToView('sendCheckFailed', {message: 'API request error'});
dAppPromise.resolve(null);
}
};
this.onCancelAction = () => {
dAppPromise.resolve(null);
};
this.sendToView('showPopup', {
name: 'sendConfirm',
amount: totalAmount.toString(),
toAddress: request.messages.length === 1 ? request.messages[0].toAddress : `${request.messages.length} addresses`,
fee: fee.toString(),
needEncryptComment: request.messages[0].needEncryptComment // todo
}, needQueue);
}
this.sendToView('sendCheckSucceeded');
return dAppPromise;
}
/**
* @param request {{expireAt?: number, messages: [{amount: BN, toAddress: string, comment?: string | Uint8Array | Cell, needEncryptComment: boolean, stateInit?: Cell}]}}
* @param privateKeyBase64 {string | null} null if Ledger
* @param totalAmount {BN}
* @return {Promise<Cell | null>} successfully sent BoC
*/
async send(request, privateKeyBase64, totalAmount) {
try {
let query;
if (this.isLedger) {
if (request.messages.length !== 1) {
throw new Error('Ledger support only 1 message at once');
}
const message = request.messages[0];
if (message.needEncryptComment) {
throw new Error('encrypted comment dont supported by Ledger');
}
if (message.stateInit) {
throw new Error('stateInit dont supported by Ledger');
}
if (!this.ledgerApp) {
await this.createLedger((await storage.getItem('ledgerTransportType')) || 'hid');
}
let addressFormat = 0;
const toAddress = new Address(message.toAddress);
if (toAddress.isUserFriendly) {
addressFormat += this.ledgerApp.ADDRESS_FORMAT_USER_FRIENDLY;
if (toAddress.isUrlSafe) {
addressFormat += this.ledgerApp.ADDRESS_FORMAT_URL_SAFE;
}
if (toAddress.isBounceable) {
addressFormat += this.ledgerApp.ADDRESS_FORMAT_BOUNCEABLE;
}
if (toAddress.isTestOnly) {
addressFormat += this.ledgerApp.ADDRESS_FORMAT_TEST_ONLY;
}
}
const seqno = await this.getMySeqno();
query = await this.ledgerApp.transfer(ACCOUNT_NUMBER, this.walletContract, message.toAddress, message.amount, seqno, addressFormat);
this.sendToView('showPopup', {name: 'processing'});
this.processingVisible = true;
} else {
const keyPair = nacl.sign.keyPair.fromSeed(TonWeb.utils.base64ToBytes(privateKeyBase64));
query = await this.sign(request, keyPair);
}
/** @type {Cell | null} */
const sentBoc = await this.sendQuery(query);
if (!sentBoc) return null;
/** @type {Cell} */
const bodyCell = await query.getBody();
/** @type {Uint8Array} */
const bodyHash = await bodyCell.hash();
this.sendingData = {
bodyHashBase64: TonWeb.utils.bytesToBase64(bodyHash),
totalAmount: totalAmount
};
return sentBoc;
} catch (e) {
this.debug(e);
this.sendToView('closePopup');
alert('Error sending');
return null;
}
}
/**
* @param hexToSign {string} hex to sign
* @param privateKey {string}
* @returns {Promise<string>} signature in hex
*/
rawSign(hexToSign, privateKey) {
const keyPair = nacl.sign.keyPair.fromSeed(TonWeb.utils.base64ToBytes(privateKey));
const signature = nacl.sign.detached(TonWeb.utils.hexToBytes(hexToSign), keyPair.secretKey);
return TonWeb.utils.bytesToHex(signature);
}
/**
* @param query {{send: () => Promise<*>, getQuery: () => Promise<Cell>}}
* @return {Promise<Cell | null>} successfully sent BoC
*/
async sendQuery(query) {
const sendResponse = await query.send();
if (sendResponse["@type"] === "ok") { // response from ton-http-api
// wait for transaction, then show Done popup
return query.getQuery();
} else {
this.sendToView('closePopup');
alert('Send error');
return null;
}
}
// RAW SIGN
/**
* @param hexToSign {string} hex data to sign
* @param isConnect {boolean}
* @param needQueue {boolean}
* @returns {Promise<string>} signature in hex
*/
showSignConfirm(hexToSign, isConnect, needQueue) {
return new Promise((resolve, reject) => {
if (this.isLedger) {
alert('sign not supported by Ledger');
reject();
} else {
this.onCancelAction = () => {
reject('User cancel');
};
this.afterEnterPassword = async words => {
this.sendToView('closePopup');
const privateKeyBase64 = await Controller.wordsToPrivateKey(words);
const signature = this.rawSign(hexToSign, privateKeyBase64);
resolve(signature);
};
this.sendToView('showPopup', {
name: 'signConfirm',
data: hexToSign,
isConnect: isConnect
}, needQueue);
}
});
}
/**
* Ask user for password and set `this.publicKeyHex`
* @param needQueue {boolean}
* @return {Promise<void>}
*/
requestPublicKey(needQueue) {
return new Promise(async (resolve, reject) => {
await showExtensionWindow();
this.onCancelAction = () => {
reject('User cancel');
};
this.afterEnterPassword = async words => {
const privateKey = await Controller.wordsToPrivateKey(words);
const keyPair = nacl.sign.keyPair.fromSeed(TonWeb.utils.base64ToBytes(privateKey));
this.publicKeyHex = TonWeb.utils.bytesToHex(keyPair.publicKey);
await storage.setItem('publicKey', this.publicKeyHex);
resolve();
};
this.sendToView('showPopup', {name: 'enterPassword'}, needQueue);
});
}
/**
* @param needQueue {boolean}
* @returns {Promise<void>}
*/
showConnectConfirm(needQueue) {
return new Promise((resolve, reject) => {
this.onCancelAction = () => {
reject({
message: 'Reject request',
code: 300 // USER_REJECTS_ERROR
});
};
this.onConnectConfirmed = async () => {
this.sendToView('closePopup');
resolve();
};
this.sendToView('showPopup', {
name: 'connectConfirm',
}, needQueue);
});
}
// DISCONNECT WALLET
clearVars() {
this.myAddress = null;
this.publicKeyHex = null;
this.balance = null;
this.walletContract = null;
this.transactions = [];
this.sendingData = null;
this.processingVisible = false;
this.isLedger = false;
this.ledgerApp = null;
clearInterval(this.updateIntervalId);
}
async onDisconnectClick() {
this.clearVars();
await storage.clear();
this.sendToView('showScreen', {name: 'start'});
this.sendToDapp('ton_accounts', []);
}
// MAGIC
doMagic(enabled) {
try {
this.sendToDapp('ton_doMagic', enabled);
} catch (e) {
}
}
// PROXY
doProxy(enabled) {
}
// TRANSPORT WITH VIEW
/**
* @param method {string}
* @param params? {any} boolean or object, not array
* @param needQueue? {boolean}
* @param needResult? {boolean}
* @return {void | Promise<{magic: (string|null), proxy: (string|null), address: (string|null), words: (string|null), walletVersion: (string|null)}>}
*/
sendToView(method, params, needQueue, needResult) {
if (self.view) {
const result = self.view.onMessage(method, params);
if (needResult) {
return result;
}
} else {
const msg = {method, params};
const exec = () => {
if (popupPort) {
popupPort.postMessage(msg);
} else if (needQueue) {
queueToPopup.push(msg);
}
};
if (!needResult) {
exec();
return;
}
return new Promise((resolve) => {
msg.id = this._lastMsgId++;
this.pendingMessageResolvers.set(msg.id, resolve);
exec();
});
}
}
/**
* @param method {string}
* @param params? {any} boolean or object, not array
* @return {Promise<void>}
*/
async onViewMessage(method, params) {
switch (method) {
case 'showScreen':
switch (params.name) {
case 'created':
await this.showCreated();
break;
case 'import':
this.showImport();
break;
case 'importLedger':
await this.importLedger(params.transportType);
break;
}
break;
case 'import':
await this.import(params.words);
break;
case 'createPrivateKey':
await this.createPrivateKey();
break;
case 'passwordCreated':
await this.savePrivateKey(params.password);
break;
case 'update':
this.update(true);
break;
case 'showAddressOnDevice':
await this.showAddressOnDevice();
break;
case 'onCancelAction':
if (this.onCancelAction) {
await this.onCancelAction();
this.onCancelAction = null;
}
break;
case 'onConnectConfirmed':
if (this.onConnectConfirmed) {
this.onConnectConfirmed();
this.onConnectConfirmed = null;
}
break;
case 'onEnterPassword':
await this.onEnterPassword(params.password);
break;
case 'decryptComment':
await this.onDecryptComment(params.hash, params.encryptedComment, params.senderAddress);
break;
case 'onChangePassword':
await this.onChangePassword(params.oldPassword, params.newPassword);
break;
case 'onSend':
await this.showSendConfirm({
messages: [
{
amount: new BN(params.amount),
toAddress: params.toAddress,
comment: params.comment,
needEncryptComment: params.needEncryptComment
}
]
}, false);
break;
case 'onBackupDone':
await this.onBackupDone();
break;
case 'onConfirmBack':
this.showBackup(this.myMnemonicWords);
break;
case 'onImportBack':
this.sendToView('showScreen', {name: 'start'});
break;
case 'onConfirmDone':
this.onConfirmDone(params.words);
break;
case 'showMain':
await this.showMain();
break;
case 'onBackupWalletClick':
this.onBackupWalletClick();
break;
case 'disconnect':
await this.onDisconnectClick();
break;
case 'onClosePopup':
this.processingVisible = false;
break;
case 'onMagicClick':
await storage.setItem('magic', params ? 'true' : 'false');
this.doMagic(params);
break;
case 'onProxyClick':
await storage.setItem('proxy', params ? 'true' : 'false');
this.doProxy(params);
break;
case 'toggleTestnet':
await this.toggleTestnet();
break;
case 'toggleDebug':
await this.toggleDebug();
break;
case 'onWindowUpdate':
await storage.setItem('windowState', {
top: params.top,
left: params.left,
// -2 need for remove frames size
// TODO: check in linux and macos
height: params.height - 2,
width: params.width - 2
});
break;
}
}
// TRANSPORT WITH DAPP
/**
* @param method {string}
* @param params {any | any[]}
*/
sendToDapp(method, params) {
contentScriptPorts.forEach(port => {
port.postMessage(JSON.stringify({
type: 'gramWalletAPI',
message: {jsonrpc: '2.0', method: method, params: params}
}));
});
}
/**
* @param needQueue {boolean}
* @return {Promise<{name: 'ton_addr', address: string, network: string, walletStateInit: string, publicKey: string }>}
*/
async createTonAddrItemReply(needQueue) {
if (!this.myAddress) {
throw {
message: 'Missing connection',
code: 1 // BAD_REQUEST_ERROR
};
}
if (!this.publicKeyHex) {
await this.requestPublicKey(needQueue);
}
const walletVersion = await storage.getItem('walletVersion');
const rawAddressString = new TonWeb.utils.Address(this.myAddress).toString(false);
const WalletClass = walletVersion ? this.ton.wallet.all[walletVersion] : this.ton.wallet.default;
const wallet = new WalletClass(this.ton.provider, {
publicKey: TonWeb.utils.hexToBytes(this.publicKeyHex),
wc: 0
});
const {stateInit} = await wallet.createStateInit();
const stateInitBase64 = TonWeb.utils.bytesToBase64(await stateInit.toBoc(false));
return {
name: 'ton_addr',
address: rawAddressString,
network: this.isTestnet ? TONCONNECT_TESTNET : TONCONNECT_MAINNET,
walletStateInit: stateInitBase64,
publicKey: this.publicKeyHex
};
}
/**
* @param origin {string}
* @param payload {string}
* @param needQueue {boolean}
* @return {any} ton_proof item
*/
async createTonProofItemReply(origin, payload, needQueue) {
if (!this.myAddress) {
throw {
message: 'Missing connection',
code: 1 // BAD_REQUEST_ERROR
};
}
const timestamp = Math.round(Date.now() / 1000);
const timestampBuffer = new BigInt64Array(1);
timestampBuffer[0] = BigInt(timestamp);
const domain = new URL(origin).host;
const domainBuffer = new TextEncoder().encode(domain);
const domainLengthBuffer = new Int32Array(1);
domainLengthBuffer[0] = domainBuffer.byteLength;
const address = new TonWeb.utils.Address(this.myAddress);
const addressWorkchainBuffer = new Int32Array(1);
addressWorkchainBuffer[0] = address.wc;
const addressBuffer = new Uint8Array(4 + address.hashPart.length);
addressBuffer.set(addressWorkchainBuffer, 0);
addressBuffer.set(address.hashPart, 4);
const prefixBuffer = new TextEncoder().encode('ton-proof-item-v2/');
const payloadBuffer = new TextEncoder().encode(payload);
const messageBuffer = new Uint8Array(prefixBuffer.byteLength + addressBuffer.byteLength + domainLengthBuffer.byteLength + domainBuffer.byteLength + timestampBuffer.byteLength + payloadBuffer.byteLength);
let offset = 0;
messageBuffer.set(prefixBuffer, offset);
offset += prefixBuffer.byteLength;
messageBuffer.set(addressBuffer, offset);
offset += addressBuffer.byteLength;
messageBuffer.set(domainLengthBuffer, offset);
offset += domainLengthBuffer.byteLength;
messageBuffer.set(domainBuffer, offset);
offset += domainBuffer.byteLength;
messageBuffer.set(new Uint8Array(timestampBuffer.buffer), offset);
offset += 8;
messageBuffer.set(payloadBuffer, offset);
const ffffPrefix = new Uint8Array([0xff, 0xff]);
const tonconnectPrefix = new TextEncoder().encode('ton-connect')
const messageBufferHash = new Uint8Array(await TonWeb.utils.sha256(messageBuffer));
const bufferToSign = new Uint8Array(ffffPrefix.byteLength + tonconnectPrefix.byteLength + messageBufferHash.byteLength);
offset = 0;
bufferToSign.set(ffffPrefix, offset);
offset += ffffPrefix.byteLength;
bufferToSign.set(tonconnectPrefix, offset);
offset += tonconnectPrefix.byteLength;
bufferToSign.set(messageBufferHash, offset);
const hexToSign = TonWeb.utils.bytesToHex(new Uint8Array(await TonWeb.utils.sha256(bufferToSign)));
const signatureHex = await this.showSignConfirm(hexToSign, true, needQueue);
console.log({signatureHex});
const signatureBase64 = TonWeb.utils.bytesToBase64(TonWeb.utils.hexToBytes(signatureHex));
console.log({signatureBase64});
return {
name: 'ton_proof',
proof: {
timestamp: timestamp, // 64-bit unix epoch time of the signing operation (seconds)
domain: {
lengthBytes: domainBuffer.byteLength, // AppDomain Length
value: domain, // app domain name (as url part, without encoding)
},
signature: signatureBase64, // base64-encoded signature
payload: payload, // payload from the request
},
}
}
async onDappMessage(method, params, origin) {
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md
// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md
await this.whenReady;
const needQueue = !popupPort;
switch (method) {
case 'tonConnect_connect':
await showExtensionWindow();
if (!this.myAddress) {
throw {
message: 'Missing connection',
code: 1 // BAD_REQUEST_ERROR
}
}
const data = params[0];
const tonProof = data.items.find((item) => item.name === 'ton_proof');
if (!tonProof &&
!(await storage.getItem('tonconnect_' + this.myAddress + '_' + (this.isTestnet ? 'testnet' : 'mainnet') + '_' + origin))) {
await this.showConnectConfirm(needQueue);
}
await storage.setItem('tonconnect_' + this.myAddress + '_' + (this.isTestnet ? 'testnet' : 'mainnet') + '_' + origin, 'true');
const connectResult = [
await this.createTonAddrItemReply(needQueue),
];
if (tonProof) {
connectResult.push(await this.createTonProofItemReply(origin, tonProof.payload, needQueue))
}
return connectResult;
case 'tonConnect_reconnect':
if (!this.myAddress ||
!(await storage.getItem('tonconnect_' + this.myAddress + '_' + (this.isTestnet ? 'testnet' : 'mainnet') + '_' + origin))) {
throw {
message: 'Missing connection',
code: 1 // BAD_REQUEST_ERROR
}
}
return [
await this.createTonAddrItemReply(needQueue)
];
case 'tonConnect_disconnect':
await storage.removeItem('tonconnect_' + this.myAddress + '_' + (this.isTestnet ? 'testnet' : 'mainnet') + '_' + origin);
return;
case 'tonConnect_sendTransaction':
await showExtensionWindow();
const tx = params[0];
console.log('tonConnect_sendTransaction', params, origin, tx);
// check is dapp connected to wallet
if (!this.myAddress) {
throw {
message: 'Missing connection',
code: 1 // BAD_REQUEST_ERROR
}
}
if (!(await storage.getItem('tonconnect_' + this.myAddress + '_' + (this.isTestnet ? 'testnet' : 'mainnet') + '_' + origin))) {
throw {
message: 'dApp don\'t have an access to wallet',
code: 1 // BAD_REQUEST_ERROR
}
}
// check tonConnect_sendTransaction request
/** @type {number | undefined} */
let expireAt = undefined;
if (tx.valid_until) {
expireAt = Number(tx.valid_until);
if (isNaN(expireAt)) {
throw {
message: 'invalid validUntil',
code: 1 // BAD_REQUEST_ERROR
}
}
if (expireAt > 9999999999) {
expireAt = expireAt / 1000; // convert millis to seconds, todo: it's not good
}
if (expireAt < Date.now() / 1000) {
throw {
message: 'expired',
code: 1 // BAD_REQUEST_ERROR
}
}
}
if (tx.from) {
if (!Address.isValid(tx.from)) {
throw {
message: 'Invalid source address',
code: 1 // BAD_REQUEST_ERROR
}
}
if (new TonWeb.utils.Address(tx.from).toString(false) !== new TonWeb.utils.Address(this.myAddress).toString(false)) {
throw {
message: 'Different source address',
code: 1 // BAD_REQUEST_ERROR
}
}
}
if (tx.network) {
if (tx.network !== TONCONNECT_TESTNET && tx.network !== TONCONNECT_MAINNET) {
throw {
message: 'Invalid network',
code: 1 // BAD_REQUEST_ERROR
}
}
if ((tx.network === TONCONNECT_TESTNET) !== !!this.isTestnet) {
throw {
message: 'Different network',
code: 1 // BAD_REQUEST_ERROR
}
}
}
if (!tx.messages || !tx.messages.length) {
throw {
message: 'no messages',
code: 1 // BAD_REQUEST_ERROR
}
}
const convertTonconnectMessage = (message) => {
try {
if (!message.address) {
throw new Error('no address')
}
if (!Address.isValid(message.address)) {
throw new Error('invalid address');
}
if (!message.amount) {
throw new Error('no amount')
}
message.amount = new BN(message.amount);
if (message.payload) {
message.payload = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(message.payload));
}
if (message.stateInit) {
message.stateInit = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(message.stateInit));
}
return {
amount: message.amount,
toAddress: message.address,
comment: message.payload,
needEncryptComment: false,
stateInit: message.stateInit
}
} catch (e) {
throw {
message: e.message,
code: 1 // BAD_REQUEST_ERROR
}
}
}
const messages = [];
for (const message of tx.messages) {
messages.push(convertTonconnectMessage(message));
}
this.sendToView('showPopup', {
name: 'loader',
});
/** @type {Cell | null} */
const sentBoc = await this.showSendConfirm(
{
expireAt: expireAt,
messages
},
needQueue
);
if (!sentBoc) {
this.sendToView('closePopup');
throw {
message: 'Reject request',
code: 300 // USER_REJECTS_ERROR
}
}
return TonWeb.utils.bytesToBase64(await sentBoc.toBoc(false));
case 'ton_requestAccounts':
return (this.myAddress ? [this.myAddress] : []);
case 'ton_requestWallets':
if (!this.myAddress) {
return [];
}
if (!this.publicKeyHex) {
await this.requestPublicKey(needQueue);
}
const walletVersion = await storage.getItem('walletVersion');
return [{
address: this.myAddress,
publicKey: this.publicKeyHex,
walletVersion: walletVersion
}];
case 'ton_getBalance':
await this.updateBalance();
return (this.balance ? this.balance.toString() : '');
case 'ton_sendTransaction':
const param = params[0];
await showExtensionWindow();
if (param.data) {
if (param.dataType === 'hex') {
param.data = TonWeb.utils.hexToBytes(param.data);
} else if (param.dataType === 'base64') {
param.data = TonWeb.utils.base64ToBytes(param.data);
} else if (param.dataType === 'boc') {
param.data = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(param.data));
}
}
if (param.stateInit) {
param.stateInit = TonWeb.boc.Cell.oneFromBoc(TonWeb.utils.base64ToBytes(param.stateInit));
}
this.sendToView('showPopup', {
name: 'loader',
});
const result = await this.showSendConfirm(
{
messages: [{
amount: new BN(param.value),
toAddress: param.to,
comment: param.data,
needEncryptComment: false,
stateInit: param.stateInit,
}]
},
needQueue
);
if (!result) {
this.sendToView('closePopup');
}
return !!result;
case 'ton_rawSign':
const signParam = params[0];
await showExtensionWindow();
return this.showSignConfirm(signParam.data, false, needQueue);
case 'flushMemoryCache':
await chrome.webRequest.handlerBehaviorChanged();
return true;
default:
throw {
message: `Method "${method}" not implemented`,
code: 400 // METHOD_NOT_SUPPORTED
};
}
}
}
const controller = new Controller();
if (IS_EXTENSION) {
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'gramWalletContentScript') { // dapp
contentScriptPorts.add(port)
port.onMessage.addListener(async (msg, port) => {
if (msg.type === 'gramWalletAPI_ton_provider_connect') {
controller.whenReady.then(() => {
controller.initDapp();
});
}
if (!msg.message) return;
const origin = decodeURIComponent(msg.message.origin);
let result = undefined;
let error = undefined;
try {
result = await controller.onDappMessage(msg.message.method, msg.message.params, origin);
} catch (e) {
console.error(e);
error = {
message: e.message,
code: e.code || 0
};
}
if (port) {
port.postMessage(JSON.stringify({
type: 'gramWalletAPI',
message: {jsonrpc: '2.0', id: msg.message.id, method: msg.message.method, result, error}
}));
}
});
port.onDisconnect.addListener(port => {
contentScriptPorts.delete(port)
})
} else if (port.name === 'gramWalletPopup') { // view
popupPort = port;
popupPort.onMessage.addListener(function (msg) {
if (msg.method === 'response') {
const resolver = controller.pendingMessageResolvers.get(msg.id);
if (resolver) {
resolver(msg.result);
controller.pendingMessageResolvers.delete(msg.id);
}
} else {
controller.onViewMessage(msg.method, msg.params);
}
});
popupPort.onDisconnect.addListener(() => {
popupPort = null;
});
const runQueueToPopup = () => {
queueToPopup.forEach(msg => popupPort.postMessage(msg));
queueToPopup.length = 0;
};
if (!controller.myAddress) { // if controller not initialized yet
runQueueToPopup();
}
controller.whenReady.then(async () => {
await controller.initView();
runQueueToPopup();
});
}
});
let actionApiName = 'action';
if (chrome.runtime.getManifest().manifest_version === 2) actionApiName = 'browserAction';
chrome[actionApiName].onClicked.addListener(showExtensionWindow);
chrome.windows.onRemoved.addListener(removedWindowId => {
if (dAppPromise) dAppPromise.resolve(false);
if (removedWindowId !== extensionWindowId) return;
extensionWindowId = -1;
});
}
/******/ })()
;
================================================
FILE: docs/js/View.js
================================================
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // define __esModule on exports
/******/ __webpack_require__.r = (exports) => {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
;// CONCATENATED MODULE: ./src/js/view/Utils.js
// UI Utils
/**
* @param selector {string}
* @return {HTMLElement | null}
*/
function $(selector) {
return document.querySelector(selector);
}
/**
* @param selector {string}
* @return {NodeListOf<HTMLElement>}
*/
function $$(selector) {
return document.querySelectorAll(selector);
}
/**
* @param div {HTMLElement}
* @param visible {boolean | 'none' | 'block' | 'flex' | 'inline-block'}
*/
function toggle(div, visible) {
let d = visible;
if (visible === true) d = 'block';
if (visible === false) d = 'none';
div.style.display = d;
}
/**
* @param div {HTMLElement}
* @param isVisible {boolean}
* @param params? {{isBack?: boolean}}
*/
function toggleFaded(div, isVisible, params) {
params = params || {};
if (params.isBack) {
div.classList.add('isBack');
} else {
div.classList.remove('isBack');
}
if (isVisible) {
div.classList.add('faded-show');
div.classList.remove('faded-hide');
} else {
div.classList.remove('faded-show');
div.classList.add('faded-hide');
}
}
/**
* @param div {HTMLElement}
* @param className {string}
* @param duration {number}
*/
function triggerClass(div, className, duration) {
div.classList.add(className);
setTimeout(() => {
div.classList.remove(className);
}, duration);
}
/**
* @param params {{tag: string, clazz?: string | (string | undefined)[], text?: string, child?: (HTMLElement | undefined)[], style?: Object<string, string>}}
* @return {HTMLElement}
*/
function createElement(params) {
const item = document.createElement(params.tag);
if (params.clazz) {
if (Array.isArray(params.clazz)) {
for (let c of params.clazz) {
if (c) {
item.classList.add(c);
}
}
} else {
item.classList.add(params.clazz);
}
}
if (params.text) item.innerText = params.text;
if (params.child) {
for (let c of params.child) {
if (c) {
item.appendChild(c);
}
}
}
if (params.style) {
for (let key in params.style) {
item.style[key] = params.style[key];
}
}
return item;
}
/**
* @param el {HTMLElement}
* @param s {string}
* @return {HTMLElement}
*/
function setAddr(el, s) {
el.innerHTML = '';
el.appendChild(document.createTextNode(s.substring(0, s.length / 2)));
el.appendChild(document.createElement('wbr'));
el.appendChild(document.createTextNode(s.substring(s.length / 2)));
return el;
}
/**
* @param el {HTMLElement}
*/
function clearElement(el) {
el.innerHTML = '';
}
/**
* @param input {HTMLElement}
* @param handler {(e: Event) => void}
*/
function onInput(input, handler) {
input.addEventListener('change', handler);
input.addEventListener('input', handler);
input.addEventListener('cut', handler);
input.addEventListener('paste', handler);
}
/**
* @param n {number}
* @return {string}
*/
function doubleZero(n) {
if (n < 10) return '0' + n;
return n.toString();
}
/**
* @param date {Date}
* @return {string}
*/
function formatTime(date) {
return doubleZero(date.getHours()) + ':' + doubleZero(date.getMinutes());
}
const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
/**
* @param date {Date}
* @return {string}
*/
function formatDate(date) {
return MONTH_NAMES[date.getMonth()] + ' ' + date.getDate();
}
/**
* @param date {Date}
* @return {string}
*/
function formatDateFull(date) {
return date.toString();
}
/**
* @param text {string}
* @return {boolean}
*/
function copyToClipboard(text) {
/** @type {HTMLTextAreaElement} */
const textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed"; //avoid scrolling to bottom
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
/** @type {boolean} */
let result = false;
try {
result = document.execCommand('copy');
} catch (err) {
}
document.body.removeChild(textArea);
return result;
}
const IMPORT_WORDS_COUNT = 24;
const CONFIRM_WORDS_COUNT = 3;
;// CONCATENATED MODULE: ./src/js/view/Lottie.js
/**
* @type {Object<string, any>} lottie name -> lottie element
*/
const lotties = {};
/**
* @param div {HTMLElement}
* @return {Promise<void>}
*/
function initLottie(div) {
return new Promise((resolve, reject) => {
const url = div.getAttribute('src');
const name = div.getAttribute('data-name');
const w = Number(div.getAttribute('width'));
const h = Number(div.getAttribute('height'));
const xmlHttp = new XMLHttpRequest();
xmlHttp.responseType = 'arraybuffer';
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4) {
if (xmlHttp.status === 200) {
const canvas = document.createElement('canvas');
canvas.setAttribute('width', w * window.devicePixelRatio);
canvas.setAttribute('height', h * window.devicePixelRatio);
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
div.appendChild(canvas);
const ctx = canvas.getContext('2d');
const animationData = JSON.parse(new TextDecoder('utf-8').decode(pako.inflate(xmlHttp.response)));
lotties[name] = {
ctx: ctx,
player: lottie.loadAnimation({
renderer: 'canvas',
loop: name === 'processing' || name === 'start' || name === 'about' || name === 'symbol',
autoplay: false,
animationData,
rendererSettings: {
context: ctx,
scaleMode: 'noScale',
clearCanvas: true
},
})
};
ctx.clearRect(0, 0, 1000, 1000);
resolve();
} else {
reject();
}
}
};
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
});
}
/**
* @return {Promise<void>}
*/
async function initLotties() {
const divs = $$('tgs-player');
for (let i = 0; i < divs.length; i++) {
try {
await initLottie(divs[i]);
} catch (e) {
}
}
}
/**
* @param lottie? {any}
* @param visible {boolean}
* @param params? {{hideDelay?: number}}
*/
function toggleLottie(lottie, visible, params) {
if (!lottie) return;
params = params || {};
clearTimeout(lottie.hideTimeout);
if (visible) {
lottie.player.play();
} else {
lottie.player.stop();
if (params.hideDelay) {
lottie.hideTimeout = setTimeout(() => {
lottie.ctx.clearRect(0, 0, 1000, 1000);
}, params.hideDelay);
} else {
lottie.ctx.clearRect(0, 0, 1000, 1000);
}
}
}
;// CONCATENATED MODULE: ./src/js/view/DropDown.js
class DropDown {
/**
* @param container {HTMLElement}
* @param onEnter {(input: HTMLInputElement) => void}
* @param mnemonicWords {string[]}
*/
constructor(container, onEnter, mnemonicWords) {
/** @type {HTMLElement} */
this.container = container;
/** @type {(input: HTMLInputElement) => void} */
this.onEnter = onEnter;
/** @type {string[]} */
this.mnemonicWords = mnemonicWords;
/** @type {number} */
this.selectedI = -1;
}
/**
* @param input {HTMLInputElement}
* @param text {string}
*/
show(input, text) {
clearElement(this.container);
/**
* @param e {MouseEvent}
*/
const onMouseDown = e => {
input.value = e.target.innerText;
input.classList.remove('error');
this.hide();
e.preventDefault();
this.onEnter(input);
};
this.mnemonicWords
.filter(w => w.indexOf(text) === 0)
.forEach(w => {
const item = createElement({tag: 'div', clazz: 'words-popup-item', text: w});
item.addEventListener('mousedown', onMouseDown);
this.container.appendChild(item);
});
this.selectedI = -1;
if (this.container.children.length > 0) this.select(0);
this.container.style.left = input.offsetLeft + 'px';
this.container.style.top = (input.offsetTop + input.offsetHeight) + 'px';
toggle(this.container, true);
};
hide() {
toggle(this.container, false);
clearElement(this.container);
this.selectedI = -1;
}
/**
* @param i {number}
*/
select(i) {
if (this.selectedI > -1) {
this.container.children[this.selectedI].classList.remove('selected');
}
this.selectedI = i;
if (this.selectedI > -1) {
this.container.children[this.selectedI].classList.add('selected');
const ITEM_HEIGHT = 30;
this.container.scrollTo(0, ITEM_HEIGHT * this.selectedI);
}
}
/**
* @return {null | string}
*/
getSelectedText() {
if (this.selectedI === -1) return null;
return this.container.children[this.selectedI].innerText;
}
up() {
if (this.selectedI === -1) return;
if (this.selectedI > 0) {
this.select(this.selectedI - 1);
}
}
down() {
if (this.selectedI === -1) return;
if (this.selectedI < this.container.children.length - 1) {
this.select(this.selectedI + 1);
}
}
}
;// CONCATENATED MODULE: ./src/js/view/View.js
const toNano = TonWeb.utils.toNano;
const fromNano = TonWeb.utils.fromNano;
const BN = TonWeb.utils.BN;
const IS_EXTENSION = !!(self.chrome && chrome.runtime && chrome.runtime.onConnect);
const IS_FIREFOX = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
/**
* @param text {string}
* @param containerSelector {string}
*/
const drawQRCode = (text, containerSelector) => {
const $container = $(containerSelector);
clearElement($container);
new QRCode($container, {
text: text,
width: 185 * window.devicePixelRatio,
height: 185 * window.devicePixelRatio,
colorDark: '#303757',
logo: "assets/ui/qr-logo.png",
logoBackgroundTransparent: false,
logoWidth: 44 * window.devicePixelRatio,
logoHeight: 44 * window.devicePixelRatio,
correctLevel: QRCode.CorrectLevel.L
});
const canvas = $container.querySelector('canvas');
canvas.style.width = '185px';
canvas.style.height = '185px';
};
class View {
/**
* @param mnemonicWords {string[]}
*/
constructor(mnemonicWords) {
/** @type {string[]} */
this.mnemonicWords = mnemonicWords;
/** @type {Controller | null} */
this.controller = null;
/** @type {any | null} */
this.port = null;
/** @type {string | null} */
this.myAddress = null;
/** @type {string | null} */
this.address = null;
/** @type {BN | null} */
this.balance = null;
/** @type {string | null} */
this.currentScreenName = null;
/** @type {boolean} */
this.isTestnet = false;
/** @type {string} */
this.popup = ''; // current opened popup name
/** @type {boolean} */
this.isBack = false;
/** @type {number} */
this.backupShownTime = 0;
/** @type {any | null} */
this.currentOpenTransaction = null;
/** @type {string | null} */
this.currentTransactionAddr = null;
this.createWordInputs({
count: IMPORT_WORDS_COUNT,
dropdownId: '#wordsPopup',
inputId: '#importsInput',
containerId: '#importWords',
multiColumns: true
});
this.createWordInputs({
count: CONFIRM_WORDS_COUNT,
dropdownId: '#wordsConfirmPopup',
inputId: '#confirmInput',
containerId: '#confirmWords',
multiColumns: false
});
/** @type {Promise<void>} */
this._initLotties = initLotties().then(() => {
if (this.currentScreenName) {
toggleLottie(lotties[this.currentScreenName], true);
toggleLottie(lotties.symbol, this.currentScreenName === 'main');
}
});
/**
* @param e {Event}
*/
function resetErrors(e) {
const input = e.target;
input.classList.remove('error');
}
onInput($('#amountInput'), resetErrors);
onInput($('#toWalletInput'), resetErrors);
onInput($('#commentInput'), resetErrors);
onInput($('#createPassword_repeatInput'), resetErrors);
onInput($('#enterPassword_input'), resetErrors);
onInput($('#changePassword_oldInput'), resetErrors);
onInput($('#changePassword_newInput'), resetErrors);
onInput($('#changePassword_repeatInput'), resetErrors);
/**
* @param e {ClipboardEvent}
* @return {string}
*/
function getClipboardData(e) {
const s = (e.clipboardData || window.clipboardData).getData('text');
try {
return decodeURI(s).replaceAll(/%23/g, '#');
} catch (e) { // URIError
return s;
}
}
$('#toWalletInput').addEventListener('paste', e => {
const urlString = getClipboardData(e);
if (!urlString.startsWith('ton://')) return;
/** @type {{address: string, amount?: string, text?: string} | null } */
let parsedTransferUrl = null;
try {
parsedTransferUrl = TonWeb.utils.parseTransferUrl(urlString);
} catch (e) {
$('#notify').innerText = 'Parse transfer URL error';
triggerClass($('#notify'), 'faded-show', 2000);
return;
}
$('#toWalletInput').value = parsedTransferUrl.address;
if (parsedTransferUrl.amount) {
$('#amountInput').value = fromNano(new BN(parsedTransferUrl.amount));
}
if (parsedTransferUrl.text) {
$('#commentInput').value = parsedTransferUrl.text;
}
e.preventDefault();
});
onInput($('#invoice_amountInput'), () => this.updateInvoiceLink());
onInput($('#invoice_commentInput'), () => this.updateInvoiceLink());
$("#start_createBtn").addEventListener('click', () => this.sendMessage('showScreen', {name: 'created'}));
$("#start_importBtn").addEventListener('click', () => this.sendMessage('showScreen', {name: 'import'}));
/** @type {boolean} */
let needShowLedger = false;
try {
needShowLedger = window.location.href.indexOf('ledgerReview') > -1;
} catch (e) {
}
if (needShowLedger) {
toggle($("#start_importLedgerHidBtn"), 'inline-block');
}
$("#start_importLedgerHidBtn").addEventListener('click', () => {
this.showPopup('connectLedger');
this.sendMessage('showScreen', {name: 'importLedger', transportType: 'hid'});
});
// $("#start_importLedgerBleBtn").addEventListener('click', () => this.sendMessage('showScreen', {name: 'importLedger', transportType: 'ble'}));
// $('#main_buyBtn').addEventListener('click', () => {
// window.open('https://exchange.mercuryo.io/?currency=TONCOIN&address=' + this.myAddress, '_blank');
// });
$('#import_backBtn').addEventListener('click', () => {
this.isBack = true;
this.sendMessage('onImportBack');
});
$('#import_alertBtn').addEventListener('click', () => {
this.showAlert({
title: 'Too Bad',
message: 'Without the secret words, you can\'t restore access to your wallet.',
buttons: [
{
label: 'CANCEL',
callback: () => {
this.isBack = true;
this.sendMessage('onImportBack');
}
},
{
label: 'ENTER WORDS',
callback: () => {
this.closePopup();
}
},
]
});
});
$('#import_continueBtn').addEventListener('click', async (e) => {
this.toggleButtonLoader(e.currentTarget, true);
this.sendMessage('import', {words: await this.getImportWords()});
});
$('#createdContinueButton').addEventListener('click', () => this.sendMessage('createPrivateKey'));
$('#backup_continueBtn').addEventListener('click', () => {
const currentTime = Date.now();
if (currentTime - this.backupShownTime < 60000) { // 1 minute
this.showAlert({
title: 'Sure done?',
message: 'You didn\'t have enough time to write these words down.',
buttons: [
{
label: 'I\'M SURE',
callback: () => {
this.sendMessage('onBackupDone');
}
},
{
label: 'OK, SORRY',
callback: () => {
this.closePopup();
gitextract_8l00d9tb/
├── .github/
│ └── ISSUE_TEMPLATE/
│ └── bug_report.yaml
├── .gitignore
├── build/
│ ├── gulp/
│ │ ├── config.js
│ │ ├── copy.js
│ │ ├── css.js
│ │ ├── env.js
│ │ ├── html.js
│ │ ├── manifest.js
│ │ ├── pack.js
│ │ ├── remove.js
│ │ ├── script.js
│ │ └── start.js
│ ├── gulpfile.js
│ ├── readme.md
│ └── safari/
│ ├── TON Wallet/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ ├── Main.html
│ │ │ └── Main.storyboard
│ │ ├── Resources/
│ │ │ ├── Script.js
│ │ │ └── Style.css
│ │ ├── TON_Wallet.entitlements
│ │ └── ViewController.swift
│ ├── TON Wallet Extension/
│ │ ├── Info.plist
│ │ ├── SafariWebExtensionHandler.swift
│ │ └── TON_Wallet_Extension.entitlements
│ └── TON Wallet.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata/
│ │ └── sergei.xcuserdatad/
│ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata/
│ └── sergei.xcuserdatad/
│ └── xcschemes/
│ └── xcschememanagement.plist
├── docs/
│ ├── assets/
│ │ ├── favicon/
│ │ │ ├── browserconfig.xml
│ │ │ └── site.webmanifest
│ │ └── lottie/
│ │ ├── confirm.tgs
│ │ ├── created.tgs
│ │ ├── diamond.tgs
│ │ ├── done.tgs
│ │ ├── empty.tgs
│ │ ├── intro.tgs
│ │ ├── lock.tgs
│ │ ├── money.tgs
│ │ └── paper.tgs
│ ├── index.html
│ ├── js/
│ │ ├── Controller.js
│ │ └── View.js
│ ├── libs/
│ │ ├── aes-js-3.1.2.js
│ │ └── noble-ed25519-1.7.3.js
│ └── main.css
├── license
├── package.json
├── readme.md
└── src/
├── assets/
│ ├── favicon/
│ │ ├── browserconfig.xml
│ │ └── site.webmanifest
│ └── lottie/
│ ├── confirm.tgs
│ ├── created.tgs
│ ├── diamond.tgs
│ ├── done.tgs
│ ├── empty.tgs
│ ├── intro.tgs
│ ├── lock.tgs
│ ├── money.tgs
│ └── paper.tgs
├── css/
│ └── main.css
├── index.html
├── js/
│ ├── Controller.js
│ ├── extension/
│ │ ├── background.js
│ │ ├── content.js
│ │ └── provider.js
│ ├── util/
│ │ ├── encryption.js
│ │ └── storage.js
│ └── view/
│ ├── DropDown.js
│ ├── Lottie.js
│ ├── Utils.js
│ └── View.js
└── libs/
├── aes-js-3.1.2.js
└── noble-ed25519-1.7.3.js
SYMBOL INDEX (534 symbols across 18 files)
FILE: build/gulp/config.js
constant DOTENV_PATH (line 4) | const DOTENV_PATH = '.env';
constant REQUIRED_ENV_VARS (line 12) | const REQUIRED_ENV_VARS = {
constant TARGETS (line 25) | const TARGETS = {
constant BUILD_DESTS (line 37) | const BUILD_DESTS = {
constant TARGETS_BUILD_DESTS (line 46) | const TARGETS_BUILD_DESTS = {
constant WATCH_GLOBS (line 56) | const WATCH_GLOBS = ['src/**/*'];
constant START_WEB_PORT (line 61) | const START_WEB_PORT = 8080;
FILE: build/gulp/copy.js
constant BUILD_DESTS_GLOBS (line 9) | const BUILD_DESTS_GLOBS = {
FILE: build/gulp/env.js
constant ROW_REGEXP (line 17) | const ROW_REGEXP =
FILE: build/safari/TON Wallet/Resources/Script.js
function show (line 1) | function show(enabled) {
function openPreferences (line 11) | function openPreferences() {
FILE: docs/js/Controller.js
method setItem (line 34) | setItem(key, value) {
method getItem (line 42) | getItem(key) {
method removeItem (line 51) | removeItem(key) {
method clear (line 58) | clear() {
function encrypt (line 436) | async function encrypt(plaintext, password) {
function decrypt (line 463) | async function decrypt(ciphertext, password) {
class Controller (line 492) | class Controller {
method constructor (line 493) | constructor() {
method debug (line 539) | debug(...args) {
method wordsToPrivateKey (line 548) | static async wordsToPrivateKey(words) {
method saveWords (line 558) | static async saveWords(words, password) {
method loadWords (line 566) | static async loadWords(password) {
method getApiKey (line 574) | getApiKey(isTestnet) {
method _init (line 584) | async _init() {
method _restoreDeprecatedStorage (line 622) | async _restoreDeprecatedStorage() {
method toggleTestnet (line 640) | async toggleTestnet() {
method toggleDebug (line 652) | async toggleDebug() {
method sendToIndex (line 669) | async sendToIndex(method, params) {
method getWalletInfoFromIndex (line 691) | async getWalletInfoFromIndex(address) {
method getAccountInfoFromIndex (line 702) | async getAccountInfoFromIndex(address) {
method getMySeqno (line 711) | async getMySeqno() {
method getBalance (line 720) | async getBalance(address) {
method checkContractInitialized (line 729) | async checkContractInitialized(address) {
method getTransactionsFromIndex (line 740) | async getTransactionsFromIndex(address, limit) {
method getTransactions (line 751) | async getTransactions(limit = 10) {
method sign (line 851) | async sign(request, keyPair) {
method showCreated (line 876) | async showCreated() {
method createPrivateKey (line 895) | async createPrivateKey() {
method onBackupWalletClick (line 901) | onBackupWalletClick() {
method showBackup (line 908) | showBackup(words, isFirst) {
method onBackupDone (line 912) | async onBackupDone() {
method onConfirmDone (line 920) | onConfirmDone(words) {
method createLedger (line 940) | async createLedger(transportType) {
method importLedger (line 977) | async importLedger(transportType) {
method showImport (line 991) | showImport() {
method import (line 995) | async import(words) {
method importImpl (line 1038) | async importImpl(keyPair, WalletClass) {
method showCreatePassword (line 1052) | showCreatePassword() {
method savePrivateKey (line 1060) | async savePrivateKey(password) {
method onChangePassword (line 1077) | async onChangePassword(oldPassword, newPassword) {
method onEnterPassword (line 1095) | async onEnterPassword(password) {
method showMain (line 1113) | async showMain() {
method initDapp (line 1133) | async initDapp() {
method initView (line 1142) | async initView() {
method updateBalance (line 1159) | async updateBalance() {
method update (line 1173) | async update(force) {
method showAddressOnDevice (line 1212) | async showAddressOnDevice() {
method onDecryptComment (line 1227) | onDecryptComment(hash, encryptedComment, senderAddress) {
method getFees (line 1247) | async getFees(request) {
method showSendConfirm (line 1288) | async showSendConfirm(request, needQueue) {
method send (line 1493) | async send(request, privateKeyBase64, totalAmount) {
method rawSign (line 1575) | rawSign(hexToSign, privateKey) {
method sendQuery (line 1585) | async sendQuery(query) {
method showSignConfirm (line 1605) | showSignConfirm(hexToSign, isConnect, needQueue) {
method requestPublicKey (line 1638) | requestPublicKey(needQueue) {
method showConnectConfirm (line 1662) | showConnectConfirm(needQueue) {
method clearVars (line 1684) | clearVars() {
method onDisconnectClick (line 1697) | async onDisconnectClick() {
method doMagic (line 1706) | doMagic(enabled) {
method doProxy (line 1716) | doProxy(enabled) {
method sendToView (line 1729) | sendToView(method, params, needQueue, needResult) {
method onViewMessage (line 1763) | async onViewMessage(method, params) {
method sendToDapp (line 1883) | sendToDapp(method, params) {
method createTonAddrItemReply (line 1896) | async createTonAddrItemReply(needQueue) {
method createTonProofItemReply (line 1932) | async createTonProofItemReply(origin, payload, needQueue) {
method onDappMessage (line 2007) | async onDappMessage(method, params, origin) {
FILE: docs/js/View.js
function $ (line 30) | function $(selector) {
function $$ (line 38) | function $$(selector) {
function toggle (line 46) | function toggle(div, visible) {
function toggleFaded (line 59) | function toggleFaded(div, isVisible, params) {
function triggerClass (line 80) | function triggerClass(div, className, duration) {
function createElement (line 92) | function createElement(params) {
function setAddr (line 126) | function setAddr(el, s) {
function clearElement (line 137) | function clearElement(el) {
function onInput (line 145) | function onInput(input, handler) {
function doubleZero (line 156) | function doubleZero(n) {
function formatTime (line 165) | function formatTime(date) {
function formatDate (line 175) | function formatDate(date) {
function formatDateFull (line 183) | function formatDateFull(date) {
function copyToClipboard (line 191) | function copyToClipboard(text) {
function initLottie (line 228) | function initLottie(div) {
function initLotties (line 278) | async function initLotties() {
function toggleLottie (line 293) | function toggleLottie(lottie, visible, params) {
class DropDown (line 317) | class DropDown {
method constructor (line 323) | constructor(container, onEnter, mnemonicWords) {
method show (line 338) | show(input, text) {
method hide (line 368) | hide() {
method select (line 377) | select(i) {
method getSelectedText (line 392) | getSelectedText() {
method up (line 397) | up() {
method down (line 405) | down() {
class View (line 453) | class View {
method constructor (line 457) | constructor(mnemonicWords) {
method showScreen (line 987) | showScreen(name) {
method toggleButtonLoader (line 1011) | toggleButtonLoader(el, enable) {
method showAlert (line 1019) | showAlert(params) {
method showPopup (line 1042) | showPopup(name) {
method closePopup (line 1062) | closePopup() {
method setBackupWords (line 1073) | setBackupWords(words) {
method clearBackupWords (line 1107) | clearBackupWords() {
method createWordInputs (line 1116) | createWordInputs(params) {
method clearImportWords (line 1270) | clearImportWords() {
method clearConfirmWords (line 1280) | clearConfirmWords() {
method setConfirmWords (line 1294) | setConfirmWords(words) {
method getImportWords (line 1318) | async getImportWords() {
method getConfirmWords (line 1351) | getConfirmWords() {
method clearCreatePassword (line 1387) | clearCreatePassword() {
method clearChangePassword (line 1394) | clearChangePassword() {
method setUpdating (line 1405) | setUpdating(updating) {
method onSettingsClick (line 1409) | onSettingsClick() {
method clearBalance (line 1416) | clearBalance() {
method setBalance (line 1426) | setBalance(balance, txs) {
method setTransactions (line 1448) | setTransactions(txs) {
method addDateSeparator (line 1473) | addDateSeparator(dateString) {
method addTx (line 1480) | addTx(tx) {
method onTransactionClick (line 1526) | onTransactionClick(tx) {
method onTransactionButtonClick (line 1547) | onTransactionButtonClick() {
method clearSend (line 1553) | clearSend() {
method setMyAddress (line 1565) | setMyAddress(address) {
method loadDiamond (line 1576) | async loadDiamond(address) {
method onShareAddressClick (line 1604) | onShareAddressClick(onyAddress) {
method onShowAddressOnDevice (line 1611) | onShowAddressOnDevice() {
method onCreateInvoiceClick (line 1619) | onCreateInvoiceClick() {
method updateInvoiceLink (line 1623) | updateInvoiceLink() {
method getInvoiceLink (line 1630) | getInvoiceLink() {
method onShareInvoiceClick (line 1637) | onShareInvoiceClick() {
method onCreateInvoiceQrClick (line 1644) | onCreateInvoiceQrClick() {
method drawInvoiceQr (line 1651) | drawInvoiceQr(link) {
method sendMessage (line 1662) | sendMessage(method, params) {
method onMessage (line 1676) | onMessage(method, params) {
FILE: docs/libs/aes-js-3.1.2.js
function checkInt (line 5) | function checkInt(value) {
function checkInts (line 9) | function checkInts(arrayish) {
function coerceArray (line 21) | function coerceArray(arg, copy) {
function createArray (line 54) | function createArray(length) {
function copyArray (line 58) | function copyArray(sourceArray, targetArray, targetStart, sourceStart, s...
function toBytes (line 72) | function toBytes(text) {
function fromBytes (line 92) | function fromBytes(bytes) {
function toBytes (line 120) | function toBytes(text) {
function fromBytes (line 132) | function fromBytes(bytes) {
function convertToInt32 (line 176) | function convertToInt32(bytes) {
function pkcs7pad (line 714) | function pkcs7pad(data) {
function pkcs7strip (line 725) | function pkcs7strip(data) {
FILE: docs/libs/noble-ed25519-1.7.3.js
class ExtendedPoint (line 37) | class ExtendedPoint {
method constructor (line 38) | constructor(x, y, z, t) {
method fromAffine (line 44) | static fromAffine(p) {
method toAffineBatch (line 52) | static toAffineBatch(points) {
method normalizeZ (line 56) | static normalizeZ(points) {
method equals (line 59) | equals(other) {
method negate (line 69) | negate() {
method double (line 72) | double() {
method add (line 90) | add(other) {
method subtract (line 110) | subtract(other) {
method precomputeWindow (line 113) | precomputeWindow(W) {
method wNAF (line 129) | wNAF(n, affinePoint) {
method multiply (line 172) | multiply(scalar, affinePoint) {
method multiplyUnsafe (line 175) | multiplyUnsafe(scalar) {
method isSmallOrder (line 195) | isSmallOrder() {
method isTorsionFree (line 198) | isTorsionFree() {
method toAffine (line 204) | toAffine(invZ) {
method fromRistrettoBytes (line 218) | fromRistrettoBytes() {
method toRistrettoBytes (line 221) | toRistrettoBytes() {
method fromRistrettoHash (line 224) | fromRistrettoHash() {
function constTimeNegate (line 230) | function constTimeNegate(condition, item) {
function assertExtPoint (line 234) | function assertExtPoint(other) {
function assertRstPoint (line 238) | function assertRstPoint(other) {
function legacyRist (line 242) | function legacyRist() {
class RistrettoPoint (line 245) | class RistrettoPoint {
method constructor (line 246) | constructor(ep) {
method calcElligatorRistrettoMap (line 249) | static calcElligatorRistrettoMap(r0) {
method hashToCurve (line 271) | static hashToCurve(hex) {
method fromHex (line 279) | static fromHex(hex) {
method toRawBytes (line 304) | toRawBytes() {
method toHex (line 331) | toHex() {
method toString (line 334) | toString() {
method equals (line 337) | equals(other) {
method add (line 345) | add(other) {
method subtract (line 349) | subtract(other) {
method multiply (line 353) | multiply(scalar) {
method multiplyUnsafe (line 356) | multiplyUnsafe(scalar) {
class Point (line 363) | class Point {
method constructor (line 364) | constructor(x, y) {
method _setWindowSize (line 368) | _setWindowSize(windowSize) {
method fromHex (line 372) | static fromHex(hex, strict = true) {
method fromPrivateKey (line 395) | static async fromPrivateKey(privateKey) {
method toRawBytes (line 398) | toRawBytes() {
method toHex (line 403) | toHex() {
method toX25519 (line 406) | toX25519() {
method isTorsionFree (line 411) | isTorsionFree() {
method equals (line 414) | equals(other) {
method negate (line 417) | negate() {
method add (line 420) | add(other) {
method subtract (line 423) | subtract(other) {
method multiply (line 426) | multiply(scalar) {
class Signature (line 432) | class Signature {
method constructor (line 433) | constructor(r, s) {
method fromHex (line 438) | static fromHex(hex) {
method assertValidity (line 444) | assertValidity() {
method toRawBytes (line 451) | toRawBytes() {
method toHex (line 457) | toHex() {
function concatBytes (line 461) | function concatBytes(...arrays) {
function bytesToHex (line 476) | function bytesToHex(uint8a) {
function hexToBytes (line 485) | function hexToBytes(hex) {
function numberTo32BytesBE (line 502) | function numberTo32BytesBE(num) {
function numberTo32BytesLE (line 507) | function numberTo32BytesLE(num) {
function edIsNegative (line 510) | function edIsNegative(num) {
function bytesToNumberLE (line 513) | function bytesToNumberLE(uint8a) {
function bytes255ToNumberLE (line 519) | function bytes255ToNumberLE(bytes) {
function mod (line 522) | function mod(a, b = CURVE.P) {
function invert (line 526) | function invert(number, modulo = CURVE.P) {
function invertBatch (line 544) | function invertBatch(nums, p = CURVE.P) {
function pow2 (line 561) | function pow2(x, power) {
function pow_2_252_3 (line 570) | function pow_2_252_3(x) {
function uvRatio (line 591) | function uvRatio(u, v) {
function invertSqrt (line 610) | function invertSqrt(number) {
function modlLE (line 613) | function modlLE(hash) {
function equalBytes (line 616) | function equalBytes(b1, b2) {
function ensureBytes (line 627) | function ensureBytes(hex, expectedLength) {
function normalizeScalar (line 633) | function normalizeScalar(num, max, strict = true) {
function adjustBytes25519 (line 650) | function adjustBytes25519(bytes) {
function decodeScalar25519 (line 656) | function decodeScalar25519(n) {
function checkPrivateKey (line 659) | function checkPrivateKey(key) {
function getKeyFromHash (line 668) | function getKeyFromHash(hashed) {
function sha512s (line 677) | function sha512s(...m) {
function getExtendedPublicKey (line 682) | async function getExtendedPublicKey(key) {
function getExtendedPublicKeySync (line 685) | function getExtendedPublicKeySync(key) {
function getPublicKey (line 688) | async function getPublicKey(privateKey) {
function getPublicKeySync (line 691) | function getPublicKeySync(privateKey) {
function sign (line 694) | async function sign(message, privateKey) {
function signSync (line 703) | function signSync(message, privateKey) {
function prepareVerification (line 712) | function prepareVerification(sig, message, publicKey) {
function finishVerification (line 720) | function finishVerification(publicKey, r, SB, hashed) {
function verify (line 726) | async function verify(sig, message, publicKey) {
function verifySync (line 731) | function verifySync(sig, message, publicKey) {
function getSharedSecret (line 742) | async function getSharedSecret(privateKey, publicKey) {
function cswap (line 748) | function cswap(swap, x_2, x_3) {
function montgomeryLadder (line 754) | function montgomeryLadder(pointU, scalar) {
function encodeUCoordinate (line 802) | function encodeUCoordinate(u) {
function decodeUCoordinate (line 805) | function decodeUCoordinate(uEnc) {
method scalarMult (line 812) | scalarMult(privateKey, publicKey) {
method scalarMultBase (line 820) | scalarMultBase(privateKey) {
method precompute (line 879) | precompute(windowSize = 8, point = Point.BASE) {
method get (line 890) | get() {
method set (line 893) | set(val) {
FILE: src/js/Controller.js
constant TONCONNECT_MAINNET (line 4) | const TONCONNECT_MAINNET = '-239';
constant TONCONNECT_TESTNET (line 5) | const TONCONNECT_TESTNET = '-3';
function encrypt (line 75) | async function encrypt(plaintext, password) {
function decrypt (line 102) | async function decrypt(ciphertext, password) {
constant IS_EXTENSION (line 124) | const IS_EXTENSION = !!(self.chrome && chrome.runtime && chrome.runtime....
constant ACCOUNT_NUMBER (line 126) | const ACCOUNT_NUMBER = 0;
constant DEFAULT_WALLET_VERSION (line 128) | const DEFAULT_WALLET_VERSION = 'v3R2';
constant DEFAULT_LEDGER_WALLET_VERSION (line 129) | const DEFAULT_LEDGER_WALLET_VERSION = 'v3R1';
class Controller (line 131) | class Controller {
method constructor (line 132) | constructor() {
method debug (line 178) | debug(...args) {
method wordsToPrivateKey (line 187) | static async wordsToPrivateKey(words) {
method saveWords (line 197) | static async saveWords(words, password) {
method loadWords (line 205) | static async loadWords(password) {
method getApiKey (line 213) | getApiKey(isTestnet) {
method _init (line 223) | async _init() {
method _restoreDeprecatedStorage (line 261) | async _restoreDeprecatedStorage() {
method toggleTestnet (line 279) | async toggleTestnet() {
method toggleDebug (line 291) | async toggleDebug() {
method sendToIndex (line 308) | async sendToIndex(method, params) {
method getWalletInfoFromIndex (line 330) | async getWalletInfoFromIndex(address) {
method getAccountInfoFromIndex (line 341) | async getAccountInfoFromIndex(address) {
method getMySeqno (line 350) | async getMySeqno() {
method getBalance (line 359) | async getBalance(address) {
method checkContractInitialized (line 368) | async checkContractInitialized(address) {
method getTransactionsFromIndex (line 379) | async getTransactionsFromIndex(address, limit) {
method getTransactions (line 390) | async getTransactions(limit = 10) {
method sign (line 490) | async sign(request, keyPair) {
method showCreated (line 515) | async showCreated() {
method createPrivateKey (line 534) | async createPrivateKey() {
method onBackupWalletClick (line 540) | onBackupWalletClick() {
method showBackup (line 547) | showBackup(words, isFirst) {
method onBackupDone (line 551) | async onBackupDone() {
method onConfirmDone (line 559) | onConfirmDone(words) {
method createLedger (line 579) | async createLedger(transportType) {
method importLedger (line 616) | async importLedger(transportType) {
method showImport (line 630) | showImport() {
method import (line 634) | async import(words) {
method importImpl (line 677) | async importImpl(keyPair, WalletClass) {
method showCreatePassword (line 691) | showCreatePassword() {
method savePrivateKey (line 699) | async savePrivateKey(password) {
method onChangePassword (line 716) | async onChangePassword(oldPassword, newPassword) {
method onEnterPassword (line 734) | async onEnterPassword(password) {
method showMain (line 752) | async showMain() {
method initDapp (line 772) | async initDapp() {
method initView (line 781) | async initView() {
method updateBalance (line 798) | async updateBalance() {
method update (line 812) | async update(force) {
method showAddressOnDevice (line 851) | async showAddressOnDevice() {
method onDecryptComment (line 866) | onDecryptComment(hash, encryptedComment, senderAddress) {
method getFees (line 886) | async getFees(request) {
method showSendConfirm (line 927) | async showSendConfirm(request, needQueue) {
method send (line 1132) | async send(request, privateKeyBase64, totalAmount) {
method rawSign (line 1214) | rawSign(hexToSign, privateKey) {
method sendQuery (line 1224) | async sendQuery(query) {
method showSignConfirm (line 1244) | showSignConfirm(hexToSign, isConnect, needQueue) {
method requestPublicKey (line 1277) | requestPublicKey(needQueue) {
method showConnectConfirm (line 1301) | showConnectConfirm(needQueue) {
method clearVars (line 1323) | clearVars() {
method onDisconnectClick (line 1336) | async onDisconnectClick() {
method doMagic (line 1345) | doMagic(enabled) {
method doProxy (line 1355) | doProxy(enabled) {
method sendToView (line 1368) | sendToView(method, params, needQueue, needResult) {
method onViewMessage (line 1402) | async onViewMessage(method, params) {
method sendToDapp (line 1522) | sendToDapp(method, params) {
method createTonAddrItemReply (line 1535) | async createTonAddrItemReply(needQueue) {
method createTonProofItemReply (line 1571) | async createTonProofItemReply(origin, payload, needQueue) {
method onDappMessage (line 1646) | async onDappMessage(method, params, origin) {
FILE: src/js/extension/content.js
constant PORT_NAME (line 20) | const PORT_NAME = 'gramWalletContentScript'
FILE: src/js/extension/provider.js
class TonProvider (line 3) | class TonProvider {
method constructor (line 4) | constructor() {
method on (line 27) | on(method, listener) {
method removeListener (line 39) | removeListener(method, listener) {
method emit (line 48) | emit(method, ...args) {
method send (line 57) | send(method, params = []) {
method _handleJsonRpcMessage (line 97) | async _handleJsonRpcMessage(event) {
method _connect (line 232) | _connect() {
method _destroy (line 243) | _destroy() {
method _emitNotification (line 249) | _emitNotification(result) {
method _emitConnect (line 253) | _emitConnect() {
method _emitClose (line 257) | _emitClose(code, reason) {
method _emitChainChanged (line 261) | _emitChainChanged(chainId) {
method _emitAccountsChanged (line 265) | _emitAccountsChanged(accounts) {
function tonConnectEventError (line 272) | function tonConnectEventError(message, code) {
class TonConnectBridge (line 283) | class TonConnectBridge {
method constructor (line 284) | constructor(provider, prevBridge) {
method connect (line 321) | async connect(protocolVersion, message) {
method disconnect (line 349) | async disconnect() {
method restoreConnection (line 358) | async restoreConnection() {
method send (line 377) | async send(message) {
method listen (line 398) | listen(callback) {
method notify (line 409) | async notify(event) {
function toggleMagicBadge (line 427) | function toggleMagicBadge(isTurnedOn) {
function addBadge (line 444) | function addBadge(html) {
FILE: src/js/util/storage.js
method setItem (line 11) | setItem(key, value) {
method getItem (line 19) | getItem(key) {
method removeItem (line 28) | removeItem(key) {
method clear (line 35) | clear() {
FILE: src/js/view/DropDown.js
class DropDown (line 3) | class DropDown {
method constructor (line 9) | constructor(container, onEnter, mnemonicWords) {
method show (line 24) | show(input, text) {
method hide (line 54) | hide() {
method select (line 63) | select(i) {
method getSelectedText (line 78) | getSelectedText() {
method up (line 83) | up() {
method down (line 91) | down() {
FILE: src/js/view/Lottie.js
function initLottie (line 12) | function initLottie(div) {
function initLotties (line 62) | async function initLotties() {
function toggleLottie (line 77) | function toggleLottie(lottie, visible, params) {
FILE: src/js/view/Utils.js
function $ (line 7) | function $(selector) {
function $$ (line 15) | function $$(selector) {
function toggle (line 23) | function toggle(div, visible) {
function toggleFaded (line 36) | function toggleFaded(div, isVisible, params) {
function triggerClass (line 57) | function triggerClass(div, className, duration) {
function createElement (line 69) | function createElement(params) {
function setAddr (line 103) | function setAddr(el, s) {
function clearElement (line 114) | function clearElement(el) {
function onInput (line 122) | function onInput(input, handler) {
function doubleZero (line 133) | function doubleZero(n) {
function formatTime (line 142) | function formatTime(date) {
constant MONTH_NAMES (line 146) | const MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'Ju...
function formatDate (line 152) | function formatDate(date) {
function formatDateFull (line 160) | function formatDateFull(date) {
function copyToClipboard (line 168) | function copyToClipboard(text) {
constant IMPORT_WORDS_COUNT (line 188) | const IMPORT_WORDS_COUNT = 24;
constant CONFIRM_WORDS_COUNT (line 189) | const CONFIRM_WORDS_COUNT = 3;
FILE: src/js/view/View.js
constant IS_EXTENSION (line 26) | const IS_EXTENSION = !!(self.chrome && chrome.runtime && chrome.runtime....
constant IS_FIREFOX (line 27) | const IS_FIREFOX = navigator.userAgent.toLowerCase().indexOf('firefox') ...
class View (line 55) | class View {
method constructor (line 59) | constructor(mnemonicWords) {
method showScreen (line 589) | showScreen(name) {
method toggleButtonLoader (line 613) | toggleButtonLoader(el, enable) {
method showAlert (line 621) | showAlert(params) {
method showPopup (line 644) | showPopup(name) {
method closePopup (line 664) | closePopup() {
method setBackupWords (line 675) | setBackupWords(words) {
method clearBackupWords (line 709) | clearBackupWords() {
method createWordInputs (line 718) | createWordInputs(params) {
method clearImportWords (line 872) | clearImportWords() {
method clearConfirmWords (line 882) | clearConfirmWords() {
method setConfirmWords (line 896) | setConfirmWords(words) {
method getImportWords (line 920) | async getImportWords() {
method getConfirmWords (line 953) | getConfirmWords() {
method clearCreatePassword (line 989) | clearCreatePassword() {
method clearChangePassword (line 996) | clearChangePassword() {
method setUpdating (line 1007) | setUpdating(updating) {
method onSettingsClick (line 1011) | onSettingsClick() {
method clearBalance (line 1018) | clearBalance() {
method setBalance (line 1028) | setBalance(balance, txs) {
method setTransactions (line 1050) | setTransactions(txs) {
method addDateSeparator (line 1075) | addDateSeparator(dateString) {
method addTx (line 1082) | addTx(tx) {
method onTransactionClick (line 1128) | onTransactionClick(tx) {
method onTransactionButtonClick (line 1149) | onTransactionButtonClick() {
method clearSend (line 1155) | clearSend() {
method setMyAddress (line 1167) | setMyAddress(address) {
method loadDiamond (line 1178) | async loadDiamond(address) {
method onShareAddressClick (line 1206) | onShareAddressClick(onyAddress) {
method onShowAddressOnDevice (line 1213) | onShowAddressOnDevice() {
method onCreateInvoiceClick (line 1221) | onCreateInvoiceClick() {
method updateInvoiceLink (line 1225) | updateInvoiceLink() {
method getInvoiceLink (line 1232) | getInvoiceLink() {
method onShareInvoiceClick (line 1239) | onShareInvoiceClick() {
method onCreateInvoiceQrClick (line 1246) | onCreateInvoiceQrClick() {
method drawInvoiceQr (line 1253) | drawInvoiceQr(link) {
method sendMessage (line 1264) | sendMessage(method, params) {
method onMessage (line 1278) | onMessage(method, params) {
FILE: src/libs/aes-js-3.1.2.js
function checkInt (line 5) | function checkInt(value) {
function checkInts (line 9) | function checkInts(arrayish) {
function coerceArray (line 21) | function coerceArray(arg, copy) {
function createArray (line 54) | function createArray(length) {
function copyArray (line 58) | function copyArray(sourceArray, targetArray, targetStart, sourceStart, s...
function toBytes (line 72) | function toBytes(text) {
function fromBytes (line 92) | function fromBytes(bytes) {
function toBytes (line 120) | function toBytes(text) {
function fromBytes (line 132) | function fromBytes(bytes) {
function convertToInt32 (line 176) | function convertToInt32(bytes) {
function pkcs7pad (line 714) | function pkcs7pad(data) {
function pkcs7strip (line 725) | function pkcs7strip(data) {
FILE: src/libs/noble-ed25519-1.7.3.js
class ExtendedPoint (line 37) | class ExtendedPoint {
method constructor (line 38) | constructor(x, y, z, t) {
method fromAffine (line 44) | static fromAffine(p) {
method toAffineBatch (line 52) | static toAffineBatch(points) {
method normalizeZ (line 56) | static normalizeZ(points) {
method equals (line 59) | equals(other) {
method negate (line 69) | negate() {
method double (line 72) | double() {
method add (line 90) | add(other) {
method subtract (line 110) | subtract(other) {
method precomputeWindow (line 113) | precomputeWindow(W) {
method wNAF (line 129) | wNAF(n, affinePoint) {
method multiply (line 172) | multiply(scalar, affinePoint) {
method multiplyUnsafe (line 175) | multiplyUnsafe(scalar) {
method isSmallOrder (line 195) | isSmallOrder() {
method isTorsionFree (line 198) | isTorsionFree() {
method toAffine (line 204) | toAffine(invZ) {
method fromRistrettoBytes (line 218) | fromRistrettoBytes() {
method toRistrettoBytes (line 221) | toRistrettoBytes() {
method fromRistrettoHash (line 224) | fromRistrettoHash() {
function constTimeNegate (line 230) | function constTimeNegate(condition, item) {
function assertExtPoint (line 234) | function assertExtPoint(other) {
function assertRstPoint (line 238) | function assertRstPoint(other) {
function legacyRist (line 242) | function legacyRist() {
class RistrettoPoint (line 245) | class RistrettoPoint {
method constructor (line 246) | constructor(ep) {
method calcElligatorRistrettoMap (line 249) | static calcElligatorRistrettoMap(r0) {
method hashToCurve (line 271) | static hashToCurve(hex) {
method fromHex (line 279) | static fromHex(hex) {
method toRawBytes (line 304) | toRawBytes() {
method toHex (line 331) | toHex() {
method toString (line 334) | toString() {
method equals (line 337) | equals(other) {
method add (line 345) | add(other) {
method subtract (line 349) | subtract(other) {
method multiply (line 353) | multiply(scalar) {
method multiplyUnsafe (line 356) | multiplyUnsafe(scalar) {
class Point (line 363) | class Point {
method constructor (line 364) | constructor(x, y) {
method _setWindowSize (line 368) | _setWindowSize(windowSize) {
method fromHex (line 372) | static fromHex(hex, strict = true) {
method fromPrivateKey (line 395) | static async fromPrivateKey(privateKey) {
method toRawBytes (line 398) | toRawBytes() {
method toHex (line 403) | toHex() {
method toX25519 (line 406) | toX25519() {
method isTorsionFree (line 411) | isTorsionFree() {
method equals (line 414) | equals(other) {
method negate (line 417) | negate() {
method add (line 420) | add(other) {
method subtract (line 423) | subtract(other) {
method multiply (line 426) | multiply(scalar) {
class Signature (line 432) | class Signature {
method constructor (line 433) | constructor(r, s) {
method fromHex (line 438) | static fromHex(hex) {
method assertValidity (line 444) | assertValidity() {
method toRawBytes (line 451) | toRawBytes() {
method toHex (line 457) | toHex() {
function concatBytes (line 461) | function concatBytes(...arrays) {
function bytesToHex (line 476) | function bytesToHex(uint8a) {
function hexToBytes (line 485) | function hexToBytes(hex) {
function numberTo32BytesBE (line 502) | function numberTo32BytesBE(num) {
function numberTo32BytesLE (line 507) | function numberTo32BytesLE(num) {
function edIsNegative (line 510) | function edIsNegative(num) {
function bytesToNumberLE (line 513) | function bytesToNumberLE(uint8a) {
function bytes255ToNumberLE (line 519) | function bytes255ToNumberLE(bytes) {
function mod (line 522) | function mod(a, b = CURVE.P) {
function invert (line 526) | function invert(number, modulo = CURVE.P) {
function invertBatch (line 544) | function invertBatch(nums, p = CURVE.P) {
function pow2 (line 561) | function pow2(x, power) {
function pow_2_252_3 (line 570) | function pow_2_252_3(x) {
function uvRatio (line 591) | function uvRatio(u, v) {
function invertSqrt (line 610) | function invertSqrt(number) {
function modlLE (line 613) | function modlLE(hash) {
function equalBytes (line 616) | function equalBytes(b1, b2) {
function ensureBytes (line 627) | function ensureBytes(hex, expectedLength) {
function normalizeScalar (line 633) | function normalizeScalar(num, max, strict = true) {
function adjustBytes25519 (line 650) | function adjustBytes25519(bytes) {
function decodeScalar25519 (line 656) | function decodeScalar25519(n) {
function checkPrivateKey (line 659) | function checkPrivateKey(key) {
function getKeyFromHash (line 668) | function getKeyFromHash(hashed) {
function sha512s (line 677) | function sha512s(...m) {
function getExtendedPublicKey (line 682) | async function getExtendedPublicKey(key) {
function getExtendedPublicKeySync (line 685) | function getExtendedPublicKeySync(key) {
function getPublicKey (line 688) | async function getPublicKey(privateKey) {
function getPublicKeySync (line 691) | function getPublicKeySync(privateKey) {
function sign (line 694) | async function sign(message, privateKey) {
function signSync (line 703) | function signSync(message, privateKey) {
function prepareVerification (line 712) | function prepareVerification(sig, message, publicKey) {
function finishVerification (line 720) | function finishVerification(publicKey, r, SB, hashed) {
function verify (line 726) | async function verify(sig, message, publicKey) {
function verifySync (line 731) | function verifySync(sig, message, publicKey) {
function getSharedSecret (line 742) | async function getSharedSecret(privateKey, publicKey) {
function cswap (line 748) | function cswap(swap, x_2, x_3) {
function montgomeryLadder (line 754) | function montgomeryLadder(pointU, scalar) {
function encodeUCoordinate (line 802) | function encodeUCoordinate(u) {
function decodeUCoordinate (line 805) | function decodeUCoordinate(uEnc) {
method scalarMult (line 812) | scalarMult(privateKey, publicKey) {
method scalarMultBase (line 820) | scalarMultBase(privateKey) {
method precompute (line 879) | precompute(windowSize = 8, point = Point.BASE) {
method get (line 890) | get() {
method set (line 893) | set(val) {
Condensed preview — 77 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (747K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
"chars": 2851,
"preview": "name: Bug Report\ndescription: Report of critical bug such as UX inconsistencies, logical contradictions and functional s"
},
{
"path": ".gitignore",
"chars": 51,
"preview": ".idea\n.vscode\nartifacts\nnode_modules\n.DS_Store\n.env"
},
{
"path": "build/gulp/config.js",
"chars": 1749,
"preview": "/**\n * Path to \".env\" file relative to project root directory\n */\nconst DOTENV_PATH = '.env';\n\n/**\n * Required for any t"
},
{
"path": "build/gulp/copy.js",
"chars": 1333,
"preview": "const { dest, src } = require('gulp');\nconst { BUILD_DESTS } = require('./config');\n\n/**\n * Build destinations and requi"
},
{
"path": "build/gulp/css.js",
"chars": 151,
"preview": "const { dest, src } = require('gulp');\n\nconst css = buildDest => {\n return src('src/css/**/*.css').pipe(dest(buildDes"
},
{
"path": "build/gulp/env.js",
"chars": 2689,
"preview": "const { existsSync, readFileSync } = require('fs');\nconst { DOTENV_PATH, REQUIRED_ENV_VARS } = require('./config');\n\n/**"
},
{
"path": "build/gulp/html.js",
"chars": 644,
"preview": "const { dest, src } = require('gulp');\nconst replace = require('gulp-replace');\nconst { BUILD_DESTS } = require('./confi"
},
{
"path": "build/gulp/manifest.js",
"chars": 3070,
"preview": "const { writeFileSync } = require('fs');\nconst { BUILD_DESTS } = require('./config');\nconst { version } = require('../.."
},
{
"path": "build/gulp/pack.js",
"chars": 1406,
"preview": "const { spawn } = require('child_process');\nconst { dest, src } = require('gulp');\nconst zip = require('gulp-zip');\ncons"
},
{
"path": "build/gulp/remove.js",
"chars": 308,
"preview": "const { existsSync, rmSync, rmdirSync } = require('fs');\n\nconst remove = async buildDest => {\n if (!existsSync(buildDes"
},
{
"path": "build/gulp/script.js",
"chars": 1076,
"preview": "const { resolve } = require('path');\nconst webpack = require('webpack');\n\nconst script = (buildDest, done) => {\n webp"
},
{
"path": "build/gulp/start.js",
"chars": 2012,
"preview": "const { access, readFile } = require('fs');\nconst { createServer } = require('http');\nconst open = require('open');\ncons"
},
{
"path": "build/gulpfile.js",
"chars": 2025,
"preview": "const { series, task, watch } = require('gulp');\nconst { TARGETS, BUILD_DESTS, TARGETS_BUILD_DESTS, WATCH_GLOBS } = requ"
},
{
"path": "build/readme.md",
"chars": 2708,
"preview": "# Source code\n\nWe deliberately use plain js and do not use frameworks, due to the direct access to the user's private ke"
},
{
"path": "build/safari/TON Wallet/AppDelegate.swift",
"chars": 436,
"preview": "//\n// AppDelegate.swift\n// TON Wallet\n//\n// Created by Сергей Иваньков on 15.03.2022.\n//\n\nimport Cocoa\n\n@main\nclass A"
},
{
"path": "build/safari/TON Wallet/Assets.xcassets/AccentColor.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "build/safari/TON Wallet/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1191,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n \"size\" : \"16x16\",\n \"filename\": \"16.png\"\n "
},
{
"path": "build/safari/TON Wallet/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "build/safari/TON Wallet/Base.lproj/Main.html",
"chars": 915,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta http-equ"
},
{
"path": "build/safari/TON Wallet/Base.lproj/Main.storyboard",
"chars": 8474,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
},
{
"path": "build/safari/TON Wallet/Resources/Script.js",
"chars": 513,
"preview": "function show(enabled) {\n if (typeof enabled === \"boolean\") {\n document.body.classList.toggle(`state-on`, enab"
},
{
"path": "build/safari/TON Wallet/Resources/Style.css",
"chars": 689,
"preview": "* {\n -webkit-user-select: none;\n -webkit-user-drag: none;\n cursor: default;\n}\n\n:root {\n color-scheme: light "
},
{
"path": "build/safari/TON Wallet/TON_Wallet.entitlements",
"chars": 322,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "build/safari/TON Wallet/ViewController.swift",
"chars": 1641,
"preview": "//\n// ViewController.swift\n// TON Wallet\n//\n// Created by Сергей Иваньков on 15.03.2022.\n//\n\nimport Cocoa\nimport Safa"
},
{
"path": "build/safari/TON Wallet Extension/Info.plist",
"chars": 426,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "build/safari/TON Wallet Extension/SafariWebExtensionHandler.swift",
"chars": 773,
"preview": "//\n// SafariWebExtensionHandler.swift\n// TON Wallet Extension\n//\n// Created by Сергей Иваньков on 15.03.2022.\n//\n\nimp"
},
{
"path": "build/safari/TON Wallet Extension/TON_Wallet_Extension.entitlements",
"chars": 322,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "build/safari/TON Wallet.xcodeproj/project.pbxproj",
"chars": 24240,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "build/safari/TON Wallet.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "build/safari/TON Wallet.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "build/safari/TON Wallet.xcodeproj/xcuserdata/sergei.xcuserdatad/xcschemes/xcschememanagement.plist",
"chars": 345,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "docs/assets/favicon/browserconfig.xml",
"chars": 473,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square70x70logo s"
},
{
"path": "docs/assets/favicon/site.webmanifest",
"chars": 1231,
"preview": "{\n \"name\": \"TON Wallet\",\n \"short_name\": \"TON Wallet\",\n \"icons\": [\n {\n \"src\": \"36x36.png\",\n "
},
{
"path": "docs/index.html",
"chars": 26899,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" translate=\"no\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"Content-Security"
},
{
"path": "docs/js/Controller.js",
"chars": 83869,
"preview": "/******/ (() => { // webpackBootstrap\n/******/ \t\"use strict\";\n/******/ \t// The require scope\n/******/ \tvar __webpack_req"
},
{
"path": "docs/js/View.js",
"chars": 68911,
"preview": "/******/ (() => { // webpackBootstrap\n/******/ \t\"use strict\";\n/******/ \t// The require scope\n/******/ \tvar __webpack_req"
},
{
"path": "docs/libs/aes-js-3.1.2.js",
"chars": 63732,
"preview": "/*! MIT License. Copyright 2015-2018 Richard Moore <me@ricmoo.com>. See LICENSE.txt. */\n(function(root) {\n \"use stric"
},
{
"path": "docs/libs/noble-ed25519-1.7.3.js",
"chars": 34311,
"preview": "(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n "
},
{
"path": "docs/main.css",
"chars": 21094,
"preview": "@font-face {\n font-family: Helvetica, Arial, sans-serif;\n /*src: local('Mulish Regular'), local('Mulish-Regular'),"
},
{
"path": "license",
"chars": 35147,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "package.json",
"chars": 576,
"preview": "{\n \"name\": \"ton-wallet\",\n \"version\": \"1.1.50\",\n \"devDependencies\": {\n \"gulp\": \"4.0.2\",\n \"gulp-replace\": \"1.1.3\""
},
{
"path": "readme.md",
"chars": 1472,
"preview": "# ⚠️ Archived\n\nThis is the first web wallet in TON. The code may be useful for learning or nostalgia. The actual code of"
},
{
"path": "src/assets/favicon/browserconfig.xml",
"chars": 473,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n <msapplication>\n <tile>\n <square70x70logo s"
},
{
"path": "src/assets/favicon/site.webmanifest",
"chars": 1231,
"preview": "{\n \"name\": \"TON Wallet\",\n \"short_name\": \"TON Wallet\",\n \"icons\": [\n {\n \"src\": \"36x36.png\",\n "
},
{
"path": "src/css/main.css",
"chars": 21094,
"preview": "@font-face {\n font-family: Helvetica, Arial, sans-serif;\n /*src: local('Mulish Regular'), local('Mulish-Regular'),"
},
{
"path": "src/index.html",
"chars": 26944,
"preview": "<!DOCTYPE html>\n<html lang=\"en\" translate=\"no\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"Content-Security"
},
{
"path": "src/js/Controller.js",
"chars": 71941,
"preview": "import storage from './util/storage.js';\nimport {decryptMessageComment, encryptMessageComment, makeSnakeCells, parseSnak"
},
{
"path": "src/js/extension/background.js",
"chars": 675,
"preview": "if(typeof importScripts !== 'function') {\n const injectScript = path => {\n return new Promise(resolve => {\n "
},
{
"path": "src/js/extension/content.js",
"chars": 1821,
"preview": "const container = document.head || document.documentElement;\nconst scriptTag = document.createElement('script');\nscriptT"
},
{
"path": "src/js/extension/provider.js",
"chars": 15962,
"preview": "// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#sample-class-implementation\n(() => {\n class TonProvi"
},
{
"path": "src/js/util/encryption.js",
"chars": 10146,
"preview": "// This JS library implements TON message comment encryption and decryption for Web\n// Reference C++ code - SimpleEncryp"
},
{
"path": "src/js/util/storage.js",
"chars": 778,
"preview": "/**\n * `localStorage` polyfill for Chrome Extension environment\n */\n\nexport default self.localStorage || {\n /**\n "
},
{
"path": "src/js/view/DropDown.js",
"chars": 2754,
"preview": "import {clearElement, createElement, toggle} from \"./Utils.js\";\n\nexport default class DropDown {\n /**\n * @param c"
},
{
"path": "src/js/view/Lottie.js",
"chars": 3058,
"preview": "import {$$} from \"./Utils.js\";\n\n/**\n * @type {Object<string, any>} lottie name -> lottie element\n */\nconst lotties = {};"
},
{
"path": "src/js/view/Utils.js",
"chars": 4615,
"preview": "// UI Utils\n\n/**\n * @param selector {string}\n * @return {HTMLElement | null}\n */\nfunction $(selector) {\n return docu"
},
{
"path": "src/js/view/View.js",
"chars": 58236,
"preview": "import {\n $,\n $$,\n clearElement,\n CONFIRM_WORDS_COUNT,\n copyToClipboard,\n createElement,\n formatDat"
},
{
"path": "src/libs/aes-js-3.1.2.js",
"chars": 63732,
"preview": "/*! MIT License. Copyright 2015-2018 Richard Moore <me@ricmoo.com>. See LICENSE.txt. */\n(function(root) {\n \"use stric"
},
{
"path": "src/libs/noble-ed25519-1.7.3.js",
"chars": 34311,
"preview": "(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n "
}
]
// ... and 19 more files (download for full content)
About this extraction
This page contains the full source code of the toncenter/ton-wallet GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 77 files (701.8 KB), approximately 210.4k tokens, and a symbol index with 534 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.