diff --git a/.prettierrc.js b/.prettierrc.cjs
similarity index 100%
rename from .prettierrc.js
rename to .prettierrc.cjs
diff --git a/package.json b/package.json
index 1cdb91d..3db9520 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"eslint-plugin-promise": "^6.1.1",
"prettier": "^2.7.1",
"prettier-plugin-packagejson": "^2.2.18",
+ "prettier-plugin-svelte": "^3.2.4",
"sharp": "^0.32.6",
"typescript": "^4.7.4",
"typescript-eslint": "^8.0.0-alpha.20"
diff --git a/packages/site/src/components/CreateAccount.svelte b/packages/site/src/components/CreateAccount.svelte
new file mode 100644
index 0000000..8fe5d70
--- /dev/null
+++ b/packages/site/src/components/CreateAccount.svelte
@@ -0,0 +1,73 @@
+
+
+
Create Jungle4 Account
+
diff --git a/packages/site/src/lib/account.ts b/packages/site/src/lib/account.ts
index 007ab98..8ffdecc 100644
--- a/packages/site/src/lib/account.ts
+++ b/packages/site/src/lib/account.ts
@@ -1,3 +1,7 @@
import { writable } from 'svelte/store';
export const accountName = writable(null);
+
+export const accountPermission = writable(null);
+
+export const accountPublicKey = writable(null);
diff --git a/packages/site/src/lib/rpc-methods.ts b/packages/site/src/lib/rpc-methods.ts
new file mode 100644
index 0000000..79d6fe3
--- /dev/null
+++ b/packages/site/src/lib/rpc-methods.ts
@@ -0,0 +1,21 @@
+import { accountName } from './account';
+import { invokeSnap } from './snap';
+
+export async function connectAccount() {
+ const result = await invokeSnap({ method: 'eos_connectAccount' });
+ console.log('accountName', result);
+ accountName.set(result);
+}
+
+export async function getConnectedAccount() {
+ const account = await invokeSnap({ method: 'eos_getConnectedAccount' });
+ console.log('account', account);
+ accountName.set(account);
+ // accountPublicKey.set(account.publicKey);
+ // accountPermission.set(account.permission);
+}
+
+export async function testTransaction() {
+ const result = await invokeSnap({ method: 'eos_signTransaction' });
+ console.log('result', result);
+}
diff --git a/packages/site/src/routes/+page.svelte b/packages/site/src/routes/+page.svelte
index 86ce700..77980d9 100644
--- a/packages/site/src/routes/+page.svelte
+++ b/packages/site/src/routes/+page.svelte
@@ -6,6 +6,8 @@
import { setSnap, requestSnap, invokeSnap } from '$lib/snap';
import flask_fox from '../assets/flask_fox.svg';
import { accountName } from '$lib/account';
+ import CreateAccount from '../components/CreateAccount.svelte';
+ import { connectAccount, getConnectedAccount, testTransaction } from '$lib/rpc-methods';
// import type {RpcMethodTypes} from '@greymass/eos-snap';
let provider: MetaMaskInpageProvider;
@@ -19,76 +21,9 @@
provider = $snapProvider; // gotta be a better way of narrowing this type
isFlask.set(await checkIsFlask(provider));
setSnap();
+ getConnectedAccount();
}
});
-
- async function connectAccount() {
- const result = await invokeSnap({ method: 'eos_connectAccount' });
- console.log('accountName', result);
- accountName.set(result);
- }
-
- let account = { name: '', publicKey: '' };
-
- type AccountData = {
- accountName: string;
- activeKey: string;
- ownerKey: string;
- chainId: string;
- };
-
- async function handleFormSubmit(event: Event) {
- const formData = new FormData(event.target as HTMLFormElement);
-
- const accountData: AccountData = {
- accountName: formData.get('account') as string,
- activeKey: formData.get('publicKey') as string,
- ownerKey: formData.get('publicKey') as string,
- chainId: '73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d'
- };
-
- const name = await createAccount(accountData, 'https://jungle4.greymass.com');
-
- if (typeof name !== 'undefined') {
- console.log(`Account ${name} created`);
- connectAccount();
- }
- }
-
- async function createAccount(accountData: AccountData, chainUrl: string) {
- const data = {
- accountName: accountData.accountName,
- activeKey: accountData.activeKey,
- ownerKey: accountData.ownerKey,
- network: accountData.chainId
- };
-
- try {
- const response = await fetch(`${chainUrl}/account/create`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data)
- });
-
- console.log(response);
-
- if (response.status === 201) {
- console.log('success:', JSON.stringify(await response.text(), null, 2));
- return accountData.accountName;
- }
-
- console.log('failure:', JSON.stringify(await response.text(), null, 2));
- } catch (error) {
- console.error('error getting response', error);
- }
- }
-
- async function testTransaction() {
- const result = await invokeSnap({ method: 'eos_signTransaction' });
- console.log('result', result);
- }
@@ -101,27 +36,20 @@
Is flask: {$isFlask}
Is metamask ready: {$isMetaMaskReady}
Is snap installed: {isSnapInstalled}
+
+
The snap will need to be re-installed after any changes to the code.
+We disable the connection button when an account is already connected.
+
-Create Jungle4 Account
-
+
diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json
index 5f1bab2..0f5bd94 100644
--- a/packages/snap/snap.manifest.json
+++ b/packages/snap/snap.manifest.json
@@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/template-snap-monorepo.git"
},
"source": {
- "shasum": "T9QRYWyyVoAGWQZTG3ByknNkPuBK8CI6OQxkQ96gg5I=",
+ "shasum": "jyxS5srKby/XVNf3aoW+WrKPwt1e6NPo9aRhJyaY+E8=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
@@ -17,6 +17,7 @@
}
},
"initialPermissions": {
+ "snap_manageState": {},
"snap_dialog": {},
"endowment:page-home": {},
"endowment:network-access": {},
diff --git a/packages/snap/src/api/index.ts b/packages/snap/src/api/index.ts
index 67d5afb..71d05b0 100644
--- a/packages/snap/src/api/index.ts
+++ b/packages/snap/src/api/index.ts
@@ -1,4 +1,12 @@
-import { APIClient, Name, PermissionLevel, PublicKey, SignedTransaction, TransactionHeader, UInt32, Weight } from '@wharfkit/antelope';
+import {
+ APIClient as AntelopeAPIClient,
+ Name,
+ PermissionLevel,
+ PublicKey,
+ TransactionHeader,
+ UInt32,
+ Weight,
+} from '@wharfkit/antelope';
import { Account } from '../models';
type NetworkAccount = {
@@ -8,51 +16,48 @@ type NetworkAccount = {
authorizing_account: PermissionLevel;
weight: Weight;
threshold: UInt32;
-}
+};
interface APIGateway {
fetchAccounts(publicKey: PublicKey): Promise;
fetchAccountByKey(publicKey: PublicKey): Promise;
getTransactionHeader(secondsAhead?: number): Promise;
- pushTransaction(signedTransaction: SignedTransaction): Promise;
}
-export class Client implements APIGateway {
- client: APIClient
- chainUrl: string
+export class ApiClient implements APIGateway {
+ client: AntelopeAPIClient;
- constructor(chainUrl: string) {
- this.client = new APIClient({url: chainUrl});
- this.chainUrl = chainUrl;
+ constructor(url: string) {
+ this.client = new AntelopeAPIClient({ url });
}
- private dataToAccount(data: NetworkAccount): Account {
+ private accountDecoder(data: NetworkAccount): Account {
return new Account(
String(data.account_name),
String(data.permission_name),
- String(data.authorizing_key)
+ String(data.authorizing_key),
);
}
public async fetchAccounts(publicKey: PublicKey) {
- const { accounts } = await this.client.v1.chain.get_accounts_by_authorizers({keys: [publicKey.toString()]});
- return accounts.map(this.dataToAccount);
+ const { accounts } = await this.client.v1.chain.get_accounts_by_authorizers(
+ { keys: [publicKey.toString()] },
+ );
+ return accounts.map(this.accountDecoder);
}
public async fetchAccountByKey(publicKey: PublicKey) {
- const { accounts } = await this.client.v1.chain.get_accounts_by_authorizers({keys: [publicKey.toString()]});
+ const { accounts } = await this.client.v1.chain.get_accounts_by_authorizers(
+ { keys: [publicKey.toString()] },
+ );
if (!accounts[0]) return null;
const [account] = accounts;
- return this.dataToAccount(account);
+ return this.accountDecoder(account);
}
public async getTransactionHeader(secondsAhead = 90) {
- return this.client.v1.chain.get_info().then(info => info.getTransactionHeader(secondsAhead));
+ return this.client.v1.chain
+ .get_info()
+ .then((info) => info.getTransactionHeader(secondsAhead));
}
-
- public async pushTransaction(signedTransaction: SignedTransaction) {
- const result = await this.client.v1.chain.push_transaction(signedTransaction);
- return String(result.processed);
- }
-
}
diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts
index 92e6d44..5e5be6a 100644
--- a/packages/snap/src/index.ts
+++ b/packages/snap/src/index.ts
@@ -1,5 +1,9 @@
-import { OnRpcRequestHandler } from '@metamask/snaps-sdk';
-import { signTransaction, connectAccount } from './rpc';
+import {
+ type OnRpcRequestHandler,
+ MethodNotFoundError,
+} from '@metamask/snaps-sdk';
+
+import { signTransaction, connectAccount, getConnectedAccount } from './rpc';
export * from './rpc-types';
@@ -13,9 +17,7 @@ export * from './rpc-types';
* @returns The result of `snap_dialog`.
* @throws If the request method is not valid for this snap.
*/
-export const onRpcRequest: OnRpcRequestHandler = async ({
- request,
-}) => {
+export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
switch (request.method) {
case 'eos_connectAccount':
return await connectAccount();
@@ -23,8 +25,11 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
case 'eos_signTransaction':
return await signTransaction();
+ case 'eos_getConnectedAccount':
+ return await getConnectedAccount();
+
default:
- throw new Error('Method not found.');
+ // eslint-disable-next-line @typescript-eslint/no-throw-literal
+ throw new MethodNotFoundError(request.method);
}
};
-
diff --git a/packages/snap/src/lib/chains.ts b/packages/snap/src/lib/chains.ts
new file mode 100644
index 0000000..e200c9b
--- /dev/null
+++ b/packages/snap/src/lib/chains.ts
@@ -0,0 +1,33 @@
+import { Chains } from '@wharfkit/common';
+import { Chain } from '../models';
+import { StateManager } from './manageState';
+
+const coinType = {
+ EOS: 194,
+ Jungle4: 194,
+ WAX: 14001,
+ Telos: 424,
+};
+
+export const supportedChains = {
+ EOS: { id: Chains.EOS.id, url: Chains.EOS.url, coinType: coinType.EOS },
+ Jungle4: {
+ id: Chains.Jungle4.id,
+ url: Chains.Jungle4.url,
+ coinType: coinType.Jungle4,
+ },
+ WAX: { id: Chains.WAX.id, url: Chains.WAX.url, coinType: coinType.WAX },
+ Telos: {
+ id: Chains.Telos.id,
+ url: Chains.Telos.url,
+ coinType: coinType.Telos,
+ },
+};
+
+export type SupportedChain = keyof typeof supportedChains;
+
+export async function getCurrentChain() {
+ const state = new StateManager();
+ const savedChain = (await state.getValue('currentChain')) as string;
+ return Chain.from(savedChain);
+}
diff --git a/packages/snap/src/lib/keyDeriver.ts b/packages/snap/src/lib/keyDeriver.ts
index 7f65147..90e3742 100644
--- a/packages/snap/src/lib/keyDeriver.ts
+++ b/packages/snap/src/lib/keyDeriver.ts
@@ -1,50 +1,45 @@
import { getBIP44AddressKeyDeriver } from '@metamask/key-tree';
-import {
- Bytes,
- KeyType as AntelopeKeyType,
- PrivateKey,
- PublicKey
-} from '@wharfkit/antelope';
+import * as Antelope from '@wharfkit/antelope';
+import type { Chain } from 'src/models';
/**
- * Get the key deriver for EOS.
+ * Get the key deriver for the given coin type.
*
* @param coinType - The SLIP-0044 registered coin type for BIP-0044.
* @returns The key deriver.
*/
-async function getKeyDeriver(coinType: number = 194) {
- const eosNode = await snap.request({
+async function getKeyDeriver(coinType = 194) {
+ const networkNode = await snap.request({
method: 'snap_getBip44Entropy',
- params: {
- coinType,
- },
+ params: { coinType },
});
- return getBIP44AddressKeyDeriver(eosNode);
+ return getBIP44AddressKeyDeriver(networkNode);
}
/**
* Derive an Antelope public key from the key tree at the given address index.
*
- * @param coinType - The SLIP-0044 registered coin type for BIP-0044.
+ * @param chain - The chain to derive the key for.
* @param addressIndex - The index of the address to derive.
* @returns The public key.
* @throws If the key tree is not initialized.
*/
-export async function derivePublicKey(coinType?: number, addressIndex = 0) {
- const privateKey = await derivePrivateKey(coinType, addressIndex);
+export async function derivePublicKey(chain: Chain, addressIndex = 0) {
+ const privateKey = await derivePrivateKey(chain, addressIndex);
return privateKey.toPublic();
}
/**
* Derive an Antelope private key from the key tree at the given address index.
*
- * @param coinType - The SLIP-0044 registered coin type for BIP-0044.
+ * @param chain - The chain to derive the key for.
* @param addressIndex - The index of the address to derive.
* @returns The private key.
* @throws If the key tree is not initialized.
*/
-export async function derivePrivateKey(coinType?: number, addressIndex = 0) {
+export async function derivePrivateKey(chain: Chain, addressIndex = 0) {
+ const { coinType } = chain;
const keyDeriver = await getKeyDeriver(coinType);
const derived = await keyDeriver(addressIndex);
@@ -52,5 +47,8 @@ export async function derivePrivateKey(coinType?: number, addressIndex = 0) {
throw new Error('Private key not found');
}
- return new PrivateKey(AntelopeKeyType.K1, Bytes.from(derived.privateKeyBytes));
+ return new Antelope.PrivateKey(
+ Antelope.KeyType.K1,
+ Antelope.Bytes.from(derived.privateKeyBytes),
+ );
}
diff --git a/packages/snap/src/lib/manageState.ts b/packages/snap/src/lib/manageState.ts
new file mode 100644
index 0000000..19223ea
--- /dev/null
+++ b/packages/snap/src/lib/manageState.ts
@@ -0,0 +1,36 @@
+import { assert, type Json } from '@metamask/snaps-sdk';
+
+export class StateManager {
+ public async get() {
+ const state = await snap.request({
+ method: 'snap_manageState',
+ params: { operation: 'get' },
+ });
+ return state;
+ }
+
+ public async getValue(key: string) {
+ const state = await this.get();
+ assert(state, 'State not found');
+ assert(state[key], `Key ${key} not found in state`);
+ return state[key];
+ }
+
+ public async set(obj: Record) {
+ const state = await this.get();
+ await snap.request({
+ method: 'snap_manageState',
+ params: {
+ operation: 'update',
+ newState: { ...state, ...obj },
+ },
+ });
+ }
+
+ public async clear() {
+ await snap.request({
+ method: 'snap_manageState',
+ params: { operation: 'clear' },
+ });
+ }
+}
diff --git a/packages/snap/src/models.ts b/packages/snap/src/models.ts
index 7392b59..f1f10d4 100644
--- a/packages/snap/src/models.ts
+++ b/packages/snap/src/models.ts
@@ -1,3 +1,40 @@
+import { Checksum256 } from '@wharfkit/antelope';
+
export class Account {
- constructor(public name: string, public permission: string, public publicKey: string) {}
+ constructor(
+ public name: string,
+ public permission: string,
+ public publicKey: string,
+ ) {}
+}
+
+export class Chain {
+ id: Checksum256;
+ url: string;
+ coinType: number;
+
+ constructor({
+ id,
+ url,
+ coinType,
+ }: {
+ id: string;
+ url: string;
+ coinType: string;
+ }) {
+ this.id = Checksum256.from(id);
+ this.url = url;
+ this.coinType = parseInt(coinType, 10);
+ }
+
+ static from(json: string): Chain {
+ console.log(json);
+ const parsed = JSON.parse(json);
+ console.log(parsed);
+ return new Chain({
+ id: parsed.id,
+ url: parsed.url,
+ coinType: parsed.coinType,
+ });
+ }
}
diff --git a/packages/snap/src/rpc.ts b/packages/snap/src/rpc.ts
index 2f3b26a..8d8d622 100644
--- a/packages/snap/src/rpc.ts
+++ b/packages/snap/src/rpc.ts
@@ -1,28 +1,33 @@
import { assert } from '@metamask/snaps-sdk';
-import { Chains } from '@wharfkit/common';
-import { Session, SignedTransaction } from '@wharfkit/session';
+import { getCurrentChain, SupportedChain, supportedChains } from './lib/chains';
+import { Session } from '@wharfkit/session';
import { WalletPluginPrivateKey } from '@wharfkit/wallet-plugin-privatekey';
-import { Client } from './api';
+import { ApiClient } from './api';
import { derivePrivateKey, derivePublicKey } from './lib/keyDeriver';
+import { StateManager } from './lib/manageState';
import { makeMockTransaction } from './lib/mockTransfer';
-import { userConfirmedAccount, alertNoAccountFound, userConfirmedTransaction } from './ui';
+import {
+ alertNoAccountFound,
+ userConfirmedAccount,
+ userConfirmedTransaction,
+} from './ui';
-// There's something that can be done here with the chain and chain ID
-// For example, we might want to use the chain ID to determine the chain URL
-// and we need to associate that to the coin type for the derivation path
-
-export async function connectAccount() {
- const publicKey = await derivePublicKey();
- const chain = {
- id: Chains.Jungle4.id,
- url: Chains.Jungle4.url,
- }
- const api = new Client(chain.url);
+export async function connectAccount(chainName: SupportedChain = 'Jungle4') {
+ const chain = supportedChains[chainName];
+ const publicKey = await derivePublicKey(chain);
+ const api = new ApiClient(chain.url);
const account = await api.fetchAccountByKey(publicKey);
+ console.log(JSON.stringify(account));
+ console.log(JSON.stringify(chain));
if (account) {
const confirmed = await userConfirmedAccount(account.name);
if (!confirmed) return null;
+ const state = new StateManager();
+ await state.set({
+ account: JSON.stringify(account),
+ currentChain: JSON.stringify(chain),
+ });
return account.name;
} else {
await alertNoAccountFound(publicKey.toString());
@@ -30,54 +35,57 @@ export async function connectAccount() {
}
}
-
-
-export async function signTransaction() { // TODO: will need params
- const publicKey = await derivePublicKey();
- const chain = {
- id: Chains.Jungle4.id,
- url: Chains.Jungle4.url,
- }
- const api = new Client(chain.url);
+// TODO: will need params
+export async function signTransaction() {
+ console.log('signTransaction');
+ const chain = await getCurrentChain();
+ const api = new ApiClient(chain.url);
+ const publicKey = await derivePublicKey(chain);
const account = await api.fetchAccountByKey(publicKey);
- assert(account, 'Account not found')
- const header = await api.getTransactionHeader();
+
+ assert(account, 'Account not found');
// Will be replaced with actual transaction data from params
- const memo = 'test'
+ const memo = 'test';
const transferObject = {
- from: account.name.toString(),
+ from: account.name,
to: 'teamgreymass',
quantity: '0.1337 EOS',
memo: memo || 'wharfkit is the best <3',
- }
+ };
- const transaction = makeMockTransaction(account, header, transferObject) // TODO: will need params
+ const header = await api.getTransactionHeader();
+ const transaction = makeMockTransaction(account, header, transferObject); // TODO: will need params
+ console.log(JSON.stringify(transaction));
- const confirmed = await userConfirmedTransaction(transferObject)
+ const confirmed = await userConfirmedTransaction(transferObject);
if (confirmed) {
- const privateKey = await derivePrivateKey();
- assert(privateKey, 'Private key not found')
- // const signature = privateKey.signDigest(transaction.signingDigest(chain.id))
- // const signedTransaction = SignedTransaction.from({
- // ...transaction,
- // signatures: [signature],
- // })
- // const result = await api.pushTransaction(signedTransaction)
+ const privateKey = await derivePrivateKey(chain);
+ console.log(privateKey);
+ assert(privateKey, 'Private key not found');
const sessionArgs = {
- chain,
+ chain: {
+ id: chain.id,
+ url: chain.url,
+ },
actor: account.name,
permission: account.permission,
- walletPlugin: new WalletPluginPrivateKey(privateKey)
- }
- const session = new Session(sessionArgs)
- const result = await session.transact(transaction)
- console.log(JSON.stringify(result))
- return String(result)
-
+ walletPlugin: new WalletPluginPrivateKey(privateKey),
+ };
+ console.log(sessionArgs);
+ const session = new Session(sessionArgs);
+ console.log(JSON.stringify(session));
+ const result = await session.transact(transaction);
+ console.log(JSON.stringify(result));
+ return String(result);
}
+ return null;
+}
- return null
-
+export async function getConnectedAccount() {
+ const state = new StateManager();
+ const account = (await state.getValue('account')) as string;
+ if (!account) return null;
+ return account;
}
diff --git a/yarn.lock b/yarn.lock
index 63cd705..81e7a49 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10153,7 +10153,7 @@ __metadata:
languageName: node
linkType: hard
-"prettier-plugin-svelte@npm:^3.1.2":
+"prettier-plugin-svelte@npm:^3.1.2, prettier-plugin-svelte@npm:^3.2.4":
version: 3.2.4
resolution: "prettier-plugin-svelte@npm:3.2.4"
peerDependencies:
@@ -10804,6 +10804,7 @@ __metadata:
eslint-plugin-promise: ^6.1.1
prettier: ^2.7.1
prettier-plugin-packagejson: ^2.2.18
+ prettier-plugin-svelte: ^3.2.4
sharp: ^0.32.6
typescript: ^4.7.4
typescript-eslint: ^8.0.0-alpha.20