Skip to content

Metamask as wallet options #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,13 @@
"@randlabs/myalgo-connect": "^1.4.2",
"algosdk": "^2.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"snapalgo-sdk": "^1.1.6"
},
"peerDependenciesMeta": {
"snapalgo-sdk": {
"optional": false
},
"@blockshake/defly-connect": {
"optional": true
},
Expand All @@ -128,6 +132,7 @@
"types": "dist/index.d.ts",
"dependencies": {
"immer": "^9.0.15",
"snapalgo-sdk": "^1.1.6",
"zustand": "^4.3.8"
},
"config": {
Expand Down
6 changes: 4 additions & 2 deletions src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import algosigner from './algosigner'
import walletconnect from './walletconnect2'
import kmd from './kmd'
import mnemonic from './mnemonic'
import metamask from './metamask'

export { pera, myalgo, defly, exodus, algosigner, walletconnect, kmd, mnemonic }
export { pera, myalgo, defly, exodus, algosigner, walletconnect, kmd, mnemonic, metamask}

export default {
[pera.metadata.id]: pera,
Expand All @@ -19,5 +20,6 @@ export default {
[algosigner.metadata.id]: algosigner,
[walletconnect.metadata.id]: walletconnect,
[kmd.metadata.id]: kmd,
[mnemonic.metadata.id]: mnemonic
[mnemonic.metadata.id]: mnemonic,
[metamask.metadata.id]: metamask,
}
214 changes: 214 additions & 0 deletions src/clients/metamask/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/**
* Helpful resources:
* https://github.com/PureStake/algosigner/blob/develop/docs/dApp-integration.md
*/
import type _algosdk from 'algosdk'
import BaseWallet from '../base'
import Algod, { getAlgodClient } from '../../algod'
import { PROVIDER_ID, DEFAULT_NETWORK } from '../../constants'
import type { DecodedTransaction, DecodedSignedTransaction, Network } from '../../types'
import { ICON } from './constants'
import type {
WindowExtended,
AlgoSignerTransaction,
AlgoSigner,
MetamaskClientConstructor,
InitParams
} from './types'
import { useWalletStore } from '../../store'
import {Wallet} from 'snapalgo-sdk';


class MetamaskClient extends BaseWallet {
#client: any
network: Network
walletStore: typeof useWalletStore
init: boolean

constructor({ metadata, client, algosdk, algodClient, network }: MetamaskClientConstructor) {
super(metadata, algosdk, algodClient)
this.#client = client
this.network = network
this.walletStore = useWalletStore
this.init = false
}

static metadata = {
id: PROVIDER_ID.METAMASK,
name: 'Metamask',
icon: ICON,
isWalletConnect: false
}

static async init({ algodOptions, algosdkStatic, network = DEFAULT_NETWORK }: InitParams) {



const algosdk = algosdkStatic || (await Algod.init(algodOptions)).algosdk
const algodClient = getAlgodClient(algosdk, algodOptions)
const client = Wallet;

return new MetamaskClient({
metadata: MetamaskClient.metadata,
client: client,
algosdk: algosdk,
algodClient: algodClient,
network
})

}

async connect() {

if(this.init === false){
this.#client = new Wallet();
}
console.log(this.getGenesisID());
const { accounts } = await this.#client.enable({ genesisID: this.getGenesisID() })
console.log("accounts is")
console.log(accounts);

if (accounts.length === 0) {
throw new Error(`No accounts found for ${MetamaskClient.metadata.id}`)
}

const mappedAccounts = await Promise.all(
accounts.map(async (address:string, index:any) => {
// check to see if this is a rekeyed account
const { 'auth-addr': authAddr } = await this.getAccountInfo(address)

const currentAccountInfo:any = await (window as WindowExtended)?.ethereum.request({
method: 'wallet_invokeSnap',
params:{
snapId:'npm:@algorandfoundation/algorand-metamask-snap',
request:{
method: 'getCurrentAccount',
}
}
})


return {
name: currentAccountInfo.name,
address,
providerId: MetamaskClient.metadata.id,
...(authAddr && { authAddr })
}
})
)

// sort the accounts in the order they were returned by AlgoSigner
mappedAccounts.sort((a, b) => accounts.indexOf(a.address) - accounts.indexOf(b.address))
this.init = true;
return {
...MetamaskClient.metadata,
accounts: mappedAccounts
}
}

// eslint-disable-next-line @typescript-eslint/require-await
async reconnect(onDisconnect: () => void) {
if (window === undefined || (window as WindowExtended).algorand === undefined) {
onDisconnect()
}

return null
}

// eslint-disable-next-line @typescript-eslint/require-await
async disconnect() {
//disconnect not supported by metamask
return
}

async signTransactions(
connectedAccounts: string[],
transactions: Uint8Array[],
indexesToSign?: number[],
returnGroup = true
) {
// Decode the transactions to access their properties.
const decodedTxns = transactions.map((txn) => {
let rawTxn = this.algosdk.decodeObj(txn);
return rawTxn;
}) as Array<DecodedTransaction | DecodedSignedTransaction>

const signedIndexes: number[] = []

// Marshal the transactions,
// and add the signers property if they shouldn't be signed.
const txnsToSign = decodedTxns.reduce<AlgoSignerTransaction[]>((acc, txn, i) => {
const isSigned = 'txn' in txn

const sender = this.algosdk.encodeAddress(isSigned ? txn.txn.snd : txn.snd)
const authAddress = this.getAuthAddress(sender) // rekeyed-to account, or undefined

if (indexesToSign && indexesToSign.length && indexesToSign.includes(i)) {
signedIndexes.push(i)
acc.push({
txn: this.#client.base64Encode(transactions[i]),
...(authAddress && { authAddr: authAddress })
})
} else if (!isSigned && connectedAccounts.includes(sender)) {
signedIndexes.push(i)
acc.push({
txn: this.#client.base64Encode(transactions[i]),
...(authAddress && { authAddr: authAddress })
})
} else {
acc.push({
txn: this.#client.base64Encode(
isSigned
? this.algosdk.decodeSignedTransaction(transactions[i]).txn.toByte()
: this.algosdk.decodeUnsignedTransaction(transactions[i]).toByte()
),
signers: []
})
}

return acc
}, [])

// Sign them with the client.
const result = await this.#client.signTxns(txnsToSign)

// Join the newly signed transactions with the original group of transactions
// if `returnGroup` is true
const signedTxns = transactions.reduce<Uint8Array[]>((acc, txn, i) => {
if (signedIndexes.includes(i)) {
const signedByUser = result[i]
signedByUser && acc.push(new Uint8Array(Buffer.from(signedByUser, 'base64')))
} else if (returnGroup) {
acc.push(txn)
}

return acc
}, [])

return signedTxns
}

getGenesisID() {
if (this.network === 'betanet') {
return 'betanet-v1.0'
}
if (this.network === 'testnet') {
return 'testnet-v1.0'
}
if (this.network === 'mainnet') {
return 'mainnet-v1.0'
}
return this.network
}

getAuthAddress(address: string): string | undefined {
const accounts = this.walletStore.getState().accounts
const account = accounts.find(
(acct) => acct.address === address && acct.providerId === this.metadata.id
)

return account?.authAddr
}
}

export default MetamaskClient
Loading