Skip to content

Commit 579677f

Browse files
committed
refactor(examples): secp256k1 transfer example reduce example code size
1 parent 382f73b commit 579677f

File tree

4 files changed

+149
-168
lines changed

4 files changed

+149
-168
lines changed

examples/secp256k1-transfer/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "@lumos-examples/secp256k1-transfer",
4-
"version": "0.19.0-alpha.0",
4+
"version": "0.19.0-alpha.3",
55
"description": "",
66
"main": "index.js",
77
"scripts": {
@@ -12,7 +12,7 @@
1212
"author": "",
1313
"license": "MIT",
1414
"dependencies": {
15-
"@ckb-lumos/lumos": "0.19.0-alpha.1",
15+
"@ckb-lumos/lumos": "0.19.0-alpha.3",
1616
"@types/react": "^17.0.34",
1717
"@types/react-dom": "^17.0.11",
1818
"bulma": "^0.9.4",

examples/secp256k1-transfer/src/index.tsx

+11-34
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
import "bulma/css/bulma.css";
2-
import React, { useEffect, useMemo, FC, ReactNode } from "react";
3-
import { useList, useSetState, useAsync } from "react-use";
2+
import React, { useEffect, FC, ReactNode } from "react";
3+
import { useList, useSetState } from "react-use";
44
import ReactDOM from "react-dom";
55
import { nanoid } from "nanoid";
66
import { BI } from "@ckb-lumos/lumos";
7-
import {
8-
fetchAddressBalance,
9-
createUnsignedTxSkeleton,
10-
generateAccountFromPrivateKey,
11-
transfer,
12-
Account,
13-
getPaidTransactionFee,
14-
MIN_CELL_CAPACITY,
15-
signTransaction,
16-
} from "./lib";
7+
import { fetchAddressBalance, generateAccountFromPrivateKey, transfer, Account, MIN_CELL_CAPACITY } from "./lib";
178

189
type TransferTarget = {
1910
capacity: BI;
@@ -29,37 +20,23 @@ export function App() {
2920
accountInfo: null as Account | null,
3021
balance: BI.from(0),
3122
txHash: "",
23+
fee: BI.from(0),
3224
});
33-
const [transferTargets, transferTargetsActions] = useList([createTransferTarget()]);
25+
const [transferTargets, transferTargetsActions] = useList<TransferTarget>([createTransferTarget()]);
3426

35-
// Step 1: get the unsigned transaction skeleton
36-
// `useAsync` method can keep the transaction is newest from state
37-
const { value: unsignedTxSkeleton } = useAsync(async () => {
38-
if (!state.accountInfo) {
39-
return null;
40-
}
41-
const skeleton = await createUnsignedTxSkeleton({ targets: transferTargets, address: state.accountInfo.address });
42-
return skeleton;
43-
}, [state.accountInfo, state.privateKey, transferTargets]);
44-
45-
// Step 2: sign the transaction and send it to CKB test network
4627
// this method will be called when you click "Transfer" button
4728
const doTransfer = () => {
4829
if (!state.accountInfo) {
4930
return;
5031
}
5132

52-
const tx = signTransaction(unsignedTxSkeleton, state.privateKey);
53-
transfer(tx).then((txHash) => {
54-
setState({ txHash });
55-
});
33+
transfer({ targets: transferTargets, address: state.accountInfo.address }, state.privateKey).then(
34+
({ txHash, fee }) => {
35+
setState({ txHash: txHash, fee });
36+
}
37+
);
5638
};
5739

58-
// recalculate when transaction changes
59-
const transactionFee = useMemo(() => (unsignedTxSkeleton ? getPaidTransactionFee(unsignedTxSkeleton) : BI.from(0)), [
60-
unsignedTxSkeleton,
61-
]);
62-
6340
// fetch and update account info and balance when private key changes
6441
useEffect(() => {
6542
if (state.privateKey) {
@@ -140,7 +117,7 @@ export function App() {
140117
Add New Transfer Target
141118
</div>
142119
</th>
143-
<th>Transaction fee {(transactionFee.toNumber() / 1e8).toString()}</th>
120+
<th>Transaction fee {(state.fee.toNumber() / 1e8).toString()}</th>
144121
<th>
145122
<button className="button is-primary" onClick={doTransfer}>
146123
Transfer!

examples/secp256k1-transfer/src/lib.ts

+50-46
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Indexer, helpers, Address, Script, RPC, hd, config, commons, BI, Transaction } from "@ckb-lumos/lumos";
1+
import { Indexer, helpers, Address, Script, RPC, hd, config, commons, BI } from "@ckb-lumos/lumos";
22
import { BIish } from "@ckb-lumos/bi";
33
import { payFeeByFeeRate } from "@ckb-lumos/common-scripts/lib/common";
44

@@ -15,6 +15,50 @@ export type Account = {
1515
pubKey: string;
1616
};
1717

18+
/**
19+
* send a transaction to CKB testnet
20+
* @returns Promise with transaction hash
21+
*/
22+
export async function transfer(options: TransactionIO, privateKey: string): Promise<{ txHash: string; fee: BI }> {
23+
// step 1, create an raw transaction
24+
// an raw transaction have it's inputs and outputs, but no signature
25+
let txSkeleton = helpers.TransactionSkeleton({ cellProvider: indexer });
26+
for (const target of options.targets) {
27+
// add each outputs to the transaction skeleton
28+
txSkeleton = await commons.common.transfer(
29+
txSkeleton,
30+
[options.address],
31+
target.address,
32+
target.capacity,
33+
options.address,
34+
undefined,
35+
{ config: AGGRON4 }
36+
);
37+
}
38+
39+
// these methods add transaction fee to transaction.
40+
// see the calculate algorithm in https://docs.nervos.org/docs/essays/faq/#how-do-you-calculate-transaction-fee
41+
txSkeleton = await payFeeByFeeRate(txSkeleton, [options.address], 1000, undefined, { config: AGGRON4 });
42+
43+
// step2: sign an transaction
44+
45+
txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
46+
47+
// message is the hash of raw transaction
48+
const message = txSkeleton.get("signingEntries").get(0)?.message;
49+
const signature = hd.key.signRecoverable(message!, privateKey);
50+
const tx = helpers.sealTransaction(txSkeleton, [signature]);
51+
52+
// step3: send the transaction to block chain
53+
const txHash = await rpc.sendTransaction(tx, "passthrough");
54+
55+
// how about transaction fee? it's just sum(transaction.inputs) - sum(transaction.outputs).
56+
// the transaction fee will be sent to miner.
57+
58+
const transactionFee = getPaidTransactionFee(txSkeleton);
59+
return { txHash, fee: transactionFee };
60+
}
61+
1862
export const generateAccountFromPrivateKey = (privKey: string): Account => {
1963
const pubKey = hd.key.privateToPublic(privKey);
2064
const args = hd.key.publicKeyToBlake160(pubKey);
@@ -42,6 +86,7 @@ export function getPaidTransactionFee(skeleton: helpers.TransactionSkeletonType)
4286
const outputs = skeleton.outputs.reduce((acc, cur) => acc.add(cur.cellOutput.capacity), BI.from(0));
4387
return inputs.sub(outputs);
4488
}
89+
4590
/**
4691
* fetch all cells and calculate the sum of their capacities
4792
*/
@@ -55,54 +100,13 @@ export async function fetchAddressBalance(address: string): Promise<BI> {
55100
return balance;
56101
}
57102

58-
interface Options {
103+
/**
104+
* Transaction input and output
105+
*/
106+
interface TransactionIO {
59107
targets: {
60108
address: string;
61109
capacity: BIish;
62110
}[];
63111
address: string;
64112
}
65-
66-
/**
67-
* create an unsigned transaction skeleton which includes several inputs and outputs(for multiple transaction receivers)
68-
*/
69-
export async function createUnsignedTxSkeleton(options: Options) {
70-
let txSkeleton = helpers.TransactionSkeleton({ cellProvider: indexer });
71-
for (const target of options.targets) {
72-
txSkeleton = await commons.common.transfer(
73-
txSkeleton,
74-
[options.address],
75-
target.address,
76-
target.capacity,
77-
options.address,
78-
undefined,
79-
{ config: AGGRON4 }
80-
);
81-
}
82-
83-
txSkeleton = await payFeeByFeeRate(txSkeleton, [options.address], 1000, undefined, { config: AGGRON4 });
84-
return txSkeleton;
85-
}
86-
87-
/**
88-
* sign a transaction skeleton
89-
* @param txSkeleton unsigned transaction skeleton
90-
* @param privateKey the private key which can unlock input cells
91-
* @returns
92-
*/
93-
export function signTransaction(txSkeleton: helpers.TransactionSkeletonType, privateKey: string) {
94-
txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
95-
const message = txSkeleton.get("signingEntries").get(0)?.message;
96-
const signature = hd.key.signRecoverable(message!, privateKey);
97-
const tx = helpers.sealTransaction(txSkeleton, [signature]);
98-
return tx;
99-
}
100-
101-
/**
102-
* send a transaction to CKB testnet
103-
* @returns Promise with transaction hash
104-
*/
105-
export async function transfer(tx: Transaction): Promise<string> {
106-
const hash = await rpc.sendTransaction(tx, "passthrough");
107-
return hash;
108-
}

0 commit comments

Comments
 (0)