Skip to content

Commit

Permalink
SCW-325: Crossmint Connect: Send transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
lluiserena committed Jan 31, 2024
1 parent d64729b commit 130db15
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 51 deletions.
36 changes: 28 additions & 8 deletions example/components/EVMConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,52 @@ import { useState } from "react";

import { BlockchainTypes, CrossmintEVMWalletAdapter, CrossmintEnvironment } from "@crossmint/connect";

function maticToWeiHex(matic: number): string {
const wei = matic * Math.pow(10, 18);
return `0x${wei.toString(16)}`;
}

export default function EVMConnectButton() {
const [address, setAddress] = useState<string | undefined>(undefined);
const [signedMessage, setSignedMessage] = useState<string | undefined>(undefined);
const [crossmintConnect, setCrossmintConnect] = useState<CrossmintEVMWalletAdapter | undefined>(undefined)
const [txHash, setTxHash] = useState<string | undefined>(undefined);
const [crossmintConnect, setCrossmintConnect] = useState<CrossmintEVMWalletAdapter | undefined>(undefined);

async function handleClick() {
// prompt user to trust your app
const crossmintConnect = new CrossmintEVMWalletAdapter({
chain: BlockchainTypes.POLYGON,
environment: CrossmintEnvironment.LOCAL,
maxTimeAutoConnectMs: 100000
});
const _address = await crossmintConnect.connect();


// store the result in react state
setAddress(_address);
setCrossmintConnect(crossmintConnect)
setCrossmintConnect(crossmintConnect);
}

const signMessage = async () => {
const message = "Hello, world!";
if (!crossmintConnect) {
throw new Error('not set')
throw new Error("not set");
}
// Sign the message
const signature = await crossmintConnect.signMessage(message);
setSignedMessage(signature);
}
};

const sendTransaction = async () => {
const tx = {
to: "0x3DdfBa136f0ca9E430ac444Aa426928E5088c03A",
value: maticToWeiHex(0.01),
};
if (!crossmintConnect) {
throw new Error("not set");
}
// Sign the message
const txHash = await crossmintConnect.sendTransaction(tx);
setTxHash(txHash);
};

const connected = address != null;

Expand All @@ -41,10 +58,13 @@ export default function EVMConnectButton() {
{connected ? `${address.slice(0, 6)}...` : "Connect"}
</button>
<div>
<button onClick={signMessage}>SignMessage</button>
<button onClick={signMessage}>Sign Message</button>
{signedMessage && <div>{signedMessage}</div>}
</div>
<div>
<button onClick={sendTransaction}>Send Transaction</button>
{txHash && <div>{txHash}</div>}
</div>
</>
);

}
8 changes: 4 additions & 4 deletions example/pages/evm-button.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import EVMConnectButton from "../components/EVMConnectButton"
import EVMConnectButton from "../components/EVMConnectButton";

const EVMConnectButtonPage = () => {
return <EVMConnectButton />
}
return <EVMConnectButton />;
};

export default EVMConnectButtonPage
export default EVMConnectButtonPage;
5 changes: 2 additions & 3 deletions example/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import type { NextPage } from "next";
import Head from "next/head";

import EVMConnectButton from "../components/EVMConnectButton";
import { SendTransaction } from "../components/SendTransaction";
import { SignMessage } from "../components/SignMessage";
import styles from "../styles/Home.module.css";
Expand All @@ -15,9 +16,7 @@ const Home: NextPage = () => {
<link rel="icon" href="/favicon.ico" />
</Head>

<WalletMultiButton />
<SignMessage />
<SendTransaction />
<EVMConnectButton />
</div>
);
};
Expand Down
36 changes: 18 additions & 18 deletions example/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
6 changes: 2 additions & 4 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@
eip1193-provider "^1.0.1"
js-sha3 "^0.8.0"

