Skip to content

Commit

Permalink
feat: Transaction component - add calls support (#1220)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xAlec authored Sep 10, 2024
1 parent e77bdbf commit 8c6b4e3
Show file tree
Hide file tree
Showing 20 changed files with 698 additions and 179 deletions.
5 changes: 5 additions & 0 deletions .changeset/stupid-goats-hope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

**feat**: `Transaction` component - added calls support. by @0xAlec #1220
22 changes: 22 additions & 0 deletions playground/nextjs-app-router/components/AppProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export enum OnchainKitComponent {
Transaction = 'transaction',
Wallet = 'wallet',
}

export enum TransactionTypes {
Calls = 'calls',
Contracts = 'contracts',
}

export type Paymaster = {
url: string;
enabled: boolean;
Expand All @@ -23,6 +29,8 @@ type State = {
clearWalletType?: () => void;
chainId?: number;
setChainId?: (chainId: number) => void;
transactionType?: TransactionTypes;
setTransactionType?: (transactionType: TransactionTypes) => void;
paymasters?: Record<number, Paymaster>; // paymasters is per network
setPaymaster?: (chainId: number, url: string, enabled: boolean) => void;
};
Expand All @@ -42,6 +50,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
useState<OnchainKitComponent>();
const [walletType, setWalletTypeState] = useState<WalletPreference>();
const [chainId, setChainIdState] = useState<number>();
const [transactionType, setTransactionTypeState] = useState<TransactionTypes>(
TransactionTypes.Contracts,
);
const [paymasters, setPaymastersState] =
useState<Record<number, Paymaster>>();

Expand All @@ -51,6 +62,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const storedWalletType = localStorage.getItem('walletType');
const storedChainId = localStorage.getItem('chainId');
const storedPaymasters = localStorage.getItem('paymasters');
const storedTransactionType = localStorage.getItem('transactionType');

if (storedActiveComponent) {
setActiveComponent(storedActiveComponent as OnchainKitComponent);
Expand All @@ -64,6 +76,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
if (storedPaymasters) {
setPaymastersState(JSON.parse(storedPaymasters));
}
if (storedTransactionType) {
setTransactionTypeState(storedTransactionType as TransactionTypes);
}
}, []);

// Connect to wallet if walletType changes
Expand Down Expand Up @@ -106,6 +121,11 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
setPaymastersState(newObj);
};

const setTransactionType = (transactionType: TransactionTypes) => {
localStorage.setItem('transactionType', transactionType.toString());
setTransactionTypeState(transactionType);
};

