-
-
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 auth provider
- Loading branch information
1 parent
f825a1b
commit 5652252
Showing
16 changed files
with
1,145 additions
and
25 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
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,244 @@ | ||
/** | ||
* 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 | ||
} | ||
} | ||
|
||
async connect(_: () => void, email: string): Promise<Wallet> { | ||
await this.#client.auth.loginWithMagicLink({ email: email }) | ||
|
||
const userInfo = await this.#client.user.getMetadata() | ||
|
||
return { | ||
...MagicAuth.metadata, | ||
accounts: [ | ||
{ | ||
name: `MagicWallet ${userInfo.email ?? ''} 1`, | ||
address: userInfo.publicAddress ?? 'N/A', | ||
providerId: MagicAuth.metadata.id | ||
} | ||
] | ||
} | ||
} | ||
|
||
async reconnect() { | ||
const isLoggedIn = await this.#client.user.isLoggedIn() | ||
|
||
if (!isLoggedIn) { | ||
return null | ||
} | ||
|
||
const userInfo = await this.#client.user.getMetadata() | ||
|
||
return { | ||
...MagicAuth.metadata, | ||
accounts: [ | ||
{ | ||
name: `MagicWallet ${userInfo.email ?? ''} 1`, | ||
address: userInfo.publicAddress ?? 'N/A', | ||
providerId: MagicAuth.metadata.id | ||
} | ||
] | ||
} | ||
} | ||
|
||
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 pera from './client' | ||
|
||
export default pera |
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,33 @@ | ||
import type algosdk from 'algosdk' | ||
import type { Network } from '../../types/node' | ||
import type { Metadata } from '../../types/wallet' | ||
import { SDKBase, InstanceWithExtensions } from '@magic-sdk/provider' | ||
import { AlgorandExtension } from '@magic-ext/algorand' | ||
|
||
export type MagicAuthConnectOptions = { | ||
apiKey: string | ||
} | ||
|
||
export interface MagicAuthTransaction { | ||
txn: string | ||
/** | ||
* Optional list of addresses that must sign the transactions. | ||
* Wallet skips to sign this txn if signers is empty array. | ||
* If undefined, wallet tries to sign it. | ||
*/ | ||
signers?: string[] | ||
} | ||
|
||
export type MagicAuthConstructor = { | ||
metadata: Metadata | ||
client: InstanceWithExtensions< | ||
SDKBase, | ||
{ | ||
algorand: AlgorandExtension | ||
} | ||
> | ||
clientOptions: MagicAuthConnectOptions | ||
algosdk: typeof algosdk | ||
algodClient: algosdk.Algodv2 | ||
network: Network | ||
} |
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
Oops, something went wrong.