"@crossmint/connect@^0.0.9-alpha.8":
version "0.0.9-alpha.8"
resolved "https://registry.yarnpkg.com/@crossmint/connect/-/connect-0.0.9-alpha.8.tgz#36057082a99349099e2d26b61d64077dd3e43da9"
integrity sha512-BAsUQqob/bJ4PXCtu1No0gbSbVUTpcifNqK1AlSwx/MO0hTN0FK2sxpwiEh/HO16W/C36USi61zQkGZIIaa1mA==
"@crossmint/connect@file:..":
version "0.10.0-alpha.0"
dependencies:
"@solana/wallet-adapter-base" "^0.9.7"
"@solana/web3.js" "^1.47.3"
Expand Down
68 changes: 56 additions & 12 deletions src/CrossmintEmbed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import WindowAdapter from "./adapters/WindowAdapter";
import { ALLOWED_ORIGINS } from "./consts/origins";
import { CrossmintEmbedConfig } from "./types";
import { CrossmintEmbedConfig, TransactionRequest } from "./types";
import {
CrossmintEmbedRequestAccountData,
CrossmintEmbedRequestType,
Expand All @@ -22,7 +22,7 @@ export const EVM_CHAINS = [
"arbitrumnova",
"zkatana",
] as const;
export type EVMChain = (typeof EVM_CHAINS)[number];
export type EVMChain = typeof EVM_CHAINS[number];

export interface WalletProjection {
chain: EVMChain;
Expand All @@ -39,16 +39,16 @@ export interface EVMAAWalletProjection extends WalletProjection {

type LoginData = {
accounts?: (WalletProjection | EVMAAWalletProjection)[];
}
};

export default class CrossmintEmbed {
private _config: CrossmintEmbedConfig;

private get _frameUrl() {
const { environment, chain, projectId, forceWalletSelection } = this._config;
const projectIdQueryParam = projectId != null ? `&projectId=${projectId}` : "";
const forceWalletSelectionQueryParam = forceWalletSelection != null ? `&forceWalletSelection=${forceWalletSelection}` : "";

const forceWalletSelectionQueryParam =
forceWalletSelection != null ? `&forceWalletSelection=${forceWalletSelection}` : "";
return `${environment}/2023-06-09/frame?chain=${chain}${projectIdQueryParam}${forceWalletSelectionQueryParam}`;
}

Expand Down Expand Up @@ -80,23 +80,21 @@ export default class CrossmintEmbed {

return await new Promise<LoginData | null>(async (resolve, reject) => {
console.log("[crossmint-connect] Waiting login");
let loginData: LoginData | null = null

let loginData: LoginData | null = null;

const handleMessage = async (e: MessageEvent<any>) => {
if (!ALLOWED_ORIGINS.includes(e.origin)) return;

const { request, data } = e.data;

switch (request) {
case CrossmintEmbedRequestType.REQUEST_ACCOUNTS:
loginData = data as LoginData
loginData = data as LoginData;
// await StorageAdapter.storeAccountForChain(_account, this._config.chain);
crossmintWindow.controlledWindow?.close();
break;
case CrossmintEmbedRequestType.USER_REJECT:
console.log("[crossmint-connect] User rejected login");
loginData = null
loginData = null;
break;
default:
break;
Expand Down Expand Up @@ -139,7 +137,7 @@ export default class CrossmintEmbed {
case CrossmintEmbedRequestType.SIGN_MESSAGE:
const { signedMessage } = data;
if (walletId && deviceId) {
_signedMessage = signedMessage
_signedMessage = signedMessage;
} else {
_signedMessage = new Uint8Array(signedMessage.split(",").map(Number));
}
Expand Down Expand Up @@ -172,7 +170,7 @@ export default class CrossmintEmbed {
});
}

/// Does not support AA signatures because currently, if an AA wallet is connected, we only allow one wallet
/// Does not support AA signatures because currently, if an AA wallet is connected, we only allow one wallet
async signMessages(
message: Uint8Array,
publicKeys: string[]
Expand Down Expand Up @@ -230,6 +228,52 @@ export default class CrossmintEmbed {
});
}

async sendTransaction<T>(tx: TransactionRequest, walletId?: string, deviceId?: string) {
const crossmintWindow = new WindowAdapter();
crossmintWindow.init({ parentWindow: window, url: this._frameUrl });

return await new Promise<T | undefined | null>(async (resolve, reject) => {
console.log("[crossmint-connect] Waiting Send Transaction");

let _txHash: string | undefined | null = undefined;

const handleMessage = async (e: MessageEvent<any>) => {
if (!ALLOWED_ORIGINS.includes(e.origin)) return;

const { request, data } = e.data;

switch (request) {
case CrossmintEmbedRequestType.SEND_TRANSACTION:
const { txHash } = data;
_txHash = txHash;
crossmintWindow.controlledWindow?.close();
break;
case CrossmintEmbedRequestType.USER_REJECT:
console.log("[crossmint-connect] User rejected signMessage");
break;
default:
break;
}
};

window.addEventListener("message", handleMessage);

while (crossmintWindow.open && crossmintWindow.controlledWindow) {
await this.postMessage(
crossmintWindow.controlledWindow,
CrossmintEmbedRequestType.SEND_TRANSACTION,
{ tx, walletId, deviceId },
this._frameUrl
);

await sleep(100);
}

window.removeEventListener("message", handleMessage);
resolve(_txHash);
});
}

async cleanUp() {
// await StorageAdapter.clear();
}
Expand Down
27 changes: 25 additions & 2 deletions src/evm-wallet-adapter/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ethers } from "ethers";

import CrossmintEmbed, { EVMAAWalletProjection, WalletProjection } from "../CrossmintEmbed";
import { CROSSMINT_LOGO_21x21, CrossmintWalletName } from "../consts/branding";
import { BlockchainTypes, CrossmintEmbedConfig, CrossmintEmbedParams } from "../types";
import { BlockchainTypes, CrossmintEmbedConfig, CrossmintEmbedParams, TransactionRequest } from "../types";
import { buildConfig } from "../utils/config";

export class CrossmintEVMWalletAdapter {
Expand Down Expand Up @@ -51,7 +51,6 @@ export class CrossmintEVMWalletAdapter {
const client = CrossmintEmbed.init(this._config);

const loginData = await client.login();
console.log(loginData)
if (loginData?.accounts?.[0] == null) {
throw new WalletWindowClosedError("User rejected the request or closed the window");
}
Expand Down Expand Up @@ -137,6 +136,30 @@ export class CrossmintEVMWalletAdapter {
throw error;
}
}

async sendTransaction(tx: TransactionRequest): Promise<string> {
try {
if (!this._client || !this.connected || this._accounts == null) throw new Error("Not connected");

let txHash
const account = this._accounts[0]
if (isAAWallet(account)) {
txHash = await this._client.sendTransaction<TransactionRequest>(tx, account.walletId, account.deviceId);
} else {
txHash = await this._client.sendTransaction<Uint8Array>(tx);
}

if (txHash === null) {
throw new WalletWindowClosedError("User rejected the request");
}
if (txHash === undefined) {
throw new WalletSignTransactionError("User rejected the request or closed the window");
}
return isAAWallet(account) ? txHash as string : "";
} catch (error: any) {
throw error;
}
}
}

function isAAWallet(wallet: WalletProjection | EVMAAWalletProjection): wallet is EVMAAWalletProjection {
Expand Down
25 changes: 25 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { BigNumberish, BytesLike } from "ethers";
import { AccessListish } from "ethers/lib/utils";

export * from "./requests";
export * from "./config";

Expand All @@ -7,3 +10,25 @@ export enum BlockchainTypes {
POLYGON = "polygon",
CARDANO = "cardano",
}

export type TransactionRequest = {
to?: string,
from?: string,
nonce?: BigNumberish,

gasLimit?: BigNumberish,
gasPrice?: BigNumberish,

data?: BytesLike,
value?: BigNumberish,
chainId?: number

type?: number;
accessList?: AccessListish;

maxPriorityFeePerGas?: BigNumberish;
maxFeePerGas?: BigNumberish;

customData?: Record<string, any>;
ccipReadEnabled?: boolean;
}
1 change: 1 addition & 0 deletions src/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface SiteMetadata {
export enum CrossmintEmbedRequestType {
REQUEST_ACCOUNTS = "crossmint_requestAccounts",
SIGN_MESSAGE = "crossmint_signMessage",
SEND_TRANSACTION = "crossmint_sendTransaction",
USER_REJECT = "crossmint_userReject",
}

Expand Down

0 comments on commit 130db15

Please sign in to comment.