Skip to content

Commit

Permalink
Add transfer method
Browse files Browse the repository at this point in the history
  • Loading branch information
Lykhoyda committed Jan 3, 2024
1 parent 2c1c397 commit bb4205e
Show file tree
Hide file tree
Showing 11 changed files with 1,637 additions and 611 deletions.
50 changes: 43 additions & 7 deletions packages/site/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isLocalSnap,
sendHello,
shouldDisplayReconnectButton,
transfer,
} from '../utils';
import {
ConnectButton,
Expand All @@ -26,6 +27,7 @@ const Container = styled.div`
flex: 1;
margin-top: 7.6rem;
margin-bottom: 7.6rem;
${({ theme }) => theme.mediaQueries.small} {
padding-left: 2.4rem;
padding-right: 2.4rem;
Expand All @@ -50,6 +52,7 @@ const Subtitle = styled.p`
font-weight: 500;
margin-top: 0;
margin-bottom: 0;
${({ theme }) => theme.mediaQueries.small} {
font-size: ${({ theme }) => theme.fontSizes.text};
}
Expand Down Expand Up @@ -79,6 +82,7 @@ const Notice = styled.div`
& > * {
margin: 0;
}
${({ theme }) => theme.mediaQueries.small} {
margin-top: 1.2rem;
padding: 1.6rem;
Expand All @@ -95,6 +99,7 @@ const ErrorMessage = styled.div`
margin-top: 2.4rem;
max-width: 60rem;
width: 100%;
${({ theme }) => theme.mediaQueries.small} {
padding: 1.6rem;
margin-bottom: 1.2rem;
Expand All @@ -107,6 +112,8 @@ const Index = () => {
const [state, dispatch] = useContext(MetaMaskContext);
const [address, setAddress] = useState('');
const [balance, setBalance] = useState('');
const [transferValue, setTransferValue] = useState('');
const [toAddress, setToAddress] = useState('');

const isMetaMaskReady = isLocalSnap(defaultSnapOrigin)
? state.isFlask
Expand Down Expand Up @@ -224,13 +231,42 @@ const Index = () => {
!shouldDisplayReconnectButton(state.installedSnap)
}
/>
<button onClick={() => handleGetAddress()}>
Get Address and Balance
</button>
<ul>
<li>{address}</li>
<li>{balance}</li>
</ul>
<CardContainer>
<Notice>
<ul>
<li>Address: {address}</li>
<li>Balance: {balance}</li>
</ul>
<button
style={{ marginTop: '20px' }}
onClick={() => handleGetAddress()}
>
Get Address and Balance
</button>
</Notice>
<Notice style={{ display: 'flex', flexDirection: 'column' }}>
<label>
<p>To Address</p>
<input
value={toAddress}
onChange={(e) => setToAddress(e.target.value)}
/>
</label>
<label>
<p>Amount</p>
<input
value={transferValue}
onChange={(e) => setTransferValue(e.target.value)}
/>
</label>
<button
style={{ marginTop: '20px' }}
onClick={() => transfer(toAddress, transferValue)}
>
Transfer Funds
</button>
</Notice>
</CardContainer>
<Notice>
<p>
Please note that the <b>snap.manifest.json</b> and{' '}
Expand Down
11 changes: 10 additions & 1 deletion packages/site/src/utils/snap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { defaultSnapOrigin } from '../config';
import { GetSnapsResponse, Snap } from '../types';

/**
* Get the installed snaps in MetaMask.
*
Expand Down Expand Up @@ -84,4 +83,14 @@ export const getBalance = async (address: string) => {
return balance as string;
};

export const transfer = async (to: string, amount: string) => {
await window.ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: defaultSnapOrigin,
request: { method: 'transfer', params: { to, amount } },
},
});
};

export const isLocalSnap = (snapId: string) => snapId.startsWith('local:');
20 changes: 10 additions & 10 deletions packages/snap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,25 @@
},
"dependencies": {
"@metamask/key-tree": "^9.0.0",
"@metamask/snaps-types": "^3.0.0",
"@metamask/snaps-ui": "^3.0.0",
"@polkadot/util": "^12.5.1",
"@polkadot/util-crypto": "^12.5.1",
"@metamask/snaps-types": "^3.0.1",
"@metamask/snaps-ui": "^3.1.0",
"@polkadot/util": "^12.6.2",
"@polkadot/util-crypto": "^12.6.2",
"@subsquid/ss58": "^2.0.1",
"buffer": "^6.0.3"
},
"devDependencies": {
"@jest/globals": "^29.5.0",
"@lavamoat/allow-scripts": "^2.5.1",
"@metamask/auto-changelog": "^3.3.0",
"@lavamoat/allow-scripts": "^3.0.0",
"@metamask/auto-changelog": "^3.4.2",
"@metamask/eslint-config": "^12.2.0",
"@metamask/eslint-config-jest": "^12.1.0",
"@metamask/eslint-config-nodejs": "^12.1.0",
"@metamask/eslint-config-typescript": "^12.1.0",
"@metamask/snaps-cli": "^3.0.0",
"@metamask/snaps-jest": "^2.0.0",
"@polkadot/api": "^10.10.1",
"@polkadot/rpc-provider": "^10.10.1",
"@metamask/snaps-cli": "^3.0.1",
"@metamask/snaps-jest": "^3.1.0",
"@polkadot/api": "^10.11.2",
"@polkadot/rpc-provider": "^10.11.2",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint": "^8.21.0",
Expand Down
25 changes: 25 additions & 0 deletions packages/snap/polkadot/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Struct } from '@polkadot/types-codec';
import type { Balance } from '@polkadot/types/interfaces/runtime';
import { ApiPromise, HttpProvider } from '@polkadot/api';

const ROCOCO_RPC_URL = 'https://rococo-rpc.polkadot.io/';

export type AccountData = {
readonly free: Balance;
} & Struct;

export async function initApi(): Promise<ApiPromise> {
let provider: HttpProvider;

try {
provider = new HttpProvider(ROCOCO_RPC_URL);
} catch (error) {
console.error('Error on provider creation', error);
throw error;
}

const apiPromise = new ApiPromise({ provider });
await apiPromise.isReady;

return apiPromise;
}
24 changes: 24 additions & 0 deletions packages/snap/polkadot/getKeyPair.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getBIP44AddressKeyDeriver } from '@metamask/key-tree';
import { Keyring } from '@polkadot/api';

async function getKeyPair() {
const polkadotTypeNode = await snap.request({
method: 'snap_getBip44Entropy',
params: {
coinType: 354,
},
});

const deriveAddressKey = await getBIP44AddressKeyDeriver(polkadotTypeNode);
const addressKey0 = await deriveAddressKey(0);

const keyring = new Keyring({ ss58Format: 42 });

if (!addressKey0.privateKeyBytes) {
throw new Error('No private key found');
}

return keyring.addFromSeed(addressKey0.privateKeyBytes);
}

export default getKeyPair;
21 changes: 3 additions & 18 deletions packages/snap/rpc/getAddress.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
import { getBIP44AddressKeyDeriver } from '@metamask/key-tree';
import { Keyring } from '@polkadot/api';
import getKeyPair from '../polkadot/getKeyPair';

async function getAddress() {
const polkadotTypeNode = await snap.request({
method: 'snap_getBip44Entropy',
params: {
coinType: 434,
},
});
const keypair = await getKeyPair();

const deriveAddressKey = await getBIP44AddressKeyDeriver(polkadotTypeNode);
const addressKey0 = await deriveAddressKey(0);

const keyring = new Keyring({ ss58Format: 2 });

if (!addressKey0.privateKeyBytes) {
throw new Error('No private key found');
}

return keyring.addFromSeed(addressKey0.privateKeyBytes).address;
return keypair.address;
}

export default getAddress;
26 changes: 1 addition & 25 deletions packages/snap/rpc/getBalance.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,4 @@
import { ApiPromise, HttpProvider } from '@polkadot/api';
import type { Struct } from '@polkadot/types-codec';
import type { Balance } from '@polkadot/types/interfaces/runtime';

const KUSAMA_RPC_URL = 'https://kusama-rpc.polkadot.io/';

export type AccountData = {
readonly free: Balance;
} & Struct;

async function initApi(): Promise<ApiPromise> {
let provider: HttpProvider;

try {
provider = new HttpProvider(KUSAMA_RPC_URL);
} catch (error) {
console.error('Error on provider creation', error);
throw error;
}

const apiPromise = new ApiPromise({ provider });
await apiPromise.isReady;

return apiPromise;
}
import { AccountData, initApi } from '../polkadot/api';

export async function getBalance(address: string) {
const polkadotApi = await initApi();
Expand Down
31 changes: 31 additions & 0 deletions packages/snap/rpc/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { heading, panel, text } from '@metamask/snaps-ui';
import getKeyPair from '../polkadot/getKeyPair';
import { initApi } from '../polkadot/api';

export async function transfer(
to: string,
amount: string,
): Promise<{ signature: string } | void> {
const isConfirmed = await snap.request({
method: 'snap_dialog',
params: {
type: 'confirmation',
content: panel([
heading('Confirm Transfer'),
text(`Are you sure you want to transfer ${amount} to ${to}?`),
]),
},
});

if (isConfirmed) {
const user = await getKeyPair();
const api = await initApi();

const transferTransaction = await api.tx.balances
.transferKeepAlive(to, amount)
.signAndSend(user);

console.log('paymentInfo', transferTransaction);
console.log(JSON.stringify(transferTransaction));
}
}
4 changes: 2 additions & 2 deletions packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/template-snap-monorepo.git"
},
"source": {
"shasum": "mJ94joj9ZlbwmzqrNcfMhekOmxJFJVruleznMJWMX7E=",
"shasum": "jpZWET+cSprFcNw64jjusUg5ovDVvwf1ZCUbpXQi90c=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -20,7 +20,7 @@
"initialPermissions": {
"snap_getBip44Entropy": [
{
"coinType": 434
"coinType": 354
}
],
"snap_dialog": {},
Expand Down
12 changes: 12 additions & 0 deletions packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { panel, text } from '@metamask/snaps-ui';
import { assert, object, string } from 'superstruct';
import getAddress from '../rpc/getAddress';
import { getBalance } from '../rpc/getBalance';
import { transfer } from '../rpc/transfer';

/**
* Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`.
*
Expand All @@ -13,6 +15,7 @@ import { getBalance } from '../rpc/getBalance';
* @returns The result of `snap_dialog`.
* @throws If the request method is not valid for this snap.
*/

export const onRpcRequest: OnRpcRequestHandler = async ({
origin,
request,
Expand All @@ -37,6 +40,15 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
case 'getBalance':
assert(request.params, object({ address: string() }));
return await getBalance(request.params.address);
case 'transfer':
assert(
request.params,
object({
to: string(),
amount: string(),
}),
);
return await transfer(request.params.to, request.params.amount);

default:
throw new Error('Method not found.');
Expand Down
Loading

0 comments on commit bb4205e

Please sign in to comment.