Skip to content
Snippets Groups Projects
Verified Commit 88d45eb5 authored by Mateusz Tyszczak's avatar Mateusz Tyszczak :scroll:
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #117393 canceled
# Build directories
.DS_Store
dist/
build/
.cache/
hive-metamask-snap/
# Logs
snapper.log.json
logs
*.log
npm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Output of 'npm pack'
*.tgz
image: node:20.18.3
stages:
- build
- deploy
variables:
GIT_DEPTH: 0
GIT_STRATEGY: clone
GIT_SUBMODULE_STRATEGY: recursive
cache:
key:
files:
- pnpm-lock.yaml
paths:
- node_modules/
- .pnpm-store/
default:
tags:
- public-runner-docker
.npm_based_job:
before_script:
- corepack enable
- corepack prepare pnpm@10.0.0 --activate
- pnpm config set store-dir .pnpm-store
- pnpm install
build:
extends: .npm_based_job
stage: build
script:
- pnpm build
- pnpm pack
artifacts:
paths:
- hiveio-metamask-snap-*.tgz
when: always
expire_in: 1 week
publish_dev_package:
extends: .npm_based_job
stage: deploy
variables:
REGISTRY_URL: "gitlab.syncad.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
PUBLISH_TOKEN: "${CI_JOB_TOKEN}"
script:
- echo "@hiveio:registry=https://${REGISTRY_URL}" > .npmrc
- echo "//${REGISTRY_URL}:_authToken=\"${PUBLISH_TOKEN}\"" >> .npmrc
- pnpm publish --no-git-checks --access=public hiveio-metamask-snap-*.tgz
needs:
- job: build
artifacts: true
publish_npmjs_package:
extends: .npm_based_job
stage: deploy
variables:
REGISTRY_URL: "registry.npmjs.org/"
PUBLISH_TOKEN: "${INTERNAL_HIDDEN_PUBLISH_TOKEN}"
script:
- echo "@hiveio:registry=https://${REGISTRY_URL}" > .npmrc
- echo "//${REGISTRY_URL}:_authToken=\"${PUBLISH_TOKEN}\"" >> .npmrc
- pnpm publish --no-git-checks --access=public hiveio-metamask-snap-*.tgz
needs:
- job: build
artifacts: true
[submodule "npm-common-config"]
path = npm-common-config
url = ../common-ci-configuration.git
.npmrc 0 → 120000
./npm-common-config/pnpm-config/.npmrc
\ No newline at end of file
MIT No Attribution
Copyright 2024 Hive Community
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Hive Wallet Metamask Snap
Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet
## Development Setup
```bash
# Clone the repository and its submodules
git clone --recurse-submodules https://gitlab.syncad.com/hive/metamask-snap.git
# Install dependencies
pnpm install
# Start development server
pnpm start
```
# License
[MIT License](LICENSE.md)
Subproject commit 5806284d3c6feb2ce52bdb6077c20a9a578bb643
{
"name": "@hiveio/metamask-snap",
"version": "1.0.0",
"description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet",
"main": "./dist/bundle.js",
"files": [
"dist/bundle.js",
"src/assets/icon.svg",
"snap.manifest.json"
],
"private": false,
"homepage": "https://gitlab.syncad.com/hive/metamask-snap#readme",
"bugs": {
"url": "https://gitlab.syncad.com/hive/metamask-snap/-/issues"
},
"repository": {
"type": "git",
"url": "git+https://gitlab.syncad.com/hive/metamask-snap.git"
},
"license": "MIT",
"author": {
"name": "Hive Community"
},
"packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b",
"engines": {
"node": "^20.18.1 || >= 21.2"
},
"type": "commonjs",
"scripts": {
"prebuild": "./scripts/snapper.sh",
"build": "mm-snap build",
"prepublishOnly": "mm-snap manifest",
"serve": "mm-snap serve",
"prestart": "npm run prebuild",
"start": "mm-snap watch"
},
"dependencies": {},
"devDependencies": {
"@hiveio/beekeeper": "1.27.10-250304140859",
"@hiveio/wax": "1.27.6-rc7-250304235913",
"@metamask/key-tree": "^10.0.2",
"@metamask/snaps-cli": "~6.6.0",
"@metamask/snaps-sdk": "~6.14.0",
"@metamask/utils": "^11.2.0",
"@sayfer_io/snapper": "^0.19.0"
}
}
This diff is collapsed.
#!/bin/bash
set -e
SCRIPT_PATH=$(dirname $(readlink -f $0))
SNAP_PATH=$(dirname $SCRIPT_PATH)
LOG_FILE="${SNAP_PATH}/snapper.log.json"
# Run snapper
pnpm exec snapper -p "${SNAP_PATH}" --output "${LOG_FILE}"
# Display simplified human-readable report and exit with error code if any issues found
if [ -f "${LOG_FILE}" ]; then
jq -r '.ESLinting[] | "\(.position.filePath):\(.position.lineNum):\n\(.type): \(.description)\n"' "${LOG_FILE}"
exit 1
fi;
import type { SnapConfig } from '@metamask/snaps-cli';
import { resolve } from 'path';
const config: SnapConfig = {
bundler: 'webpack',
input: resolve(__dirname, 'src/index.ts'),
server: {
port: 8080,
},
customizeWebpackConfig: webpackConfig => {
webpackConfig.module = webpackConfig.module ?? {};
webpackConfig.module.rules = webpackConfig.module.rules ?? [];
webpackConfig.module.rules.push({
test: /\.wasm$/,
type: "asset/inline"
});
return webpackConfig;
},
polyfills: {
buffer: true,
},
};
export default config;
{
"version": "1.0.0",
"description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet",
"proposedName": "Hive Wallet",
"repository": {
"type": "git",
"url": "git+https://gitlab.syncad.com/hive/metamask-snap.git"
},
"source": {
"shasum": "t3+iz1sq3+FdHFLcZta14bGp8gKBJBHHb8U+tnUG/xE=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
"iconPath": "src/assets/icon.svg",
"packageName": "@hiveio/metamask-snap",
"registry": "https://registry.npmjs.org/"
}
}
},
"initialPermissions": {
"snap_dialog": {},
"endowment:rpc": {
"dapps": true,
"snaps": false
},
"endowment:webassembly": {},
"snap_getBip32Entropy": [
{
"path": ["m", "48'", "13'"],
"curve": "secp256k1"
}
],
"snap_getBip32PublicKey": [
{
"path": ["m", "48'", "13'"],
"curve": "secp256k1"
}
]
},
"platformVersion": "6.14.0",
"manifestVersion": "0.1"
}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="50" height="50" viewBox="0 0 50 50"><style>.c{fill:#e31337;}</style>
<g transform="matrix(0.22 0 0 0.22 25 25)"><g><g transform="matrix(1 0 0 1 9.98 0)">
<path class="c" transform="translate(-120, -95)" d="M 157.27 107.26 C 157.59550718851202 107.28803453556017 157.8867940105528 107.47331027241086 158.05017441300336 107.75623731080088 C 158.21355481545393 108.0391643491909 158.22842414272077 108.38406145626317 158.09 108.68 L 111.34 189.53 C 111.16262187747556 189.81410152059811 110.85479421739262 189.9905393257676 110.52000000000001 190 L 81.94 190 C 81.60177905846827 190.00064518341455 81.2892867422873 189.8195387262217 81.1217047125343 189.52575294566708 C 80.95412268278129 189.23196716511242 80.95729050451172 188.87080105178703 81.13 188.58 L 127.88 107.73000000000002 C 128.04718686772347 107.44041661601455 128.35562313181325 107.26144742574024 128.69 107.26000000000002 Z M 129.48 84.09 C 129.14520578260738 84.08053932576762 128.83737812252446 83.90410152059812 128.66 83.62 L 81.13 1.42 C 80.9572905045117 1.129198948212992 80.95412268278127 0.7680328348875951 81.12170471253428 0.4742470543329369 C 81.28928674228729 0.1804612737782787 81.60177905846827 -0.0006451834145517754 81.94 0 L 110.52 0 C 110.85479421739261 0.009460674232391096 111.16262187747554 0.1858984794018836 111.33999999999999 0.46999999999999975 L 158.87 82.67 C 159.04270949548828 82.96080105178702 159.04587731721873 83.32196716511241 158.87829528746573 83.61575294566707 C 158.7107132577127 83.90953872622173 158.39822094153175 84.09064518341455 158.06 84.09 Z" stroke-linecap="round"/>
</g><g>
<path class="c" transform="translate(-110, -95)" d="M 135.13 1.42 C 134.95066767946898 1.1198413280657535 134.95275574583977 0.7449493368468116 135.1354204725965 0.4468069092668695 C 135.31808519935322 0.14866448168692736 135.65113723566728 -0.023453809637205003 136 0 L 164.62 0 C 164.95493536363585 -0.0006228108660463062 165.26433333858506 0.17890440916621442 165.43 0.4700000000000004 L 219.92000000000002 94.53 C 220.0900051921206 94.82027341417273 220.0900051921206 95.17972658582727 219.92000000000002 95.47 L 165.43 189.53 C 165.26433333858506 189.82109559083378 164.95493536363585 190.00062281086605 164.62 190 L 136 190 C 135.6599937714216 190.00428165843292 135.3441820026906 189.82460721332617 135.17414011865083 189.53014443852558 C 135.00409823461106 189.23568166372502 135.00633907417142 188.87234303636552 135.18 188.58 L 189.34 95 Z M 111.86999999999999 94.52 C 112.04863279495407 94.8294010767585 112.04863279495407 95.21059892324149 111.86999999999999 95.52 L 57.13 189.53 C 56.943565207359455 189.8021638588064 56.63489558643201 189.96486727222714 56.30500000000001 189.96486727222714 C 55.975104413568005 189.96486727222714 55.66643479264055 189.8021638588064 55.480000000000004 189.53 L 0.13 95.48 C -0.04863279495408171 95.1705989232415 -0.04863279495408185 94.78940107675851 0.12999999999999978 94.48 L 54.87 0.47 C 55.056434792640545 0.19783614119359838 55.36510441356799 0.0351327277728668 55.69499999999999 0.0351327277728668 C 56.024895586431995 0.0351327277728668 56.33356520735945 0.19783614119359844 56.519999999999996 0.47 Z" stroke-linecap="round"/>
</g></g></g></svg>
\ No newline at end of file
import createBeekeeper, { type IBeekeeperUnlockedWallet, type IBeekeeperInstance, type IBeekeeperSession } from "@hiveio/beekeeper";
let _beekeeper: undefined | IBeekeeperInstance;
let _session: undefined | IBeekeeperSession;
const getBeekeeperSession = async (): Promise<IBeekeeperSession> => {
if (!_beekeeper || !_session) {
_beekeeper = await createBeekeeper({ enableLogs: false, inMemory: true });
_session = _beekeeper.createSession("salt");
}
return _session;
};
export const getTempWallet = async (): Promise<IBeekeeperUnlockedWallet> => {
const session = await getBeekeeperSession();
const walletName = "w" + Date.now();
const { wallet } = await session.createWallet(walletName, "pass", true);
return wallet;
};
import { createWaxFoundation, type IWaxBaseInterface } from "@hiveio/wax";
let _wax: undefined | IWaxBaseInterface;
export const getWax = async (): Promise<IWaxBaseInterface> => {
if (!_wax)
_wax = await createWaxFoundation();
return _wax;
};
import type { RpcRequest, RpcResponse } from './rpc';
import { getPublicKeys } from './snap/getPublicKeys';
import { signTransaction } from './snap/signTransaction';
/**
* Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`.
* @param args - The request handler args as object.
* @param args.origin - The origin of the request, e.g., the website that
* invoked the snap.
* @param args.request - A validated JSON-RPC request object.
* @returns The result of `snap_dialog`.
* @throws If the request method is not valid for this snap.
*/
export const onRpcRequest = async ({
origin,
request,
}: { origin: string; request: RpcRequest }): Promise<RpcResponse> => {
switch (request.method) {
case 'hive_getPublicKeys':
return {
publicKeys: await getPublicKeys(request.params.keys)
};
case 'hive_signTransaction':
return {
signatures: await signTransaction(origin, request.params.transaction, request.params.keys)
}
default:
throw new Error('Method not found.');
}
};
import type { THexString, TPublicKey, TRole } from "@hiveio/wax";
export type KeyIndex = {
accountIndex?: number;
role: TRole;
}
export type PublicKeyData = Required<KeyIndex> & {
publicKey: TPublicKey;
}
export type GetPublicKeyRequest = {
method: 'hive_getPublicKeys';
params: {
keys: KeyIndex[];
};
};
export type SignTransactionRequest = {
method: 'hive_signTransaction';
params: {
transaction: string;
keys: KeyIndex[];
};
}
export type GetPublicKeyResponse = {
publicKeys: PublicKeyData[];
}
export type SignTransactionResponse = {
signatures: THexString[];
}
export type RpcRequest = GetPublicKeyRequest | SignTransactionRequest;
export type RpcResponse = GetPublicKeyResponse | SignTransactionResponse;
import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx";
import { KeyIndex } from "../../rpc";
export const ConfirmSign = (origin: string, transaction: string, keys: KeyIndex[]) => snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: (
<Box>
<Text>
<Bold>{ origin }</Bold> asked to sign a transaction:
</Text>
<Copyable value={ transaction } />
<Text>
Confirm if you want to sign it using your:
</Text>
{ keys.map(key => (<Text>
- <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex || 0) }</Italic>)
</Text>))}
</Box>
)
}
});
import type { KeyIndex, PublicKeyData } from "../rpc";
import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils";
import { keyIndexToPath } from "../utils/key-management";
export const getPublicKeys = async (keys: KeyIndex[]): Promise<PublicKeyData[]> => {
const wax = await getWax();
const publicKeys: PublicKeyData[] = [];
for(const key of keys) {
const bip32 = await snap.request({
method: 'snap_getBip32PublicKey',
params: {
curve: "secp256k1",
path: keyIndexToPath(key),
compressed: true
}
});
const publicKey = wax.convertRawPublicKeyToWif(remove0x(bip32));
publicKeys.push({
accountIndex: key.accountIndex ?? 0,
publicKey,
role: key.role
});
}
return publicKeys;
};
import type { KeyIndex } from "../rpc";
import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils";
import { keyIndexToPath } from "../utils/key-management";
import { getTempWallet } from "../hive/beekeeper";
import { ConfirmSign } from "./dialogs/ConfirmSign";
import type { THexString } from "@hiveio/wax";
import { SLIP10Node } from "@metamask/key-tree";
export const signTransaction = async (origin: string, transaction: string, keys: KeyIndex[]): Promise<THexString[]> => {
if (keys.length < 1)
throw new Error('No keys provided');
const confirmSign = await ConfirmSign(origin, transaction, keys);
if(!confirmSign)
throw new Error('User denied the transaction');
// The order is important: First create wax, then transaction and if all success then create wallet
const wax = await getWax();
const tx = wax.createTransactionFromJson(transaction);
const wallet = await getTempWallet();
try {
const signatures: THexString[] = [];
for(const key of keys) {
const snapResponse = await snap.request({
method: 'snap_getBip32Entropy',
params: {
curve: "secp256k1",
path: keyIndexToPath(key)
}
});
const node = await SLIP10Node.fromJSON(snapResponse);
if (!node.privateKey)
throw new Error('No private key found');
const wif = wax.convertRawPrivateKeyToWif(remove0x(node.privateKey));
const publicKey = await wallet.importKey(wif);
const signature = tx.sign(wallet, publicKey);
signatures.push(signature);
}
return signatures;
} finally {
wallet.close();
}
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment