diff --git a/package-lock.json b/package-lock.json index 6878e799..4d73d425 100644 --- a/package-lock.json +++ b/package-lock.json @@ -217,8 +217,9 @@ "version": "0.34.3", "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.3.tgz", "integrity": "sha512-jMjY/S0doZnWYNV90x0jmU3B+UcrsfGYnukxYrRbj0CVvGI/MX3JbHsxSrx2d4mbnXaUsqJmAcDfoQWA6r0lOw==", - "extraneous": true, "license": "ISC", + "optional": true, + "peer": true, "dependencies": { "@panva/hkdf": "^1.1.1", "@types/cookie": "0.6.0", @@ -249,8 +250,9 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.6" } @@ -259,8 +261,9 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -269,8 +272,9 @@ "version": "10.11.3", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -280,8 +284,9 @@ "version": "5.2.3", "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "pretty-format": "^3.8.0" }, @@ -293,8 +298,9 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", - "extraneous": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@auth/prisma-adapter": { "version": "2.11.3", @@ -2048,9 +2054,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2067,9 +2070,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2086,9 +2086,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2105,9 +2102,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2124,9 +2118,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2143,9 +2134,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2162,9 +2150,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2181,9 +2166,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2200,9 +2182,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2225,9 +2204,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2250,9 +2226,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2275,9 +2248,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2300,9 +2270,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2325,9 +2292,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2350,9 +2314,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2375,9 +2336,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -4511,9 +4469,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4530,9 +4485,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4549,9 +4501,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4568,9 +4517,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5100,9 +5046,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5124,9 +5067,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5148,9 +5088,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5172,9 +5109,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5196,9 +5130,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -5220,9 +5151,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7096,8 +7024,9 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-9.0.1.tgz", "integrity": "sha512-wD2WpbkaEP4170s13/HUxPcAV5y4ZXaKo1TfNklS5zDefPinIgXOpgz1kpEvobAsaLPa2KeH7AKKX/od1mrBJw==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@simplewebauthn/types": "^9.0.1" } @@ -7107,8 +7036,9 @@ "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-9.0.1.tgz", "integrity": "sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "extraneous": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@sinclair/typebox": { "version": "0.34.52", @@ -8154,8 +8084,9 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "extraneous": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@types/cors": { "version": "2.8.19", @@ -9022,9 +8953,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9039,9 +8967,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9056,9 +8981,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9073,9 +8995,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9090,9 +9009,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9107,9 +9023,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9124,9 +9037,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9141,9 +9051,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9158,9 +9065,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -9175,9 +9079,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -10662,6 +10563,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/bare-ansi-escapes": { @@ -11250,6 +11152,7 @@ "version": "1.1.21", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -11588,6 +11491,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -11670,6 +11574,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, "funding": [ { "type": "github", @@ -11826,6 +11731,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/confbox": { @@ -14430,6 +14336,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, "license": "ISC" }, "node_modules/grammex": { @@ -14533,6 +14440,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -16436,6 +16344,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -17948,6 +17857,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -20229,8 +20139,9 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", - "extraneous": true, "license": "MIT", + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -23322,6 +23233,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -23727,8 +23639,9 @@ "version": "2.9.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", - "extraneous": true, "license": "ISC", + "optional": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/src/__tests__/tx-builders/stakingCert.test.ts b/src/__tests__/tx-builders/stakingCert.test.ts index 7aa7e4f3..d06f3b50 100644 --- a/src/__tests__/tx-builders/stakingCert.test.ts +++ b/src/__tests__/tx-builders/stakingCert.test.ts @@ -13,6 +13,7 @@ type CertCall = | { type: "register"; address: string } | { type: "deregister"; address: string } | { type: "delegate"; address: string; poolHex: string } + | { type: "voteDelegation"; address: string; dRep: unknown } | { type: "withdrawal"; address: string; amount: string }; function createCertBuilderMock() { @@ -31,6 +32,10 @@ function createCertBuilderMock() { calls.push({ type: "delegate", address, poolHex }); return builder; }, + voteDelegationCertificate: (dRep: unknown, address: string) => { + calls.push({ type: "voteDelegation", address, dRep }); + return builder; + }, certificateScript: (_scriptCbor: string) => builder, withdrawal: (address: string, amount: string) => { calls.push({ type: "withdrawal", address, amount }); @@ -79,11 +84,12 @@ describe("buildStakingCertificateActions", () => { rewardAddress: REWARD_ADDRESS, stakingScript: STAKING_SCRIPT_CBOR, poolHex: POOL_HEX, + dRep: "Always Abstain", }); actions.delegate.execute(); - expect(calls).toHaveLength(1); + expect(calls).toHaveLength(2); expect(calls[0]).toMatchObject({ type: "delegate", address: REWARD_ADDRESS, poolHex: POOL_HEX }); }); @@ -94,11 +100,12 @@ describe("buildStakingCertificateActions", () => { rewardAddress: REWARD_ADDRESS, stakingScript: STAKING_SCRIPT_CBOR, poolHex: POOL_HEX, + dRep: "Always Abstain", }); actions.register_and_delegate.execute(); - expect(calls).toHaveLength(2); + expect(calls).toHaveLength(3); expect(calls[0]).toMatchObject({ type: "register", address: REWARD_ADDRESS }); expect(calls[1]).toMatchObject({ type: "delegate", address: REWARD_ADDRESS, poolHex: POOL_HEX }); }); @@ -110,6 +117,7 @@ describe("buildStakingCertificateActions", () => { rewardAddress: REWARD_ADDRESS, stakingScript: STAKING_SCRIPT_CBOR, poolHex: "", + dRep: "Always Abstain", }); expect(() => actions.delegate.execute()).not.toThrow(); @@ -157,11 +165,12 @@ describe("buildStakingActionConfigs", () => { stakingScript: STAKING_SCRIPT_CBOR, poolHex: POOL_HEX, rewards: "0", + dRep: "Always Abstain", }); configs.registerAndDelegate.execute(); - expect(calls).toHaveLength(2); + expect(calls).toHaveLength(3); expect(calls[0]).toMatchObject({ type: "register", address: REWARD_ADDRESS }); expect(calls[1]).toMatchObject({ type: "delegate", address: REWARD_ADDRESS, poolHex: POOL_HEX }); }); @@ -184,3 +193,50 @@ describe("buildStakingActionConfigs", () => { } }); }); + +describe("staking requires a DRep", () => { + const base = { + rewardAddress: REWARD_ADDRESS, + stakingScript: STAKING_SCRIPT_CBOR, + poolHex: POOL_HEX, + }; + + it("delegate throws without a DRep", () => { + const { builder } = createCertBuilderMock(); + const actions = buildStakingCertificateActions({ txBuilder: builder as never, ...base }); + expect(() => actions.delegate.execute()).toThrow(/requires a DRep/); + expect(() => actions.register_and_delegate.execute()).toThrow(/requires a DRep/); + }); + + it("delegate emits pool then vote delegation (abstain / no-confidence)", () => { + for (const [dRep, expected] of [ + ["Always Abstain", { alwaysAbstain: null }], + ["Always No Confidence", { alwaysNoConfidence: null }], + ] as const) { + const { builder, calls } = createCertBuilderMock(); + buildStakingCertificateActions({ txBuilder: builder as never, ...base, dRep }).delegate.execute(); + expect(calls.map((c) => c.type)).toEqual(["delegate", "voteDelegation"]); + expect(calls[1]).toMatchObject({ dRep: expected }); + } + }); + + it("register_and_delegate emits register, delegate, vote delegation", () => { + const { builder, calls } = createCertBuilderMock(); + buildStakingCertificateActions({ + txBuilder: builder as never, + ...base, + dRep: "Always Abstain", + }).register_and_delegate.execute(); + expect(calls.map((c) => c.type)).toEqual(["register", "delegate", "voteDelegation"]); + }); + + it("skips the vote delegation when the stake credential already has a DRep", () => { + const { builder, calls } = createCertBuilderMock(); + buildStakingCertificateActions({ + txBuilder: builder as never, + ...base, + dRepAlreadyDelegated: true, + }).delegate.execute(); + expect(calls.map((c) => c.type)).toEqual(["delegate"]); + }); +}); diff --git a/src/components/pages/wallet/info/signers/card-show-signers.tsx b/src/components/pages/wallet/info/signers/card-show-signers.tsx index f6887c2e..ace4355d 100644 --- a/src/components/pages/wallet/info/signers/card-show-signers.tsx +++ b/src/components/pages/wallet/info/signers/card-show-signers.tsx @@ -104,12 +104,25 @@ export default function ShowSigners({ appWallet }: ShowSignersProps) { const { mutate: updateWalletVerifiedList } = api.wallet.updateWalletVerifiedList.useMutation({ - onSuccess: async () => { - toast({ - title: "Wallet verified", - description: "You have successfully verified this wallet", - duration: 5000, - }); + onSuccess: async (updated) => { + // The server only ever records the signed-in session address, and + // returns success even when that isn't the connected wallet's address + // (wallet switched after sign-in). Don't claim success in that case. + if (userAddress && !updated.verified.includes(userAddress)) { + toast({ + title: "Verification not recorded", + description: + "Your signed-in session is for a different address than the connected wallet. Re-authorize with the connected wallet and try again.", + variant: "destructive", + duration: 8000, + }); + } else { + toast({ + title: "Wallet verified", + description: "You have successfully verified this wallet", + duration: 5000, + }); + } void ctx.wallet.getWallet.invalidate({ address: userAddress, walletId: appWallet.id, @@ -117,6 +130,12 @@ export default function ShowSigners({ appWallet }: ShowSignersProps) { }, onError: (e) => { console.error(e); + toast({ + title: "Verification failed", + description: e.message, + variant: "destructive", + duration: 6000, + }); }, }); @@ -146,12 +165,9 @@ export default function ShowSigners({ appWallet }: ShowSignersProps) { const result = await checkSignature(nonce, signature); if (result) { - const _verified = appWallet.verified; - _verified.push(userAddress); - updateWalletVerifiedList({ walletId: appWallet.id, - verified: _verified, + verified: [...appWallet.verified, userAddress], }); } } diff --git a/src/components/pages/wallet/staking/StakingActions/stake.tsx b/src/components/pages/wallet/staking/StakingActions/stake.tsx index 944d2122..bab4b4f6 100644 --- a/src/components/pages/wallet/staking/StakingActions/stake.tsx +++ b/src/components/pages/wallet/staking/StakingActions/stake.tsx @@ -20,6 +20,7 @@ export default function StakeButton({ utxos, network, poolHex, + dRep, action, }: { stakingInfo: StakingInfo; @@ -28,10 +29,12 @@ export default function StakeButton({ utxos: UTxO[]; network: number; poolHex: string; + dRep?: string; action: StakingAction; }) { const { newTransaction } = useTransaction(); const [loading, setLoading] = useState(false); + const needsDRep = action === "delegate" || action === "registerAndDelegate"; async function handleStake() { setLoading(true); @@ -65,6 +68,8 @@ export default function StakeButton({ stakingScript, poolHex, rewards: stakingInfo.rewards, + dRep, + dRepAlreadyDelegated: !!stakingInfo.dRepId, }); const actionConfig = actionConfigs[action]; @@ -122,7 +127,11 @@ export default function StakeButton({ } return ( -
)} + {!stakingInfo.dRepId && ( +Not a valid DRep id.
+ )} +