-
-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introducing magic.link provider (email based authentication) (#124
) * feat: introducing magic auth provider * chore: adding email into account object as optional * chore: bumping magic sdk; fixing merge conflicts * docs: updating docs * refactor: addressing pr comments * chore: patching babel class prop compatibility * chore: minor tweaks * chore: prettier tweaks * chore: addressing pr changes * chore: minor typo
- Loading branch information
1 parent
380bd15
commit 85f8bef
Showing
19 changed files
with
3,822 additions
and
3,097 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
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,3 +1,4 @@ | ||
module.exports = { | ||
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] | ||
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], | ||
plugins: [['@babel/plugin-transform-class-properties', { loose: 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
/** | ||
* Documentation: | ||
* https://magic.link/docs/blockchains/other-chains/other/algorand | ||
*/ | ||
import Algod, { getAlgodClient } from '../../algod' | ||
import BaseClient from '../base' | ||
import { DEFAULT_NETWORK, PROVIDER_ID } from '../../constants' | ||
import { debugLog } from '../../utils/debugLog' | ||
import { ICON } from './constants' | ||
import type { DecodedSignedTransaction, DecodedTransaction, Network } from '../../types/node' | ||
import type { InitParams } from '../../types/providers' | ||
import type { Wallet } from '../../types/wallet' | ||
import type { MagicAuthTransaction, MagicAuthConstructor, MagicAuthConnectOptions } from './types' | ||
import { AlgorandExtension } from '@magic-ext/algorand' | ||
import { SDKBase, InstanceWithExtensions } from '@magic-sdk/provider' | ||
|
||
class MagicAuth extends BaseClient { | ||
#client: InstanceWithExtensions< | ||
SDKBase, | ||
{ | ||
algorand: AlgorandExtension | ||
} | ||
> | ||
clientOptions?: MagicAuthConnectOptions | ||
network: Network | ||
|
||
constructor({ | ||
metadata, | ||
client, | ||
clientOptions, | ||
algosdk, | ||
algodClient, | ||
network | ||
}: MagicAuthConstructor) { | ||
super(metadata, algosdk, algodClient) | ||
this.#client = client | ||
this.clientOptions = clientOptions | ||
this.network = network | ||
this.metadata = MagicAuth.metadata | ||
} | ||
|
||
static metadata = { | ||
id: PROVIDER_ID.MAGIC, | ||
name: 'Magic', | ||
icon: ICON, | ||
isWalletConnect: true | ||
} | ||
|
||
static async init({ | ||
clientOptions, | ||
algodOptions, | ||
clientStatic, | ||
getDynamicClient, | ||
algosdkStatic, | ||
network = DEFAULT_NETWORK | ||
}: InitParams<PROVIDER_ID.MAGIC>): Promise<BaseClient | null> { | ||
try { | ||
debugLog(`${PROVIDER_ID.MAGIC.toUpperCase()} initializing...`) | ||
|
||
let Magic | ||
if (clientStatic) { | ||
Magic = clientStatic | ||
} else if (getDynamicClient) { | ||
Magic = await getDynamicClient() | ||
} else if (!clientOptions || !clientOptions.apiKey) { | ||
throw new Error( | ||
'Magic provider missing API Key to be passed by required property: clientOptions' | ||
) | ||
} else { | ||
throw new Error( | ||
'Magic provider missing required property: clientStatic or getDynamicClient' | ||
) | ||
} | ||
|
||
const algosdk = algosdkStatic || (await Algod.init(algodOptions)).algosdk | ||
const algodClient = getAlgodClient(algosdk, algodOptions) | ||
|
||
const magic = new Magic(clientOptions?.apiKey as string, { | ||
extensions: { | ||
algorand: new AlgorandExtension({ | ||
rpcUrl: '' | ||
}) | ||
} | ||
}) | ||
|
||
const provider = new MagicAuth({ | ||
metadata: MagicAuth.metadata, | ||
client: magic, | ||
clientOptions: clientOptions as MagicAuthConnectOptions, | ||
algosdk, | ||
algodClient, | ||
network | ||
}) | ||
|
||
debugLog(`${PROVIDER_ID.MAGIC.toUpperCase()} initialized`, '✅') | ||
|
||
return provider | ||
} catch (e) { | ||
console.error('Error initializing...', e) | ||
return null | ||
} | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
async connect(_: () => void, arg?: any): Promise<Wallet> { | ||
if (!arg || typeof arg !== 'string') { | ||
throw new Error('Magic Link provider requires an email (string) to connect') | ||
} | ||
|
||
const email = arg | ||
await this.#client.auth.loginWithMagicLink({ email: email }) | ||
const userInfo = await this.#client.user.getInfo() | ||
|
||
return { | ||
...MagicAuth.metadata, | ||
accounts: [ | ||
{ | ||
name: `MagicWallet ${userInfo.email ?? ''} 1`, | ||
address: userInfo.publicAddress ?? 'N/A', | ||
providerId: MagicAuth.metadata.id, | ||
email: userInfo.email ?? '' | ||
} | ||
] | ||
} | ||
} | ||
|
||
async reconnect() { | ||
const isLoggedIn = await this.#client.user.isLoggedIn() | ||
|
||
if (!isLoggedIn) { | ||
return null | ||
} | ||
|
||
const userInfo = await this.#client.user.getInfo() | ||
|
||
return { | ||
...MagicAuth.metadata, | ||
accounts: [ | ||
{ | ||
name: `MagicWallet ${userInfo.email ?? ''} 1`, | ||
address: userInfo.publicAddress ?? 'N/A', | ||
providerId: MagicAuth.metadata.id, | ||
email: userInfo.email ?? '' | ||
} | ||
] | ||
} | ||
} | ||
|
||
async disconnect() { | ||
await this.#client.user.logout() | ||
} | ||
|
||
async signTransactions( | ||
connectedAccounts: string[], | ||
txnGroups: Uint8Array[] | Uint8Array[][], | ||
indexesToSign?: number[], | ||
returnGroup = true | ||
) { | ||
// If txnGroups is a nested array, flatten it | ||
const transactions: Uint8Array[] = Array.isArray(txnGroups[0]) | ||
? (txnGroups as Uint8Array[][]).flatMap((txn) => txn) | ||
: (txnGroups as Uint8Array[]) | ||
|
||
// Decode the transactions to access their properties. | ||
const decodedTxns = transactions.map((txn) => { | ||
return this.algosdk.decodeObj(txn) | ||
}) 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<MagicAuthTransaction[]>((acc, txn, i) => { | ||
const isSigned = 'txn' in txn | ||
|
||
// If the indexes to be signed is specified, designate that it should be signed | ||
if (indexesToSign && indexesToSign.length && indexesToSign.includes(i)) { | ||
signedIndexes.push(i) | ||
acc.push({ | ||
txn: Buffer.from(transactions[i]).toString('base64') | ||
}) | ||
// If the indexes to be signed is specified, but it's not included in it, | ||
// designate that it should not be signed | ||
} else if (indexesToSign && indexesToSign.length && !indexesToSign.includes(i)) { | ||
acc.push({ | ||
txn: isSigned | ||
? Buffer.from( | ||
this.algosdk.encodeUnsignedTransaction( | ||
this.algosdk.decodeSignedTransaction(transactions[i]).txn | ||
) | ||
).toString('base64') | ||
: Buffer.from(transactions[i]).toString('base64'), | ||
signers: [] | ||
}) | ||
// If the transaction is unsigned and is to be sent from a connected account, | ||
// designate that it should be signed | ||
} else if (!isSigned && connectedAccounts.includes(this.algosdk.encodeAddress(txn['snd']))) { | ||
signedIndexes.push(i) | ||
acc.push({ | ||
txn: Buffer.from(transactions[i]).toString('base64') | ||
}) | ||
// Otherwise, designate that it should not be signed | ||
} else if (isSigned) { | ||
acc.push({ | ||
txn: Buffer.from( | ||
this.algosdk.encodeUnsignedTransaction( | ||
this.algosdk.decodeSignedTransaction(transactions[i]).txn | ||
) | ||
).toString('base64'), | ||
signers: [] | ||
}) | ||
} else if (!isSigned) { | ||
acc.push({ | ||
txn: Buffer.from(transactions[i]).toString('base64'), | ||
signers: [] | ||
}) | ||
} | ||
|
||
return acc | ||
}, []) | ||
|
||
// Sign them with the client. | ||
try { | ||
console.log(txnsToSign) | ||
const result = await this.#client.algorand.signGroupTransactionV2(txnsToSign) | ||
console.log(result) | ||
const decodedSignedTxns: Uint8Array[] = result.map((txn: string) => { | ||
return Buffer.from(txn, 'base64') | ||
}) | ||
|
||
// Join the newly signed transactions with the original group of transactions. | ||
const signedTxns = transactions.reduce<Uint8Array[]>((acc, txn, i) => { | ||
if (signedIndexes.includes(i)) { | ||
const signedByUser = decodedSignedTxns.shift() | ||
signedByUser && acc.push(signedByUser) | ||
} else if (returnGroup) { | ||
acc.push(transactions[i]) | ||
} | ||
|
||
return acc | ||
}, []) | ||
|
||
return signedTxns | ||
} catch (e) { | ||
console.error(e) | ||
return [] | ||
} | ||
} | ||
} | ||
|
||
export default MagicAuth |
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,3 @@ | ||
export const ICON = | ||
'data:image/svg+xml;base64,' + | ||
'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLSBHZW5lcmF0ZWQgYnkgUGl4ZWxtYXRvciBQcm8gMy40LjMgLS0+Cjxzdmcgd2lkdGg9IjQ3IiBoZWlnaHQ9IjQ3IiB2aWV3Qm94PSIwIDAgNDcgNDciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8cGF0aCBpZD0iUGF0aCIgZmlsbD0iIzY4NTFmZiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9Im5vbmUiIGQ9Ik0gMjMuOTYwODYxIDEuODA3NjkgQyAyNS44MzUwNzcgNC4xMDMxNTMgMjcuOTAyMjE2IDYuMjM0ODkgMzAuMTM3NTM5IDguMTc4MTY5IEMgMjguNjQ3OTY4IDEzLjAwOTMyMyAyNy44NDYwOTIgMTguMTQyMDk0IDI3Ljg0NjA5MiAyMy40NjIxNTQgQyAyNy44NDYwOTIgMjguNzgyMzA3IDI4LjY0ODA2MiAzMy45MTUxNjkgMzAuMTM3NjMgMzguNzQ2MzY4IEMgMjcuOTAyMjE2IDQwLjY4OTcyNCAyNS44MzUwNzcgNDIuODIxNDc2IDIzLjk2MDg2MSA0NS4xMTY5ODUgQyAyMi4wODY1NTQgNDIuODIxNDc2IDIwLjAxOTQxNSA0MC42ODk2MzIgMTcuNzgzOTk4IDM4Ljc0NjM2OCBDIDE5LjI3MzQ3NiAzMy45MTUxNjkgMjAuMDc1NDQ1IDI4Ljc4MjQgMjAuMDc1NDQ1IDIzLjQ2MjMzNyBDIDIwLjA3NTQ0NSAxOC4xNDIyNzcgMTkuMjczNDc2IDEzLjAwOTUwNiAxNy43ODM5OTggOC4xNzgzMTggQyAyMC4wMTk0MTUgNi4yMzUwMDEgMjIuMDg2NTU0IDQuMTAzMjEgMjMuOTYwODYxIDEuODA3NjkgWiBNIDEzLjUxMTQyNyAzNS40MDY0MDMgQyAxMS4xNDUxMzkgMzMuNzQ3ODE0IDguNjMzODE2IDMyLjI4MjA2MyA2LjAwMDI2OSAzMS4wMzE5MzcgQyA2LjczMDc3NiAyOC42Mzc0NzYgNy4xMjM3NTQgMjYuMDk1NzgzIDcuMTIzNzU0IDIzLjQ2MjQyOSBDIDcuMTIzNzU0IDIwLjgyODg5MiA2LjczMDc2MiAxOC4yODcyMDEgNi4wMDAyMzUgMTUuODkyNzM4IEMgOC42MzM4MTYgMTQuNjQyNjE2IDExLjE0NTE3NSAxMy4xNzY4NjEgMTMuNTExNTAxIDExLjUxODI3NiBDIDE0LjQxNjMxMSAxNS4zNTI1NTQgMTQuODk1MDc0IDE5LjM1MTQxNCAxNC44OTUwNzQgMjMuNDYyMTU0IEMgMTQuODk1MDc0IDI3LjU3Mjk4NSAxNC40MTYyODMgMzEuNTcxOTM4IDEzLjUxMTQyNyAzNS40MDY0MDMgWiBNIDMzLjAyNzA0NiAyMy40NjIzMzcgQyAzMy4wMjcwNDYgMjcuNTcyOTg1IDMzLjUwNTc1MyAzMS41NzE4NDYgMzQuNDEwNTUzIDM1LjQwNjEyNCBDIDM2Ljc3Njg1OSAzMy43NDc2MzEgMzkuMjg4MDk0IDMyLjI4MTg3NiA0MS45MjE1MzkgMzEuMDMxODQ1IEMgNDEuMTkxMDE3IDI4LjYzNzM4NCA0MC43OTgwNjEgMjYuMDk1NjkyIDQwLjc5ODA2MSAyMy40NjIyNDYgQyA0MC43OTgwNjEgMjAuODI4OCA0MS4xOTEwMTcgMTguMjg3MjAxIDQxLjkyMTUzOSAxNS44OTI4MyBDIDM5LjI4ODA5NCAxNC42NDI3MDggMzYuNzc2NzY4IDEzLjE3NzA0OCAzNC40MTA1NTMgMTEuNTE4NTU1IEMgMzMuNTA1NzUzIDE1LjM1MjgzMSAzMy4wMjcwNDYgMTkuMzUxNjkyIDMzLjAyNzA0NiAyMy40NjIzMzcgWiIvPgo8L3N2Zz4K' |
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,3 @@ | ||
import magic from './client' | ||
|
||
export default magic |
Oops, something went wrong.