Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/wt 1684 buy fees #889

Merged
merged 20 commits into from
Sep 26, 2023
Merged
13 changes: 10 additions & 3 deletions packages/checkout/sdk-sample-app/src/components/Buy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { Web3Provider } from '@ethersproject/providers';
import LoadingButton from './LoadingButton';
import { useEffect, useState } from 'react';
import { SuccessMessage, ErrorMessage } from './messages';
import { Box, FormControl, TextInput } from '@biom3/react';
import { Body, Box, FormControl, TextInput } from '@biom3/react';
import { Orderbook } from '@imtbl/orderbook';
import { Environment } from '@imtbl/config';

interface BuyProps {
checkout: Checkout;
Expand Down Expand Up @@ -32,12 +34,14 @@ export default function Buy({ checkout, provider }: BuyProps) {
setError(null);
setLoading(true);
try {
await checkout.buy({
const buyResult = await checkout.buy({
provider,
orderId,
orders: [{id: orderId, takerFees: [{amount: {percentageDecimal: 0.01}, recipient: '0x96654086969DCaa88933E753Aa52d46EAB269Ff7'}]}],
});
console.log(buyResult);
setLoading(false);
} catch (err: any) {
console.log(err);
setError(err);
setLoading(false);
console.log(err.message);
Expand Down Expand Up @@ -67,9 +71,12 @@ export default function Buy({ checkout, provider }: BuyProps) {
)}
</FormControl>
<br />
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'base.spacing.x2'}}>
<LoadingButton onClick={buyClick} loading={loading}>
Buy
</LoadingButton>
<Body size="xSmall">(adds 1% taker fee)</Body>
</Box>
{!error && <SuccessMessage>Buy success.</SuccessMessage>}
{error && (
<ErrorMessage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function Cancel({ checkout, provider }: CancelProps) {
try {
await checkout.cancel({
provider,
orderId,
orderIds: [orderId],
});
setLoading(false);
} catch (err: any) {
Expand Down
85 changes: 85 additions & 0 deletions packages/checkout/sdk-sample-app/src/components/Listings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Checkout, BuyResult, BuyStatusType, BuyStatus } from '@imtbl/checkout-sdk';
import { Web3Provider } from '@ethersproject/providers';
import LoadingButton from './LoadingButton';
import { useEffect, useState } from 'react';
import { SuccessMessage, ErrorMessage } from './messages';
import { Body, Box, FormControl, TextInput } from '@biom3/react';
import { OrderStatus, Orderbook } from '@imtbl/orderbook';
import { Environment } from '@imtbl/config';

interface ListingsProps {
checkout: Checkout;
provider: Web3Provider | undefined;
}

export default function Listings({ checkout, provider }: ListingsProps) {
const [sellContractAddress, setSellContractAddress] = useState<string>('');
const [orderIdError, setAddressError] = useState<any>(null);
const [error, setError] = useState<any>(null);
const [loading, setLoading] = useState<boolean>(false);

async function getListingsClick() {
if (!sellContractAddress) {
setAddressError('Please enter an collection address');
return;
}
if (!checkout) {
console.error('missing checkout, please connect first');
return;
}
if (!provider) {
console.error('missing provider, please connect first');
return;
}
setError(null);
setLoading(true);
try {
const orderBook = new Orderbook({baseConfig: {environment: checkout.config.environment}})
const listingsResult = await orderBook.listListings({
sellItemContractAddress: sellContractAddress,
status: OrderStatus.ACTIVE
})
console.log('listings:', listingsResult)
setLoading(false);
} catch (err: any) {
setError(err);
setLoading(false);
console.log(err.message);
console.log(err.type);
console.log(err.data);
console.log(err.stack);
}
}

const updateSellContractAddress = (event: any) => {
setSellContractAddress(event.target.value);
setAddressError('');
}

useEffect(() => {
setError(null);
setLoading(false);
}, [checkout]);

return (
<Box>
<FormControl validationStatus={orderIdError ? 'error' : 'success'} >
<FormControl.Label>Sell Collection Address</FormControl.Label>
<TextInput onChange={updateSellContractAddress} />
{orderIdError && (
<FormControl.Validation>{orderIdError}</FormControl.Validation>
)}
</FormControl>
<br />
<LoadingButton onClick={getListingsClick} loading={loading}>
Get Listings
</LoadingButton>
{!error && <SuccessMessage>Get listings success. Check console for result</SuccessMessage>}
{error && (
<ErrorMessage>
{error.message}. Check console logs for more details.
</ErrorMessage>
)}
</Box>
);
}
11 changes: 7 additions & 4 deletions packages/checkout/sdk-sample-app/src/components/Sell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Web3Provider } from '@ethersproject/providers';
import LoadingButton from './LoadingButton';
import { useEffect, useState } from 'react';
import { SuccessMessage, ErrorMessage } from './messages';
import { Box, FormControl, Select, TextInput, Option, OptionKey } from '@biom3/react';
import { Box, FormControl, Select, TextInput, Option, OptionKey, Body } from '@biom3/react';
import { utils } from 'ethers';

interface SellProps {
Expand Down Expand Up @@ -226,9 +226,12 @@ export default function Sell({ checkout, provider }: SellProps) {
</FormControl>
{tokenForm()}
<br />
<LoadingButton onClick={sellClick} loading={loading}>
Sell
</LoadingButton>
<Box sx={{display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 'base.spacing.x2'}}>
<LoadingButton onClick={sellClick} loading={loading}>
Sell
</LoadingButton>
<Body size="xSmall">(adds 2.5% maker fee)</Body>
</Box>
{(!error && success) && <SuccessMessage>Sell success.</SuccessMessage>}
{error && (
<ErrorMessage>
Expand Down
13 changes: 13 additions & 0 deletions packages/checkout/sdk-sample-app/src/pages/SmartCheckout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Buy from '../components/Buy';
import { SmartCheckoutForm } from '../components/SmartCheckoutForm';
import Sell from '../components/Sell';
import Cancel from '../components/Cancel';
import Listings from '../components/Listings';

export default function SmartCheckout() {
const [environment, setEnvironment] = useState(Environment.SANDBOX);
Expand Down Expand Up @@ -87,6 +88,18 @@ export default function SmartCheckout() {
</Divider>
<CheckConnection checkout={checkout} provider={provider} />

<Divider
sx={{
marginTop: 'base.spacing.x6',
marginBottom: 'base.spacing.x2',
}}
>
Get Active Listings for Collection
</Divider>
<Listings
checkout={checkout}
provider={provider} />

<Divider
sx={{
marginTop: 'base.spacing.x6',
Expand Down
12 changes: 6 additions & 6 deletions packages/checkout/sdk/src/Checkout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,11 +453,11 @@ describe('Connect', () => {

await checkout.buy({
provider,
orderId: '1',
orders: [{ id: '1', takerFees: [] }],
});

expect(buy).toBeCalledTimes(1);
expect(buy).toBeCalledWith(checkout.config, provider, '1');
expect(buy).toBeCalledWith(checkout.config, provider, [{ id: '1', takerFees: [] }]);
});

it('should throw error for buy function if is production', async () => {
Expand All @@ -471,7 +471,7 @@ describe('Connect', () => {

await expect(checkout.buy({
provider,
orderId: '1',
orders: [{ id: '1' }],
})).rejects.toThrow('This endpoint is not currently available.');

expect(buy).toBeCalledTimes(0);
Expand Down Expand Up @@ -562,14 +562,14 @@ describe('Connect', () => {

await checkout.cancel({
provider,
orderId: '1234',
orderIds: ['1234'],
});

expect(cancel).toBeCalledTimes(1);
expect(cancel).toBeCalledWith(
checkout.config,
provider,
'1234',
['1234'],
);
});

Expand All @@ -583,7 +583,7 @@ describe('Connect', () => {

await expect(checkout.cancel({
provider,
orderId: '1234',
orderIds: ['1234'],
})).rejects.toThrow('This endpoint is not currently available.');

expect(cancel).toBeCalledTimes(0);
Expand Down
12 changes: 10 additions & 2 deletions packages/checkout/sdk/src/Checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,17 @@ export class Checkout {
// eslint-disable-next-line no-console
console.warn('This endpoint is currently under construction.');

if (params.orders.length > 1) {
// eslint-disable-next-line no-console
console.warn('This endpoint currently only actions the first order in the array.');
}

const web3Provider = await provider.validateProvider(
this.config,
params.provider,
);

await buy.buy(this.config, web3Provider, params.orderId);
await buy.buy(this.config, web3Provider, params.orders);
}

/**
Expand Down Expand Up @@ -343,12 +348,15 @@ export class Checkout {
// eslint-disable-next-line no-console
console.warn('This endpoint is currently under construction.');

// eslint-disable-next-line no-console
console.warn('This endpoint currently only actions the first order in the array.');

const web3Provider = await provider.validateProvider(
this.config,
params.provider,
);

await cancel.cancel(this.config, web3Provider, params.orderId);
await cancel.cancel(this.config, web3Provider, params.orderIds);
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/checkout/sdk/src/errors/checkoutError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum CheckoutErrorType {
CANCEL_ORDER_LISTING_ERROR = 'CANCEL_ORDER_LISTING_ERROR',
PREPARE_ORDER_LISTING_ERROR = 'PREPARE_ORDER_LISTING_ERROR',
CREATE_ORDER_LISTING_ERROR = 'CREATE_ORDER_LISTING_ERROR',
FULFILL_ORDER_LISTING_ERROR = 'FULFILL_ORDER_LISTING_ERROR',
SWITCH_NETWORK_UNSUPPORTED = 'SWITCH_NETWORK_UNSUPPORTED',
GET_ERC20_ALLOWANCE_ERROR = 'GET_ERC20_ALLOWANCE_ERROR',
GET_ERC721_ALLOWANCE_ERROR = 'GET_ERC721_ALLOWANCE_ERROR',
Expand Down
10 changes: 10 additions & 0 deletions packages/checkout/sdk/src/instance/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Provider } from '@ethersproject/providers';
import { Contract, ContractInterface, Signer } from 'ethers';

export function getTokenContract(
address: string,
contractInterface: ContractInterface,
signerOrProvider: Provider | Signer | undefined,
) {
return new Contract(address, contractInterface, signerOrProvider);
}
1 change: 1 addition & 0 deletions packages/checkout/sdk/src/instance/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './instance';
export * from './contract';
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import {
} from '@imtbl/orderbook';
import { PopulatedTransaction, TypedDataDomain } from 'ethers';
import {
getUnsignedTransactions,
getUnsignedERC20ApprovalTransactions,
getUnsignedERC721Transactions,
ZacharyCouchman marked this conversation as resolved.
Show resolved Hide resolved
getUnsignedFulfilmentTransactions,
getUnsignedMessage,
} from './getUnsignedActions';

describe('getUnsignedActions', () => {
describe('getUnsignedTransactions', () => {
describe('getUnsignedERC721Transactions', () => {
it('should get the unsigned transactions', async () => {
const actions: Action[] = [
{
Expand Down Expand Up @@ -51,7 +53,7 @@ describe('getUnsignedActions', () => {
},
];

await expect(getUnsignedTransactions(actions)).resolves.toEqual({
await expect(getUnsignedERC721Transactions(actions)).resolves.toEqual({
approvalTransactions: [{ from: '0xAPPROVAL1' }, { from: '0xAPPROVAL2' }],
fulfilmentTransactions: [{ from: '0xTRANSACTION1' }, { from: '0xTRANSACTION2' }],
});
Expand All @@ -60,13 +62,75 @@ describe('getUnsignedActions', () => {
it('should return empty arrays if no transactions or signable messages', async () => {
const actions: Action[] = [];

await expect(getUnsignedTransactions(actions)).resolves.toEqual({
await expect(getUnsignedERC721Transactions(actions)).resolves.toEqual({
approvalTransactions: [],
fulfilmentTransactions: [],
});
});
});

describe('getUnsignedERC20ApprovalTransactions', () => {
it('should get the unsigned erc20 approval transactions', async () => {
const actions: Action[] = [
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.APPROVAL,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xAPPROVAL1' } as PopulatedTransaction),
},
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.APPROVAL,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xAPPROVAL2' } as PopulatedTransaction),
},
ZacharyCouchman marked this conversation as resolved.
Show resolved Hide resolved
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.FULFILL_ORDER,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xTRANSACTION1' } as PopulatedTransaction),
},
];

await expect(getUnsignedERC20ApprovalTransactions(actions)).resolves
.toEqual([{ from: '0xAPPROVAL1' }, { from: '0xAPPROVAL2' }]);
});

it('should return an empty arrays if no transactions', async () => {
const actions: Action[] = [];

await expect(getUnsignedERC20ApprovalTransactions(actions)).resolves.toEqual([]);
});
});

describe('getUnsignedFulfilmentTransactions', () => {
it('should get the unsigned fulfil transactions', async () => {
const actions: Action[] = [
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.FULFILL_ORDER,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xTRANSACTION1' } as PopulatedTransaction),
},
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.FULFILL_ORDER,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xTRANSACTION2' } as PopulatedTransaction),
ZacharyCouchman marked this conversation as resolved.
Show resolved Hide resolved
},
{
type: ActionType.TRANSACTION,
purpose: TransactionPurpose.APPROVAL,
buildTransaction: jest.fn().mockResolvedValue({ from: '0xAPPROVAL1' } as PopulatedTransaction),
},
];

await expect(getUnsignedFulfilmentTransactions(actions)).resolves
.toEqual([{ from: '0xTRANSACTION1' }, { from: '0xTRANSACTION2' }]);
});

it('should return an empty arrays if no transactions', async () => {
const actions: Action[] = [];

await expect(getUnsignedFulfilmentTransactions(actions)).resolves.toEqual([]);
});
});

describe('getUnsignedMessage', () => {
it('should get the signed message', () => {
const actions: Action[] = [
Expand Down