Skip to content

Commit

Permalink
Refactor CIP30 helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljscript committed May 20, 2024
1 parent 1d8a7e1 commit cc1e609
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 192 deletions.
22 changes: 12 additions & 10 deletions apps/wallet-mobile/src/features/Discover/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {connectionStorageMaker, dappConnectorApiMaker, dappConnectorMaker, Resol
import {DappConnector} from '@yoroi/dapp-connector'
import {App} from '@yoroi/types'

import {cip30ExtensionMaker} from '../../../yoroi-wallets/cardano/cip30'
import {YoroiWallet} from '../../../yoroi-wallets/cardano/types'

export const validUrl = (url: string) => {
Expand Down Expand Up @@ -67,25 +68,26 @@ type CreateDappConnectorOptions = {
export const createDappConnector = (options: CreateDappConnectorOptions) => {
const {wallet, appStorage, confirmConnection, signTx, signData} = options
const api = dappConnectorApiMaker()
const cip30 = cip30ExtensionMaker(wallet)
const handlerWallet: ResolverWallet = {
id: wallet.id,
networkId: wallet.networkId,
getUsedAddresses: (params) => wallet.CIP30getUsedAddresses(params),
getUnusedAddresses: () => wallet.CIP30getUnusedAddresses(),
getBalance: (tokenId) => wallet.CIP30getBalance(tokenId),
getChangeAddress: () => wallet.CIP30getChangeAddress(),
getRewardAddresses: () => wallet.CIP30getRewardAddresses(),
submitTx: (cbor) => wallet.CIP30submitTx(cbor),
getCollateral: (value) => wallet.CIP30getCollateral(value),
getUtxos: (value, pagination) => wallet.CIP30getUtxos(value, pagination),
getUsedAddresses: (params) => cip30.getUsedAddresses(params),
getUnusedAddresses: () => cip30.getUnusedAddresses(),
getBalance: (tokenId) => cip30.getBalance(tokenId),
getChangeAddress: () => cip30.getChangeAddress(),
getRewardAddresses: () => cip30.getRewardAddresses(),
submitTx: (cbor) => cip30.submitTx(cbor),
getCollateral: (value) => cip30.getCollateral(value),
getUtxos: (value, pagination) => cip30.getUtxos(value, pagination),
confirmConnection: (origin: string) => confirmConnection(origin, manager),
signData: async (address, payload) => {
const rootKey = await signData(address, payload)
return wallet.CIP30signData(rootKey, address, payload)
return cip30.signData(rootKey, address, payload)
},
signTx: async (cbor: string, partial?: boolean) => {
const rootKey = await signTx(cbor)
return wallet.CIP30signTx(rootKey, cbor, partial)
return cip30.signTx(rootKey, cbor, partial)
},
}
const storage = connectionStorageMaker({storage: appStorage.join('dapp-connections/')})
Expand Down
2 changes: 1 addition & 1 deletion apps/wallet-mobile/src/features/Discover/common/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {YoroiWallet} from '../../../yoroi-wallets/cardano/types'
import {Logger} from '../../../yoroi-wallets/logging'
import {ConfirmRawTxWithOs} from '../../Swap/common/ConfirmRawTx/ConfirmRawTxWithOs'
import {ConfirmRawTxWithPassword} from '../../Swap/common/ConfirmRawTx/ConfirmRawTxWithPassword'
import {walletConfig} from './wallet-config'
import {useStrings} from './useStrings'
import {walletConfig} from './wallet-config'

export const useConnectWalletToWebView = (wallet: YoroiWallet, webViewRef: React.RefObject<WebView | null>) => {
const {manager, sessionId} = useDappConnector()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import {calcLockedDeposit} from '../assetUtils'
import {encryptWithPassword} from '../catalyst/catalystCipher'
import {generatePrivateKeyForCatalyst} from '../catalyst/catalystUtils'
import {AddressChain, AddressChainJSON, Addresses, AddressGenerator} from '../chain'
import * as cip30 from '../cip30'
import {API_ROOT, MAX_GENERATED_UNUSED, PRIMARY_TOKEN, PRIMARY_TOKEN_INFO} from '../constants/mainnet/constants'
import {CardanoError, InvalidState} from '../errors'
import {ADDRESS_TYPE_TO_CHANGE} from '../formatPath'
Expand All @@ -69,7 +68,6 @@ import {
legacyWalletChecksum,
NoOutputsError,
NotEnoughMoneyToSendError,
Pagination,
RegistrationStatus,
walletChecksum,
WalletEvent,
Expand Down Expand Up @@ -540,7 +538,7 @@ export class ByronWallet implements YoroiWallet {
}

// returns the address in bech32 (Shelley) or base58 (Byron) format
private getChangeAddress(): string {
getChangeAddress(): string {
const candidateAddresses = this.internalChain.addresses
const unseen = candidateAddresses.filter((addr) => !this.isUsedAddress(addr))
assert(unseen.length > 0, 'Cannot find change address')
Expand Down Expand Up @@ -1463,47 +1461,6 @@ export class ByronWallet implements YoroiWallet {
isEasyConfirmationEnabled: this.isEasyConfirmationEnabled,
}
}

CIP30getBalance(tokenId = '*'): Promise<CSL.Value> {
return cip30.getBalance(this, tokenId)
}

CIP30getUnusedAddresses(): Promise<CSL.Address[]> {
return cip30.getUnusedAddresses(this)
}

CIP30getUsedAddresses(pagination?: Pagination): Promise<CSL.Address[]> {
return cip30.getUsedAddresses(this, pagination)
}

CIP30getChangeAddress() {
const changeAddr = this.getChangeAddress()
return Cardano.Wasm.Address.fromBech32(changeAddr)
}

CIP30getRewardAddresses(): Promise<CSL.Address[]> {
return cip30.getRewardAddress(this)
}

CIP30getUtxos(value?: string, pagination?: Pagination): Promise<CSL.TransactionUnspentOutput[] | null> {
return cip30.getUtxos(this, value, pagination)
}

CIP30getCollateral(value?: string): Promise<CSL.TransactionUnspentOutput[] | null> {
return cip30.getCollateral(this, value)
}

CIP30submitTx(cbor: string): Promise<string> {
return cip30.submitTx(this, cbor)
}

CIP30signData(rootKey: string, address: string, payload: string): Promise<{signature: string; key: string}> {
return cip30.signData(this, rootKey, address, payload)
}

CIP30signTx(rootKey: string, cbor: string, partial = false): Promise<CSL.TransactionWitnessSet> {
return cip30.signTx(this, rootKey, cbor, partial)
}
}

const toHex = (bytes: Uint8Array) => Buffer.from(bytes).toString('hex')
Expand Down
167 changes: 89 additions & 78 deletions apps/wallet-mobile/src/yoroi-wallets/cardano/cip30.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,94 @@ import {Pagination, YoroiWallet} from './types'
import {createRawTxSigningKey, identifierToCardanoAsset} from './utils'
import {collateralConfig, findCollateralCandidates, utxosMaker} from './utxoManager/utxos'

export const cip30ExtensionMaker = (wallet: YoroiWallet) => {
return new CIP30Extension(wallet)
}

class CIP30Extension {
constructor(private wallet: YoroiWallet) {}

getBalance(tokenId = '*'): Promise<CSL.Value> {
return _getBalance(tokenId, this.wallet.utxos, this.wallet.primaryTokenInfo.id)
}

async getUnusedAddresses(): Promise<CSL.Address[]> {
const bech32Addresses = this.wallet.receiveAddresses.filter((address) => !this.wallet.isUsedAddressIndex[address])
return Promise.all(bech32Addresses.map((addr) => Cardano.Wasm.Address.fromBech32(addr)))
}

getUsedAddresses(pagination?: Pagination): Promise<CSL.Address[]> {
const allAddresses = this.wallet.externalAddresses
const selectedAddresses = paginate(allAddresses, pagination)
return Promise.all(selectedAddresses.map((addr) => Cardano.Wasm.Address.fromBech32(addr)))
}

getChangeAddress(): Promise<CSL.Address> {
const changeAddr = this.wallet.getChangeAddress()
return Cardano.Wasm.Address.fromBech32(changeAddr)
}

async getRewardAddresses(): Promise<CSL.Address[]> {
const address = await CardanoMobile.Address.fromHex(this.wallet.rewardAddressHex)
return [address]
}

getUtxos(value?: string, pagination?: Pagination): Promise<CSL.TransactionUnspentOutput[] | null> {
return _getUtxos(this.wallet, value, pagination)
}

async getCollateral(value?: string): Promise<CSL.TransactionUnspentOutput[] | null> {
const valueStr = value?.trim() ?? collateralConfig.minLovelace.toString()
const valueNum = new BigNumber(valueStr)

if (valueNum.gte(collateralConfig.maxLovelace)) {
throw new Error('Collateral value is too high')
}

const currentCollateral = this.wallet.getCollateralInfo()
const canUseCurrentCollateral = currentCollateral.utxo && valueNum.lte(currentCollateral.utxo.amount)

if (canUseCurrentCollateral && currentCollateral.utxo) {
const utxo = await cardanoUtxoFromRemoteFormat(rawUtxoToRemoteUnspentOutput(currentCollateral.utxo))
return [utxo]
}

const oneUtxoCollateral = await _drawCollateralInOneUtxo(this.wallet, asQuantity(valueNum))
if (oneUtxoCollateral) {
return [oneUtxoCollateral]
}

const multipleUtxosCollateral = await _drawCollateralInMultipleUtxos(this.wallet, asQuantity(valueNum))
if (multipleUtxosCollateral && multipleUtxosCollateral.length > 0) {
return multipleUtxosCollateral
}

return null
}

async submitTx(cbor: string): Promise<string> {
const base64 = Buffer.from(cbor, 'hex').toString('base64')
const txId = await Cardano.calculateTxId(base64, 'base64')
await this.wallet.submitTransaction(base64)
return txId
}

async signData(_rootKey: string, address: string, _payload: string): Promise<{signature: string; key: string}> {
const normalisedAddress = await normalizeToAddress(CardanoMobile, address)
const bech32Address = await normalisedAddress?.toBech32(undefined)
if (!bech32Address) throw new Error('Invalid wallet state')
throw new Error('Not implemented')
}

async signTx(rootKey: string, cbor: string, partial = false): Promise<CSL.TransactionWitnessSet> {
const signers = await getTransactionSigners(cbor, this.wallet, partial)
const keys = await Promise.all(signers.map(async (signer) => createRawTxSigningKey(rootKey, signer)))
const signedTxBytes = await signRawTransaction(CardanoMobile, cbor, keys)
const signedTx = await CardanoMobile.Transaction.fromBytes(signedTxBytes)
return signedTx.witnessSet()
}
}

const remoteAssetToMultiasset = async (remoteAssets: UtxoAsset[]): Promise<CSL.MultiAsset> => {
const groupedAssets = remoteAssets.reduce((res, a) => {
;(res[toPolicyId(a.assetId)] = res[toPolicyId(a.assetId)] || []).push(a)
Expand Down Expand Up @@ -90,55 +178,7 @@ const _getBalance = async (tokenId = '*', utxos: RawUtxo[], primaryTokenId: stri
return value
}

export const getBalance = async (wallet: YoroiWallet, tokenId = '*') => {
return _getBalance(tokenId, wallet.utxos, wallet.primaryTokenInfo.id)
}

export const submitTx = async (wallet: YoroiWallet, cbor: string) => {
const base64 = Buffer.from(cbor, 'hex').toString('base64')
const txId = await Cardano.calculateTxId(base64, 'base64')
await wallet.submitTransaction(base64)
return txId
}

export const signTx = async (wallet: YoroiWallet, rootKey: string, cbor: string, partial = false) => {
const signers = await getTransactionSigners(cbor, wallet, partial)
const keys = await Promise.all(signers.map(async (signer) => createRawTxSigningKey(rootKey, signer)))
const signedTxBytes = await signRawTransaction(CardanoMobile, cbor, keys)
const signedTx = await CardanoMobile.Transaction.fromBytes(signedTxBytes)
return signedTx.witnessSet()
}

export const getCollateral = async (wallet: YoroiWallet, value?: string) => {
const valueStr = value?.trim() ?? collateralConfig.minLovelace.toString()
const valueNum = new BigNumber(valueStr)

if (valueNum.gte(collateralConfig.maxLovelace)) {
throw new Error('Collateral value is too high')
}

const currentCollateral = wallet.getCollateralInfo()
const canUseCurrentCollateral = currentCollateral.utxo && valueNum.lte(currentCollateral.utxo.amount)

if (canUseCurrentCollateral && currentCollateral.utxo) {
const utxo = await cardanoUtxoFromRemoteFormat(rawUtxoToRemoteUnspentOutput(currentCollateral.utxo))
return [utxo]
}

const oneUtxoCollateral = await _drawCollateralInOneUtxo(wallet, asQuantity(valueNum))
if (oneUtxoCollateral) {
return [oneUtxoCollateral]
}

const multipleUtxosCollateral = await _drawCollateralInMultipleUtxos(wallet, asQuantity(valueNum))
if (multipleUtxosCollateral && multipleUtxosCollateral.length > 0) {
return multipleUtxosCollateral
}

return null
}

export const getUtxos = async (wallet: YoroiWallet, value?: string, pagination?: Pagination) => {
export const _getUtxos = async (wallet: YoroiWallet, value?: string, pagination?: Pagination) => {
const valueStr = value?.trim() ?? ''

if (valueStr.length === 0) {
Expand Down Expand Up @@ -167,35 +207,6 @@ export const getUtxos = async (wallet: YoroiWallet, value?: string, pagination?:
return paginate(validUtxos, pagination)
}

export const getRewardAddress = async (wallet: YoroiWallet) => {
const address = await CardanoMobile.Address.fromHex(wallet.rewardAddressHex)
return [address]
}

export const getUsedAddresses = async (wallet: YoroiWallet, pagination?: Pagination) => {
const allAddresses = wallet.externalAddresses
const selectedAddresses = paginate(allAddresses, pagination)
return Promise.all(selectedAddresses.map((addr) => Cardano.Wasm.Address.fromBech32(addr)))
}

export const getUnusedAddresses = async (wallet: YoroiWallet) => {
const bech32Addresses = wallet.receiveAddresses.filter((address) => !wallet.isUsedAddressIndex[address])
return Promise.all(bech32Addresses.map((addr) => Cardano.Wasm.Address.fromBech32(addr)))
}

export const signData = async (
_wallet: YoroiWallet,
_rootKey: string,
address: string,
_payload: string,
): Promise<{key: string; signature: string}> => {
const normalisedAddress = await normalizeToAddress(CardanoMobile, address)
const bech32Address = await normalisedAddress?.toBech32(undefined)
if (!bech32Address) throw new Error('Invalid wallet state')

throw new Error('Not implemented')
}

const _getRequiredUtxos = async (
wallet: YoroiWallet,
amounts: Balance.Amounts,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {calcLockedDeposit} from '../assetUtils'
import {encryptWithPassword} from '../catalyst/catalystCipher'
import {generatePrivateKeyForCatalyst} from '../catalyst/catalystUtils'
import {AddressChain, AddressChainJSON, Addresses, AddressGenerator} from '../chain'
import * as cip30 from '../cip30'
import {createSwapCancellationLedgerPayload} from '../common/signatureUtils'
import * as MAINNET from '../constants/mainnet/constants'
import * as SANCHONET from '../constants/sanchonet/constants'
Expand All @@ -66,7 +65,6 @@ import {
isYoroiWallet,
NoOutputsError,
NotEnoughMoneyToSendError,
Pagination,
RegistrationStatus,
walletChecksum,
WalletEvent,
Expand Down Expand Up @@ -485,13 +483,12 @@ export const makeShelleyWallet = (constants: typeof MAINNET | typeof TESTNET | t

// =================== utils =================== //
// returns the address in bech32 (Shelley) or base58 (Byron) format
private getChangeAddress(): string {
getChangeAddress(): string {
const candidateAddresses = this.internalChain.addresses
const unseen = candidateAddresses.filter((addr) => !this.isUsedAddress(addr))
assert(unseen.length > 0, 'Cannot find change address')
const changeAddress = _.first(unseen)
if (!changeAddress) throw new Error('invalid wallet state')

return changeAddress
}

Expand Down Expand Up @@ -979,47 +976,6 @@ export const makeShelleyWallet = (constants: typeof MAINNET | typeof TESTNET | t
return doesCardanoAppVersionSupportCIP36(await getCardanoAppMajorVersion(this.hwDeviceInfo, useUSB))
}

CIP30getBalance(tokenId = '*'): Promise<CSL.Value> {
return cip30.getBalance(this, tokenId)
}

CIP30getUnusedAddresses(): Promise<CSL.Address[]> {
return cip30.getUnusedAddresses(this)
}

CIP30getUsedAddresses(pagination?: Pagination): Promise<CSL.Address[]> {
return cip30.getUsedAddresses(this, pagination)
}

CIP30getChangeAddress() {
const changeAddr = this.getChangeAddress()
return Cardano.Wasm.Address.fromBech32(changeAddr)
}

CIP30getRewardAddresses(): Promise<CSL.Address[]> {
return cip30.getRewardAddress(this)
}

CIP30getUtxos(value?: string, pagination?: Pagination): Promise<CSL.TransactionUnspentOutput[] | null> {
return cip30.getUtxos(this, value, pagination)
}

CIP30getCollateral(value?: string): Promise<CSL.TransactionUnspentOutput[] | null> {
return cip30.getCollateral(this, value)
}

CIP30submitTx(cbor: string): Promise<string> {
return cip30.submitTx(this, cbor)
}

CIP30signData(rootKey: string, address: string, payload: string): Promise<{signature: string; key: string}> {
return cip30.signData(this, rootKey, address, payload)
}

CIP30signTx(rootKey: string, cbor: string, partial = false): Promise<CSL.TransactionWitnessSet> {
return cip30.signTx(this, rootKey, cbor, partial)
}

async signSwapCancellationWithLedger(cbor: string, useUSB: boolean): Promise<void> {
if (!this.hwDeviceInfo) throw new Error('Invalid wallet state')

Expand Down

0 comments on commit cc1e609

Please sign in to comment.