@ngeth/ethers-angular v0.0.26
Angular toolkit for ethers
Getting Started
npm install @ngeth/ethers-core @ngeth/ethers-angular ethersIn the app.module.ts add the providers:
import { ngEthersProviders, EthersModule } from '@ngeth/ethers-angular';
import { InjectedProviders } form '@ngeth/ethers-core';
@NgModule({
// Use EthersModule only for the pipes, directive or components
imports: [BrowserModule, EthersModule],
providers: [ngEthersProviders(InjectedProviders)]
})This will exposes the NgERC1193 provider
Use it in a component:
import { NgERC1193 } from '@ngeth/ethers-angular';
@Component({
template: `
<p *ngIf="account$ | async as account; else noAccount">{{ account | address }}</p>
<ng-template #noAccount>
<button (click)="connect()">Connect your wallet</button>
</ng-template>
`
})
export class AppComponent {
account$ = this.erc1193.account$;
constructor(private erc1193: NgERC1193) {}
connect() {
this.erc1193.enable();
}
}Install
Standalone
npm install @ngeth/ethers-core @ngeth/ethers-angular ethersWith @ngeth/hardhat
npx nx @ngeth/hardhat:ng-add --outputType angularContract
ngContract
You can wrap any contract generated by @ngeth/hardhat with the ngContract mixin :
import { Signer } from '@ethersproject/abstract-signer';
import { ngContract } from '@ngeth/ethers-angular';
@Injectable({ providedIn: 'root' })
export class MyERC721 extends ngContract(ERC721) {
// Use `ngEthersProviders` to inject the Signer. See how below
constructor(signer: Signer) {
super(addresses.hardhat.ERC721, signer);
}
tokensOwnedBy(address: string): Observable<BigNumber[]> {
const receivedFilter = this.filters.Transfer(undefined, address);
const sentFilter = this.filters.Transfer(address);
return combineLatest([
this.from(receivedFilter),
this.from(sentFilter),
]).pipe(
map(([received, sent]) => erc721Tokens(received, sent))
);
}
}The ngContract wrap the events from ethers inside as Observable with the from method. Every observable is cached and multicasted.
Now you can inject it in your component:
import { Signer } from '@ethersproject/abstract-signer';
import {} from '@ngeth/ethers-core';
@Component({})
export class AppComponent {
myTokens$: Observable<BigNumber[]>;
constructor(private signer: Signer, private erc721: MyERC721) {}
async ngOnInit() {
const address = await this.signer.getAddress();
this.myTokens$ = this.erc721.tokensOwnedBy(address);
}
}Providers
ngEthersProviders
Utils function to bind the Provider & Signer to the NgERC1193 wallet manager
import { InjectedProviders } form '@ngeth/ethers-core';
...
providers: [
ngEthersProviders(InjectedProviders)
]NgERC1193
The NgERC1193 manages the signers and providers. It's an extension of the ERC1193 for @ngeth/ethers-core with observable/zone helpers for angular. You can inject an ERC1193 with ngEthersProviders.
Select an ERC1193
You can create your own ERC1193 wallet manager or use an existing one. In this example we'll use the InjectedProviders from @ngeth/ethers-core. This will provide access to the ethereum providers in the browser (MetaMask and Coinbase).
import { ngEthersProviders } from '@ngeth/ethers-angular';
import { InjectedProviders } form '@ngeth/ethers-core';
@NgModule({
providers: [ngEthersProviders(InjectedProviders)]
})Then you can access it like that:
@Component({...})
export class AppComponent {
account$ = this.erc1193.account$;
constructor(private erc1193: NgERC1193) {}
connect() {
this.erc1193.enable();
}
async changeChain(chainId: string) {
await this.erc1193.addChain(chainId);
await this.erc1193.switchChain(chainId);
}
}Extends it
If you want to extends the NgERC1193 class you can use the mixin ngErc1193. It'll wrap an existing ERC1193 class with NgERC1193. Here is an example with a custom wallet selector using @angular/material.
@Injectable({ providedIn: 'root' })
export class MyInjectedERC1193 extends ngErc1193(InjectedProviders) {
constructor(private dialog: MatDialog) {
super();
}
protected override async getWallet() {
if (!this.wallets.length) return;
if (this.wallets.length === 1) return this.wallets[0];
const ref = this.dialog.open(SelectWalletComponent, { labels: this.wallets.map(w => w.label) });
const label = await firstValueFrom(ref.afterClosed());
return this.wallets.find(w => w.label === label);
}
}Now you can inject it as any other Injectable class:
@Component({...})
export class AppComponent {
constructor(private erc1193: MyInjectedERC1193) {}
}Note: If you want to inject the Provider or Signer you'll still need to use the ngEthersProviders in the providers list.
@NgModule({
providers: [ngEthersProviders(MyInjectedERC1193)]
})SUPPORTED_CHAINS
Specify a list of supported chain for the application. If value is "*", all chains are supported
{ provide: SUPPORTED_CHAINS, useValue: [1] }CUSTOM_CHAINS
Add a list of custom chains for the ChainManager. Useful for local development with hardhat for example
{ provide: CUSTOM_CHAINS, useValue: { '0x1234': { name: 'Custom Chain', chain: 'ETH',... } } }rpcProvider
Utils function to set the ethers.js's Provider as injectable dependancy
providers: [rpcProvider()]EthersModule
Provides useful components & pipes for an angular web3 project
@NgModule({
imports: [EthersModule]
})Components
eth-blocky
A blocky stuled representation of an address
<eth-blocky address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"></eth-blocky>Directives
input[type="ethAddress"]
A ControlValueAccessor for ethereum addresses
<input type="ethAddress" [formControl]="control" />input[type="ether"]
A ControlValueAccessor to transform an ether value into a BigNumber of wei
<input type="ether" [formControl]="control" />Pipes
address
Checksum & format an ethereum address
<!-- 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -->
<p>{{ account | address }}</p>
<!-- 0xf39F...2266 -->
<p>{{ account | address:'short' }}</p>bignumber
Transform a bignumber-like value into a string
<p>{{ gasUsed | bignumber }}</p>ether
Transform a bignumber-like value into a ether string using the ether symbol
<p>{{ price | ether }}</p>ethCurrency
Transform a bignumber-like value into a the native currency of a chain. It'll default to the current chain if not chainId is specified.
<!-- Use the current selected chain -->
<p>{{ price | ethCurrency | async }}</p>
<!-- Support number or hex chainID -->
<p>{{ price | ethCurrency:1 | async }}</p>
<p>{{ price | ethCurrency:'0x01' | async }}</p>This is an async pipe because it relies on the chain metadata.
chain
Get the chain metadata of a chainId
<menu>
<ng-container *ngFor="let chainId of availableChainIds">
<li *ngIf="chainId | chain | async as chain">
<button (click)="select(chainId)">{{ chain.name }}</button>
<li>
</ng-container>
</menu>explore
Link to the block explorer search of a specific chain
<ng-container *ngIf="chainId$ | async as chainId">
<article *ngIf="chainId | chain | async as chain">
<a [href]="account | explorer:chain">
<eth-blocky [address]="account"></eth-blocky>
<h3>{{ account | address }}</h3>
</a>
</article>
</ng-container>Guards
HasInjectedProviderGuard
Check if the user has an injected provided by looking at window.ethereum
- Redirect to
/no-injected-provider - Custom redirect:
data.hasInjectedProviderRedirect
{
path: 'account',
canActivate: [HasInjectedProviderGuard],
data: {
hasInjectedProviderRedirect: '/get-provider'
}
}HasWalletGuard
Check if the user has an active erc1193 wallet selected. In the case of InjectedProviders it can be either MetaMask or Coinbase
- Redirect to:
/no-wallet - Custom redirect:
data.hasWalletRedirect
{
path: 'wallet',
canActivate: [HasWalletGuard],
data: {
hasWalletRedirect: '/get-wallet'
}
}IsConnectedGuard
Check if the user has a connected account
- Redirect to:
/no-connected - Custom redirect:
data.isConnectedRedirect
{
path: 'wallet',
canActivate: [IsConnectedGuard],
data: {
isConnectedRedirect: '/connect-to-metamask'
}
}HasSignerGuard
Check if the user has a connected account
- Redirect to:
/no-signer - Custom redirect:
data.hasSignerRedirect
{
path: 'wallet',
canActivate: [IsConnectedGuard],
data: {
hasSignerRedirect: '/select-account'
}
}IsSupportedChainGuard
Check if the current user chain is supported by the application
- Redirect to:
/unsupported-chain - Custom redirect:
data.isSupportedChainRedirect
@NgModule({
imports: [
RouterModule.forChild({
path: '',
canActivate: [IsSupportedChainGuard],
data: {
isSupportedChainRedirect: '/change-chain'
}
})
],
providers: [
{ provide: SUPPORTED_CHAINS, useValue: [1] }
]
})