return (
<AppContext.Provider
value={{
Expand All @@ -118,6 +138,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
setChainId,
paymasters,
setPaymaster,
transactionType,
setTransactionType,
}}
>
{children}
Expand Down
2 changes: 2 additions & 0 deletions playground/nextjs-app-router/components/Demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwapDemo from './demo/Swap';
import TransactionDemo from './demo/Transaction';
import WalletDemo from './demo/Wallet';
import { ActiveComponent } from './form/active-component';
import { TransactionOptions } from './form/transaction-options';

// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: TODO Refactor this component
function Demo() {
Expand Down Expand Up @@ -46,6 +47,7 @@ function Demo() {
<ActiveComponent />
<WalletType />
<Chain />
<TransactionOptions />
<PaymasterUrl />
</form>
<a
Expand Down
29 changes: 20 additions & 9 deletions playground/nextjs-app-router/components/demo/Transaction.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCapabilities } from '@/lib/hooks';
import { clickContracts } from '@/lib/transactions';
import { clickCalls, clickContracts } from '@/lib/transactions';
import {
Transaction,
TransactionButton,
Expand All @@ -13,32 +13,43 @@ import {
TransactionToastLabel,
} from '@coinbase/onchainkit/transaction';
import { useCallback, useContext, useEffect } from 'react';
import type { Address } from 'viem';
import { useAccount } from 'wagmi';
import { AppContext } from '../AppProvider';
import { AppContext, TransactionTypes } from '../AppProvider';

function TransactionDemo() {
const { chainId } = useContext(AppContext);
const account = useAccount();
const { chainId, transactionType } = useContext(AppContext);
const capabilities = useCapabilities();
const contracts = clickContracts;
const calls = clickCalls;
useEffect(() => {
console.log('Playground.Transaction.chainId:', chainId);
}, [chainId]);
const handleOnStatus = useCallback((status) => {
console.log('Playground.Transaction.onStatus:', status);
}, []);

useEffect(() => {
console.log('Playground.Transaction.transactionType:', transactionType);
if (transactionType === TransactionTypes.Calls) {
console.log('Playground.Transaction.calls:', calls);
} else {
console.log('Playground.Transaction.contracts:', contracts);
}
}, [transactionType, calls, contracts]);

return (
<div className="mx-auto grid w-1/2 gap-8">
<Transaction
chainId={chainId ?? 84532} // something breaks if we don't have default network?
address={account.address as Address}
contracts={contracts}
{...(transactionType === TransactionTypes.Calls
? { calls }
: { contracts })}
capabilities={capabilities}
onStatus={handleOnStatus}
>
<TransactionButton text="Click" />
<TransactionButton
text="Click"
disabled={!chainId && !transactionType}
/>
<TransactionSponsor />
<TransactionStatus>
<TransactionStatusLabel />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { useContext } from 'react';
import {
AppContext,
OnchainKitComponent,
TransactionTypes,
} from '../AppProvider';

export function TransactionOptions() {
const { activeComponent, transactionType, setTransactionType } =
useContext(AppContext);

return (
activeComponent === OnchainKitComponent.Transaction && (
<div className="grid gap-2">
<Label htmlFor="chain">Transaction Options</Label>
<Select
value={transactionType}
onValueChange={(value) =>
value ? setTransactionType?.(value as TransactionTypes) : value
}
>
<SelectTrigger>
<SelectValue placeholder="Select transaction type" />
</SelectTrigger>
<SelectContent>
<SelectItem value={TransactionTypes.Calls}>Calls</SelectItem>
<SelectItem value={TransactionTypes.Contracts}>
Contracts
</SelectItem>
</SelectContent>
</Select>
</div>
)
);
}
4 changes: 0 additions & 4 deletions playground/nextjs-app-router/components/transaction/Click.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,17 @@ import {
TransactionToastLabel,
} from '@coinbase/onchainkit/transaction';
import { useContext } from 'react';
import type { Address } from 'viem';
import { useAccount } from 'wagmi';
import { AppContext } from '../AppProvider';

export function Click() {
const { chainId } = useContext(AppContext);
const account = useAccount();
const capabilities = useCapabilities();
const contracts = clickContracts;
console.log('Transaction.click.chainId:', chainId);

return (
<Transaction
chainId={chainId ?? 84532} // something breaks if we don't have default network?
address={account.address as Address}
contracts={contracts}
capabilities={capabilities}
>
Expand Down
26 changes: 26 additions & 0 deletions playground/nextjs-app-router/lib/transactions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { encodeFunctionData } from 'viem';
import { clickAbi } from './abi/Click';
import { deployedContracts } from './constants';

Expand All @@ -8,4 +9,29 @@ export const clickContracts = [
functionName: 'click',
args: [],
},
{
address: deployedContracts[85432].click,
abi: clickAbi,
functionName: 'click',
args: [],
},
];

export const clickCalls = [
{
data: encodeFunctionData({
abi: clickAbi,
functionName: 'click',
args: [],
}),
to: deployedContracts[85432].click,
},
{
data: encodeFunctionData({
abi: clickAbi,
functionName: 'click',
args: [],
}),
to: deployedContracts[85432].click,
},
];
2 changes: 2 additions & 0 deletions src/transaction/components/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { TransactionReact } from '../types';
import { TransactionProvider } from './TransactionProvider';

export function Transaction({
calls,
capabilities,
chainId,
className,
Expand All @@ -21,6 +22,7 @@ export function Transaction({

return (
<TransactionProvider
calls={calls}
capabilities={capabilities}
chainId={chainId}
contracts={contracts}
Expand Down
8 changes: 4 additions & 4 deletions src/transaction/components/TransactionButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ describe('TransactionButton', () => {
expect(button).toBeDisabled();
});

it('should have disabled when contracts are missing', () => {
it('should have disabled when transactions are missing', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
contracts: undefined,
transactions: undefined,
lifeCycleStatus: { statusName: 'init', statusData: null },
});
const { getByRole } = render(<TransactionButton text="Submit" />);
Expand All @@ -118,9 +118,9 @@ describe('TransactionButton', () => {

it('should enable button when not in progress, not missing props, and not waiting for receipt', () => {
(useTransactionContext as vi.Mock).mockReturnValue({
contracts: {},
isLoading: false,
lifeCycleStatus: { statusName: 'init', statusData: null },
transactions: [],
transactionId: undefined,
transactionHash: undefined,
receipt: undefined,
Expand Down Expand Up @@ -157,7 +157,7 @@ describe('TransactionButton', () => {
const onSubmit = vi.fn();
(useTransactionContext as vi.Mock).mockReturnValue({
address: '123',
contracts: [{}],
transactions: [{}],
lifeCycleStatus: { statusName: 'init', statusData: null },
onSubmit,
receipt: undefined,
Expand Down
4 changes: 2 additions & 2 deletions src/transaction/components/TransactionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export function TransactionButton({
text: buttonText = 'Transact',
}: TransactionButtonReact) {
const {
contracts,
chainId,
errorMessage,
isLoading,
lifeCycleStatus,
onSubmit,
receipt,
transactions,
transactionHash,
transactionId,
} = useTransactionContext();
Expand All @@ -32,7 +32,7 @@ export function TransactionButton({

const isInProgress =
lifeCycleStatus.statusName === 'transactionPending' || isLoading;
const isMissingProps = !contracts || !address;
const isMissingProps = !transactions || !address;
const isWaitingForReceipt = !!transactionId || !!transactionHash;

const isDisabled =
Expand Down
Loading

0 comments on commit 8c6b4e3

Please sign in to comment.