Skip to content

Commit

Permalink
add endpoint and example for sending native tokens (#48)
Browse files Browse the repository at this point in the history
* add endpoint and example for sending native tokens

* approve native send recipient address
  • Loading branch information
alecananian authored Jun 18, 2024
1 parent 5284fdf commit a95c2a9
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 49 deletions.
67 changes: 67 additions & 0 deletions apps/api/src/routes/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import "../middleware/auth";
import "../middleware/chain";
import "../middleware/swagger";
import {
type CreateSendNativeTransactionBody,
type CreateSendNativeTransactionReply,
type CreateTransactionBody,
type CreateTransactionReply,
type ErrorReply,
type ReadTransactionParams,
type ReadTransactionReply,
createSendNativeTransactionBodySchema,
createSendNativeTransactionReplySchema,
createTransactionBodySchema,
createTransactionReplySchema,
readTransactionReplySchema,
Expand Down Expand Up @@ -91,6 +95,69 @@ export const transactionsRoutes =
},
);

app.post<{
Body: CreateSendNativeTransactionBody;
Reply: CreateSendNativeTransactionReply | ErrorReply;
}>(
"/transactions/send-native",
{
schema: {
summary: "Send native tokens",
description:
"Send the chain's native (gas) token to the provided recipient",
security: [{ authToken: [] }],
body: createSendNativeTransactionBodySchema,
response: {
200: createSendNativeTransactionReplySchema,
},
},
},
async (req, reply) => {
const {
chainId,
userAddress,
authError,
body: { to, amount, backendWallet: overrideBackendWallet },
} = req;
if (!userAddress) {
throw new TdkError({
code: "TDK_UNAUTHORIZED",
message: "Unauthorized",
data: { authError },
});
}

const backendWallet =
overrideBackendWallet ?? env.DEFAULT_BACKEND_WALLET;
try {
const { result } = await engine.backendWallet.sendTransaction(
chainId.toString(),
backendWallet,
{
toAddress: to,
value: amount,
data: "0x",
},
false,
userAddress,
);
reply.send(result);
} catch (err) {
throw new TdkError({
code: "TDK_CREATE_TRANSACTION",
message: `Error creating native send transaction: ${parseEngineErrorMessage(err) ?? "Unknown error"}`,
data: {
chainId,
backendWallet,
userAddress,
to,
amount,
},
});
}
},
);

app.get<{
Params: ReadTransactionParams;
Reply: ReadTransactionReply | ErrorReply;
Expand Down
25 changes: 25 additions & 0 deletions apps/api/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,25 @@ export const createTransactionReplySchema = Type.Object({
}),
});

export const createSendNativeTransactionBodySchema = Type.Object({
to: Type.String({
description: "The recipient address",
examples: [EXAMPLE_WALLET_ADDRESS],
}),
amount: Type.String({
description: "The amount to send, in wei",
examples: ["1000000000000000000"],
}),
backendWallet: Type.Optional(Type.String()),
});

export const createSendNativeTransactionReplySchema = Type.Object({
queueId: Type.String({
description: "The transaction queue ID",
examples: [EXAMPLE_QUEUE_ID],
}),
});

const readTransactionParamsSchema = Type.Object({
queueId: Type.String(),
});
Expand All @@ -437,6 +456,12 @@ export type CreateTransactionBody = Static<typeof createTransactionBodySchema>;
export type CreateTransactionReply = Static<
typeof createTransactionReplySchema
>;
export type CreateSendNativeTransactionBody = Static<
typeof createSendNativeTransactionBodySchema
>;
export type CreateSendNativeTransactionReply = Static<
typeof createSendNativeTransactionReplySchema
>;
export type ReadTransactionParams = Static<typeof readTransactionParamsSchema>;
export type ReadTransactionReply = Static<typeof readTransactionReplySchema>;

Expand Down
3 changes: 2 additions & 1 deletion examples/connect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"dependencies": {
"@treasure-dev/tdk-react": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"viem": "^2.15.1"
},
"devDependencies": {
"@treasure-project/tailwind-config": "^2.1.0",
Expand Down
58 changes: 40 additions & 18 deletions examples/connect/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import {
type AddressString,
Button,
ConnectButton,
formatAmount,
getContractAddress,
useTreasure,
} from "@treasure-dev/tdk-react";
import { formatEther, parseEther } from "viem";

export const App = () => {
const { tdk, chainId, user } = useTreasure();
const magicAddress = getContractAddress(chainId, "MAGIC");

const handleMintMagic = async () => {
const handleMintMagic = async (amount: number) => {
if (!user?.smartAccountAddress) {
return;
}
Expand Down Expand Up @@ -42,7 +44,7 @@ export const App = () => {
functionName: "mint",
args: [
user.smartAccountAddress as AddressString,
1000000000000000000000n,
parseEther(amount.toString()),
],
},
{ includeAbi: true },
Expand All @@ -52,20 +54,20 @@ export const App = () => {
}
};

// const handleSendEth = async () => {
// if (!user?.smartAccountAddress) {
// return;
// }
const handleSendEth = async (amount: number) => {
if (!user?.smartAccountAddress) {
return;
}

// try {
// await tdk.transaction.sendNative({
// to: "0x55d0cf68a1afe0932aff6f36c87efa703508191c",
// amount: 100000000000000n,
// });
// } catch (err) {
// console.error("Error sending ETH:", err);
// }
// };
try {
await tdk.transaction.sendNative({
to: "0xE647b2c46365741e85268ceD243113d08F7E00B8",
amount: parseEther(amount.toString()),
});
} catch (err) {
console.error("Error sending ETH:", err);
}
};

return (
<div className="mx-auto max-w-5xl space-y-8 p-8">
Expand All @@ -87,7 +89,13 @@ export const App = () => {
(a, b) => Number(b.endTimestamp) - Number(a.endTimestamp),
)
.map(
({ signer, isAdmin, endTimestamp, approvedTargets }) => (
({
signer,
isAdmin,
endTimestamp,
approvedTargets,
nativeTokenLimitPerTransaction,
}) => (
<li key={signer}>
<p className="font-medium">
{signer}{" "}
Expand Down Expand Up @@ -117,6 +125,16 @@ export const App = () => {
))}
</ul>
</div>
<p>
<span className="font-medium">
Native token limit per transaction:
</span>{" "}
{formatAmount(
formatEther(
BigInt(nativeTokenLimitPerTransaction),
),
)}
</p>
</>
) : null}
</li>
Expand All @@ -130,8 +148,12 @@ export const App = () => {
<div className="space-y-1">
<h1 className="text-xl font-medium">Test Transactions</h1>
<div className="flex flex-wrap gap-2">
<Button onClick={handleMintMagic}>Mint 1,000 MAGIC</Button>
{/* <Button onClick={handleSendEth}>Send 0.0001 ETH</Button> */}
<Button onClick={() => handleMintMagic(1000)}>
Mint 1,000 MAGIC
</Button>
<Button onClick={() => handleSendEth(0.0001)}>
Send 0.0001 ETH
</Button>
</div>
</div>
</>
Expand Down
8 changes: 6 additions & 2 deletions examples/connect/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TreasureProvider } from "@treasure-dev/tdk-react";
import "@treasure-dev/tdk-react/dist/index.css";
import "@treasure-project/tailwind-config/fonts.css";
import ReactDOM from "react-dom/client";
import { parseEther } from "viem";

import { App } from "./App.tsx";
import "./index.css";
Expand All @@ -15,8 +16,11 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
onAuthenticated={async (_, startUserSession) => {
await startUserSession({
chainId: 421614,
approvedTargets: ["0x55d0cf68a1afe0932aff6f36c87efa703508191c"],
nativeTokenLimitPerTransaction: 1,
approvedTargets: [
"0x55d0cf68a1afe0932aff6f36c87efa703508191c",
"0xE647b2c46365741e85268ceD243113d08F7E00B8",
],
nativeTokenLimitPerTransaction: parseEther("1"),
});
}}
>
Expand Down
21 changes: 11 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 31 additions & 8 deletions packages/core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import type {
} from "abitype";

import type {
CreateSendNativeTransactionBody,
CreateSendNativeTransactionReply,
CreateTransactionBody,
LoginBody,
LoginReply,
Expand Down Expand Up @@ -176,21 +178,44 @@ export class TDKAPI {
args: params.args as any,
backendWallet: params.backendWallet ?? this.backendWallet,
});
if (!waitForCompletion) {
return result;
}

return waitForCompletion ? this.transaction.wait(result.queueId) : result;
},
sendNative: async (
params: {
to: string;
amount: bigint;
backendWallet?: string;
},
{
waitForCompletion = true,
}: { includeAbi?: boolean; waitForCompletion?: boolean } = {},
) => {
const result = await this.post<
CreateSendNativeTransactionBody,
CreateSendNativeTransactionReply
>("/transactions/send-native", {
...params,
amount: params.amount.toString(),
backendWallet: params.backendWallet ?? this.backendWallet,
});

return waitForCompletion ? this.transaction.wait(result.queueId) : result;
},
get: (queueId: string) =>
this.get<ReadTransactionReply>(`/transactions/${queueId}`),
wait: async (queueId: string, maxRetries = 15, retryMs = 2_500) => {
let retries = 0;
let transaction: ReadTransactionReply;
do {
if (retries > 0) {
await new Promise((r) => setTimeout(r, 2_500));
await new Promise((r) => setTimeout(r, retryMs));
}

transaction = await this.transaction.get(result.queueId);
transaction = await this.transaction.get(queueId);
retries += 1;
} while (
retries < 15 &&
retries < maxRetries &&
transaction.status !== "errored" &&
transaction.status !== "cancelled" &&
transaction.status !== "mined"
Expand All @@ -210,8 +235,6 @@ export class TDKAPI {

return transaction;
},
get: (queueId: string) =>
this.get<ReadTransactionReply>(`/transactions/${queueId}`),
};

harvester = {
Expand Down
Loading

0 comments on commit a95c2a9

Please sign in to comment.