a multi-provider and tool for interacting with deployed contracts (#763)
* wip: a multi-provider and tool for seeing deployed contracts * refactor: break things out a bit * feature: loadJson * feature: OpticsContext extends MultiProvider * feature: hardcode mainnet deploy * bugs: empty ethHelper and domain handling * feature: resolveDomain * feature: resolve token info * feature: draft send implementation * feature: imporve sendCoins.ts * feature: OpticsMessage class * feature: BridgeMessage extends OpticsMessage * refactor: move all optics-specific behavior into folder * refactor: rearrange contracts and imporve exports * feature: parseMessage * refactor: simplify and improve message classes * feature: re-implement status call on OpticsMessage * feature: resolve asset on BridgeMessage * refactor: BridgeMessage is generic over action type * bug: properly export new BridgeMessage variants * feature: add a metamask file * feature: dev and staging + bug: improper signer registration * refactor: abstract out reconnectionbuddies-main-deployment
parent
73e80a063c
commit
e630652fc9
@ -0,0 +1,5 @@ |
|||||||
|
{ |
||||||
|
"tabWidth": 2, |
||||||
|
"singleQuote": true, |
||||||
|
"trailingComma": "all" |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"name": "optics-provider", |
||||||
|
"version": "0.0.0", |
||||||
|
"description": "multi-provider for Optics", |
||||||
|
"main": "src/index.ts", |
||||||
|
"scripts": { |
||||||
|
"test": "echo \"Error: no test specified\" && exit 1" |
||||||
|
}, |
||||||
|
"author": "Celo Labs Inc.", |
||||||
|
"license": "MIT OR Apache-2.0", |
||||||
|
"dependencies": { |
||||||
|
"ethers": "^5.4.6" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
import { ethers } from 'ethers'; |
||||||
|
import fs from 'fs'; |
||||||
|
|
||||||
|
export abstract class Contracts { |
||||||
|
readonly args: any; |
||||||
|
|
||||||
|
constructor(...args: any) { |
||||||
|
this.args = args; |
||||||
|
} |
||||||
|
|
||||||
|
abstract toObject(): any; |
||||||
|
|
||||||
|
abstract connect(signer: ethers.Signer): void; |
||||||
|
|
||||||
|
toJson(): string { |
||||||
|
return JSON.stringify(this.toObject()); |
||||||
|
} |
||||||
|
|
||||||
|
toJsonPretty(): string { |
||||||
|
return JSON.stringify(this.toObject(), null, 2); |
||||||
|
} |
||||||
|
|
||||||
|
saveJson(filepath: string) { |
||||||
|
fs.writeFileSync(filepath, this.toJsonPretty()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export interface Domain { |
||||||
|
domain: number; |
||||||
|
name: string; |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
export { MultiProvider } from './provider'; |
||||||
|
|
||||||
|
export { mainnet, dev, staging, OpticsContext } from './optics'; |
||||||
|
|
||||||
|
// intended usage
|
||||||
|
// import {mainnet} from 'optics-provider';
|
||||||
|
|
||||||
|
// mainnet.registerRpcProvider('celo', 'https://forno.celo.org');
|
||||||
|
// mainnet.registerRpcProvider('polygon', '...');
|
||||||
|
// mainnet.registerRpcProvider('ethereum', '...');
|
||||||
|
// mainnet.registerSigner('celo', ...);
|
||||||
|
// mainnet.registerSigner('polygon', ...);
|
||||||
|
// mainnet.registerSigner('ethereum', ...);
|
||||||
|
|
||||||
|
// mainnet.doWhatever
|
@ -0,0 +1,45 @@ |
|||||||
|
export type MetamaskNetwork = { |
||||||
|
chainId: string; |
||||||
|
chainName: string; |
||||||
|
nativeCurrency: { name: string; symbol: string; decimals: number }; |
||||||
|
rpcUrls: string[]; |
||||||
|
blockExplorerUrls: string[]; |
||||||
|
iconUrls: string[]; |
||||||
|
}; |
||||||
|
|
||||||
|
export const CELO_PARAMS: MetamaskNetwork = { |
||||||
|
chainId: '0xa4ec', |
||||||
|
chainName: 'Celo', |
||||||
|
nativeCurrency: { name: 'Celo', symbol: 'CELO', decimals: 18 }, |
||||||
|
rpcUrls: ['https://forno.celo.org'], |
||||||
|
blockExplorerUrls: ['https://explorer.celo.org/'], |
||||||
|
iconUrls: ['future'], |
||||||
|
}; |
||||||
|
|
||||||
|
export const ALFAJORES_PARAMS: MetamaskNetwork = { |
||||||
|
chainId: '0xaef3', |
||||||
|
chainName: 'Alfajores Testnet', |
||||||
|
nativeCurrency: { name: 'Alfajores Celo', symbol: 'A-CELO', decimals: 18 }, |
||||||
|
rpcUrls: ['https://alfajores-forno.celo-testnet.org'], |
||||||
|
blockExplorerUrls: ['https://alfajores-blockscout.celo-testnet.org/'], |
||||||
|
iconUrls: ['future'], |
||||||
|
}; |
||||||
|
|
||||||
|
export const BAKLAVA_PARAMS: MetamaskNetwork = { |
||||||
|
chainId: '0xf370', |
||||||
|
chainName: 'Baklava Testnet', |
||||||
|
nativeCurrency: { name: 'Baklava Celo', symbol: 'B-CELO', decimals: 18 }, |
||||||
|
rpcUrls: ['https://baklava-forno.celo-testnet.org'], |
||||||
|
blockExplorerUrls: ['https://baklava-blockscout.celo-testnet.org/'], |
||||||
|
iconUrls: ['future'], |
||||||
|
}; |
||||||
|
|
||||||
|
export async function connect(params: MetamaskNetwork) { |
||||||
|
const w = window as any; |
||||||
|
if (w.ethereum) { |
||||||
|
await w.ethereum.request({ |
||||||
|
method: 'wallet_addEthereumChain', |
||||||
|
params: [params], |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,234 @@ |
|||||||
|
import { BigNumberish, ContractTransaction, ethers } from 'ethers'; |
||||||
|
import { MultiProvider } from '..'; |
||||||
|
import { ERC20, ERC20__factory } from '../../../typechain/optics-xapps'; |
||||||
|
import { BridgeContracts } from './contracts/BridgeContracts'; |
||||||
|
import { CoreContracts } from './contracts/CoreContracts'; |
||||||
|
import { ResolvedTokenInfo, TokenIdentifier } from './tokens'; |
||||||
|
import { canonizeId } from '../utils'; |
||||||
|
import { |
||||||
|
devDomains, |
||||||
|
mainnetDomains, |
||||||
|
OpticsDomain, |
||||||
|
stagingDomains, |
||||||
|
} from './domains'; |
||||||
|
import { Replica } from '../../../typechain/optics-core'; |
||||||
|
|
||||||
|
type Address = string; |
||||||
|
|
||||||
|
export class OpticsContext extends MultiProvider { |
||||||
|
private cores: Map<number, CoreContracts>; |
||||||
|
private bridges: Map<number, BridgeContracts>; |
||||||
|
|
||||||
|
constructor( |
||||||
|
domains: OpticsDomain[], |
||||||
|
cores: CoreContracts[], |
||||||
|
bridges: BridgeContracts[], |
||||||
|
) { |
||||||
|
super(); |
||||||
|
domains.forEach((domain) => this.registerDomain(domain)); |
||||||
|
this.cores = new Map(); |
||||||
|
this.bridges = new Map(); |
||||||
|
|
||||||
|
cores.forEach((core) => { |
||||||
|
this.cores.set(core.domain, core); |
||||||
|
}); |
||||||
|
bridges.forEach((bridge) => { |
||||||
|
this.bridges.set(bridge.domain, bridge); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
static fromDomains(domains: OpticsDomain[]): OpticsContext { |
||||||
|
const cores = domains.map((domain) => CoreContracts.fromObject(domain)); |
||||||
|
const bridges = domains.map((domain) => BridgeContracts.fromObject(domain)); |
||||||
|
return new OpticsContext(domains, cores, bridges); |
||||||
|
} |
||||||
|
|
||||||
|
private reconnect(domain: number) { |
||||||
|
const connection = this.getConnection(domain); |
||||||
|
if (!connection) { |
||||||
|
throw new Error('Reconnect failed: no connection'); |
||||||
|
} |
||||||
|
// re-register contracts
|
||||||
|
const core = this.cores.get(domain); |
||||||
|
if (core) { |
||||||
|
core.connect(connection); |
||||||
|
} |
||||||
|
const bridge = this.bridges.get(domain); |
||||||
|
if (bridge) { |
||||||
|
bridge.connect(connection); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
registerProvider( |
||||||
|
nameOrDomain: string | number, |
||||||
|
provider: ethers.providers.Provider, |
||||||
|
) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
super.registerProvider(domain, provider); |
||||||
|
this.reconnect(domain); |
||||||
|
} |
||||||
|
|
||||||
|
registerSigner(nameOrDomain: string | number, signer: ethers.Signer) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
super.registerSigner(domain, signer); |
||||||
|
this.reconnect(domain); |
||||||
|
} |
||||||
|
|
||||||
|
unregisterSigner(nameOrDomain: string | number): void { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
super.unregisterSigner(domain); |
||||||
|
this.reconnect(domain); |
||||||
|
} |
||||||
|
|
||||||
|
getCore(nameOrDomain: string | number): CoreContracts | undefined { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
return this.cores.get(domain); |
||||||
|
} |
||||||
|
|
||||||
|
getBridge(nameOrDomain: string | number): BridgeContracts | undefined { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
return this.bridges.get(domain); |
||||||
|
} |
||||||
|
|
||||||
|
// gets the replica of Home on Remote
|
||||||
|
getReplicaFor( |
||||||
|
home: string | number, |
||||||
|
remote: string | number, |
||||||
|
): Replica | undefined { |
||||||
|
return this.getCore(remote)?.replicas.get(this.resolveDomain(home)) |
||||||
|
?.contract; |
||||||
|
} |
||||||
|
|
||||||
|
// resolve the local repr of a token on its domain
|
||||||
|
async resolveTokenRepresentation( |
||||||
|
nameOrDomain: string | number, |
||||||
|
token: TokenIdentifier, |
||||||
|
): Promise<ERC20 | undefined> { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
const bridge = this.getBridge(domain); |
||||||
|
|
||||||
|
const tokenDomain = this.resolveDomain(token.domain); |
||||||
|
const tokenId = canonizeId(token.id); |
||||||
|
|
||||||
|
const address = await bridge?.bridgeRouter[ |
||||||
|
'getLocalAddress(uint32,bytes32)' |
||||||
|
](tokenDomain, tokenId); |
||||||
|
|
||||||
|
if (!address) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let contract = new ERC20__factory().attach(address); |
||||||
|
|
||||||
|
const connection = this.getConnection(domain); |
||||||
|
if (connection) { |
||||||
|
contract = contract.connect(connection); |
||||||
|
} |
||||||
|
return contract; |
||||||
|
} |
||||||
|
|
||||||
|
// resolve all token representations
|
||||||
|
async tokenRepresentations( |
||||||
|
token: TokenIdentifier, |
||||||
|
): Promise<ResolvedTokenInfo> { |
||||||
|
const tokens: Map<number, ERC20> = new Map(); |
||||||
|
|
||||||
|
await Promise.all( |
||||||
|
this.domainNumbers.map(async (domain) => { |
||||||
|
let tok = await this.resolveTokenRepresentation(domain, token); |
||||||
|
if (tok) { |
||||||
|
tokens.set(domain, tok); |
||||||
|
} |
||||||
|
}), |
||||||
|
); |
||||||
|
|
||||||
|
return { |
||||||
|
domain: this.resolveDomain(token.domain), |
||||||
|
id: token.id, |
||||||
|
tokens, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
async resolveCanonicalToken( |
||||||
|
nameOrDomain: string | number, |
||||||
|
representation: Address, |
||||||
|
): Promise<TokenIdentifier | undefined> { |
||||||
|
const bridge = this.getBridge(nameOrDomain); |
||||||
|
if (!bridge) { |
||||||
|
throw new Error(`Bridge not available on ${nameOrDomain}`); |
||||||
|
} |
||||||
|
|
||||||
|
const token = await bridge.bridgeRouter.getCanonicalAddress(representation); |
||||||
|
if (token[0] === 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
return { |
||||||
|
domain: token[0], |
||||||
|
id: token[1], |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// send tokens from domain to domain
|
||||||
|
async send( |
||||||
|
from: string | number, |
||||||
|
to: string | number, |
||||||
|
token: TokenIdentifier, |
||||||
|
amount: BigNumberish, |
||||||
|
recipient: Address, |
||||||
|
): Promise<ContractTransaction> { |
||||||
|
const fromBridge = this.getBridge(from); |
||||||
|
if (!fromBridge) { |
||||||
|
throw new Error(`Bridge not available on ${from}`); |
||||||
|
} |
||||||
|
|
||||||
|
const fromToken = await this.resolveTokenRepresentation(from, token); |
||||||
|
if (!fromToken) { |
||||||
|
throw new Error(`Token not available on ${from}`); |
||||||
|
} |
||||||
|
|
||||||
|
const bridgeAddress = fromBridge?.bridgeRouter.address; |
||||||
|
if (!bridgeAddress) { |
||||||
|
throw new Error(`No bridge for ${from}`); |
||||||
|
} |
||||||
|
|
||||||
|
const sender = this.getSigner(from); |
||||||
|
if (!sender) { |
||||||
|
throw new Error(`No signer for ${from}`); |
||||||
|
} |
||||||
|
const senderAddress = await sender.getAddress(); |
||||||
|
|
||||||
|
const approved = await fromToken.allowance(senderAddress, bridgeAddress); |
||||||
|
|
||||||
|
// Approve if necessary
|
||||||
|
if (approved.lt(amount)) { |
||||||
|
await fromToken.approve(bridgeAddress, amount); |
||||||
|
} |
||||||
|
|
||||||
|
return fromBridge.bridgeRouter.send( |
||||||
|
fromToken.address, |
||||||
|
amount, |
||||||
|
to, |
||||||
|
recipient, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async sendNative( |
||||||
|
from: string | number, |
||||||
|
to: string | number, |
||||||
|
amount: BigNumberish, |
||||||
|
recipient: Address, |
||||||
|
): Promise<ContractTransaction> { |
||||||
|
const ethHelper = this.getBridge(from)?.ethHelper; |
||||||
|
if (!ethHelper) { |
||||||
|
throw new Error(`No ethHelper for ${from}`); |
||||||
|
} |
||||||
|
|
||||||
|
const toDomain = this.resolveDomain(to); |
||||||
|
|
||||||
|
return ethHelper.sendToEVMLike(toDomain, recipient, { value: amount }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const mainnet = OpticsContext.fromDomains(mainnetDomains); |
||||||
|
export const dev = OpticsContext.fromDomains(devDomains); |
||||||
|
export const staging = OpticsContext.fromDomains(stagingDomains); |
@ -0,0 +1,67 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
import { ethers } from 'ethers'; |
||||||
|
import { |
||||||
|
BridgeRouter, |
||||||
|
BridgeRouter__factory, |
||||||
|
ETHHelper, |
||||||
|
ETHHelper__factory, |
||||||
|
} from '../../../../typechain/optics-xapps'; |
||||||
|
import { Contracts } from '../../contracts'; |
||||||
|
|
||||||
|
type Address = string; |
||||||
|
|
||||||
|
export class BridgeContracts extends Contracts { |
||||||
|
domain: number; |
||||||
|
bridgeRouter: BridgeRouter; |
||||||
|
ethHelper?: ETHHelper; |
||||||
|
|
||||||
|
constructor( |
||||||
|
domain: number, |
||||||
|
br: Address, |
||||||
|
ethHelper?: Address, |
||||||
|
signer?: ethers.Signer, |
||||||
|
) { |
||||||
|
super(domain, br, ethHelper, signer); |
||||||
|
this.domain = domain; |
||||||
|
this.bridgeRouter = new BridgeRouter__factory(signer).attach(br); |
||||||
|
if (ethHelper) { |
||||||
|
this.ethHelper = new ETHHelper__factory(signer).attach(ethHelper); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
connect(providerOrSigner: ethers.providers.Provider | ethers.Signer): void { |
||||||
|
this.bridgeRouter = this.bridgeRouter.connect(providerOrSigner); |
||||||
|
if (this.ethHelper) { |
||||||
|
this.ethHelper = this.ethHelper.connect(providerOrSigner); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static fromObject(data: any, signer?: ethers.Signer) { |
||||||
|
if (!data.domain || !data.bridgeRouter) { |
||||||
|
throw new Error('missing address'); |
||||||
|
} |
||||||
|
|
||||||
|
const domain = data.domain; |
||||||
|
const br = data.bridgeRouter.proxy ?? data.bridgeRouter; |
||||||
|
const eh = data.ethHelper; |
||||||
|
|
||||||
|
return new BridgeContracts(domain, br, eh); |
||||||
|
} |
||||||
|
|
||||||
|
static loadJson(filepath: string, signer?: ethers.Signer) { |
||||||
|
return this.fromObject( |
||||||
|
JSON.parse(fs.readFileSync(filepath, 'utf8')), |
||||||
|
signer, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
toObject(): any { |
||||||
|
const obj: any = { |
||||||
|
bridgeRouter: this.bridgeRouter.address, |
||||||
|
}; |
||||||
|
if (this.ethHelper) { |
||||||
|
obj.ethHelper = this.ethHelper.address; |
||||||
|
} |
||||||
|
return obj; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
|
||||||
|
import { ethers } from 'ethers'; |
||||||
|
import { |
||||||
|
Home, |
||||||
|
Home__factory, |
||||||
|
Replica, |
||||||
|
Replica__factory, |
||||||
|
} from '../../../../typechain/optics-core'; |
||||||
|
import { Contracts } from '../../contracts'; |
||||||
|
import { ReplicaInfo } from '../domains/domain'; |
||||||
|
|
||||||
|
type Address = string; |
||||||
|
|
||||||
|
type InternalReplica = { |
||||||
|
domain: number; |
||||||
|
contract: Replica; |
||||||
|
}; |
||||||
|
|
||||||
|
export class CoreContracts extends Contracts { |
||||||
|
readonly domain; |
||||||
|
home: Home; |
||||||
|
replicas: Map<number, InternalReplica>; |
||||||
|
|
||||||
|
constructor( |
||||||
|
domain: number, |
||||||
|
home: Address, |
||||||
|
replicas: ReplicaInfo[], |
||||||
|
signer?: ethers.Signer, |
||||||
|
) { |
||||||
|
super(domain, home, replicas, signer); |
||||||
|
this.domain = domain; |
||||||
|
this.home = new Home__factory(signer).attach(home); |
||||||
|
|
||||||
|
this.replicas = new Map(); |
||||||
|
replicas.forEach((replica) => { |
||||||
|
this.replicas.set(replica.domain, { |
||||||
|
contract: new Replica__factory(signer).attach(replica.address), |
||||||
|
domain: replica.domain, |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
connect(providerOrSigner: ethers.providers.Provider | ethers.Signer): void { |
||||||
|
this.home = this.home.connect(providerOrSigner); |
||||||
|
|
||||||
|
Array.from(this.replicas.values()).forEach((replica: InternalReplica) => { |
||||||
|
replica.contract = replica.contract.connect(providerOrSigner); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
toObject(): any { |
||||||
|
const replicas: ReplicaInfo[] = Array.from(this.replicas.values()).map( |
||||||
|
(replica) => { |
||||||
|
return { |
||||||
|
domain: replica.domain, |
||||||
|
address: replica.contract.address, |
||||||
|
}; |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
return { |
||||||
|
home: this.home.address, |
||||||
|
replicas: replicas, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
static fromObject(data: any, signer?: ethers.Signer): CoreContracts { |
||||||
|
if (!data.domain || !data.home || !data.replicas) { |
||||||
|
throw new Error('Missing key'); |
||||||
|
} |
||||||
|
return new CoreContracts(data.domain, data.home, data.replicas, signer); |
||||||
|
} |
||||||
|
|
||||||
|
static loadJson(filepath: string, signer?: ethers.Signer) { |
||||||
|
return this.fromObject( |
||||||
|
JSON.parse(fs.readFileSync(filepath, 'utf8')), |
||||||
|
signer, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
import { OpticsDomain } from './domain'; |
||||||
|
|
||||||
|
export const alfajores: OpticsDomain = { |
||||||
|
name: 'alfajores', |
||||||
|
domain: 1000, |
||||||
|
bridgeRouter: '0xdaa6e362f9BE0CDaCe107b298639034b8dEC617a', |
||||||
|
home: '0x47AaF05B1C36015eC186892C43ba4BaF91246aaA', |
||||||
|
replicas: [ |
||||||
|
{ domain: 2000, address: '0x7804079cF55110dE7Db5aA67eB1Be00cBE9CA526' }, |
||||||
|
{ |
||||||
|
domain: 3000, |
||||||
|
address: '0x6B8D6947B9b70f3ff1b547a15B969F625d28104a', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const kovan: OpticsDomain = { |
||||||
|
name: 'kovan', |
||||||
|
domain: 3000, |
||||||
|
bridgeRouter: '0x383Eb849c707fE38f3DfBF45679C0c6f21Ba82fF', |
||||||
|
ethHelper: '0x6D84B823D7FB68E4d6f7Cc334fDd393f6C3a6980', |
||||||
|
home: '0x5B55C29A10aEe6D5750F128C6a8f490de763ccc7', |
||||||
|
replicas: [ |
||||||
|
{ domain: 2000, address: '0xC1AB4d72548Cc1C248EAdcD340035C3b213a47C3' }, |
||||||
|
{ |
||||||
|
domain: 1000, |
||||||
|
address: '0xE63E73339501EE3A8d2928d6C88cf30aC8556Ee0', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const rinkeby: OpticsDomain = { |
||||||
|
name: 'rinkeby', |
||||||
|
domain: 2000, |
||||||
|
bridgeRouter: '0xE9fB0b6351Dec7d346282b8274653D36b8199AAF', |
||||||
|
ethHelper: '0x7a539d7B7f4Acab1d7ce8b3681c3e286511ee444', |
||||||
|
home: '0x6E6010E6bd43a9d2F7AE3b7eA9f61760e58758f3', |
||||||
|
replicas: [ |
||||||
|
{ domain: 1000, address: '0x6A5F9531D1877ebE96Bc0631DbF64BBCf1f7421c' }, |
||||||
|
{ |
||||||
|
domain: 3000, |
||||||
|
address: '0x6554bc7a5C35bA64Bf48FA8a9e662d8808aaa890', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const devDomains = [alfajores, kovan, rinkeby]; |
@ -0,0 +1,14 @@ |
|||||||
|
import { Domain } from '../../domains'; |
||||||
|
import { Address } from '../../utils'; |
||||||
|
|
||||||
|
export interface OpticsDomain extends Domain { |
||||||
|
bridgeRouter: Address; |
||||||
|
ethHelper?: Address; |
||||||
|
home: Address; |
||||||
|
replicas: ReplicaInfo[]; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ReplicaInfo { |
||||||
|
domain: number; |
||||||
|
address: Address; |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export { OpticsDomain, ReplicaInfo } from './domain'; |
||||||
|
export { mainnetDomains } from './mainnet'; |
||||||
|
export { devDomains } from './dev'; |
||||||
|
export { stagingDomains } from './staging'; |
@ -0,0 +1,50 @@ |
|||||||
|
import { OpticsDomain } from './domain'; |
||||||
|
|
||||||
|
export const ethereum: OpticsDomain = { |
||||||
|
name: 'ethereum', |
||||||
|
domain: 6648936, |
||||||
|
bridgeRouter: '0x6a39909e805A3eaDd2b61fFf61147796ca6aBB47', |
||||||
|
ethHelper: '0xf1c1413096ff2278C3Df198a28F8D54e0369cF3A', |
||||||
|
home: '0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97', |
||||||
|
replicas: [ |
||||||
|
{ |
||||||
|
domain: 1667591279, |
||||||
|
address: '0x07b5B57b08202294E657D51Eb453A189290f6385', |
||||||
|
}, |
||||||
|
{ |
||||||
|
domain: 1886350457, |
||||||
|
address: '0x7725EadaC5Ee986CAc8317a1d2fB16e59e079E8b', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const polygon: OpticsDomain = { |
||||||
|
name: 'polygon', |
||||||
|
domain: 1886350457, |
||||||
|
bridgeRouter: '0xf244eA81F715F343040569398A4E7978De656bf6', |
||||||
|
ethHelper: '0xc494bFEE14b5E1E118F93CfedF831f40dFA720fA', |
||||||
|
home: '0x97bbda9A1D45D86631b243521380Bc070D6A4cBD', |
||||||
|
replicas: [ |
||||||
|
{ domain: 6648936, address: '0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97' }, |
||||||
|
{ |
||||||
|
domain: 1667591279, |
||||||
|
address: '0x681Edb6d52138cEa8210060C309230244BcEa61b', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const celo: OpticsDomain = { |
||||||
|
name: 'celo', |
||||||
|
domain: 1667591279, |
||||||
|
bridgeRouter: '0xf244eA81F715F343040569398A4E7978De656bf6', |
||||||
|
home: '0x97bbda9A1D45D86631b243521380Bc070D6A4cBD', |
||||||
|
replicas: [ |
||||||
|
{ domain: 6648936, address: '0xf25c5932bb6efc7afa4895d9916f2abd7151bf97' }, |
||||||
|
{ |
||||||
|
domain: 1886350457, |
||||||
|
address: '0x681Edb6d52138cEa8210060C309230244BcEa61b', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const mainnetDomains = [ethereum, celo, polygon]; |
@ -0,0 +1,47 @@ |
|||||||
|
import { OpticsDomain } from './domain'; |
||||||
|
|
||||||
|
export const alfajores: OpticsDomain = { |
||||||
|
name: 'alfajores', |
||||||
|
domain: 1000, |
||||||
|
bridgeRouter: '0xd6930Ee55C141E5Bb4079d5963cF64320956bb3E', |
||||||
|
home: '0x47AaF05B1C36015eC186892C43ba4BaF91246aaA', |
||||||
|
replicas: [ |
||||||
|
{ domain: 2000, address: '0x7804079cF55110dE7Db5aA67eB1Be00cBE9CA526' }, |
||||||
|
{ |
||||||
|
domain: 3000, |
||||||
|
address: '0x6B8D6947B9b70f3ff1b547a15B969F625d28104a', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const kovan: OpticsDomain = { |
||||||
|
name: 'kovan', |
||||||
|
domain: 3000, |
||||||
|
bridgeRouter: '0x359089D34687bDbFD019fCC5093fFC21bE9905f5', |
||||||
|
ethHelper: '0x411ABcFD947212a0D64b97C9882556367b61704a', |
||||||
|
home: '0x5B55C29A10aEe6D5750F128C6a8f490de763ccc7', |
||||||
|
replicas: [ |
||||||
|
{ domain: 2000, address: '0xC1AB4d72548Cc1C248EAdcD340035C3b213a47C3' }, |
||||||
|
{ |
||||||
|
domain: 1000, |
||||||
|
address: '0xE63E73339501EE3A8d2928d6C88cf30aC8556Ee0', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const rinkeby: OpticsDomain = { |
||||||
|
name: 'rinkeby', |
||||||
|
domain: 2000, |
||||||
|
bridgeRouter: '0x8FbEA25D0bFDbff68F2B920df180e9498E9c856A', |
||||||
|
ethHelper: '0x1BEBC8F1260d16EE5d1CFEE9366bB474bD13DC5f', |
||||||
|
home: '0x6E6010E6bd43a9d2F7AE3b7eA9f61760e58758f3', |
||||||
|
replicas: [ |
||||||
|
{ domain: 1000, address: '0x6A5F9531D1877ebE96Bc0631DbF64BBCf1f7421c' }, |
||||||
|
{ |
||||||
|
domain: 3000, |
||||||
|
address: '0x6554bc7a5C35bA64Bf48FA8a9e662d8808aaa890', |
||||||
|
}, |
||||||
|
], |
||||||
|
}; |
||||||
|
|
||||||
|
export const stagingDomains = [alfajores, kovan, rinkeby]; |
@ -0,0 +1,17 @@ |
|||||||
|
export { BridgeContracts } from './contracts/BridgeContracts'; |
||||||
|
export { CoreContracts } from './contracts/CoreContracts'; |
||||||
|
export { |
||||||
|
TransferMessage, |
||||||
|
DetailsMessage, |
||||||
|
RequestDetailsMesasage, |
||||||
|
} from './messages/BridgeMessage'; |
||||||
|
export { OpticsMessage } from './messages/OpticsMessage'; |
||||||
|
export { ResolvedTokenInfo, TokenIdentifier } from './tokens'; |
||||||
|
|
||||||
|
export { |
||||||
|
OpticsDomain, |
||||||
|
mainnetDomains, |
||||||
|
devDomains, |
||||||
|
stagingDomains, |
||||||
|
} from './domains'; |
||||||
|
export { OpticsContext, mainnet, dev, staging } from './OpticsContext'; |
@ -0,0 +1,166 @@ |
|||||||
|
import { BigNumber } from '@ethersproject/bignumber'; |
||||||
|
import { arrayify, hexlify } from '@ethersproject/bytes'; |
||||||
|
import { BridgeContracts, CoreContracts, OpticsContext } from '..'; |
||||||
|
import { ResolvedTokenInfo, TokenIdentifier } from '../tokens'; |
||||||
|
import { DispatchEvent, OpticsMessage, parseMessage } from './OpticsMessage'; |
||||||
|
|
||||||
|
const ACTION_LEN = { |
||||||
|
identifier: 1, |
||||||
|
tokenId: 36, |
||||||
|
transfer: 65, |
||||||
|
details: 66, |
||||||
|
requestDetails: 1, |
||||||
|
}; |
||||||
|
|
||||||
|
type Transfer = { |
||||||
|
action: 'transfer'; |
||||||
|
to: string; |
||||||
|
amount: BigNumber; |
||||||
|
}; |
||||||
|
|
||||||
|
export type Details = { |
||||||
|
action: 'details'; |
||||||
|
name: string; |
||||||
|
symbol: string; |
||||||
|
decimals: number; |
||||||
|
}; |
||||||
|
|
||||||
|
export type RequestDetails = { action: 'requestDetails' }; |
||||||
|
|
||||||
|
export type Action = Transfer | Details | RequestDetails; |
||||||
|
|
||||||
|
export type ParsedBridgeMessage<T extends Action> = { |
||||||
|
token: TokenIdentifier; |
||||||
|
action: T; |
||||||
|
}; |
||||||
|
|
||||||
|
export type ParsedTransferMessage = ParsedBridgeMessage<Transfer>; |
||||||
|
export type ParsedDetailsMessage = ParsedBridgeMessage<Details>; |
||||||
|
export type ParsedRequestDetailsMesasage = ParsedBridgeMessage<RequestDetails>; |
||||||
|
|
||||||
|
function parseAction(buf: Uint8Array): Action { |
||||||
|
if (buf.length === ACTION_LEN.requestDetails) { |
||||||
|
return { action: 'requestDetails' }; |
||||||
|
} |
||||||
|
|
||||||
|
// Transfer
|
||||||
|
if (buf.length === ACTION_LEN.transfer) { |
||||||
|
// trim identifer
|
||||||
|
buf = buf.slice(ACTION_LEN.identifier); |
||||||
|
return { |
||||||
|
action: 'transfer', |
||||||
|
to: hexlify(buf.slice(0, 32)), |
||||||
|
amount: BigNumber.from(hexlify(buf.slice(32))), |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// Details
|
||||||
|
if (buf.length === ACTION_LEN.details) { |
||||||
|
// trim identifer
|
||||||
|
buf = buf.slice(ACTION_LEN.identifier); |
||||||
|
// TODO(james): improve this to show real strings
|
||||||
|
return { |
||||||
|
action: 'details', |
||||||
|
|
||||||
|
name: hexlify(buf.slice(0, 32)), |
||||||
|
symbol: hexlify(buf.slice(32, 64)), |
||||||
|
decimals: buf[64], |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
throw new Error('Bad action'); |
||||||
|
} |
||||||
|
|
||||||
|
function parseBody( |
||||||
|
messageBody: string, |
||||||
|
): ParsedTransferMessage | ParsedDetailsMessage | ParsedRequestDetailsMesasage { |
||||||
|
const buf = arrayify(messageBody); |
||||||
|
|
||||||
|
const tokenId = buf.slice(0, 36); |
||||||
|
const token = { |
||||||
|
domain: Buffer.from(tokenId).readUInt32BE(0), |
||||||
|
id: hexlify(buf.slice(4)), |
||||||
|
}; |
||||||
|
|
||||||
|
const action = parseAction(buf.slice(36)); |
||||||
|
const parsedMessage = { |
||||||
|
action, |
||||||
|
token, |
||||||
|
}; |
||||||
|
|
||||||
|
switch (action.action) { |
||||||
|
case 'transfer': |
||||||
|
return parsedMessage as ParsedTransferMessage; |
||||||
|
case 'details': |
||||||
|
return parsedMessage as ParsedDetailsMessage; |
||||||
|
case 'requestDetails': |
||||||
|
return parsedMessage as ParsedRequestDetailsMesasage; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class BridgeMessage<T extends Action> extends OpticsMessage { |
||||||
|
readonly token: TokenIdentifier; |
||||||
|
readonly action: T; |
||||||
|
|
||||||
|
readonly fromBridge: BridgeContracts; |
||||||
|
readonly toBridge: BridgeContracts; |
||||||
|
|
||||||
|
constructor( |
||||||
|
event: DispatchEvent, |
||||||
|
parsed: ParsedBridgeMessage<T>, |
||||||
|
context: OpticsContext, |
||||||
|
) { |
||||||
|
super(event, context); |
||||||
|
|
||||||
|
const fromBridge = context.getBridge(this.message.from); |
||||||
|
const toBridge = context.getBridge(this.message.destination); |
||||||
|
|
||||||
|
if (!fromBridge || !toBridge) { |
||||||
|
throw new Error('missing bridge'); |
||||||
|
} |
||||||
|
|
||||||
|
this.fromBridge = fromBridge; |
||||||
|
this.toBridge = toBridge; |
||||||
|
this.token = parsed.token; |
||||||
|
|
||||||
|
this.action = parsed.action; |
||||||
|
} |
||||||
|
|
||||||
|
static fromEvent( |
||||||
|
event: DispatchEvent, |
||||||
|
context: OpticsContext, |
||||||
|
): TransferMessage | DetailsMessage | RequestDetailsMesasage { |
||||||
|
// kinda hate this but ok
|
||||||
|
const parsedEvent = parseMessage(event.args.message); |
||||||
|
const parsed = parseBody(parsedEvent.body); |
||||||
|
|
||||||
|
switch (parsed.action.action) { |
||||||
|
case 'transfer': |
||||||
|
return new BridgeMessage( |
||||||
|
event, |
||||||
|
parsed as ParsedTransferMessage, |
||||||
|
context, |
||||||
|
); |
||||||
|
case 'details': |
||||||
|
return new BridgeMessage( |
||||||
|
event, |
||||||
|
parsed as ParsedDetailsMessage, |
||||||
|
context, |
||||||
|
); |
||||||
|
case 'requestDetails': |
||||||
|
return new BridgeMessage( |
||||||
|
event, |
||||||
|
parsed as ParsedRequestDetailsMesasage, |
||||||
|
context, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async asset(): Promise<ResolvedTokenInfo> { |
||||||
|
return await this.context.tokenRepresentations(this.token); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export type TransferMessage = BridgeMessage<Transfer>; |
||||||
|
export type DetailsMessage = BridgeMessage<Details>; |
||||||
|
export type RequestDetailsMesasage = BridgeMessage<RequestDetails>; |
@ -0,0 +1,106 @@ |
|||||||
|
import { BigNumber, BigNumberish } from '@ethersproject/bignumber'; |
||||||
|
import { TypedEvent } from '../../../../typechain/optics-core/commons'; |
||||||
|
import { arrayify, hexlify } from '@ethersproject/bytes'; |
||||||
|
import { OpticsContext } from '..'; |
||||||
|
|
||||||
|
// match the typescript declaration
|
||||||
|
export type DispatchEvent = TypedEvent< |
||||||
|
[string, BigNumber, BigNumber, string, string] |
||||||
|
> & { |
||||||
|
args: { |
||||||
|
messageHash: string; |
||||||
|
leafIndex: BigNumber; |
||||||
|
destinationAndNonce: BigNumber; |
||||||
|
committedRoot: string; |
||||||
|
message: string; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export type ParsedMessage = { |
||||||
|
from: number; |
||||||
|
sender: string; |
||||||
|
nonce: number; |
||||||
|
destination: number; |
||||||
|
recipient: string; |
||||||
|
body: string; |
||||||
|
}; |
||||||
|
|
||||||
|
enum MessageStatus { |
||||||
|
None = 0, |
||||||
|
Proven, |
||||||
|
Processed, |
||||||
|
} |
||||||
|
|
||||||
|
export function parseMessage(message: string): ParsedMessage { |
||||||
|
const buf = Buffer.from(arrayify(message)); |
||||||
|
|
||||||
|
const from = buf.readUInt32BE(0); |
||||||
|
const sender = hexlify(buf.slice(4, 36)); |
||||||
|
const nonce = buf.readUInt32BE(36); |
||||||
|
const destination = buf.readUInt32BE(40); |
||||||
|
const recipient = hexlify(buf.slice(44, 76)); |
||||||
|
const body = hexlify(buf.slice(76)); |
||||||
|
|
||||||
|
return { from, sender, nonce, destination, recipient, body }; |
||||||
|
} |
||||||
|
|
||||||
|
export class OpticsMessage { |
||||||
|
readonly event: DispatchEvent; |
||||||
|
readonly messageHash: string; |
||||||
|
readonly leafIndex: BigNumber; |
||||||
|
readonly destinationAndNonce: BigNumber; |
||||||
|
readonly committedRoot: string; |
||||||
|
readonly message: ParsedMessage; |
||||||
|
|
||||||
|
protected context: OpticsContext; |
||||||
|
|
||||||
|
constructor(event: DispatchEvent, context: OpticsContext) { |
||||||
|
this.event = event; |
||||||
|
this.messageHash = event.args.messageHash; |
||||||
|
this.leafIndex = event.args.leafIndex; |
||||||
|
this.destinationAndNonce = event.args.destinationAndNonce; |
||||||
|
this.committedRoot = event.args.committedRoot; |
||||||
|
this.message = parseMessage(event.args.message); |
||||||
|
|
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
|
||||||
|
async status(): Promise<MessageStatus> { |
||||||
|
const replica = this.context.getReplicaFor(this.from, this.destination); |
||||||
|
if (!replica) { |
||||||
|
throw new Error( |
||||||
|
`No replica on ${this.destination} for home ${this.from}`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return await replica.messages(this.messageHash); |
||||||
|
} |
||||||
|
|
||||||
|
get from(): number { |
||||||
|
return this.message.from; |
||||||
|
} |
||||||
|
|
||||||
|
get origin(): number { |
||||||
|
return this.from; |
||||||
|
} |
||||||
|
|
||||||
|
get sender(): string { |
||||||
|
return this.message.sender; |
||||||
|
} |
||||||
|
|
||||||
|
get nonce(): number { |
||||||
|
return this.message.nonce; |
||||||
|
} |
||||||
|
|
||||||
|
get destination(): number { |
||||||
|
return this.message.destination; |
||||||
|
} |
||||||
|
|
||||||
|
get recipient(): string { |
||||||
|
return this.message.recipient; |
||||||
|
} |
||||||
|
|
||||||
|
get body(): string { |
||||||
|
return this.message.body; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
import * as ethers from 'ethers'; |
||||||
|
|
||||||
|
import { mainnet } from '.'; |
||||||
|
|
||||||
|
const celoTokenAddr = '0x471EcE3750Da237f93B8E339c536989b8978a438'; |
||||||
|
|
||||||
|
const amount = ethers.constants.WeiPerEther.mul(100); |
||||||
|
const privkey = process.env.PRIVKEY_LMAO; |
||||||
|
if (!privkey) { |
||||||
|
throw new Error('set PRIVKEY_LMAO'); |
||||||
|
} |
||||||
|
|
||||||
|
const celoRpc = 'https://forno.celo.org'; |
||||||
|
mainnet.registerRpcProvider('celo', celoRpc); |
||||||
|
mainnet.registerWalletSigner('celo', privkey); |
||||||
|
|
||||||
|
async function doThing() { |
||||||
|
const address = await mainnet.getAddress('celo'); |
||||||
|
if (!address) { |
||||||
|
throw new Error('no address'); |
||||||
|
} |
||||||
|
|
||||||
|
const tx = await mainnet.send( |
||||||
|
'celo', |
||||||
|
'ethereum', |
||||||
|
{ domain: 'celo', id: celoTokenAddr }, |
||||||
|
amount, |
||||||
|
address, |
||||||
|
); |
||||||
|
console.log(`sendTx is ${tx.hash}`); |
||||||
|
await tx.wait(1); |
||||||
|
} |
||||||
|
|
||||||
|
doThing(); |
@ -0,0 +1,13 @@ |
|||||||
|
import { BytesLike } from 'ethers'; |
||||||
|
import { ERC20 } from '../../../typechain/optics-xapps'; |
||||||
|
|
||||||
|
export interface TokenIdentifier { |
||||||
|
domain: string | number; |
||||||
|
id: BytesLike; |
||||||
|
} |
||||||
|
|
||||||
|
export type ResolvedTokenInfo = { |
||||||
|
domain: number; |
||||||
|
id: BytesLike; |
||||||
|
tokens: Map<number, ERC20>; |
||||||
|
}; |
@ -0,0 +1,110 @@ |
|||||||
|
import * as ethers from 'ethers'; |
||||||
|
import { Domain } from './domains'; |
||||||
|
import { mainnetDomains } from './optics/domains/mainnet'; |
||||||
|
|
||||||
|
type Provider = ethers.providers.Provider; |
||||||
|
|
||||||
|
export class MultiProvider { |
||||||
|
private domains: Map<number, Domain>; |
||||||
|
private providers: Map<number, Provider>; |
||||||
|
private signers: Map<number, ethers.Signer>; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
this.domains = new Map(); |
||||||
|
this.providers = new Map(); |
||||||
|
this.signers = new Map(); |
||||||
|
} |
||||||
|
|
||||||
|
registerDomain(domain: Domain) { |
||||||
|
this.domains.set(domain.domain, domain); |
||||||
|
} |
||||||
|
|
||||||
|
getDomain(domain: number): Domain | undefined { |
||||||
|
return this.domains.get(domain); |
||||||
|
} |
||||||
|
|
||||||
|
get domainNumbers(): number[] { |
||||||
|
return Array.from(this.domains.keys()); |
||||||
|
} |
||||||
|
|
||||||
|
resolveDomain(nameOrDomain: string | number): number { |
||||||
|
if (typeof nameOrDomain === 'string') { |
||||||
|
return Array.from(this.domains.values()).filter( |
||||||
|
(domain) => domain.name === nameOrDomain, |
||||||
|
)[0].domain; |
||||||
|
} else { |
||||||
|
return nameOrDomain; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
registerProvider(nameOrDomain: string | number, provider: Provider) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
|
||||||
|
if (!this.domains.get(domain)) { |
||||||
|
throw new Error('Must have domain to register provider'); |
||||||
|
} |
||||||
|
|
||||||
|
this.providers.set(domain, provider); |
||||||
|
const signer = this.signers.get(domain); |
||||||
|
if (signer) { |
||||||
|
this.signers.set(domain, signer.connect(provider)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
registerRpcProvider(nameOrDomain: string | number, rpc: string) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
|
||||||
|
const provider = new ethers.providers.JsonRpcProvider(rpc); |
||||||
|
this.registerProvider(domain, provider); |
||||||
|
} |
||||||
|
|
||||||
|
getProvider(nameOrDomain: string | number): Provider | undefined { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
|
||||||
|
return this.providers.get(domain); |
||||||
|
} |
||||||
|
|
||||||
|
registerSigner(nameOrDomain: string | number, signer: ethers.Signer) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
|
||||||
|
const provider = this.providers.get(domain); |
||||||
|
if (!provider && !signer.provider) { |
||||||
|
throw new Error('Must have a provider before registering signer'); |
||||||
|
} |
||||||
|
|
||||||
|
if (provider) { |
||||||
|
this.signers.set(domain, signer.connect(provider)); |
||||||
|
} else { |
||||||
|
this.registerProvider(domain, signer.provider!); |
||||||
|
this.signers.set(domain, signer); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
unregisterSigner(nameOrDomain: string | number) { |
||||||
|
this.signers.delete(this.resolveDomain(nameOrDomain)); |
||||||
|
} |
||||||
|
|
||||||
|
registerWalletSigner(nameOrDomain: string | number, privkey: string) { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
|
||||||
|
const wallet = new ethers.Wallet(privkey); |
||||||
|
this.registerSigner(domain, wallet); |
||||||
|
} |
||||||
|
|
||||||
|
getSigner(nameOrDomain: string | number): ethers.Signer | undefined { |
||||||
|
const domain = this.resolveDomain(nameOrDomain); |
||||||
|
return this.signers.get(domain); |
||||||
|
} |
||||||
|
|
||||||
|
getConnection( |
||||||
|
nameOrDomain: string | number, |
||||||
|
): ethers.Signer | ethers.providers.Provider | undefined { |
||||||
|
return this.getSigner(nameOrDomain) ?? this.getProvider(nameOrDomain); |
||||||
|
} |
||||||
|
|
||||||
|
async getAddress(nameOrDomain: string | number): Promise<string | undefined> { |
||||||
|
const signer = this.getSigner(nameOrDomain); |
||||||
|
|
||||||
|
return await signer?.getAddress(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
import { BytesLike } from '@ethersproject/bytes'; |
||||||
|
import { ethers } from 'ethers'; |
||||||
|
|
||||||
|
export type Address = string; |
||||||
|
|
||||||
|
// ensure that a bytes-like is 32 long. left-pad with 0s if not
|
||||||
|
export function canonizeId(data: BytesLike): Uint8Array { |
||||||
|
const buf = ethers.utils.arrayify(data); |
||||||
|
if (buf.length > 32) { |
||||||
|
throw new Error('Too long'); |
||||||
|
} |
||||||
|
if (buf.length !== 20 && buf.length != 32) { |
||||||
|
throw new Error('bad input, expect address or bytes32'); |
||||||
|
} |
||||||
|
return ethers.utils.zeroPad(buf, 32); |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */ |
||||||
|
|
||||||
|
/* Basic Options */ |
||||||
|
// "incremental": true, /* Enable incremental compilation */ |
||||||
|
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, |
||||||
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, |
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||||
|
// "checkJs": true, /* Report errors in .js files. */ |
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ |
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */ |
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */ |
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */ |
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||||
|
// "composite": true, /* Enable project compilation */ |
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ |
||||||
|
// "removeComments": true, /* Do not emit comments to output. */ |
||||||
|
// "noEmit": true, /* Do not emit outputs. */ |
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||||
|
|
||||||
|
/* Strict Type-Checking Options */ |
||||||
|
"strict": true /* Enable all strict type-checking options. */, |
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||||
|
|
||||||
|
/* Additional Checks */ |
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ |
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ |
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ |
||||||
|
|
||||||
|
/* Module Resolution Options */ |
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */ |
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, |
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||||
|
|
||||||
|
/* Source Map Options */ |
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||||
|
|
||||||
|
/* Experimental Options */ |
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||||
|
|
||||||
|
/* Advanced Options */ |
||||||
|
"skipLibCheck": true /* Skip type checking of declaration files. */, |
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue