Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dapp-example",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"dependencies": {
"@emurgo/cardano-serialization-lib-browser": "14.1.2",
Expand Down
5 changes: 3 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useNetwork, {NETWORK_CARDANO, NETWORK_ETHEREUM} from './hooks/networkProv
import BitcoinAccessButton from './components/bitcoinAccessButton'
import BitcoinMainTab from './components/tabs/bitcoinMainTab'
import {CONNECTED, NO_PROVIDER} from './utils/connectionStates'
import {isCip95Tab} from './utils/cip95'
import NetworkToggle from './components/networkToggle'
import EthereumAccessButton from './components/ethereumAccessButton'
import EthereumMainTab from './components/tabs/ethereumMainTab'
Expand All @@ -25,7 +26,7 @@ import EthTransactionsTab from './components/tabs/subtabs/ethTransactionsTab'
import Erc20Tab from './components/tabs/subtabs/erc20Tab'

const App = () => {
const {connectionState, selectedWallet, setConnectionState, setConnectionStateFalse} = useCardano()
const {connectionState, selectedWallet, setConnectionState, setConnectionStateFalse, cip95Available} = useCardano()
const {activeNetwork} = useNetwork()
const isWalletConnected = connectionState === CONNECTED
const isNoProvider = connectionState === NO_PROVIDER
Expand Down Expand Up @@ -119,7 +120,7 @@ const App = () => {
value: 'testTxs',
children: <TestTxsTab />,
},
]
].filter((tab) => cip95Available || !isCip95Tab(tab.value))

