Skip to content

Commit

Permalink
feat: improve components and demo app based on latest feedback (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
krzysu authored Jan 23, 2025
1 parent 28d6813 commit 9add6e6
Show file tree
Hide file tree
Showing 27 changed files with 620 additions and 265 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ node_modules
.vscode
tmp
.history
.github/copilot-instructions.md
.github/copilot-instructions.md
.clinerules
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CreateTransactionInput } from '@circle-fin/developer-controlled-wallets';
import {
Balance,
CreateTransactionInput,
} from '@circle-fin/developer-controlled-wallets';
import { TokenSelect } from '@circle-libs/circle-react-elements';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoaderCircle } from 'lucide-react';
import { useState } from 'react';
Expand All @@ -7,23 +11,22 @@ import z from 'zod';

import { ComplianceEngineText } from '~/components/ComplianceEngineText';
import { FormErrorText } from '~/components/FormErrorText';
import { TokenSelect } from '~/components/TokenSelect';
import { Button } from '~/components/ui/button';
import { Input } from '~/components/ui/input';
import { Textarea } from '~/components/ui/textarea';
import { FeeLevel } from '~/lib/constants';
import { CircleError, ErrorResponse } from '~/lib/responses';
import { Transaction, Wallet, WalletTokenBalance } from '~/lib/types';
import { Transaction, Wallet } from '~/lib/types';
import { isAddress, isNumber } from '~/lib/utils';

export interface ScreenAddressResult {
result?: boolean;
result: 'APPROVED' | 'DENIED';
}

