exercitationemmaxime
Version:
Arkane-enabled Web3 Provider for the web
183 lines (170 loc) • 8.95 kB
text/typescript
import { ArkaneConnect, SecretType, SignatureRequestType, SignMethod, Wallet, WindowMode } from "@arkane-network/arkane-connect"
import { EIP712TypedData, PartialTxParams } from "@0x/subproviders";
import { BaseWalletSubprovider } from "@0x/subproviders/lib/src/subproviders/base_wallet_subprovider";
import { ArkaneSubProviderOptions } from "./index";
import { AuthenticationResult, ConstructorOptions } from '@arkane-network/arkane-connect/dist/src/connect/connect';
import { Network } from "@arkane-network/arkane-connect/dist/src/models/Network";
import { Account } from '@arkane-network/arkane-connect/dist/src/models/Account';
export class ArkaneSubProvider extends BaseWalletSubprovider {
readonly arkaneConnect: ArkaneConnect;
private wallets: Wallet[] = [];
private walletsFromFlow: Wallet[] = [];
public network?: Network;
private options: ArkaneSubProviderOptions;
private authenticated: boolean = false;
constructor(options: ArkaneSubProviderOptions) {
super();
const connectConstructorOptions: ConstructorOptions = {
environment: options.environment || 'production',
bearerTokenProvider: options.bearerTokenProvider,
};
if (options.signMethod) {
Object.assign(connectConstructorOptions, {signUsing: options.signMethod == 'POPUP' ? SignMethod.POPUP : SignMethod.REDIRECT});
}
if (options.windowMode) {
Object.assign(connectConstructorOptions, {windowMode: options.windowMode == 'POPUP' ? WindowMode.POPUP : WindowMode.REDIRECT});
}
this.arkaneConnect = new ArkaneConnect(options.clientId, connectConstructorOptions);
this.network = options.network;
this.options = options;
}
private async startGetAccountFlow() {
let that = this;
return this.arkaneConnect.flows.getAccount(SecretType.ETHEREUM, this.options.authenticationOptions)
.then(async (account: Account) => {
return await new Promise((resolve,
reject) => {
if (!account.isAuthenticated) {
console.debug('Not authenticated to Arkane Network');
reject('not-authenticated');
} else if (account.wallets && account.wallets.length <= 0) {
console.debug('No wallet has been linked to this application');
reject('no-wallet-linked');
} else {
console.debug("Authenticated to Arkane Network and at least one wallet is linked to this application");
that.authenticated = true;
that.walletsFromFlow = account.wallets;
resolve();
}
});
});
}
private async refreshWalletsFromApi() {
let that = this;
return this.arkaneConnect.api.getWallets({secretType: SecretType.ETHEREUM})
.then(returnedWallets => {
that.wallets = returnedWallets;
});
}
/**
* Retrieve the accounts associated with the eth-lightwallet instance.
* This method is implicitly called when issuing a `eth_accounts` JSON RPC request
* via your providerEngine instance.
*
* @return An array of accounts
*/
public async getAccountsAsync(): Promise<string[]> {
let that = this;
let promise: Promise<any>;
if (this.walletsFromFlow && this.walletsFromFlow.length > 0) {
promise = Promise.resolve(() => {
that.wallets = that.walletsFromFlow;
that.walletsFromFlow = [];
});
} else if (this.authenticated) {
promise = this.refreshWalletsFromApi();
} else {
promise = this.startGetAccountFlow();
}
return promise.then(() => {
return this.wallets.map((wallet) => wallet.address)
});
}
public async checkAuthenticated(): Promise<AuthenticationResult> {
return this.arkaneConnect.checkAuthenticated().then(authResult => {
this.authenticated = authResult.isAuthenticated;
return authResult;
});
}
/**
* Signs a transaction with the account specificed by the `from` field in txParams.
* If you've added this Subprovider to your app's provider, you can simply send
* an `eth_sendTransaction` JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param txParams Parameters of the transaction to sign
* @return Signed transaction hex string
*/
public async signTransactionAsync(txParams: PartialTxParams): Promise<string> {
let signer = this.arkaneConnect.createSigner();
return signer.signTransaction(this.constructEthereumTransationSignatureRequest(txParams))
.then((result) => {
if (result.status === 'SUCCESS') {
return result.result.signedTransaction;
} else {
throw new Error((result.errors && result.errors.join(", ")));
}
});
}
private constructEthereumTransationSignatureRequest(txParams: PartialTxParams) {
console.debug(txParams);
const retVal = {
gasPrice: txParams.gasPrice ? parseInt(txParams.gasPrice, 16) : txParams.gasPrice,
gas: txParams.gas ? parseInt(txParams.gas, 16) : txParams.gas,
to: txParams.to,
nonce: txParams.nonce ? parseInt(txParams.nonce, 16) : txParams.nonce,
data: (txParams.data) || '0x',
value: txParams.value ? parseInt(txParams.value, 16) : 0,
submit: false,
type: SignatureRequestType.ETHEREUM_TRANSACTION,
walletId: this.getWalletIdFrom(txParams.from),
network: this.network
};
return retVal;
}
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address.
* If you've added this Subprovider to your app's provider, you can simply send an `eth_sign`
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param data Hex string message to sign
* @param address Address of the account to sign with
* @return Signature hex string (order: rsv)
*/
public async signPersonalMessageAsync(data: string,
address: string): Promise<string> {
const signer = this.arkaneConnect.createSigner();
return signer.signTransaction({
type: SignatureRequestType.ETHEREUM_RAW,
walletId: this.getWalletIdFrom(address),
data: data
})
.then((result) => {
if (result.status === 'SUCCESS') {
return result.result.signature;
} else {
throw new Error((result.errors && result.errors.join(", ")));
}
});
}
/**
* Sign an EIP712 Typed Data message. The signing address will associated with the provided address.
* If you've added this Subprovider to your app's provider, you can simply send an `eth_signTypedData`
* JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
public async signTypedDataAsync(address: string,
typedData: EIP712TypedData): Promise<string> {
return Promise.reject('Not implemented yet');
}
private getWalletIdFrom(address: string):
string {
let foundWallet = this.wallets.find((wallet) => {
return wallet.address.toLowerCase() === address.toLowerCase();
});
return (foundWallet && foundWallet.id) || '';
}
}