Skip to content

Commit

Permalink
feat(wallets): ✨ add leap metamask snap support
Browse files Browse the repository at this point in the history
  • Loading branch information
DavideSegullo committed Feb 25, 2024
1 parent 52cea85 commit 525e21d
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 2 deletions.
6 changes: 4 additions & 2 deletions packages/wallets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
"@nabla-studio/chain-registry": "*",
"@nabla-studio/wallet-registry": "*",
"@keplr-wallet/types": "^0.12.38",
"@cosmostation/extension-client": "^0.1.15"
"@cosmostation/extension-client": "^0.1.15",
"base64-js": "^1.5.1"
},
"peerDependencies": {
"@cosmjs/amino": "^0.32.2",
"@cosmjs/proto-signing": "^0.32.2",
"cosmjs-types": "^0.9.0",
"long": "^5.2.3"
"long": "^5.2.3",
"@leapwallet/cosmos-snap-provider": "^0.1.25"
},
"main": "./index.js",
"module": "./index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/wallets/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './xdefi';
export * from './station';
export * from './okx';
export * from './shell';
export * from './leap-metamask-snap';
export * from './wallet-connect';
export * from './keplr-mobile';
export * from './leap-mobile';
Expand Down
242 changes: 242 additions & 0 deletions packages/wallets/src/leap-metamask-snap/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import {
ExtensionWallet,
type Key,
type SignOptions,
type SuggestChain,
assertIsDefined,
createClientNotExistError,
getClientFromExtension,
} from '@quirks/core';
import type { Snap } from '@leapwallet/cosmos-snap-provider';
import type {
OfflineAminoSigner,
StdSignDoc,
AminoSignResponse,
StdSignature,
AccountData,
Algo,
} from '@cosmjs/amino';
import type {
OfflineDirectSigner,
DirectSignResponse,
} from '@cosmjs/proto-signing';
import type { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import type { LeapMetamaskSnap } from './types';
import Long from 'long';
import { getChainInfo } from '../utils';

export class LeapMetamaskSnapWalletExtension extends ExtensionWallet<LeapMetamaskSnap> {
snap?: Snap;

override async init(): Promise<LeapMetamaskSnap | undefined> {
assertIsDefined(this.options.windowKey);

try {
this.client = await getClientFromExtension(this.options.windowKey);

if (!this.client?.isMetamask && this.options.windowKey) {
throw createClientNotExistError('ethereum');
}

this.injected = true;

if (this.client) {
this.addListeners();
}

return this.client;
} catch (err) {
this.injectionError = err as Error;
}

return undefined;
}

override async getAccount(chainId: string): Promise<Key> {
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const getKey =
cosmosSnapProvider.getKey ?? cosmosSnapProvider.default.getKey;

const key = await getKey(chainId);

return {
name: key.address,
address: new TextEncoder().encode(key.address),
bech32Address: key.address,
algo: key.algo,
pubKey: key.pubkey,
isKeystone: false,
isNanoLedger: false,
};
}

async getSignerAccount(chainId: string): Promise<AccountData> {
const key = await this.getAccount(chainId);

return {
address: key.bech32Address,
algo: key.algo as Algo,
pubkey: key.pubKey!,
};
}

override async getAccounts(chainIds: string[]): Promise<Key[]> {
assertIsDefined(this.client);

const keys = await Promise.allSettled(
chainIds.map((chainId) => this.getAccount(chainId)),
);

return keys
.map((key) => {
if (key.status === 'fulfilled') {
return key.value;
}

return undefined;
})
.filter((key) => key !== undefined) as Key[];
}

override async enable(): Promise<void> {
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const getSnap =
cosmosSnapProvider.getSnap ?? cosmosSnapProvider.default.getSnap;
const connectSnap =
cosmosSnapProvider.connectSnap ?? cosmosSnapProvider.default.connectSnap;
const snap = await getSnap();

this.snap = snap;

await connectSnap(this.snap?.id);
}

override async disable(): Promise<void> {
console.warn('disable method not implemented.');

return;
}

override async getOfflineSigner(
chainId: string,
options?: SignOptions | undefined,
): Promise<OfflineAminoSigner & OfflineDirectSigner> {
assertIsDefined(this.client);

return {
getAccounts: async () => [await this.getSignerAccount(chainId)],
signAmino: (signerAddress, signDoc) =>
this.signAmino(chainId, signerAddress, signDoc, options),
signDirect: (signerAddress, signDoc) =>
this.signDirect(chainId, signerAddress, signDoc),
};
}

override async getOfflineSignerOnlyAmino(
chainId: string,
options?: SignOptions | undefined,
): Promise<OfflineAminoSigner> {
assertIsDefined(this.client);

return {
getAccounts: async () => [await this.getSignerAccount(chainId)],
signAmino: (signerAddress, signDoc) =>
this.signAmino(chainId, signerAddress, signDoc, options),
};
}

override async getOfflineSignerAuto(
chainId: string,
options?: SignOptions | undefined,
): Promise<OfflineAminoSigner | OfflineDirectSigner> {
assertIsDefined(this.client);

return {
getAccounts: async () => [await this.getSignerAccount(chainId)],
signAmino: (signerAddress, signDoc) =>
this.signAmino(chainId, signerAddress, signDoc, options),
signDirect: (signerAddress, signDoc) =>
this.signDirect(chainId, signerAddress, signDoc),
};
}

override async signAmino(
chainId: string,
signer: string,
signDoc: StdSignDoc,
signOptions?: SignOptions | undefined,
): Promise<AminoSignResponse> {
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const requestSignAmino =
cosmosSnapProvider.requestSignAmino ??
cosmosSnapProvider.default.requestSignAmino;

return requestSignAmino(chainId, signer, signDoc, signOptions);
}

override async signDirect(
chainId: string,
signer: string,
signDoc: SignDoc,
): Promise<DirectSignResponse> {
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const requestSignature =
cosmosSnapProvider.requestSignature ??
cosmosSnapProvider.default.requestSignature;

const signResponse = await requestSignature(chainId, signer, {
...signDoc,
accountNumber: Long.fromString(signDoc.accountNumber.toString()),
});

return {
...signResponse,
signed: {
...signResponse.signed,
accountNumber: BigInt(signResponse.signed.accountNumber.toString()),
},
};
}

override async signArbitrary(
chainId: string,
signer: string,
data: string | Uint8Array,
): Promise<StdSignature> {
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const signArbitrary =
cosmosSnapProvider.signArbitrary ??
cosmosSnapProvider.default.signArbitrary;

const base64js = await import('base64-js');
const fromByteArray =
base64js.fromByteArray ?? base64js.default.fromByteArray;

const payload = typeof data === 'string' ? data : fromByteArray(data);

return signArbitrary(chainId, signer, payload);
}

override verifyArbitrary(): Promise<boolean> {
throw new Error('verifyArbitrary method not implemented.');
}

override async suggestTokens(): Promise<void> {
console.warn('suggestTokens method not implemented.');
return;
}

override async suggestChains(suggestions: SuggestChain[]): Promise<void> {
await this.enable();
const cosmosSnapProvider = await import('@leapwallet/cosmos-snap-provider');
const suggestChain =
cosmosSnapProvider.suggestChain ??
cosmosSnapProvider.default.suggestChain;

for (const suggestion of suggestions) {
const chainInfo = getChainInfo(suggestion.chain, suggestion.assetList);

await suggestChain(chainInfo, {});
}
}
}
8 changes: 8 additions & 0 deletions packages/wallets/src/leap-metamask-snap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LeapMetamaskSnapWalletExtension } from './extension';
import { leapMetamaskSnapOptions } from './registry';

const leapMetamaskSnapExtension = new LeapMetamaskSnapWalletExtension(
leapMetamaskSnapOptions,
);

export { leapMetamaskSnapExtension };
7 changes: 7 additions & 0 deletions packages/wallets/src/leap-metamask-snap/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { WalletOptions } from '@quirks/core';
import { leapmetamasksnap } from '@nabla-studio/wallet-registry';

export const leapMetamaskSnapOptions: WalletOptions = {
...leapmetamasksnap,
windowKey: 'ethereum',
};
3 changes: 3 additions & 0 deletions packages/wallets/src/leap-metamask-snap/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface LeapMetamaskSnap {
isMetamask: boolean;
}
2 changes: 2 additions & 0 deletions packages/wallets/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default defineConfig({
'@nabla-studio/chain-registry',
'@nabla-studio/wallet-registry',
'@cosmostation/extension-client',
'@leapwallet/cosmos-snap-provider',
'base64-js',
],
},
},
Expand Down

0 comments on commit 525e21d

Please sign in to comment.