const ethereumTabsData = [
{
Expand Down
18 changes: 10 additions & 8 deletions src/components/cards/staking/delegateCard.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import {useEffect, useState} from 'react'
import logger from '../../../utils/logger'
import {getFixedTxFromBytes, getTransactionWitnessSetFromBytes} from '../../../utils/cslTools'
import {
getFixedTxFromBytes,
getStakeKeyHashFromRewardAddressHex,
getTransactionWitnessSetFromBytes,
} from '../../../utils/cslTools'
import ApiCardWithModal from '../apiCardWithModal'
import {ModalWindowContent} from '../../ui-constants'
import InputWithLabel from '../../inputWithLabel'
import {firstOrThrow} from '../../../utils/helpFunctions'
import {fetchAccountInfo} from './logic/withdraw'
import {buildDelegationTx, getStakeKeyHashFromPubKey, resolveDelegationStakeKey} from './logic/delegate'
import {buildDelegationTx} from './logic/delegate'

const DelegateCard = ({api, onRawResponse, onResponse, onWaiting}) => {
const [networkType, setNetworkType] = useState('preprod')
Expand Down Expand Up @@ -54,18 +58,16 @@ const DelegateCard = ({api, onRawResponse, onResponse, onWaiting}) => {
const delegateClick = async () => {
try {
onWaiting(true)
const registeredPubKeys = (await api?.cip95.getRegisteredPubStakeKeys()) || []
const unregisteredPubKeys = (await api?.cip95.getUnregisteredPubStakeKeys()) || []
const {pubKeyHex, needsRegistration} = resolveDelegationStakeKey({registeredPubKeys, unregisteredPubKeys})
const stakeKeyHash = getStakeKeyHashFromPubKey(pubKeyHex)
const rewardAddressHex = firstOrThrow(await api?.getRewardAddresses(), 'No reward address available from wallet')
const stakeKeyHash = getStakeKeyHashFromRewardAddressHex(rewardAddressHex)
const hexUtxos = await api?.getUtxos()
const changeAddressHex = await api?.getChangeAddress()
const tx = buildDelegationTx({
hexUtxos,
changeAddressHex,
stakeKeyHash,
poolId,
registerStakeKey: needsRegistration,
registerStakeKey: !stakeRegistered,
})

const fixedTx = getFixedTxFromBytes(tx.to_bytes())
Expand Down Expand Up @@ -93,7 +95,7 @@ const DelegateCard = ({api, onRawResponse, onResponse, onWaiting}) => {
const apiProps = {
buttonLabel: 'Delegate to Pool',
clickFunction: delegateClick,
btnDisabled: poolId.trim().length === 0,
btnDisabled: poolId.trim().length === 0 || !showSuccessInfo,
}

return (
Expand Down
93 changes: 74 additions & 19 deletions src/components/cards/staking/delegateCard.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import {render, screen, fireEvent, waitFor} from '@testing-library/react'
import DelegateCard from './delegateCard'
import {fetchAccountInfo} from './logic/withdraw'
import {buildDelegationTx} from './logic/delegate'
import {
getFixedTxFromBytes,
getStakeKeyHashFromRewardAddressHex,
getTransactionWitnessSetFromBytes,
} from '../../../utils/cslTools'

jest.mock('../../../utils/cslTools', () => ({
getFixedTxFromBytes: jest.fn(),
getTransactionWitnessSetFromBytes: jest.fn(),
getStakeKeyHashFromRewardAddressHex: jest.fn(),
}))

jest.mock('./logic/delegate', () => ({
buildDelegationTx: jest.fn(),
getStakeKeyHashFromPubKey: jest.fn(),
resolveDelegationStakeKey: jest.fn(),
}))

jest.mock('../apiCardWithModal', () => ({
Expand All @@ -34,15 +39,55 @@ describe('DelegateCard', () => {
const api = {
getNetworkId: jest.fn().mockResolvedValue(1),
getRewardAddresses: jest.fn().mockResolvedValue(['aabbcc']),
getUtxos: jest.fn().mockResolvedValue(['utxo']),
getChangeAddress: jest.fn().mockResolvedValue('change'),
signTx: jest.fn().mockResolvedValue('sig-hex'),
submitTx: jest.fn().mockResolvedValue('tx-id'),
cip95: {
getRegisteredPubStakeKeys: jest.fn(),
getUnregisteredPubStakeKeys: jest.fn(),
},
}

const fillPoolId = () => {
fireEvent.change(screen.getByLabelText('Pool ID'), {
target: {value: 'deadbeef01234567890abcdef01234567890abcdef01234567890abc'},
})
}

const loadAccountInfo = async () => {
fireEvent.change(screen.getByLabelText('Blockfrost Project ID'), {target: {value: 'mainnetProjectId'}})
fireEvent.click(screen.getByRole('button', {name: 'Get Account info'}))
await waitFor(() => {
expect(screen.getByText(/Stake key registered: yes/)).toBeInTheDocument()
})
}

beforeEach(() => {
jest.clearAllMocks()
api.getNetworkId.mockResolvedValue(1)
api.getRewardAddresses.mockResolvedValue(['aabbcc'])
api.getUtxos.mockResolvedValue(['utxo'])
api.getChangeAddress.mockResolvedValue('change')
api.signTx.mockResolvedValue('sig-hex')
api.submitTx.mockResolvedValue('tx-id')
fetchAccountInfo.mockResolvedValue({
ok: true,
stakeRegistered: true,
delegation: 'pool1abc',
})
getStakeKeyHashFromRewardAddressHex.mockReturnValue('stake-key-hash')
buildDelegationTx.mockReturnValue({to_bytes: () => new Uint8Array([1])})
getFixedTxFromBytes.mockReturnValue({
to_hex: () => 'signed-tx',
add_vkey_witness: jest.fn(),
})
getTransactionWitnessSetFromBytes.mockReturnValue({
vkeys: () => ({len: () => 0, get: () => null}),
})
})

it('renders the pool ID field and keeps Send disabled until a pool ID is entered', async () => {
it('renders the pool ID field and keeps Send disabled until account info is loaded', async () => {
render(<DelegateCard api={api} onRawResponse={() => {}} onResponse={() => {}} onWaiting={() => {}} />)
await waitFor(() => expect(api.getNetworkId).toHaveBeenCalled())

Expand All @@ -52,28 +97,15 @@ describe('DelegateCard', () => {
expect(screen.getByRole('button', {name: 'Send'})).toBeDisabled()
expect(screen.getByRole('button', {name: 'Get Account info'})).toBeDisabled()

fireEvent.change(screen.getByLabelText('Pool ID'), {
target: {value: 'deadbeef01234567890abcdef01234567890abcdef01234567890abc'},
})
expect(screen.getByRole('button', {name: 'Send'})).not.toBeDisabled()
fillPoolId()
expect(screen.getByRole('button', {name: 'Send'})).toBeDisabled()
})

it('shows current pool info after Get Account info succeeds', async () => {
fetchAccountInfo.mockResolvedValue({
ok: true,
stakeRegistered: true,
delegation: 'pool1abc',
})

render(<DelegateCard api={api} onRawResponse={() => {}} onResponse={() => {}} onWaiting={() => {}} />)
await waitFor(() => expect(api.getNetworkId).toHaveBeenCalled())

fireEvent.change(screen.getByLabelText('Blockfrost Project ID'), {target: {value: 'mainnetProjectId'}})
fireEvent.click(screen.getByRole('button', {name: 'Get Account info'}))

await waitFor(() => {
expect(screen.getByText(/Stake key registered: yes/)).toBeInTheDocument()
})
await loadAccountInfo()
expect(screen.getByText(/Current pool: pool1abc/)).toBeInTheDocument()
expect(fetchAccountInfo).toHaveBeenCalledWith('mainnet', 'aabbcc', 'mainnetProjectId')
})
Expand All @@ -91,4 +123,27 @@ describe('DelegateCard', () => {
expect(screen.getByText(/Something went wrong while getting delegation info/)).toBeInTheDocument()
})
})

it('builds the delegation tx from the CIP-30 reward address, without CIP-95', async () => {
const onRawResponse = jest.fn()
const onResponse = jest.fn()
render(<DelegateCard api={api} onRawResponse={onRawResponse} onResponse={onResponse} onWaiting={() => {}} />)
await waitFor(() => expect(api.getNetworkId).toHaveBeenCalled())

fillPoolId()
await loadAccountInfo()
fireEvent.click(screen.getByRole('button', {name: 'Send'}))

await waitFor(() => expect(api.submitTx).toHaveBeenCalled())
expect(api.cip95.getRegisteredPubStakeKeys).not.toHaveBeenCalled()
expect(api.cip95.getUnregisteredPubStakeKeys).not.toHaveBeenCalled()
expect(getStakeKeyHashFromRewardAddressHex).toHaveBeenCalledWith('aabbcc')
expect(buildDelegationTx).toHaveBeenCalledWith(
expect.objectContaining({
stakeKeyHash: 'stake-key-hash',
registerStakeKey: false,
}),
)
expect(onRawResponse).toHaveBeenCalledWith('tx-id')
})
})
6 changes: 0 additions & 6 deletions src/components/cards/staking/logic/delegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,10 @@ import {
getCslUtxos,
getLargestFirstMultiAsset,
getPoolKeyHash,
getPublicKeyFromHex,
getStakeDelegCert,
getStakeKeyRegCert,
getTxBuilder,
} from '../../../../utils/cslTools'
import {resolveDelegationStakeKey} from './stakeKey'

export {resolveDelegationStakeKey}

export const getStakeKeyHashFromPubKey = (pubKeyHex) => getPublicKeyFromHex(pubKeyHex).hash().to_hex()

export const buildDelegationTx = ({hexUtxos, changeAddressHex, stakeKeyHash, poolId, registerStakeKey}) => {
const txBuilder = getTxBuilder()
Expand Down
9 changes: 0 additions & 9 deletions src/components/cards/staking/logic/stakeKey.js

This file was deleted.

23 changes: 0 additions & 23 deletions src/components/cards/staking/logic/stakeKey.test.js

This file was deleted.

10 changes: 3 additions & 7 deletions src/components/cards/staking/withdrawCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
getCslUtxos,
getLargestFirstMultiAsset,
getFixedTxFromBytes,
getPublicKeyFromHex,
getStakeKeyDeregCert,
getStakeKeyHashFromRewardAddressHex,
getTransactionWitnessSetFromBytes,
} from '../../../utils/cslTools'
import ApiCardWithModal from '../apiCardWithModal'
Expand Down Expand Up @@ -79,12 +79,8 @@ const WithdrawCard = ({api, onRawResponse, onResponse, onWaiting}) => {
try {
onWaiting(true)
// build withdraw
const pubStakeKey = await api?.cip95.getRegisteredPubStakeKeys()
const stakeKeyHash = getPublicKeyFromHex(
firstOrThrow(pubStakeKey, 'No registered stake key available from wallet'),
)
.hash()
.to_hex()
const rewardAddressHex = firstOrThrow(await api?.getRewardAddresses(), 'No reward address available from wallet')
const stakeKeyHash = getStakeKeyHashFromRewardAddressHex(rewardAddressHex)
const txBuilderWithWithdrawal = await getTxBuilderWithWithdrawal(stakeKeyHash, networkType, rewardAmount)
const utxos = await api?.getUtxos()

Expand Down
Loading
Loading