export interface SendTransactionFormProps {
/** The wallet */
wallet: Wallet;
balances: WalletTokenBalance[];
balances: Balance[];
onSendTransaction: (data: CreateTransactionInput) => Promise<Transaction | CircleError>;
onScreenAddress?: (address: string) => Promise<ScreenAddressResult>;
onSent?: (data: Transaction) => void;
Expand All @@ -49,8 +52,9 @@ export function SendTransactionForm({
onSent,
onScreenAddress,
}: SendTransactionFormProps) {
const [screeningAddressResult, setScreeningAddressResult] =
useState<ScreenAddressResult>({});
const [screeningAddressResult, setScreeningAddressResult] = useState<
'APPROVED' | 'DENIED'
>();
const [requestError, setRequestError] = useState<string>('');
const [transactionData, setTransactionData] = useState({} as Transaction);

Expand Down Expand Up @@ -96,11 +100,11 @@ export function SendTransactionForm({
if (isAddress(address)) {
onScreenAddress(address)
.then((res: ScreenAddressResult) => {
setScreeningAddressResult(res);
setScreeningAddressResult(res.result);
})
.catch(console.error);
} else {
setScreeningAddressResult({});
setScreeningAddressResult('DENIED');
}
}
};
Expand All @@ -114,8 +118,8 @@ export function SendTransactionForm({
{...register('destinationAddress')}
onChange={onChangeAddress}
/>
{screeningAddressResult.result !== undefined ? (
<ComplianceEngineText result={screeningAddressResult.result} />
{screeningAddressResult !== undefined ? (
<ComplianceEngineText result={screeningAddressResult === 'APPROVED'} />
) : (
<FormErrorText message={errors.destinationAddress?.message} />
)}
Expand All @@ -125,11 +129,13 @@ export function SendTransactionForm({
<Controller
name="tokenId"
control={control}
defaultValue={balances.find((b) => b.token.symbol === 'USDC')?.token.id ?? ''}
render={({ field }) => (
<TokenSelect
balances={balances}
onValueChange={field.onChange}
className={`${errors.tokenId?.message ? 'border border-destructive' : ''}`}
error={errors.tokenId}
defaultToUsdc
/>
)}
/>
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions packages/circle-demo-webapp/app/lib/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { initiateDeveloperControlledWalletsClient } from '@circle-fin/developer-

const apiKey = process.env.CIRCLE_API_KEY!;
const entitySecret = process.env.CIRCLE_SECRET!;

export const sdk = initiateDeveloperControlledWalletsClient({
apiKey,
entitySecret,
Expand Down
35 changes: 0 additions & 35 deletions packages/circle-demo-webapp/app/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,6 @@ export interface WalletSet {
name?: string;
}

/**
* The token balance
* https://developers.circle.com/api-reference/w3s/developer-controlled-wallets/list-wallet-balance
*/
export interface WalletTokenBalance {
/** The token balance amount */
amount: string;
token: {
/** System-generated unique identifier of the resource. */
id: string;
/** Blockchain name of the specified token. */
name: string;
standard?: string;
/** The blockchain network that the resource is to be created on or is currently on. */
blockchain: TypeBlockchain;
/** Number of decimal places shown in the token amount. */
decimals: number;
/** Defines if the token is a native token of the specified blockchain. If TRUE, the token is a native token. */
isNative: boolean;
/** Blockchain symbol of the specified token. */
symbol: string;
/**
* Blockchain generated unique identifier, associated with wallet (account),
* smart contract or other blockchain objects.
*/
tokenAddress?: string;
/** Date and time the resource was last updated, in ISO-8601 UTC format. */
updateDate: string;
/** Date and time the resource was created, in ISO-8601 UTC format. */
createDate: string;
};
/** Date and time the resource was last updated, in ISO-8601 UTC format. */
updateDate: string;
}

/**
* NFTs for a wallet
* https://developers.circle.com/api-reference/w3s/developer-controlled-wallets/list-wallet-nfts
Expand Down
31 changes: 7 additions & 24 deletions packages/circle-demo-webapp/app/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,33 +47,16 @@ export async function callGetFetch<ReturnType>(
return (await res.json()) as ReturnType;
}

export type ValidInputTypes = Uint8Array | bigint | string | number | boolean;
export const isSolanaAddress = (value: string): boolean => {
return /^[1-9A-HJ-NP-Za-km-z]{43,44}$/.test(value);
};

export const isHexStrict = (hex: ValidInputTypes) =>
typeof hex === 'string' && /^((-)?0x[0-9a-f]+|(0x))$/i.test(hex);
const isEthAddress = (address: string): boolean => {
return /^0x[a-fA-F0-9]{40}$/.test(address);
};

export const isAddress = (value: string): boolean => {
let valueToCheck: string;

if (!isHexStrict(value)) {
valueToCheck = value.toLowerCase().startsWith('0x') ? value : `0x${value}`;
} else {
valueToCheck = value;
}

// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(valueToCheck)) {
return false;
}
// If it's ALL lowercase or ALL upppercase
if (
/^(0x|0X)?[0-9a-f]{40}$/.test(valueToCheck) ||
/^(0x|0X)?[0-9A-F]{40}$/.test(valueToCheck)
) {
return true;
// Otherwise check each case
}
return true;
return isSolanaAddress(value) || isEthAddress(value);
};

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
import { ActionFunction } from '@remix-run/node';
import { Blockchain } from '@circle-fin/developer-controlled-wallets';
import { ActionFunction, ActionFunctionArgs } from '@remix-run/node';

import { assertCircleErrorResponse, errorResponse } from '~/lib/server.responses';

export const action: ActionFunction = () => {
const apiKey = process.env.CIRCLE_API_KEY!;
const url = 'https://api.circle.com/v1/w3s/compliance/screening/addresses';

interface RequestBody {
address: string;
blockchain: Blockchain;
}

export interface ScreenAddressResult {
data: {
result: boolean;
};
}

export const action: ActionFunction = async ({ request }: ActionFunctionArgs) => {
const { address, blockchain } = (await request.json()) as RequestBody;

try {
return Response.json({
result: 'APPROVED',
id: {},
address: '0x1bf9ad0cc2ad298c69a2995aa806ee832788218c',
chain: 'MATIC-AMOY',
details: [
{
id: 'c4d1da72-111e-4d52-bdbf-2e74a2d803d5',
vendor: 'VENDOR',
response: {},
createDate: '2023-01-01T12:04:05Z',
},
],
alertId: {},
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
address,
chain: blockchain,
idempotencyKey: crypto.randomUUID(),
}),
});

const result = (await response.json()) as ScreenAddressResult;

return Response.json(result.data);
} catch (e: unknown) {
assertCircleErrorResponse(e);

Expand Down
Loading

0 comments on commit 9add6e6

Please sign in to comment.