-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Adjusted MM snap /core versions * Added changeset * WIP - Checksum error * - Added a test website for testing the snap - Made adjustments to the core mm snap functions to allow for a dynamically passed in snap id - Improved the UI of the snap popups * - Removed lint dependency - Change "origin" to "snapId" * Added changeset * - Renamed file - Added notice on Sign Arbitrary requests * - Removed verify arbitrary from snap, moved to SeiWallet creation * - Added api.ts tests - Added mock files (from cosmos-metamask-snap) - Removed unused files from test ui - Improved styles of test ui - Exported helper function to create SeiWallet for MM Snap * Added optional account index to cosmjs, removed values from CHANGELOG
- Loading branch information
1 parent
27a9782
commit 2d1b863
Showing
80 changed files
with
8,576 additions
and
398 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@sei-js/metamask-snap': patch | ||
'@sei-js/core': patch | ||
--- | ||
|
||
Added MetaMask Snap and helper functions to library (experimental as this hasn't completed audit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/metamask-snap/src/lib/index.ts → packages/core/src/lib/metamask-snap/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export * from './config'; | ||
export * from './cosmjs'; | ||
export * from './snapWallet'; | ||
export * from './types'; | ||
export * from './utils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { sign as signSecp256k1, getPublicKey as getSecp256k1PublicKey } from '@noble/secp256k1'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; | ||
import { BIP44Node } from '@metamask/key-tree'; | ||
import { AccountData, encodeSecp256k1Signature, StdSignDoc } from '@cosmjs/amino'; | ||
import { Buffer } from 'buffer'; | ||
import { getSnapEthereumProvider, sendReqToSnap } from './utils'; | ||
import { compressedPubKeyToAddress, serializeAminoSignDoc, serializeDirectSignDoc, verifyArbitrary } from '../utils'; | ||
import { CosmJSOfflineSigner } from './cosmjs'; | ||
import { SeiWallet } from '../wallet'; | ||
|
||
export class SnapWallet { | ||
constructor(private privateKey: Uint8Array, private compressedPubKey: Uint8Array, private address: string) {} | ||
|
||
static create(privateKey: string) { | ||
const sanitizedPvtKey = privateKey.replace('0x', ''); | ||
const pvtKeyBytes = Buffer.from(sanitizedPvtKey, 'hex'); | ||
const compressedPubKey = getSecp256k1PublicKey(pvtKeyBytes, true); | ||
const seiAddress = compressedPubKeyToAddress(compressedPubKey); | ||
return new SnapWallet(pvtKeyBytes, compressedPubKey, seiAddress); | ||
} | ||
|
||
getAccounts() { | ||
return [ | ||
{ | ||
address: this.address, | ||
algo: 'secp256k1', | ||
pubkey: this.compressedPubKey | ||
} | ||
] as AccountData[]; | ||
} | ||
|
||
async signDirect(signerAddress: string, signDoc: SignDoc) { | ||
const accounts = this.getAccounts(); | ||
const account = accounts.find((acc) => acc.address === signerAddress); | ||
|
||
if (!account) { | ||
throw new Error('Signer address does not match wallet address'); | ||
} | ||
|
||
const hash = sha256(serializeDirectSignDoc(signDoc)); | ||
const signature = await signSecp256k1(hash, this.privateKey, { | ||
canonical: true, | ||
extraEntropy: true, | ||
der: false | ||
}); | ||
|
||
return { | ||
signed: { ...signDoc, accountNumber: signDoc.accountNumber.toString() }, | ||
signature: encodeSecp256k1Signature(account.pubkey, signature) | ||
}; | ||
} | ||
|
||
async signAmino(signerAddress: string, signDoc: StdSignDoc, options?: { extraEntropy: boolean }) { | ||
const accounts = this.getAccounts(); | ||
const account = accounts.find((acc) => acc.address === signerAddress); | ||
if (!account) { | ||
throw new Error('Signer address does not match wallet address'); | ||
} | ||
|
||
if (!account.pubkey) { | ||
throw new Error('Unable to derive keypair'); | ||
} | ||
|
||
const hash = sha256(serializeAminoSignDoc(signDoc)); | ||
const extraEntropy = options?.extraEntropy ? true : undefined; | ||
const signature = await signSecp256k1(hash, this.privateKey, { | ||
canonical: true, | ||
extraEntropy, | ||
der: false | ||
}); | ||
|
||
return { | ||
signed: signDoc, | ||
signature: encodeSecp256k1Signature(account.pubkey, signature) | ||
}; | ||
} | ||
} | ||
|
||
export async function getWallet(account_index = 0, snapId: string): Promise<SnapWallet> { | ||
const account: BIP44Node = await sendReqToSnap('getPrivateKey', { account_index }, snapId); | ||
|
||
if (account.privateKey) { | ||
return SnapWallet.create(account.privateKey); | ||
} | ||
throw new Error(`Error creating sei wallet!`); | ||
} | ||
|
||
export const getMetaMaskSnapSeiWallet = (snapId: string): SeiWallet => { | ||
return { | ||
getAccounts: async (chainId) => { | ||
const offlineSigner = new CosmJSOfflineSigner(chainId, snapId); | ||
return offlineSigner.getAccounts(); | ||
}, | ||
connect: async (_: string) => { | ||
const provider = await getSnapEthereumProvider(); | ||
const installedSnaps: any = await provider.request({ method: 'wallet_getSnaps' }); | ||
if (!installedSnaps || !installedSnaps[snapId]) { | ||
await provider.request({ | ||
method: 'wallet_requestSnaps', | ||
params: { | ||
[snapId]: {} | ||
} | ||
}); | ||
} | ||
}, | ||
disconnect: async (_: string) => { | ||
throw new Error('Not implemented'); | ||
}, | ||
getOfflineSigner: async (chainId) => { | ||
return new CosmJSOfflineSigner(chainId, snapId); | ||
}, | ||
getOfflineSignerAmino: async (chainId) => { | ||
// This signer includes both signDirect and signAmino, so just return it | ||
return new CosmJSOfflineSigner(chainId, snapId); | ||
}, | ||
signArbitrary: async (chainId, signer, message) => { | ||
const offlineSigner = new CosmJSOfflineSigner(chainId, snapId); | ||
return offlineSigner.signArbitrary(signer, message); | ||
}, | ||
verifyArbitrary: async (_: string, signingAddress, data, signature) => { | ||
if (!signingAddress || !data) { | ||
throw new Error('Invalid params'); | ||
} | ||
return await verifyArbitrary(signingAddress, data, signature); | ||
}, | ||
walletInfo: { | ||
windowKey: 'ethereum', | ||
name: 'Sei Metamask Snap', | ||
website: 'https://metamask.io/', | ||
icon: 'https://github.com/MetaMask/brand-resources/raw/master/SVG/SVG_MetaMask_Icon_Color.svg' | ||
}, | ||
isMobileSupported: true | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { MetaMaskInpageProvider } from '@metamask/providers'; | ||
|
||
export type EthereumProvider = MetaMaskInpageProvider & { | ||
providers: MetaMaskInpageProvider[]; | ||
detected: MetaMaskInpageProvider[]; | ||
setProvider: (provider: MetaMaskInpageProvider) => void; | ||
}; | ||
|
||
declare global { | ||
interface Window { | ||
ethereum: EthereumProvider; | ||
} | ||
} | ||
|
||
export type SignAminoOptions = { | ||
isADR36?: boolean; | ||
preferNoSetFee?: boolean; | ||
enableExtraEntropy?: boolean; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { EthereumProvider } from './types'; | ||
|
||
/** | ||
* The fool proof version of getting the ethereum provider suggested by | ||
* https://github.com/Montoya/snap-connect-test/blob/0dad2dd53ab2ecbf4b4369230d3aaaeca08c6dae/index.html#L41 | ||
* | ||
* @returns the ethereum provider which supports snaps | ||
*/ | ||
export const getSnapEthereumProvider = async (): Promise<EthereumProvider> => { | ||
let mmFound = false; | ||
if ('detected' in window.ethereum) { | ||
for (const provider of window.ethereum.detected) { | ||
try { | ||
// Detect snaps support | ||
await provider.request({ | ||
method: 'wallet_getSnaps' | ||
}); | ||
// enforces MetaMask as provider | ||
window.ethereum.setProvider(provider); | ||
|
||
mmFound = true; | ||
// @ts-ignore | ||
return provider; | ||
} catch { | ||
// no-op | ||
} | ||
} | ||
} | ||
|
||
if (!mmFound && 'providers' in window.ethereum) { | ||
for (const provider of window.ethereum.providers) { | ||
try { | ||
// Detect snaps support | ||
await provider.request({ | ||
method: 'wallet_getSnaps' | ||
}); | ||
|
||
// @ts-ignore | ||
window.ethereum = provider; | ||
|
||
mmFound = true; | ||
// @ts-ignore | ||
return provider; | ||
} catch { | ||
// no-op | ||
} | ||
} | ||
} | ||
|
||
return window.ethereum; | ||
}; | ||
|
||
export const sendReqToSnap = async (method: string, params: any, snapId: string): Promise<any> => { | ||
const provider = await getSnapEthereumProvider(); | ||
return provider.request({ | ||
method: 'wallet_invokeSnap', | ||
params: { | ||
snapId, | ||
request: { | ||
method, | ||
params | ||
} | ||
} | ||
}); | ||
}; |
Oops, something went wrong.