Skip to content

Commit

Permalink
feat(global): adds WalletManager and AssetsManager
Browse files Browse the repository at this point in the history
  • Loading branch information
allemanfredi committed Feb 11, 2025
1 parent 33b2fb3 commit 27441a3
Show file tree
Hide file tree
Showing 15 changed files with 2,486 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.DS_Store
node_modules
dist
*pk
*pk
.env
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
},
"dependencies": {
"@ethereumjs/util": "^9.1.0",
"@ethersproject/constants": "^5.7.0",
"borsh": "^2.0.0",
"dotenv": "^16.4.7",
"ethers": "^6.13.5"
}
}
51 changes: 51 additions & 0 deletions src/AssetsManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ethers, JsonRpcProvider } from 'ethers'
import { Wallet } from './WalletManager'
import erc20Abi from './kms/keyring/abi/erc20'

export type NetworkConfigs = {
[key: string]: {
rpc: string
}
}

export interface AssetsManagerConfigs {
networkConfigs: NetworkConfigs
}

const DEFAULT_CONFIGS = {
eth: {
rpc: 'https://eth.llamarpc.com',
},
arb: {
rpc: 'https://arbitrum.llamarpc.com',
},
gno: {
rpc: 'https://gnosis.drpc.org',
},
}

export class AssetsManager {
networkConfigs: NetworkConfigs = DEFAULT_CONFIGS

constructor(configs?: AssetsManagerConfigs) {
if (configs) {
this.networkConfigs = { ...DEFAULT_CONFIGS, ...configs.networkConfigs }
}
}

async transferToken(wallet: Wallet, amount: number, tokenAddress: string, destinationAddressAsEIP3770: string) {
const [chain, recipient] = destinationAddressAsEIP3770.split(':')

const networkConfig = this.networkConfigs[chain]
if (!networkConfig) throw new Error('network configs not supported. You can add it during the initialization')
const provider = new JsonRpcProvider(networkConfig.rpc)

const asset = new ethers.Contract(tokenAddress, erc20Abi, provider)
const decimals = (await asset.decimals()) as bigint
const onchainAmount = (BigInt(amount * 10 ** 18) * BigInt(Math.pow(10, Number(decimals)))) / BigInt(10 ** 18)

const data = await wallet.kms.prepareTransfer(tokenAddress, onchainAmount.toString(), recipient, provider)
const signature = await wallet.sign(data)
await wallet.kms.postSignature(signature, data, provider)
}
}
56 changes: 56 additions & 0 deletions src/WalletManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { KeyringKms, KeyringKmsConfigs, KeyringSignOptions } from './kms/keyring'
import { IKms } from './kms/types'

export class WalletManager<
O extends KeyringSignOptions = KeyringSignOptions,
K extends IKms<O> = KeyringKms,
C = KeyringKmsConfigs
> {
private wallets: { [key: string]: Wallet<O, K, C> } = {}

createNewWallet(label: string, configs?: WalletConfigs<K, C, O>): Wallet<O, K, C> {
if (this.wallets[label]) throw new Error('wallet already existent')
this.wallets[label] = new Wallet(configs)
return this.wallets[label]
}
}

export interface WalletConfigs<K extends IKms<O>, C, O> {
kms: {
constructor: new (configs?: C) => K
configs?: C
}
}

export class Wallet<
O extends KeyringSignOptions = KeyringSignOptions,
K extends IKms<O> = KeyringKms,
C = KeyringKmsConfigs
> {
kms: K
private initialized: boolean = false

constructor(configs?: WalletConfigs<K, C, O>) {
if (configs?.kms) {
this.kms = new configs.kms.constructor(configs.kms.configs)
} else {
this.kms = (new KeyringKms() as unknown) as K
}
}

async initialize(): Promise<void> {
await this.kms.initialize()
this.initialized = true
}

async sign(data: Buffer, options?: O): Promise<Buffer> {
await this.checkIfInitialized()
return this.kms.sign(data, options || ({} as O))
}

private async checkIfInitialized() {
if (!this.initialized) {
await this.initialize()
}
}
}
43 changes: 10 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
import { IKms } from './kms/types'
import { KeyringKms, KeyringKmsConfigs, KeyringSignOptions } from './kms/keyring'
import { AssetsManager } from './AssetsManager'
import { KeyringKms } from './kms/keyring'
import { LitKms } from './kms/lit'
import { WalletManager } from './WalletManager'

