Skip to content

Commit

Permalink
Merge pull request #4 from leapwallet/add-snap-provider
Browse files Browse the repository at this point in the history
Add snap provider
  • Loading branch information
leapsamvel authored Jul 26, 2023
2 parents 0488254 + 989a864 commit ea40054
Show file tree
Hide file tree
Showing 14 changed files with 319 additions and 2 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/publish-provider.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
on:
push:
branches:
- release
paths:
- 'packages/comos-snap-provider'
jobs:
release:
runs-on: ubuntu-latest
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
GH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
steps:
- uses: actions/checkout@v2
- uses: actions/[email protected]
with:
node-version: '16.13.0'
registry-url: 'https://npm.pkg.github.com'
scope: '@leapwallet'

- name: Build Provider
run: yarn build:provider

- name: Bump version and publish package
run:
yarn publish --non-interactive
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ dist/
build/
coverage/
.cache/
dist/

# Logs
logs
Expand Down Expand Up @@ -77,4 +78,4 @@ node_modules/
.yarnrc.yml


.npmrc
.npmrc
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"lint:misc": "prettier '**/*.json' '**/*.md' '!**/CHANGELOG.md' '**/*.yml' --ignore-path .gitignore",
"start": "yarn workspaces foreach --parallel --interlaced --verbose run start",
"start:snap": "yarn workspace snap start",
"build:provider": "yarn workspace @leapwallet/cosmos-snap-provider build",
"test": "echo \"TODO\""
},
"devDependencies": {
Expand Down
50 changes: 50 additions & 0 deletions packages/cosmos-snap-provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# cosmos-snap-provider

## Usage

```typescript
import { getSnap, connectSnap, getKey } from '@leapwallet/cosmos-snap-provider'

async function connect(){
//check if snap is installed
const snapInstalled = await getSnap()
if(!snapInstalled) {
// Install snap if not already installed
connectSnap()
}

}

async function getAccount(){
await connect()
const chainId = 'cosmoshub-4'
const key = await getKey(chainId)
return key
}

```

## Usage with cosmjs

```typescript
import { SigningStargateClient } from '@cosmjs/cosmwasm-stargate'
import { GasPrice } from '@cosmjs/stargate'


import { cosmjsOfflineSigner } from '@leapwallet/cosmos-snap-provider'



const offlineSigner = new cosmjsOfflineSigner(chainId);
const accounts = await offlineSigner.getAccounts();
const rpcUrl = "" // Replace with a RPC URL for the given chainId
const stargateClient = await SigningStargateClient.connectWithSigner(
rpcUrl,
offlineSigner,
{
gasPrice: GasPrice.fromString("0.0025ujuno"),
}
)

```

18 changes: 18 additions & 0 deletions packages/cosmos-snap-provider/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@leapwallet/cosmos-snap-provider",
"packageManager": "[email protected]",
"files": [
"dist/**/*"
],
"repository": {
"url": "[email protected]:leapwallet/cosmos-metamask-snap.git"
},
"publishConfig": {
"registry": "https://npm.pkg.github.com"
},
"scripts": {
"build": "npx tsc",
"start": "npx tsc watch",
"prepublish": "yarn build"
}
}
7 changes: 7 additions & 0 deletions packages/cosmos-snap-provider/src/app-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export {};

declare global {
interface Window {
ethereum: any;
}
}
2 changes: 2 additions & 0 deletions packages/cosmos-snap-provider/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const defaultSnapOrigin =
process.env.SNAP_ORIGIN ?? `npm:@leapwallet/metamask-cosmos-snap`;
54 changes: 54 additions & 0 deletions packages/cosmos-snap-provider/src/cosmjs-offline-signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { AccountData, AminoSignResponse } from '@cosmjs/amino';
import { getKey, requestSignature } from './snap';
import { DirectSignResponse, OfflineDirectSigner } from '@cosmjs/proto-signing';

export class cosmjsOfflineSigner implements OfflineDirectSigner {
constructor(private chainId: string) {}

async getAccounts(): Promise<AccountData[]> {
const key = await getKey(this.chainId);
return [
{
address: key.address,
algo: 'secp256k1',
pubkey: key.pubkey,
},
];
}

async signDirect(
signerAddress: string,
signDoc: SignDoc,
): Promise<DirectSignResponse> {
if (this.chainId !== signDoc.chainId) {
throw new Error('Chain ID does not match signer chain ID');
}
const accounts = await this.getAccounts();

if (accounts.find((account) => account.address !== signerAddress)) {
throw new Error('Signer address does not match wallet address');
}

return requestSignature(
this.chainId,
signerAddress,
signDoc,
) as Promise<DirectSignResponse>;
}

//This has been added as a placeholder.
async signAmino(
signerAddress: string,
signDoc: SignDoc,
): Promise<AminoSignResponse> {
return this.signDirect(
signerAddress,
signDoc,
) as unknown as Promise<AminoSignResponse>;
}
}

export function getOfflineSigner(chainId: string) {
return new cosmjsOfflineSigner(chainId);
}
3 changes: 3 additions & 0 deletions packages/cosmos-snap-provider/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './snap';
export * from './types';
export * from './cosmjs-offline-signer';
133 changes: 133 additions & 0 deletions packages/cosmos-snap-provider/src/snap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { AccountData } from '@cosmjs/amino';
import { defaultSnapOrigin } from './config';
import { GetSnapsResponse, Snap } from './types';
import Long from 'long';

/**
* Get the installed snaps in MetaMask.
*
* @returns The snaps installed in MetaMask.
*/

export const getSnaps = async (): Promise<GetSnapsResponse> => {
return (await window.ethereum.request({
method: 'wallet_getSnaps',
})) as unknown as GetSnapsResponse;
};

/**
* Connect a snap to MetaMask.
*
* @param snapId - The ID of the snap.
* @param params - The params to pass with the snap to connect.
*/
export const connectSnap = async (
snapId: string = defaultSnapOrigin,
params: Record<'version' | string, unknown> = {},
) => {
await window.ethereum.request({
method: 'wallet_requestSnaps',
params: {
[snapId]: params,
},
});
};

/**
* Get the snap from MetaMask.
*
* @param version - The version of the snap to install (optional).
* @returns The snap object returned by the extension.
*/
export const getSnap = async (version?: string): Promise<Snap | undefined> => {
try {
const snaps = await getSnaps();

return Object.values(snaps).find(
(snap) =>
snap.id === defaultSnapOrigin && (!version || snap.version === version),
);
} catch (e) {
console.log('Failed to obtain installed snap', e);
return undefined;
}
};

export const requestSignature = async (
chainId: string,
signerAddress: string,
signDoc: {
bodyBytes?: Uint8Array | null;
authInfoBytes?: Uint8Array | null;
chainId?: string | null;
accountNumber?: Long | null;
},
) => {
const signature = await window.ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: defaultSnapOrigin,
request: {
method: 'signDirect',
params: {
chainId,
signerAddress,
signDoc,
},
},
},
});

const accountNumber = signDoc.accountNumber;
//@ts-ignore
const modifiedAccountNumber = new Long(
accountNumber!.low,
accountNumber!.high,
accountNumber!.unsigned,
);

const modifiedSignature = {
//@ts-ignore
signature: signature.signature,
signed: {
// @ts-ignore
...signature.signed,
accountNumber: `${modifiedAccountNumber.toString()}`,
authInfoBytes: new Uint8Array(
//@ts-ignore
Object.values(signature.signed.authInfoBytes),
),

bodyBytes: new Uint8Array(
//@ts-ignore
Object.values(signature.signed.bodyBytes),
),
},
};

console.log('logging modified signature', modifiedSignature);
return modifiedSignature;
};

export const getKey = async (chainId: string): Promise<AccountData> => {
const accountData = await window.ethereum.request({
method: 'wallet_invokeSnap',
params: {
snapId: defaultSnapOrigin,
request: {
method: 'getKey',
params: {
chainId,
},
},
},
});

if (!accountData) throw new Error('No account data found');
//@ts-ignore
accountData.pubkey = Uint8Array.from(Object.values(accountData.pubkey));

return accountData as AccountData;
};

export const isLocalSnap = (snapId: string) => snapId.startsWith('local:');
8 changes: 8 additions & 0 deletions packages/cosmos-snap-provider/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type GetSnapsResponse = Record<string, Snap>;

export type Snap = {
permissionName: string;
id: string;
version: string;
initialPermissions: Record<string, unknown>;
};
8 changes: 8 additions & 0 deletions packages/cosmos-snap-provider/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
},

"include": ["src"]
}
2 changes: 1 addition & 1 deletion 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/leapwallet/cosmos-metamask-snap.git"
},
"source": {
"shasum": "vJWY7R5OCsm/bUewNONAkhjmrx0SF/oYik5gpGjEfHI=",
"shasum": "N7FF/rqOb8EudU8zDolOwRBso/ShuzrUT6mE/x+K0Xw="
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2158,6 +2158,12 @@ __metadata:
languageName: unknown
linkType: soft

"@leapwallet/cosmos-snap-provider@workspace:packages/cosmos-snap-provider":
version: 0.0.0-use.local
resolution: "@leapwallet/cosmos-snap-provider@workspace:packages/cosmos-snap-provider"
languageName: unknown
linkType: soft

"@leapwallet/metamask-cosmos-snap@workspace:packages/snap":
version: 0.0.0-use.local
resolution: "@leapwallet/metamask-cosmos-snap@workspace:packages/snap"
Expand Down

0 comments on commit ea40054

Please sign in to comment.