Skip to content

Commit

Permalink
Add grantee signer client and related updates (#44)
Browse files Browse the repository at this point in the history
* Add grantee signer client and related updates

* granteeSigner testing flow; fix lint errors

* fix MsgExec encoding issue; clean up demo app

---------

Co-authored-by: Burnt Val <[email protected]>
  • Loading branch information
justinbarry and BurntVal authored Jan 17, 2024
1 parent b38f8dc commit 56b9f87
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 90 deletions.
5 changes: 5 additions & 0 deletions .changeset/tasty-masks-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@burnt-labs/abstraxion": minor
---

Add grantee signer client to seamlessly handle grantor/grantee relationships
10 changes: 5 additions & 5 deletions apps/demo-app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import "@burnt-labs/abstraxion/styles.css";

const inter = Inter({ subsets: ["latin"] });

// Example XION seat contract
export const seatContractAddress =
"xion1z70cvc08qv5764zeg3dykcyymj5z6nu4sqr7x8vl4zjef2gyp69s9mmdka";

export default function RootLayout({
children,
}: {
Expand All @@ -16,11 +20,7 @@ export default function RootLayout({
<body className={inter.className}>
<AbstraxionProvider
config={{
contracts: [
"xion1ly5vunf97qzevm6cu8jq3c5ltj2mlwf4s7g6x5du4atd206m2w0qf2hxsz",
"xion1ug4wpsjpn9k0r9rcdx5dq39h6hhe9uvwn3z0gfqnpz6xxvw3cd3sygy3x6",
],
dashboardUrl: "https://dashboard.burnt.com",
contracts: [seatContractAddress],
}}
>
{children}
Expand Down
83 changes: 43 additions & 40 deletions apps/demo-app/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
} from "@burnt-labs/abstraxion";
import { Button } from "@burnt-labs/ui";
import "@burnt-labs/ui/styles.css";
import type { InstantiateResult } from "@cosmjs/cosmwasm-stargate";
import type { ExecuteResult } from "@cosmjs/cosmwasm-stargate";
import { seatContractAddress } from "./layout";

type InitiateResultOrUndefined = InstantiateResult | undefined;
type ExecuteResultOrUndefined = ExecuteResult | undefined;
export default function Page(): JSX.Element {
// Abstraxion hooks
const { data: account } = useAbstraxionAccount();
Expand All @@ -19,54 +20,56 @@ export default function Page(): JSX.Element {
// General state hooks
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [initiateResult, setInitiateResult] =
useState<InitiateResultOrUndefined>(undefined);
const [executeResult, setExecuteResult] =
useState<ExecuteResultOrUndefined>(undefined);

const blockExplorerUrl = `https://explorer.burnt.com/xion-testnet-1/tx/${initiateResult?.transactionHash}`;
const blockExplorerUrl = `https://explorer.burnt.com/xion-testnet-1/tx/${executeResult?.transactionHash}`;

const instantiateTestContract = async (): Promise<void> => {
function getTimestampInSeconds(date: Date | null) {
if (!date) return 0;
const d = new Date(date);
return Math.floor(d.getTime() / 1000);
}

const now = new Date();
now.setSeconds(now.getSeconds() + 15);
const oneYearFromNow = new Date();
oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);

async function claimSeat() {
setLoading(true);
try {
if (!client) {
setIsOpen(true);
return;
}
const initMsg = {
metadata: {
metadata: {
name: "Abstraxion House",
hub_url: "abstraxion_house",
description: "Generalized Abstraction",
tags: [],
social_links: [],
creator: account.bech32Address,
thumbnail_image_url: "https://fakeimg.pl/200/",
banner_image_url: "https://fakeimg.pl/500/",
},
},
ownable: {
const msg = {
sales: {
claim_item: {
token_id: String(getTimestampInSeconds(now)),
owner: account.bech32Address,
token_uri: "",
extension: {},
},
};
},
};

const hubResult = await client.instantiate(
account.bech32Address || "",
1,
initMsg,
"my-hub",
try {
const claimRes = await client?.execute(
account.bech32Address,
seatContractAddress,
msg,
{
amount: [{ amount: "0", denom: "uxion" }],
gas: "500000",
},
"",
[],
);
setInitiateResult(hubResult);

setExecuteResult(claimRes);
} catch (error) {
// eslint-disable-next-line no-console -- No UI exists yet to display errors
console.log(error);
} finally {
setLoading(false);
}
};
}

return (
<main className="m-auto flex min-h-screen max-w-xs flex-col items-center justify-center gap-4 p-4">
Expand All @@ -80,7 +83,7 @@ export default function Page(): JSX.Element {
}}
structure="base"
>
{account.wallet ? (
{account.bech32Address ? (
<div className="flex items-center justify-center">VIEW ACCOUNT</div>
) : (
"CONNECT"
Expand All @@ -91,11 +94,11 @@ export default function Page(): JSX.Element {
disabled={loading}
fullWidth
onClick={() => {
void instantiateTestContract();
void claimSeat();
}}
structure="base"
>
{loading ? "LOADING..." : "INSTANTIATE TEST CONTRACT"}
{loading ? "LOADING..." : "CLAIM SEAT"}
</Button>
) : null}
<Abstraxion
Expand All @@ -104,19 +107,19 @@ export default function Page(): JSX.Element {
setIsOpen(false);
}}
/>
{initiateResult ? (
{executeResult ? (
<div className="flex flex-col rounded border-2 border-black p-2 dark:border-white">
<div className="mt-2">
<p className="text-zinc-500">
<span className="font-bold">Contract Address:</span>
<span className="font-bold">Transaction Hash</span>
</p>
<p className="text-sm">{initiateResult.contractAddress}</p>
<p className="text-sm">{executeResult.transactionHash}</p>
</div>
<div className="mt-2">
<p className=" text-zinc-500">
<span className="font-bold">Block Height:</span>
</p>
<p className="text-sm">{initiateResult.height}</p>
<p className="text-sm">{executeResult.height}</p>
</div>
<div className="mt-2">
<Link
Expand Down
5 changes: 5 additions & 0 deletions packages/abstraxion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
},
"dependencies": {
"@apollo/client": "^3.8.8",
"@cosmjs/cosmwasm-stargate": "^0.31.3",
"@cosmjs/proto-signing": "^0.31.3",
"@cosmjs/stargate": "^0.31.3",
"cosmjs-types": "^0.8.0",
"@cosmjs/tendermint-rpc": "^0.31.1",
"@burnt-labs/constants": "workspace:*",
"@burnt-labs/signers": "workspace:*",
"@burnt-labs/ui": "workspace:*",
Expand Down
111 changes: 111 additions & 0 deletions packages/abstraxion/src/GranteeSignerClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
DeliverTxResponse,
SigningCosmWasmClient,
SigningCosmWasmClientOptions,
} from "@cosmjs/cosmwasm-stargate";
import { EncodeObject, OfflineSigner } from "@cosmjs/proto-signing";
import { SignerData, StdFee } from "@cosmjs/stargate";
import type { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { MsgExec } from "cosmjs-types/cosmos/authz/v1beta1/tx";
import {
HttpEndpoint,
Tendermint37Client,
TendermintClient,
} from "@cosmjs/tendermint-rpc";

interface GranteeSignerOptions {
readonly grantorAddress: string;
readonly granteeAddress: string;
}

export class GranteeSignerClient extends SigningCosmWasmClient {
protected readonly grantorAddress: string;
protected readonly granteeAddress: string;

public static async connectWithSigner(
endpoint: string | HttpEndpoint,
signer: OfflineSigner,
options: SigningCosmWasmClientOptions & GranteeSignerOptions,
): Promise<GranteeSignerClient> {
const tmClient = await Tendermint37Client.connect(endpoint);
return GranteeSignerClient.createWithSigner(tmClient, signer, options);
}

public static async createWithSigner(
cometClient: TendermintClient,
signer: OfflineSigner,
options: SigningCosmWasmClientOptions & GranteeSignerOptions,
): Promise<GranteeSignerClient> {
return new GranteeSignerClient(cometClient, signer, options);
}

protected constructor(
cometClient: TendermintClient | undefined,
signer: OfflineSigner,
{
grantorAddress,
granteeAddress,
...options
}: SigningCosmWasmClientOptions & GranteeSignerOptions,
) {
super(cometClient, signer, options);
if (grantorAddress === undefined) {
throw new Error("grantorAddress is required");
}
this.grantorAddress = grantorAddress;

if (granteeAddress === undefined) {
throw new Error("granteeAddress is required");
}
this.granteeAddress = granteeAddress;
}

public async signAndBroadcast(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee | "auto" | number,
memo = "",
): Promise<DeliverTxResponse> {
// Figure out if the signerAddress is a grantor
if (signerAddress === this.grantorAddress) {
signerAddress = this.granteeAddress;
// Wrap the signerAddress in a MsgExec
messages = [
{
typeUrl: "/cosmos.authz.v1beta1.MsgExec",
value: MsgExec.fromPartial({
grantee: this.granteeAddress,
msgs: messages.map((msg) => this.registry.encodeAsAny(msg)),
}),
},
];
}

return super.signAndBroadcast(signerAddress, messages, fee, memo);
}

public async sign(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo: string,
explicitSignerData?: SignerData,
): Promise<TxRaw> {
// Figure out if the signerAddress is a grantor
if (signerAddress === this.grantorAddress) {
signerAddress = this.granteeAddress;
// Wrap the signerAddress in a MsgExec
messages = [
{
typeUrl: "/cosmos.authz.v1beta1.MsgExec",
value: MsgExec.fromPartial({
grantee: signerAddress,
msgs: messages.map((msg) => this.registry.encodeAsAny(msg)),
}),
},
];
}

return super.sign(signerAddress, messages, fee, memo, explicitSignerData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface AbstraxionContextProps {
setAbstraxionError: React.Dispatch<React.SetStateAction<string>>;
abstraxionAccount: DirectSecp256k1HdWallet | undefined;
setAbstraxionAccount: React.Dispatch<DirectSecp256k1HdWallet | undefined>;
grantorAddress: string;
setGrantorAddress: React.Dispatch<React.SetStateAction<string>>;
contracts?: string[];
dashboardUrl?: string;
}
Expand All @@ -21,7 +23,7 @@ export const AbstraxionContext = createContext<AbstraxionContextProps>(
export const AbstraxionContextProvider = ({
children,
contracts,
dashboardUrl,
dashboardUrl = "https://dashboard.burnt.com",
}: {
children: ReactNode;
contracts?: string[];
Expand All @@ -33,6 +35,7 @@ export const AbstraxionContextProvider = ({
const [abstraxionAccount, setAbstraxionAccount] = useState<
DirectSecp256k1HdWallet | undefined
>(undefined);
const [grantorAddress, setGrantorAddress] = useState("");

return (
<AbstraxionContext.Provider
Expand All @@ -45,6 +48,8 @@ export const AbstraxionContextProvider = ({
setAbstraxionError,
abstraxionAccount,
setAbstraxionAccount,
grantorAddress,
setGrantorAddress,
contracts,
dashboardUrl,
}}
Expand Down
Loading

1 comment on commit 56b9f87

@vercel
Copy link

@vercel vercel bot commented on 56b9f87 Jan 17, 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.