export interface HandshakeConfigs<K extends IKms<O>, C, O> {
kms: {
constructor: new (configs?: C) => K
configs?: C
}
}

export class Handshake<
O extends KeyringSignOptions = KeyringSignOptions,
K extends IKms<O> = KeyringKms,
C = KeyringKmsConfigs
> {
private kms: K

constructor(configs?: HandshakeConfigs<K, C, O>) {
if (configs?.kms) {
this.kms = new configs.kms.constructor(configs.kms.configs)
} else {
this.kms = (new KeyringKms() as unknown) as K
}
}

async initialize(): Promise<void> {
return this.kms.initialize()
}
export class Handshake {
walletManager: WalletManager
assetsManager: AssetsManager

async sign(data: Buffer, options?: O): Promise<Buffer> {
return this.kms.sign(data, options || ({} as O))
constructor() {
this.walletManager = new WalletManager()
this.assetsManager = new AssetsManager()
}
}

export {
KeyringKms,
LitKms
}
export { KeyringKms, LitKms, WalletManager, AssetsManager }
16 changes: 16 additions & 0 deletions src/kms/Kms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Provider } from 'ethers'

export interface KmsConfigs {
name: string
provider?: Provider
}

export class Kms {
name: string
provider?: Provider

constructor(configs: KmsConfigs) {
this.name = configs.name
this.provider = configs.provider
}
}
60 changes: 46 additions & 14 deletions src/kms/keyring/Operation.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
import * as borsh from 'borsh'
import crypto from 'crypto'
import { hexToBytes } from '@ethereumjs/util'

const PROTOCOLS_ENUM = {
export const PROTOCOLS_LABELS = {
0: 'evm',
}

export const PROTOCOLS_ENUM = {
evm: 0,
}

export type Protocol = 'evm'

interface OperationConfigs {
protocol: Protocol
chainId: number
targetAddress: string
data: Buffer
chainId: bigint
targetAddress: Uint8Array
data: Uint8Array
salt?: Uint8Array
}

export class Operation {
protocol: Protocol
chainId: number
targetAddress: string
data: Buffer
salt: string
chainId: bigint
targetAddress: Uint8Array
data: Uint8Array
salt: Uint8Array

constructor({ protocol, chainId, targetAddress, data }: OperationConfigs) {
constructor({ protocol, chainId, targetAddress, data, salt }: OperationConfigs) {
this.protocol = protocol
this.chainId = chainId
this.targetAddress = targetAddress
this.data = data
this.salt = '0x' + crypto.randomBytes(10).toString('hex')
this.salt = salt || crypto.randomBytes(10)
}

serialize(): Uint8Array {
Expand All @@ -44,14 +48,42 @@ export class Operation {
{
protocol: PROTOCOLS_ENUM[this.protocol],
chainId: this.chainId,
targetAddress: hexToBytes(this.targetAddress),
targetAddress: this.targetAddress,
data: this.data,
salt: hexToBytes(this.salt),
salt: this.salt,
}
)
}

static from(operation: Buffer): Operation {
const deserialized = borsh.deserialize(
{
struct: {
protocol: 'u8', // ENUM
chainId: 'u64',
targetAddress: { array: { type: 'u8' } },
data: { array: { type: 'u8' } },
salt: { array: { type: 'u8' } },
},
},
operation
) as any

if (!deserialized) {
throw new Error('Failed to deserialize operation')
}

const protocolKey = deserialized.protocol as keyof typeof PROTOCOLS_LABELS
return new Operation({
protocol: PROTOCOLS_LABELS[protocolKey] as Protocol,
chainId: deserialized.chainId,
targetAddress: deserialized.targetAddress,
data: deserialized.data,
salt: deserialized.salt,
})
}

encode() {
return [PROTOCOLS_ENUM[this.protocol], this.chainId, this.targetAddress, this.data, this.salt]
return [this.protocol, this.chainId, this.targetAddress, this.data, this.salt]
}
}
Loading

0 comments on commit 27441a3

Please sign in to comment.