Skip to content

Commit ec72a9d

Browse files
authored
Merge pull request #20 from macalinao/igm/token-dnum
Add basic token utilities
2 parents fea5458 + 7d1aa78 commit ec72a9d

File tree

37 files changed

+1787
-149
lines changed

37 files changed

+1787
-149
lines changed

.changeset/shaky-papayas-judge.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@macalinao/solana-batch-accounts-loader": patch
3+
"@macalinao/wallet-adapter-compat": patch
4+
"@macalinao/dataloader-es": patch
5+
"@macalinao/token-utils": patch
6+
"@macalinao/zod-solana": patch
7+
"example-dapp": patch
8+
"@macalinao/grill": patch
9+
---
10+
11+
Add sideEffects: false to pacakges

apps/example-dapp/src/components/ui/input-token-amount.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import type { TokenInfo } from "@macalinao/grill";
12
import type * as React from "react";
23
import { Button } from "@/components/ui/button";
34
import { Input } from "@/components/ui/input";
45
import { cn } from "@/lib/utils";
5-
import type { TokenInfo } from "@/types/token";
66

77
export interface InputTokenAmountProps {
88
token: TokenInfo;
@@ -56,9 +56,9 @@ export const InputTokenAmount: React.FC<InputTokenAmountProps> = ({
5656
<div className={cn("relative", className)}>
5757
<div className="flex items-center space-x-3 rounded-md border border-input bg-transparent p-3 shadow-sm transition-colors focus-within:border-ring focus-within:ring-ring/50 focus-within:ring-[3px]">
5858
<div className="flex items-center space-x-2 min-w-0 flex-1">
59-
{token.icon && (
59+
{token.iconURL && (
6060
<img
61-
src={token.icon}
61+
src={token.iconURL}
6262
alt={token.symbol}
6363
className="w-6 h-6 rounded-full flex-shrink-0"
6464
onError={(e) => {

apps/example-dapp/src/routes/examples/transfer-sol.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { zodResolver } from "@hookform/resolvers/zod";
22
import {
33
addressSchema,
4+
formatTokenAmount,
5+
NATIVE_SOL,
6+
parseTokenAmount,
47
useAccount,
58
useKitWallet,
69
useSendTX,
@@ -25,7 +28,6 @@ import {
2528
} from "@/components/ui/card";
2629
import { Input } from "@/components/ui/input";
2730
import { InputTokenAmount } from "@/components/ui/input-token-amount";
28-
import type { TokenInfo } from "@/types/token";
2931

3032
export const Route = createFileRoute("/examples/transfer-sol")({
3133
component: () => <TransferSolPage />,
@@ -61,14 +63,6 @@ const TransferSolPage: React.FC = () => {
6163
address: signer?.address ?? null,
6264
});
6365

64-
// SOL token info
65-
const solToken: TokenInfo = {
66-
address: "11111111111111111111111111111111",
67-
symbol: "SOL",
68-
decimals: 9,
69-
name: "Solana",
70-
};
71-
7266
const {
7367
register,
7468
handleSubmit,
@@ -93,6 +87,17 @@ const TransferSolPage: React.FC = () => {
9387
return Number(userAccount.lamports) / 1e9;
9488
}, [userAccount]);
9589

90+
const availableBalanceFormatted = useMemo(() => {
91+
if (!userAccount) {
92+
return "0";
93+
}
94+
const tokenAmount = {
95+
token: NATIVE_SOL,
96+
amount: [userAccount.lamports, NATIVE_SOL.decimals] as const,
97+
};
98+
return formatTokenAmount(tokenAmount, { symbol: true });
99+
}, [userAccount]);
100+
96101
const estimatedFee = 0.000005; // ~5000 lamports
97102

98103
const totalCost = useMemo(() => {
@@ -108,6 +113,9 @@ const TransferSolPage: React.FC = () => {
108113

109114
// The recipient is already an Address type from the zod schema
110115
const recipientAddress: Address = data.recipient;
116+
117+
// Parse the amount using parseTokenAmount
118+
const parsedAmount = parseTokenAmount(NATIVE_SOL, data.amount);
111119
const amount = Number.parseFloat(data.amount);
112120

113121
// Check balance
@@ -120,7 +128,7 @@ const TransferSolPage: React.FC = () => {
120128
const instruction = getTransferSolInstruction({
121129
source: signer,
122130
destination: recipientAddress,
123-
amount: lamports(BigInt(Math.floor(amount * 1e9))),
131+
amount: lamports(parsedAmount.amount[0]),
124132
});
125133

126134
const signature = await sendTX(`Transfer ${amount.toString()} SOL`, [
@@ -196,7 +204,7 @@ const TransferSolPage: React.FC = () => {
196204
Amount to Send
197205
</label>
198206
<InputTokenAmount
199-
token={solToken}
207+
token={NATIVE_SOL}
200208
value={watch("amount")}
201209
onChange={(value) => {
202210
setValue("amount", value);
@@ -219,9 +227,7 @@ const TransferSolPage: React.FC = () => {
219227
<span className="text-muted-foreground">
220228
Available Balance:
221229
</span>
222-
<span className="font-mono">
223-
{availableBalance.toFixed(9)} SOL
224-
</span>
230+
<span className="font-mono">{availableBalanceFormatted}</span>
225231
</div>
226232
<div className="flex justify-between">
227233
<span className="text-muted-foreground">Amount to Send:</span>

apps/example-dapp/src/routes/examples/wrapped-sol.tsx

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import {
2+
formatTokenAmount,
3+
NATIVE_SOL,
4+
parseTokenAmount,
5+
type TokenInfo,
26
useAccount,
37
useAssociatedTokenAccount,
48
useKitWallet,
@@ -19,8 +23,6 @@ import {
1923
CardTitle,
2024
} from "@/components/ui/card";
2125
import { InputTokenAmount } from "@/components/ui/input-token-amount";
22-
import type { TokenInfo } from "@/types/token";
23-
import { formatTokenAmount } from "@/utils/format-token-amount";
2426
import {
2527
getCloseAccountInstructions,
2628
getWrapSOLInstructions,
@@ -46,21 +48,16 @@ const WrappedSOLPage: React.FC = () => {
4648
});
4749

4850
// Token info definitions
49-
const solToken: TokenInfo = {
50-
address: "11111111111111111111111111111111", // Native SOL program ID
51-
symbol: "SOL",
52-
decimals: 9,
53-
name: "Solana",
54-
icon: "https://cryptologos.cc/logos/solana-sol-logo.png", // Using a CDN icon
55-
};
56-
57-
const wsolToken: TokenInfo = {
58-
address: "So11111111111111111111111111111111111111112", // wSOL mint address
59-
symbol: "wSOL",
60-
decimals: 9,
61-
name: "Wrapped SOL",
62-
icon: "https://cryptologos.cc/logos/solana-sol-logo.png", // Same icon as SOL
63-
};
51+
const wsolToken: TokenInfo = useMemo(
52+
() => ({
53+
mint: WSOL_MINT, // wSOL mint address
54+
symbol: "wSOL",
55+
decimals: 9,
56+
name: "Wrapped SOL",
57+
iconURL: "https://cryptologos.cc/logos/solana-sol-logo.png", // Same icon as SOL
58+
}),
59+
[],
60+
);
6461

6562
// Calculate available balances
6663
const solBalance = useMemo(() => {
@@ -75,8 +72,13 @@ const WrappedSOLPage: React.FC = () => {
7572
if (!wsolTokenAccount) {
7673
return "0";
7774
}
78-
return formatTokenAmount(wsolTokenAccount.data.amount, 9);
79-
}, [wsolTokenAccount]);
75+
// Convert the bigint amount to a TokenAmount
76+
const tokenAmount = {
77+
token: wsolToken,
78+
amount: [wsolTokenAccount.data.amount, wsolToken.decimals] as const,
79+
};
80+
return formatTokenAmount(tokenAmount);
81+
}, [wsolTokenAccount, wsolToken]);
8082

8183
// Handle wrap SOL action
8284
const handleWrapSOL = async (): Promise<void> => {
@@ -87,10 +89,9 @@ const WrappedSOLPage: React.FC = () => {
8789

8890
setIsWrapping(true);
8991
try {
90-
// Convert amount to lamports (1 SOL = 1e9 lamports)
91-
const lamportAmount = BigInt(
92-
Math.floor(Number.parseFloat(wrapAmount) * 1e9),
93-
);
92+
// Parse the amount using parseTokenAmount
93+
const parsedAmount = parseTokenAmount(wsolToken, wrapAmount);
94+
const lamportAmount = parsedAmount.amount[0];
9495

9596
// Get the wrap instructions
9697
const instructions = await getWrapSOLInstructions(signer, lamportAmount);
@@ -228,7 +229,7 @@ const WrappedSOLPage: React.FC = () => {
228229
<div className="space-y-2">
229230
<div className="text-sm font-medium">From</div>
230231
<InputTokenAmount
231-
token={solToken}
232+
token={NATIVE_SOL}
232233
value={wrapAmount}
233234
onChange={setWrapAmount}
234235
maxAmount={solBalance}
@@ -249,9 +250,9 @@ const WrappedSOLPage: React.FC = () => {
249250
<div className="text-sm font-medium">To</div>
250251
<div className="flex items-center justify-between p-3 rounded-md border bg-muted/30">
251252
<div className="flex items-center space-x-2">
252-
{wsolToken.icon && (
253+
{wsolToken.iconURL && (
253254
<img
254-
src={wsolToken.icon}
255+
src={wsolToken.iconURL}
255256
alt={wsolToken.symbol}
256257
className="w-6 h-6 rounded-full"
257258
onError={(e) => {

apps/example-dapp/src/types/token.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

apps/example-dapp/src/utils/format-token-amount.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

bun.lock

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"workspaces": {
44
"": {
55
"name": "@macalinao/grill-monorepo",
6+
"dependencies": {
7+
"dnum": "^2.15.0",
8+
},
69
"devDependencies": {
710
"@biomejs/biome": "^2.1.4",
811
"@changesets/cli": "^2.29.5",
@@ -124,6 +127,7 @@
124127
"@macalinao/dataloader-es": "workspace:*",
125128
"@macalinao/solana-batch-accounts-loader": "workspace:*",
126129
"@macalinao/token-metadata-client": "workspace:*",
130+
"@macalinao/token-utils": "workspace:*",
127131
"@macalinao/zod-solana": "workspace:*",
128132
"@solana-program/token": "^0.5.1",
129133
"@solana/kit": "catalog:",
@@ -204,6 +208,23 @@
204208
"@solana/kit": "*",
205209
},
206210
},
211+
"packages/token-utils": {
212+
"name": "@macalinao/token-utils",
213+
"version": "0.1.0",
214+
"dependencies": {
215+
"dnum": "^2.15.0",
216+
},
217+
"devDependencies": {
218+
"@macalinao/eslint-config": "catalog:",
219+
"@macalinao/tsconfig": "catalog:",
220+
"@solana/kit": "catalog:",
221+
"@types/bun": "^1.2.19",
222+
"typescript": "catalog:",
223+
},
224+
"peerDependencies": {
225+
"@solana/kit": "catalog:",
226+
},
227+
},
207228
"packages/wallet-adapter-compat": {
208229
"name": "@macalinao/wallet-adapter-compat",
209230
"version": "0.2.4",
@@ -623,6 +644,8 @@
623644

624645
"@macalinao/token-metadata-client": ["@macalinao/token-metadata-client@workspace:packages/token-metadata-client"],
625646

647+
"@macalinao/token-utils": ["@macalinao/token-utils@workspace:packages/token-utils"],
648+
626649
"@macalinao/tsconfig": ["@macalinao/[email protected]", "", {}, "sha512-pw+l4EiyGQXwffxMQxqvJghLEm5fKGXZ3BAUYyitmkrtyD0D517RwwooSBrjCXP+sl3oefpHzUnZjowYTQd8wg=="],
627650

628651
"@macalinao/wallet-adapter-compat": ["@macalinao/wallet-adapter-compat@workspace:packages/wallet-adapter-compat"],
@@ -1711,6 +1734,8 @@
17111734

17121735
"dir-glob": ["[email protected]", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
17131736

1737+
"dnum": ["[email protected]", "", { "dependencies": { "from-exponential": "^1.1.1" } }, "sha512-7wyYHrTq32laVKSQSuWsUysCY8kMjFjfgbZmcVECROyeT3yvwF9oaSDFWL1s1s8nAfhhERC04LdtFVbmx8xRWQ=="],
1738+
17141739
"doctrine": ["[email protected]", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
17151740

17161741
"dom-accessibility-api": ["[email protected]", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
@@ -1913,6 +1938,8 @@
19131938

19141939
"fresh": ["[email protected]", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
19151940

1941+
"from-exponential": ["[email protected]", "", {}, "sha512-VBE7f5OVnYwdgB3LHa+Qo29h8qVpxhVO9Trlc+AWm+/XNAgks1tAwMFHb33mjeiof77GglsJzeYF7OqXrROP/A=="],
1942+
19161943
"fs-extra": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
19171944

19181945
"fs.realpath": ["[email protected]", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,8 @@
6363
"overrides": {
6464
"react": "^19.1.1",
6565
"react-dom": "^19.1.1"
66+
},
67+
"dependencies": {
68+
"dnum": "^2.15.0"
6669
}
6770
}

packages/codama-instruction-accounts-dedupe-visitor/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# @macalinao/codama-instruction-accounts-dedupe-visitor
22

3+
[![npm version](https://img.shields.io/npm/v/@macalinao/codama-instruction-accounts-dedupe-visitor.svg)](https://www.npmjs.com/package/@macalinao/codama-instruction-accounts-dedupe-visitor)
4+
35
Codama visitor for deduplicating and flattening instruction accounts from Anchor IDL. This package helps handle nested account structures in Anchor programs by flattening them into a single-level structure with properly prefixed names.
46

57
## Why This Package Exists

packages/codama-renderers-js-esm/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# @macalinao/codama-renderers-js-esm
22

3+
[![npm version](https://img.shields.io/npm/v/@macalinao/codama-renderers-js-esm.svg)](https://www.npmjs.com/package/@macalinao/codama-renderers-js-esm)
4+
35
ESM-native TypeScript renderer for [Codama](https://github.com/codama-idl/codama) JavaScript code generation. This package extends `@codama/renderers-js` to produce fully ESM-compatible TypeScript/JavaScript code.
46

57
## Why This Package Exists

0 commit comments

Comments
 (0)