Skip to content

Commit 69be494

Browse files
committed
send transaction docs
1 parent b505b73 commit 69be494

File tree

3 files changed

+348
-0
lines changed

3 files changed

+348
-0
lines changed

category/code-examples.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ mode: wide
4444
<Card title="Signing transactions" href="/embedded-wallets/code-examples/signing-transactions" icon="file-lines" iconType="solid" horizontal>
4545
Signing transactions
4646
</Card>
47+
<Card title="Signing & Sending transactions" href="/embedded-wallets/code-examples/signing-and-sending-transactions" icon="file-lines" iconType="solid" horizontal>
48+
Signing transactions
49+
</Card>
4750
<Card title="Import wallet or private key" href="/embedded-wallets/code-examples/import" icon="file-lines" iconType="solid" horizontal>
4851
Import wallet or private key
4952
</Card>

docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
"embedded-wallets/code-examples/create-sub-org-passkey",
142142
"embedded-wallets/code-examples/authenticate-user-passkey",
143143
"embedded-wallets/code-examples/create-passkey-session",
144+
"embedded-wallets/code-examples/signing-and-sending-transactions",
144145
"embedded-wallets/code-examples/create-user-email",
145146
"embedded-wallets/code-examples/authenticate-user-email",
146147
"embedded-wallets/code-examples/add-credential",
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
---
2+
title: "Signing & Sending Transactions"
3+
description: "Guide for signing and sending Ethereum transactions using Turnkey’s `signAndSendTransaction` API from `@turnkey/sdk-core`, with and without the React handler from `@turnkey/sdk-react-wallet-kit`."
4+
---
5+
6+
## Overview
7+
8+
Turnkey provides two primary ways to sign and broadcast transactions:
9+
10+
1. **Using the React handler (`handleSendTransaction`) from `@turnkey/sdk-react-wallet-kit`**
11+
This gives you:
12+
- modals
13+
- spinner + chain logo
14+
- success screen
15+
- explorer link
16+
- built-in polling
17+
18+
2. **Using low-level functions in `@turnkey/sdk-core`**
19+
You manually call:
20+
- `signAndSendTransaction` → submit
21+
- `pollTransactionStatus` → wait for inclusion
22+
23+
This page shows both flows with full code included.
24+
25+
---
26+
27+
# 1. Using `handleSendTransaction` (React)
28+
29+
This handler wraps everything: intent creation, signing, Turnkey submission, polling, modal UX, and final success UI.
30+
31+
## Step 1 — Configure the Provider
32+
33+
```ts
34+
import { TurnkeyProvider } from "@turnkey/sdk-react-wallet-kit";
35+
36+
const turnkeyConfig = {
37+
apiBaseUrl: "https://api.turnkey.com",
38+
defaultOrganizationId: process.env.NEXT_PUBLIC_TURNKEY_ORG_ID,
39+
rpId: window.location.hostname,
40+
iframeUrl: "https://auth.turnkey.com",
41+
};
42+
43+
export default function App({ children }) {
44+
return (
45+
<TurnkeyProvider config={turnkeyConfig}>
46+
{children}
47+
</TurnkeyProvider>
48+
);
49+
}
50+
```
51+
52+
---
53+
54+
## Step 2 — Use `handleSendTransaction` inside your UI
55+
56+
```ts
57+
const { handleSendTransaction, wallets } = useTurnkey();
58+
59+
const walletAccount = wallets[0].accounts[0];
60+
61+
await handleSendTransaction({
62+
organizationId: "<org-id>",
63+
from: walletAccount.address,
64+
to: "0xRecipient",
65+
value: "1000000000000000",
66+
data: "0x",
67+
caip2: "eip155:8453",
68+
sponsor: true,
69+
});
70+
```
71+
72+
This automatically:
73+
- opens Turnkey modal
74+
- shows chain logo
75+
- polls until INCLUDED
76+
- displays success page + explorer link
77+
78+
---
79+
80+
# 2. Using `@turnkey/sdk-core` directly (non-React)
81+
82+
For custom frameworks, Node.js servers, or full manual control.
83+
84+
You will call:
85+
86+
### `signAndSendTransaction(params)`
87+
→ returns `{ sendTransactionStatusId }`
88+
89+
### `pollTransactionStatus(params)`
90+
→ returns `{ txHash, status }`
91+
92+
## Step 1 — Create a client
93+
94+
```ts
95+
import { Turnkey } from "@turnkey/sdk-core";
96+
97+
const client = new Turnkey({
98+
apiBaseUrl: "https://api.turnkey.com",
99+
defaultOrganizationId: process.env.TURNKEY_ORG_ID,
100+
});
101+
```
102+
103+
---
104+
105+
## Step 2 — Submit the transaction
106+
107+
```ts
108+
const { sendTransactionStatusId } = await client.signAndSendTransaction({
109+
walletAccount,
110+
organizationId: "<org-id>",
111+
from: walletAccount.address,
112+
to: "0xRecipient",
113+
caip2: "eip155:8453",
114+
sponsor: true,
115+
value: "0",
116+
data: "0x",
117+
nonce: "0",
118+
});
119+
```
120+
121+
---
122+
123+
## Step 3 — Poll for inclusion
124+
125+
```ts
126+
const { txHash, status } = await client.pollTransactionStatus({
127+
sendTransactionStatusId,
128+
});
129+
130+
console.log("Mined:", txHash);
131+
```
132+
133+
---
134+
135+
# 3. Core Code (full implementations)
136+
137+
## `signAndSendTransaction` — from `@turnkey/sdk-core`
138+
139+
```ts
140+
/**
141+
* Signs and submits an Ethereum transaction using Turnkey.
142+
*
143+
* Behavior:
144+
* - Constructs transaction intent (sponsored or EIP-1559)
145+
* - Submits via Turnkey
146+
* - Returns { sendTransactionStatusId }
147+
*
148+
* Does NOT poll — caller must poll via pollTransactionStatus.
149+
*/
150+
signAndSendTransaction = async (
151+
params: SignAndSendTransactionParams
152+
): Promise<SignAndSendResult> => {
153+
const {
154+
organizationId,
155+
from,
156+
to,
157+
caip2,
158+
sponsor,
159+
value,
160+
data,
161+
nonce,
162+
gasLimit,
163+
maxFeePerGas,
164+
maxPriorityFeePerGas,
165+
walletAccount,
166+
} = params;
167+
168+
return withTurnkeyErrorHandling(
169+
async () => {
170+
const intent = {
171+
from,
172+
to,
173+
caip2,
174+
...(value ? { value } : {}),
175+
...(data ? { data } : {}),
176+
};
177+
178+
if (sponsor) {
179+
intent["sponsor"] = true;
180+
} else {
181+
if (nonce) intent["nonce"] = nonce;
182+
if (gasLimit) intent["gasLimit"] = gasLimit;
183+
if (maxFeePerGas) intent["maxFeePerGas"] = maxFeePerGas;
184+
if (maxPriorityFeePerGas)
185+
intent["maxPriorityFeePerGas"] = maxPriorityFeePerGas;
186+
}
187+
188+
const resp = await this.httpClient.ethSendTransaction({
189+
...intent,
190+
...(organizationId && { organizationId }),
191+
});
192+
193+
return { sendTransactionStatusId: resp.sendTransactionStatusId };
194+
},
195+
{
196+
errorMessage: "Failed to submit transaction",
197+
errorCode: TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR,
198+
}
199+
);
200+
};
201+
```
202+
203+
---
204+
205+
## `pollTransactionStatus` — from `@turnkey/sdk-core`
206+
207+
```ts
208+
/**
209+
* Polls Turnkey until a transaction reaches a terminal state.
210+
*
211+
* Terminal states:
212+
* - COMPLETED / INCLUDED → resolves { txHash, status }
213+
* - FAILED / CANCELLED → rejects
214+
*/
215+
pollTransactionStatus({
216+
httpClient,
217+
organizationId,
218+
sendTransactionStatusId,
219+
}: PollTransactionStatusParams): Promise<{
220+
txHash: string;
221+
status: string;
222+
}> {
223+
return withTurnkeyErrorHandling(
224+
async () => {
225+
return new Promise((resolve, reject) => {
226+
const ref = setInterval(async () => {
227+
try {
228+
const resp = await httpClient.getSendTransactionStatus({
229+
organizationId,
230+
sendTransactionStatusId,
231+
});
232+
233+
const status = resp?.txStatus;
234+
const txHash = resp?.eth?.txHash;
235+
const txError = resp?.txError;
236+
237+
if (!status) return;
238+
239+
if (txError || status === "FAILED" || status === "CANCELLED") {
240+
clearInterval(ref);
241+
reject(txError || `Transaction ${status}`);
242+
return;
243+
}
244+
245+
if (status === "COMPLETED" || status === "INCLUDED") {
246+
clearInterval(ref);
247+
resolve({ txHash: txHash!, status });
248+
}
249+
} catch (e) {
250+
console.warn("polling error:", e);
251+
}
252+
}, 500);
253+
});
254+
},
255+
{
256+
errorMessage: "Failed to poll transaction status",
257+
errorCode: TurnkeyErrorCodes.SIGN_AND_SEND_TRANSACTION_ERROR,
258+
}
259+
);
260+
}
261+
```
262+
263+
# 4. Full React Handler (uses the above two functions)
264+
265+
```ts
266+
const handleSendTransaction = useCallback(
267+
async (params: HandleSendTransactionParams): Promise<void> => {
268+
const s = await getSession();
269+
const organizationId = params.organizationId || s?.organizationId;
270+
271+
const {
272+
from,
273+
to,
274+
value,
275+
data,
276+
caip2,
277+
sponsor,
278+
gasLimit,
279+
maxFeePerGas,
280+
maxPriorityFeePerGas,
281+
nonce: providedNonce,
282+
successPageDuration = 2000,
283+
} = params;
284+
285+
const { nonce } = await generateNonces({
286+
from: from,
287+
rpcUrl: DEFAULT_RPC_BY_CHAIN[caip2],
288+
providedNonce,
289+
});
290+
291+
return new Promise((resolve, reject) => {
292+
const SendTxContainer = () => {
293+
const cleanedData =
294+
data && data !== "0x" && data !== "" ? data : undefined;
295+
296+
const action = async () => {
297+
const { sendTransactionStatusId } =
298+
await client.signAndSendTransaction({
299+
walletAccount,
300+
organizationId,
301+
from,
302+
to,
303+
caip2,
304+
sponsor: !!sponsor,
305+
value,
306+
data: cleanedData,
307+
nonce,
308+
gasLimit,
309+
maxFeePerGas,
310+
maxPriorityFeePerGas,
311+
});
312+
313+
const { txHash } = await client.pollTransactionStatus({
314+
httpClient: client.httpClient,
315+
organizationId,
316+
sendTransactionStatusId,
317+
});
318+
319+
return { txHash };
320+
};
321+
322+
return (
323+
<SendTransactionPage
324+
icon={<img src={getChainLogo(caip2)} className="h-10 w-10" />}
325+
action={action}
326+
caip2={caip2}
327+
successPageDuration={successPageDuration}
328+
onSuccess={() => resolve()}
329+
onError={(err) => reject(err)}
330+
/>
331+
);
332+
};
333+
334+
pushPage({
335+
key: "Send Transaction",
336+
content: <SendTxContainer />,
337+
preventBack: true,
338+
showTitle: false,
339+
});
340+
});
341+
},
342+
[pushPage, client]
343+
);
344+
```

0 commit comments

Comments
 (0)