Skip to content

Commit

Permalink
Abstraxion V2 Flow (#33)
Browse files Browse the repository at this point in the history
Contains updates to both the library and account dashboard.

---------

Co-authored-by: justin <[email protected]>
Co-authored-by: Burnt Nerve <[email protected]>
  • Loading branch information
3 people authored Jan 16, 2024
1 parent 620c68c commit e7e582b
Show file tree
Hide file tree
Showing 56 changed files with 1,597 additions and 12,771 deletions.
7 changes: 7 additions & 0 deletions .changeset/blue-spoons-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@burnt-labs/tailwind-config": patch
"@burnt-labs/abstraxion": patch
"@burnt-labs/ui": patch
---

Updated designs
5 changes: 5 additions & 0 deletions .changeset/rotten-horses-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"abstraxion-dashboard": minor
---

Wrap contract grant message inside a `ContractExecutionAuthorization` message
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ This is the official xion.js repo.
### Apps and Packages

- `demo-app`: a [Next.js](https://nextjs.org/) app with [Tailwind CSS](https://tailwindcss.com/)
- `ui`: a stub React component library with [Tailwind CSS](https://tailwindcss.com/)
- `abstraxion`: account abstraction react package built for XION
- `constants`: utility package for shared constants
- `signers`: utility package for account abstraction classes built on top of cosmjs
- `ui`: a React component library with [Tailwind CSS](https://tailwindcss.com/)
- `eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
- `tsconfig`: `tsconfig.json`s used throughout the monorepo

Expand Down
4 changes: 1 addition & 3 deletions apps/abstraxion-dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import { Providers } from "./providers";

import "@burnt-labs/ui/styles.css";

import "./globals.css";
import "@burnt-labs/ui/styles.css";

const akkuratLL = localFont({
src: [
Expand Down
7 changes: 6 additions & 1 deletion apps/abstraxion-dashboard/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,25 @@ import {
useAbstraxionAccount,
useAbstraxionSigningClient,
} from "../hooks";
import { useSearchParams } from "next/navigation";

export interface AccountWithAuthenticator extends AbstraxionAccount {
authenticators: Authenticators;
}

export default function Home() {
const searchParams = useSearchParams();
const [isOpen, setIsOpen] = useState(false);
const { data: account } = useAbstraxionAccount();
const { client } = useAbstraxionSigningClient();
const accountBalance = useAccountBalance(account, client);

const permissions = searchParams.get("permissions");
const grantee = searchParams.get("grantee");

return (
<>
{!account?.bech32Address ? (
{!account?.bech32Address || (permissions && grantee) ? (
<div className="ui-flex ui-h-screen ui-flex-1 ui-items-center ui-justify-center ui-overflow-y-auto ui-p-6">
<Abstraxion onClose={() => null} isOpen={true} />
</div>
Expand Down
28 changes: 18 additions & 10 deletions apps/abstraxion-dashboard/components/Abstraxion/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,37 @@ import {
AbstraxionContextProvider,
} from "@/components/AbstraxionContext";
import { apolloClient, stytchClient } from "@/lib";
import { ModalAnchor, Modal } from "@burnt-labs/ui";
import { Dialog, DialogContent } from "@burnt-labs/ui";
import { AbstraxionSignin } from "@/components/AbstraxionSignin";
import { useAbstraxionAccount } from "@/hooks";
import { Loading } from "@/components/Loading";
import { AbstraxionWallets } from "@/components/AbstraxionWallets";
import { ErrorDisplay } from "@/components/ErrorDisplay";
import { useSearchParams } from "next/navigation";
import { AbstraxionGrant } from "../AbstraxionGrant";

export interface ModalProps {
onClose: VoidFunction;
isOpen: boolean;
}

export const Abstraxion = ({ isOpen, onClose }: ModalProps) => {
const searchParams = useSearchParams();
const modalRef = useRef<HTMLDivElement>(null);

const { abstraxionError } = useContext(
AbstraxionContext,
) as AbstraxionContextProps;

const { isConnected, isConnecting, isReconnecting } = useAbstraxionAccount();
const {
isConnected,
isConnecting,
isReconnecting,
data: account,
} = useAbstraxionAccount();

const permissions = searchParams.get("permissions");
const grantee = searchParams.get("grantee");
useEffect(() => {
const closeOnEscKey = (e: any) => (e.key === "Escape" ? onClose() : null);
document.addEventListener("keydown", closeOnEscKey);
Expand All @@ -40,23 +50,21 @@ export const Abstraxion = ({ isOpen, onClose }: ModalProps) => {
if (!isOpen) return null;

return (
<ModalAnchor ref={modalRef} onClick={onClose}>
<Modal
onClick={(e: any) => {
e.stopPropagation();
}}
>
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
{abstraxionError ? (
<ErrorDisplay message={abstraxionError} onClose={onClose} />
) : isConnecting || isReconnecting ? (
<Loading />
) : account?.bech32Address && permissions && grantee ? (
<AbstraxionGrant permissions={permissions} grantee={grantee} />
) : isConnected ? (
<AbstraxionWallets />
) : (
<AbstraxionSignin />
)}
</Modal>
</ModalAnchor>
</DialogContent>
</Dialog>
);
};

Expand Down
179 changes: 179 additions & 0 deletions apps/abstraxion-dashboard/components/AbstraxionGrant/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"use client";

import Image from "next/image";

import burntAvatar from "@/public/burntAvatarCircle.png";
import { CheckIcon } from "../Icons";
import { Button, Spinner } from "@burnt-labs/ui";
import { useAbstraxionAccount, useAbstraxionSigningClient } from "@/hooks";
import { MsgGrant } from "cosmjs-types/cosmos/authz/v1beta1/tx";
import {
ContractExecutionAuthorization,
MaxCallsLimit,
} from "cosmjs-types/cosmwasm/wasm/v1/authz";
import { EncodeObject } from "@cosmjs/proto-signing";
import { useState } from "react";

interface AbstraxionGrantProps {
permissions: string;
grantee: string;
}

export const AbstraxionGrant = ({
permissions,
grantee,
}: AbstraxionGrantProps) => {
const { client } = useAbstraxionSigningClient();
const { data: account } = useAbstraxionAccount();

const [inProgress, setInProgress] = useState(false);
const [showSuccess, setShowSuccess] = useState(false);

const generateContractGrant = async () => {
const timestampThreeMonthsFromNow = Math.floor(
new Date(new Date().setMonth(new Date().getMonth() + 3)).getTime() / 1000,
);
const granter = account?.bech32Address;

if (client && granter) {
const contractExecutionAuthorizationValue =
ContractExecutionAuthorization.encode(
ContractExecutionAuthorization.fromPartial({
grants: [
{
contract: permissions,
limit: {
typeUrl: "/cosmwasm.wasm.v1.MaxCallsLimit",
value: MaxCallsLimit.encode(
MaxCallsLimit.fromPartial({
// Picking a giant number here since something like `UnlimitedCallsLimit` doesn't appear to be available
remaining: "4096",
}),
).finish(),
},
filter: {
typeUrl: "/cosmwasm.wasm.v1.AllowAllMessagesFilter",
},
},
],
}),
).finish();

const grantValue = MsgGrant.fromPartial({
grant: {
authorization: {
typeUrl: "/cosmwasm.wasm.v1.ContractExecutionAuthorization",
value: contractExecutionAuthorizationValue,
},
expiration: {
seconds: timestampThreeMonthsFromNow,
},
},
grantee,
granter,
});

return {
typeUrl: "/cosmos.authz.v1beta1.MsgGrant",
value: grantValue,
};
}
};

const grant = async () => {
setInProgress(true);
console.log({ client, account });
if (!client) {
throw new Error("no client");
}

if (!account) {
throw new Error("no account");
}

const msg = await generateContractGrant();

try {
const foo = await client?.signAndBroadcast(
account.bech32Address,
[msg as EncodeObject],
{
amount: [{ amount: "0", denom: "uxion" }],
gas: "500000",
},
);
console.log(foo);
setShowSuccess(true);
setInProgress(false);
} catch (error) {
setInProgress(false);
console.log("something went wrong: ", error);
}
};

if (inProgress) {
return (
<div className="ui-w-full ui-h-full ui-min-h-[500px] ui-flex ui-items-center ui-justify-center ui-text-white">
<Spinner />
</div>
);
}

return (
<div className="ui-flex ui-font-akkuratLL ui-flex-col ui-justify-center ui-p-12 ui-min-w-[380px] ui-text-white">
{showSuccess ? (
<>
<h1 className="ui-text-center ui-text-3xl ui-font-light ui-uppercase ui-text-white ui-mb-6">
Your sign-in <br />
was Successful!
</h1>
<p className="ui-text-center ui-text-base ui-font-normal ui-leading-normal ui-text-zinc-100 ui-opacity-50 ui-mb-6">
Please switch back to the previous window to continue your
experience.
</p>
</>
) : (
<>
<div className="ui-mb-10 ui-flex ui-items-center ui-justify-center">
<Image src={burntAvatar} alt="Burnt Avatar" />
<div className="ui-mx-6 ui-h-[1px] ui-w-10 ui-bg-white ui-opacity-20"></div>{" "}
{/* This is the divider */}
<div className="ui-h-16 ui-w-16 ui-bg-gray-300 ui-rounded-full"></div>
</div>
<div className="mb-4">
<h1 className="ui-text-base ui-font-bold ui-leading-tight">
A 3rd party would like to:
</h1>
<div className="ui-w-full ui-bg-white ui-opacity-20 ui-h-[1px] ui-mt-8" />
<ul className="ui-my-8 ui-list-disc ui-list-none">
<li className="ui-flex ui-items-baseline ui-text-sm ui-mb-4">
<span className="ui-mr-2">
<CheckIcon color="white" />
</span>
Have access to your account
</li>
<li className="ui-flex ui-items-baseline ui-text-sm">
<span className="ui-mr-2">
<CheckIcon color="white" />
</span>
View your basic profile info
</li>
</ul>
<div className="ui-w-full ui-bg-white ui-opacity-20 ui-h-[1px] ui-mb-8" />
<div className="ui-w-full ui-flex ui-flex-col ui-gap-4">
<Button
disabled={inProgress}
structure="base"
fullWidth={true}
onClick={grant}
>
Allow and Continue
</Button>
<Button structure="naked">Deny Access</Button>
</div>
</div>
</>
)}
</div>
);
};
Loading

1 comment on commit e7e582b

@vercel
Copy link

@vercel vercel bot commented on e7e582b Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.