@satoshai/kit 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @satoshai/kit
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@satoshai/kit)](https://www.npmjs.com/package/@satoshai/kit)
4
+ [![CI](https://github.com/satoshai-dev/kit/actions/workflows/ci.yml/badge.svg)](https://github.com/satoshai-dev/kit/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
3
7
  Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired hook API for connecting wallets, signing messages, and calling contracts on the Stacks blockchain.
4
8
 
5
9
  ## Features
@@ -9,7 +13,10 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
9
13
  - **`useWallets`** — Configured wallets with availability status
10
14
  - **`useAddress`** — Access connected wallet address and status
11
15
  - **`useSignMessage`** — Sign arbitrary messages
16
+ - **`useSignStructuredMessage`** — Sign SIP-018 structured data
17
+ - **`useSignTransaction`** — Sign serialized transactions (sponsored tx flows)
12
18
  - **`useWriteContract`** — Call smart contracts with post-conditions
19
+ - **`useTransferSTX`** — Native STX transfers
13
20
  - **`useBnsName`** — Resolve BNS v2 names
14
21
  - **6 wallets supported** — Xverse, Leather, OKX, Asigna, Fordefi, WalletConnect
15
22
  - **Next.js App Router compatible** — `"use client"` directives included
@@ -179,6 +186,62 @@ signMessage({ message: 'Hello Stacks' }, {
179
186
  const { publicKey, signature } = await signMessageAsync({ message: 'Hello Stacks' });
180
187
  ```
181
188
 
189
+ ### `useSignStructuredMessage()`
190
+
191
+ Sign SIP-018 structured data for typed, verifiable off-chain messages.
192
+
193
+ > **Note:** OKX wallet does not support structured message signing and will throw an error.
194
+
195
+ ```ts
196
+ import { tupleCV, stringAsciiCV, uintCV } from '@stacks/transactions';
197
+
198
+ const { signStructuredMessage, signStructuredMessageAsync, data, error, isPending } = useSignStructuredMessage();
199
+
200
+ // Callback style
201
+ signStructuredMessage({
202
+ domain: tupleCV({
203
+ name: stringAsciiCV('MyApp'),
204
+ version: stringAsciiCV('1.0'),
205
+ 'chain-id': uintCV(1),
206
+ }),
207
+ message: tupleCV({
208
+ action: stringAsciiCV('authorize'),
209
+ amount: uintCV(1000),
210
+ }),
211
+ }, {
212
+ onSuccess: ({ publicKey, signature }) => {},
213
+ onError: (error) => {},
214
+ });
215
+
216
+ // Async style
217
+ const { publicKey, signature } = await signStructuredMessageAsync({
218
+ domain: tupleCV({ ... }),
219
+ message: tupleCV({ ... }),
220
+ });
221
+ ```
222
+
223
+ ### `useTransferSTX()`
224
+
225
+ ```ts
226
+ const { transferSTX, transferSTXAsync, data, error, isPending } = useTransferSTX();
227
+
228
+ // Callback style
229
+ transferSTX({
230
+ recipient: 'SP2...',
231
+ amount: 1000000n, // in microSTX
232
+ memo: 'optional memo',
233
+ }, {
234
+ onSuccess: (txid) => {},
235
+ onError: (error) => {},
236
+ });
237
+
238
+ // Async style
239
+ const txid = await transferSTXAsync({
240
+ recipient: 'SP2...',
241
+ amount: 1000000n,
242
+ });
243
+ ```
244
+
182
245
  ### `useWriteContract()`
183
246
 
184
247
  ```ts
@@ -201,6 +264,28 @@ writeContract({
201
264
  });
202
265
  ```
203
266
 
267
+ ### `useSignTransaction()`
268
+
269
+ Sign a serialized transaction without automatically broadcasting it. Useful for sponsored transaction flows where a separate service pays the fee.
270
+
271
+ > **Note:** OKX wallet does not support raw transaction signing and will throw an error.
272
+
273
+ ```ts
274
+ const { signTransaction, signTransactionAsync, data, error, isPending } = useSignTransaction();
275
+
276
+ // Callback style
277
+ signTransaction({ transaction: '0x0100...', broadcast: false }, {
278
+ onSuccess: ({ transaction, txid }) => {},
279
+ onError: (error) => {},
280
+ });
281
+
282
+ // Async style
283
+ const { transaction, txid } = await signTransactionAsync({
284
+ transaction: '0x0100...',
285
+ broadcast: false,
286
+ });
287
+ ```
288
+
204
289
  ### `useBnsName()`
205
290
 
206
291
  ```ts
package/dist/index.cjs CHANGED
@@ -752,6 +752,153 @@ var useSignMessage = () => {
752
752
  [signMessage, signMessageAsync, reset, data, error, status]
753
753
  );
754
754
  };
755
+ var useSignStructuredMessage = () => {
756
+ const { isConnected, provider } = useAddress();
757
+ const [data, setData] = react.useState(
758
+ void 0
759
+ );
760
+ const [error, setError] = react.useState(null);
761
+ const [status, setStatus] = react.useState("idle");
762
+ const signStructuredMessageAsync = react.useCallback(
763
+ async (variables) => {
764
+ if (!isConnected) {
765
+ throw new Error("Wallet is not connected");
766
+ }
767
+ if (provider === "okx") {
768
+ throw new Error(
769
+ "Structured message signing is not supported by OKX wallet"
770
+ );
771
+ }
772
+ setStatus("pending");
773
+ setError(null);
774
+ setData(void 0);
775
+ try {
776
+ const result = await connect.request("stx_signStructuredMessage", {
777
+ message: variables.message,
778
+ domain: variables.domain
779
+ });
780
+ setData(result);
781
+ setStatus("success");
782
+ return result;
783
+ } catch (err) {
784
+ const error2 = err instanceof Error ? err : new Error(String(err));
785
+ setError(error2);
786
+ setStatus("error");
787
+ throw error2;
788
+ }
789
+ },
790
+ [isConnected, provider]
791
+ );
792
+ const signStructuredMessage = react.useCallback(
793
+ (variables, options) => {
794
+ signStructuredMessageAsync(variables).then((data2) => {
795
+ options?.onSuccess?.(data2);
796
+ options?.onSettled?.(data2, null);
797
+ }).catch((error2) => {
798
+ options?.onError?.(error2);
799
+ options?.onSettled?.(void 0, error2);
800
+ });
801
+ },
802
+ [signStructuredMessageAsync]
803
+ );
804
+ const reset = react.useCallback(() => {
805
+ setData(void 0);
806
+ setError(null);
807
+ setStatus("idle");
808
+ }, []);
809
+ return react.useMemo(
810
+ () => ({
811
+ signStructuredMessage,
812
+ signStructuredMessageAsync,
813
+ reset,
814
+ data,
815
+ error,
816
+ isError: status === "error",
817
+ isIdle: status === "idle",
818
+ isPending: status === "pending",
819
+ isSuccess: status === "success",
820
+ status
821
+ }),
822
+ [
823
+ signStructuredMessage,
824
+ signStructuredMessageAsync,
825
+ reset,
826
+ data,
827
+ error,
828
+ status
829
+ ]
830
+ );
831
+ };
832
+ var useSignTransaction = () => {
833
+ const { isConnected, provider } = useAddress();
834
+ const [data, setData] = react.useState(void 0);
835
+ const [error, setError] = react.useState(null);
836
+ const [status, setStatus] = react.useState("idle");
837
+ const signTransactionAsync = react.useCallback(
838
+ async (variables) => {
839
+ if (!isConnected) {
840
+ throw new Error("Wallet is not connected");
841
+ }
842
+ if (provider === "okx") {
843
+ throw new Error(
844
+ "Transaction signing is not supported by OKX wallet"
845
+ );
846
+ }
847
+ setStatus("pending");
848
+ setError(null);
849
+ setData(void 0);
850
+ try {
851
+ const result = await connect.request("stx_signTransaction", {
852
+ transaction: variables.transaction,
853
+ ...variables.broadcast !== void 0 && {
854
+ broadcast: variables.broadcast
855
+ }
856
+ });
857
+ setData(result);
858
+ setStatus("success");
859
+ return result;
860
+ } catch (err) {
861
+ const error2 = err instanceof Error ? err : new Error(String(err));
862
+ setError(error2);
863
+ setStatus("error");
864
+ throw error2;
865
+ }
866
+ },
867
+ [isConnected, provider]
868
+ );
869
+ const signTransaction = react.useCallback(
870
+ (variables, options) => {
871
+ signTransactionAsync(variables).then((data2) => {
872
+ options?.onSuccess?.(data2);
873
+ options?.onSettled?.(data2, null);
874
+ }).catch((error2) => {
875
+ options?.onError?.(error2);
876
+ options?.onSettled?.(void 0, error2);
877
+ });
878
+ },
879
+ [signTransactionAsync]
880
+ );
881
+ const reset = react.useCallback(() => {
882
+ setData(void 0);
883
+ setError(null);
884
+ setStatus("idle");
885
+ }, []);
886
+ return react.useMemo(
887
+ () => ({
888
+ signTransaction,
889
+ signTransactionAsync,
890
+ reset,
891
+ data,
892
+ error,
893
+ isError: status === "error",
894
+ isIdle: status === "idle",
895
+ isPending: status === "pending",
896
+ isSuccess: status === "success",
897
+ status
898
+ }),
899
+ [signTransaction, signTransactionAsync, reset, data, error, status]
900
+ );
901
+ };
755
902
 
756
903
  // src/utils/get-network-from-address.ts
757
904
  var getNetworkFromAddress = (address) => {
@@ -763,10 +910,239 @@ var getNetworkFromAddress = (address) => {
763
910
  }
764
911
  throw new Error(`Invalid Stacks address: ${address}`);
765
912
  };
913
+
914
+ // src/hooks/use-transfer-stx.ts
915
+ var useTransferSTX = () => {
916
+ const { isConnected, address, provider } = useAddress();
917
+ const [data, setData] = react.useState(void 0);
918
+ const [error, setError] = react.useState(null);
919
+ const [status, setStatus] = react.useState("idle");
920
+ const transferSTXAsync = react.useCallback(
921
+ async (variables) => {
922
+ if (!isConnected || !address) {
923
+ throw new Error("Wallet is not connected");
924
+ }
925
+ setStatus("pending");
926
+ setError(null);
927
+ setData(void 0);
928
+ try {
929
+ if (provider === "okx") {
930
+ if (!window.okxwallet) {
931
+ throw new Error("OKX wallet not found");
932
+ }
933
+ const response2 = await window.okxwallet.stacks.signTransaction({
934
+ txType: "token_transfer",
935
+ recipient: variables.recipient,
936
+ amount: String(variables.amount),
937
+ memo: variables.memo ?? "",
938
+ stxAddress: address,
939
+ anchorMode: 3
940
+ });
941
+ setData(response2.txHash);
942
+ setStatus("success");
943
+ return response2.txHash;
944
+ }
945
+ const response = await connect.request("stx_transferStx", {
946
+ recipient: variables.recipient,
947
+ amount: variables.amount,
948
+ ...variables.memo !== void 0 && {
949
+ memo: variables.memo
950
+ },
951
+ ...variables.fee !== void 0 && {
952
+ fee: variables.fee
953
+ },
954
+ ...variables.nonce !== void 0 && {
955
+ nonce: variables.nonce
956
+ },
957
+ network: getNetworkFromAddress(address)
958
+ });
959
+ if (!response.txid) {
960
+ throw new Error("No transaction ID returned");
961
+ }
962
+ setData(response.txid);
963
+ setStatus("success");
964
+ return response.txid;
965
+ } catch (err) {
966
+ const error2 = err instanceof Error ? err : new Error(String(err));
967
+ setError(error2);
968
+ setStatus("error");
969
+ throw error2;
970
+ }
971
+ },
972
+ [isConnected, address, provider]
973
+ );
974
+ const transferSTX = react.useCallback(
975
+ (variables, options) => {
976
+ transferSTXAsync(variables).then((txid) => {
977
+ options?.onSuccess?.(txid);
978
+ options?.onSettled?.(txid, null);
979
+ }).catch((error2) => {
980
+ options?.onError?.(error2);
981
+ options?.onSettled?.(void 0, error2);
982
+ });
983
+ },
984
+ [transferSTXAsync]
985
+ );
986
+ const reset = react.useCallback(() => {
987
+ setData(void 0);
988
+ setError(null);
989
+ setStatus("idle");
990
+ }, []);
991
+ return react.useMemo(
992
+ () => ({
993
+ transferSTX,
994
+ transferSTXAsync,
995
+ reset,
996
+ data,
997
+ error,
998
+ isError: status === "error",
999
+ isIdle: status === "idle",
1000
+ isPending: status === "pending",
1001
+ isSuccess: status === "success",
1002
+ status
1003
+ }),
1004
+ [transferSTX, transferSTXAsync, reset, data, error, status]
1005
+ );
1006
+ };
1007
+ function toClarityValue(value, abiType) {
1008
+ if (typeof abiType === "string") {
1009
+ switch (abiType) {
1010
+ case "uint128": {
1011
+ if (typeof value !== "bigint") {
1012
+ throw new Error(
1013
+ `@satoshai/kit: Expected bigint (uint128), got ${typeof value}`
1014
+ );
1015
+ }
1016
+ return transactions.uintCV(value);
1017
+ }
1018
+ case "int128": {
1019
+ if (typeof value !== "bigint") {
1020
+ throw new Error(
1021
+ `@satoshai/kit: Expected bigint (int128), got ${typeof value}`
1022
+ );
1023
+ }
1024
+ return transactions.intCV(value);
1025
+ }
1026
+ case "bool": {
1027
+ if (typeof value !== "boolean") {
1028
+ throw new Error(
1029
+ `@satoshai/kit: Expected boolean (bool), got ${typeof value}`
1030
+ );
1031
+ }
1032
+ return transactions.boolCV(value);
1033
+ }
1034
+ case "principal":
1035
+ case "trait_reference": {
1036
+ if (typeof value !== "string") {
1037
+ throw new Error(
1038
+ `@satoshai/kit: Expected string (${abiType}), got ${typeof value}`
1039
+ );
1040
+ }
1041
+ if (value.includes(".")) {
1042
+ const [addr, ...rest] = value.split(".");
1043
+ return transactions.contractPrincipalCV(addr, rest.join("."));
1044
+ }
1045
+ return transactions.standardPrincipalCV(value);
1046
+ }
1047
+ case "none":
1048
+ return transactions.noneCV();
1049
+ default:
1050
+ throw new Error(
1051
+ `@satoshai/kit: Unsupported ABI type "${abiType}"`
1052
+ );
1053
+ }
1054
+ }
1055
+ if ("buffer" in abiType) {
1056
+ if (!(value instanceof Uint8Array)) {
1057
+ throw new Error(
1058
+ `@satoshai/kit: Expected Uint8Array (buffer), got ${typeof value}`
1059
+ );
1060
+ }
1061
+ return transactions.bufferCV(value);
1062
+ }
1063
+ if ("string-ascii" in abiType) {
1064
+ if (typeof value !== "string") {
1065
+ throw new Error(
1066
+ `@satoshai/kit: Expected string (string-ascii), got ${typeof value}`
1067
+ );
1068
+ }
1069
+ return transactions.stringAsciiCV(value);
1070
+ }
1071
+ if ("string-utf8" in abiType) {
1072
+ if (typeof value !== "string") {
1073
+ throw new Error(
1074
+ `@satoshai/kit: Expected string (string-utf8), got ${typeof value}`
1075
+ );
1076
+ }
1077
+ return transactions.stringUtf8CV(value);
1078
+ }
1079
+ if ("optional" in abiType) {
1080
+ if (value === null || value === void 0) return transactions.noneCV();
1081
+ return transactions.someCV(toClarityValue(value, abiType.optional));
1082
+ }
1083
+ if ("list" in abiType) {
1084
+ if (!Array.isArray(value)) {
1085
+ throw new Error(
1086
+ `@satoshai/kit: Expected array (list), got ${typeof value}`
1087
+ );
1088
+ }
1089
+ const listType = abiType.list;
1090
+ return transactions.listCV(
1091
+ value.map((item) => toClarityValue(item, listType.type))
1092
+ );
1093
+ }
1094
+ if ("tuple" in abiType) {
1095
+ if (typeof value !== "object" || value === null) {
1096
+ throw new Error(
1097
+ `@satoshai/kit: Expected object (tuple), got ${value === null ? "null" : typeof value}`
1098
+ );
1099
+ }
1100
+ const entries = abiType.tuple;
1101
+ const obj = value;
1102
+ const result = {};
1103
+ for (const entry of entries) {
1104
+ result[entry.name] = toClarityValue(obj[entry.name], entry.type);
1105
+ }
1106
+ return transactions.tupleCV(result);
1107
+ }
1108
+ throw new Error(
1109
+ `@satoshai/kit: Unsupported ABI type: ${JSON.stringify(abiType)}`
1110
+ );
1111
+ }
1112
+ function namedArgsToClarityValues(args, abiArgs) {
1113
+ return abiArgs.map((abiArg) => {
1114
+ if (!(abiArg.name in args)) {
1115
+ throw new Error(
1116
+ `@satoshai/kit: Missing argument "${abiArg.name}"`
1117
+ );
1118
+ }
1119
+ return toClarityValue(
1120
+ args[abiArg.name],
1121
+ abiArg.type
1122
+ );
1123
+ });
1124
+ }
766
1125
  var preparePostConditionsForOKX = (postConditions) => postConditions.map((pc) => transactions.postConditionToHex(pc));
767
1126
  var prepareArgsForOKX = (args) => args.map((arg) => transactions.cvToHex(arg));
768
1127
 
769
1128
  // src/hooks/use-write-contract/use-write-contract.ts
1129
+ function resolveArgs(variables) {
1130
+ if (!variables.abi) {
1131
+ return variables.args;
1132
+ }
1133
+ const fn = variables.abi.functions.find(
1134
+ (f) => f.name === variables.functionName && f.access === "public"
1135
+ );
1136
+ if (!fn) {
1137
+ throw new Error(
1138
+ `@satoshai/kit: Public function "${variables.functionName}" not found in ABI`
1139
+ );
1140
+ }
1141
+ return namedArgsToClarityValues(
1142
+ variables.args,
1143
+ fn.args
1144
+ );
1145
+ }
770
1146
  var useWriteContract = () => {
771
1147
  const { isConnected, address, provider } = useAddress();
772
1148
  const [data, setData] = react.useState(void 0);
@@ -780,6 +1156,7 @@ var useWriteContract = () => {
780
1156
  setStatus("pending");
781
1157
  setError(null);
782
1158
  setData(void 0);
1159
+ const resolvedArgs = resolveArgs(variables);
783
1160
  try {
784
1161
  if (provider === "okx") {
785
1162
  if (!window.okxwallet) {
@@ -789,7 +1166,7 @@ var useWriteContract = () => {
789
1166
  contractAddress: variables.address,
790
1167
  contractName: variables.contract,
791
1168
  functionName: variables.functionName,
792
- functionArgs: prepareArgsForOKX(variables.args),
1169
+ functionArgs: prepareArgsForOKX(resolvedArgs),
793
1170
  postConditions: preparePostConditionsForOKX(
794
1171
  variables.pc.postConditions
795
1172
  ),
@@ -806,7 +1183,7 @@ var useWriteContract = () => {
806
1183
  address,
807
1184
  contract: `${variables.address}.${variables.contract}`,
808
1185
  functionName: variables.functionName,
809
- functionArgs: variables.args,
1186
+ functionArgs: resolvedArgs,
810
1187
  postConditions: variables.pc.postConditions,
811
1188
  postConditionMode: variables.pc.mode === transactions.PostConditionMode.Allow ? "allow" : "deny",
812
1189
  network: getNetworkFromAddress(address)
@@ -898,8 +1275,14 @@ var useWallets = () => {
898
1275
  return react.useMemo(() => ({ wallets }), [wallets]);
899
1276
  };
900
1277
 
1278
+ // src/utils/create-contract-config.ts
1279
+ function createContractConfig(config) {
1280
+ return config;
1281
+ }
1282
+
901
1283
  exports.SUPPORTED_STACKS_WALLETS = SUPPORTED_STACKS_WALLETS;
902
1284
  exports.StacksWalletProvider = StacksWalletProvider;
1285
+ exports.createContractConfig = createContractConfig;
903
1286
  exports.getLocalStorageWallet = getLocalStorageWallet;
904
1287
  exports.getNetworkFromAddress = getNetworkFromAddress;
905
1288
  exports.getStacksWallets = getStacksWallets;
@@ -908,6 +1291,9 @@ exports.useBnsName = useBnsName;
908
1291
  exports.useConnect = useConnect;
909
1292
  exports.useDisconnect = useDisconnect;
910
1293
  exports.useSignMessage = useSignMessage;
1294
+ exports.useSignStructuredMessage = useSignStructuredMessage;
1295
+ exports.useSignTransaction = useSignTransaction;
1296
+ exports.useTransferSTX = useTransferSTX;
911
1297
  exports.useWallets = useWallets;
912
1298
  exports.useWriteContract = useWriteContract;
913
1299
  //# sourceMappingURL=index.cjs.map