@satoshai/kit 0.4.0 → 0.5.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
@@ -10,6 +10,7 @@ Typesafe Stacks wallet & contract interaction library for React. Wagmi-inspired
10
10
  - **`useAddress`** — Access connected wallet address and status
11
11
  - **`useSignMessage`** — Sign arbitrary messages
12
12
  - **`useWriteContract`** — Call smart contracts with post-conditions
13
+ - **`useTransferSTX`** — Native STX transfers
13
14
  - **`useBnsName`** — Resolve BNS v2 names
14
15
  - **6 wallets supported** — Xverse, Leather, OKX, Asigna, Fordefi, WalletConnect
15
16
  - **Next.js App Router compatible** — `"use client"` directives included
@@ -179,6 +180,28 @@ signMessage({ message: 'Hello Stacks' }, {
179
180
  const { publicKey, signature } = await signMessageAsync({ message: 'Hello Stacks' });
180
181
  ```
181
182
 
183
+ ### `useTransferSTX()`
184
+
185
+ ```ts
186
+ const { transferSTX, transferSTXAsync, data, error, isPending } = useTransferSTX();
187
+
188
+ // Callback style
189
+ transferSTX({
190
+ recipient: 'SP2...',
191
+ amount: 1000000n, // in microSTX
192
+ memo: 'optional memo',
193
+ }, {
194
+ onSuccess: (txid) => {},
195
+ onError: (error) => {},
196
+ });
197
+
198
+ // Async style
199
+ const txid = await transferSTXAsync({
200
+ recipient: 'SP2...',
201
+ amount: 1000000n,
202
+ });
203
+ ```
204
+
182
205
  ### `useWriteContract()`
183
206
 
184
207
  ```ts
package/dist/index.cjs CHANGED
@@ -286,6 +286,7 @@ var StacksWalletProvider = ({
286
286
  const [provider, setProvider] = react.useState();
287
287
  const [isConnecting, setIsConnecting] = react.useState(false);
288
288
  const connectGenRef = react.useRef(0);
289
+ const wasConnectedRef = react.useRef(false);
289
290
  const wcInitRef = react.useRef(null);
290
291
  const walletConnectRef = react.useRef(walletConnect);
291
292
  walletConnectRef.current = walletConnect;
@@ -509,8 +510,11 @@ var StacksWalletProvider = ({
509
510
  );
510
511
  }, [address, provider]);
511
512
  react.useEffect(() => {
512
- if (!address || !provider || !onConnect) return;
513
- onConnect(provider, address);
513
+ const isConnected = !!address && !!provider;
514
+ if (isConnected && !wasConnectedRef.current) {
515
+ onConnect?.(provider, address);
516
+ }
517
+ wasConnectedRef.current = isConnected;
514
518
  }, [address, provider, onConnect]);
515
519
  const handleAddressChange = react.useCallback(
516
520
  (newAddress) => {
@@ -759,10 +763,239 @@ var getNetworkFromAddress = (address) => {
759
763
  }
760
764
  throw new Error(`Invalid Stacks address: ${address}`);
761
765
  };
766
+
767
+ // src/hooks/use-transfer-stx.ts
768
+ var useTransferSTX = () => {
769
+ const { isConnected, address, provider } = useAddress();
770
+ const [data, setData] = react.useState(void 0);
771
+ const [error, setError] = react.useState(null);
772
+ const [status, setStatus] = react.useState("idle");
773
+ const transferSTXAsync = react.useCallback(
774
+ async (variables) => {
775
+ if (!isConnected || !address) {
776
+ throw new Error("Wallet is not connected");
777
+ }
778
+ setStatus("pending");
779
+ setError(null);
780
+ setData(void 0);
781
+ try {
782
+ if (provider === "okx") {
783
+ if (!window.okxwallet) {
784
+ throw new Error("OKX wallet not found");
785
+ }
786
+ const response2 = await window.okxwallet.stacks.signTransaction({
787
+ txType: "token_transfer",
788
+ recipient: variables.recipient,
789
+ amount: String(variables.amount),
790
+ memo: variables.memo ?? "",
791
+ stxAddress: address,
792
+ anchorMode: 3
793
+ });
794
+ setData(response2.txHash);
795
+ setStatus("success");
796
+ return response2.txHash;
797
+ }
798
+ const response = await connect.request("stx_transferStx", {
799
+ recipient: variables.recipient,
800
+ amount: variables.amount,
801
+ ...variables.memo !== void 0 && {
802
+ memo: variables.memo
803
+ },
804
+ ...variables.fee !== void 0 && {
805
+ fee: variables.fee
806
+ },
807
+ ...variables.nonce !== void 0 && {
808
+ nonce: variables.nonce
809
+ },
810
+ network: getNetworkFromAddress(address)
811
+ });
812
+ if (!response.txid) {
813
+ throw new Error("No transaction ID returned");
814
+ }
815
+ setData(response.txid);
816
+ setStatus("success");
817
+ return response.txid;
818
+ } catch (err) {
819
+ const error2 = err instanceof Error ? err : new Error(String(err));
820
+ setError(error2);
821
+ setStatus("error");
822
+ throw error2;
823
+ }
824
+ },
825
+ [isConnected, address, provider]
826
+ );
827
+ const transferSTX = react.useCallback(
828
+ (variables, options) => {
829
+ transferSTXAsync(variables).then((txid) => {
830
+ options?.onSuccess?.(txid);
831
+ options?.onSettled?.(txid, null);
832
+ }).catch((error2) => {
833
+ options?.onError?.(error2);
834
+ options?.onSettled?.(void 0, error2);
835
+ });
836
+ },
837
+ [transferSTXAsync]
838
+ );
839
+ const reset = react.useCallback(() => {
840
+ setData(void 0);
841
+ setError(null);
842
+ setStatus("idle");
843
+ }, []);
844
+ return react.useMemo(
845
+ () => ({
846
+ transferSTX,
847
+ transferSTXAsync,
848
+ reset,
849
+ data,
850
+ error,
851
+ isError: status === "error",
852
+ isIdle: status === "idle",
853
+ isPending: status === "pending",
854
+ isSuccess: status === "success",
855
+ status
856
+ }),
857
+ [transferSTX, transferSTXAsync, reset, data, error, status]
858
+ );
859
+ };
860
+ function toClarityValue(value, abiType) {
861
+ if (typeof abiType === "string") {
862
+ switch (abiType) {
863
+ case "uint128": {
864
+ if (typeof value !== "bigint") {
865
+ throw new Error(
866
+ `@satoshai/kit: Expected bigint (uint128), got ${typeof value}`
867
+ );
868
+ }
869
+ return transactions.uintCV(value);
870
+ }
871
+ case "int128": {
872
+ if (typeof value !== "bigint") {
873
+ throw new Error(
874
+ `@satoshai/kit: Expected bigint (int128), got ${typeof value}`
875
+ );
876
+ }
877
+ return transactions.intCV(value);
878
+ }
879
+ case "bool": {
880
+ if (typeof value !== "boolean") {
881
+ throw new Error(
882
+ `@satoshai/kit: Expected boolean (bool), got ${typeof value}`
883
+ );
884
+ }
885
+ return transactions.boolCV(value);
886
+ }
887
+ case "principal":
888
+ case "trait_reference": {
889
+ if (typeof value !== "string") {
890
+ throw new Error(
891
+ `@satoshai/kit: Expected string (${abiType}), got ${typeof value}`
892
+ );
893
+ }
894
+ if (value.includes(".")) {
895
+ const [addr, ...rest] = value.split(".");
896
+ return transactions.contractPrincipalCV(addr, rest.join("."));
897
+ }
898
+ return transactions.standardPrincipalCV(value);
899
+ }
900
+ case "none":
901
+ return transactions.noneCV();
902
+ default:
903
+ throw new Error(
904
+ `@satoshai/kit: Unsupported ABI type "${abiType}"`
905
+ );
906
+ }
907
+ }
908
+ if ("buffer" in abiType) {
909
+ if (!(value instanceof Uint8Array)) {
910
+ throw new Error(
911
+ `@satoshai/kit: Expected Uint8Array (buffer), got ${typeof value}`
912
+ );
913
+ }
914
+ return transactions.bufferCV(value);
915
+ }
916
+ if ("string-ascii" in abiType) {
917
+ if (typeof value !== "string") {
918
+ throw new Error(
919
+ `@satoshai/kit: Expected string (string-ascii), got ${typeof value}`
920
+ );
921
+ }
922
+ return transactions.stringAsciiCV(value);
923
+ }
924
+ if ("string-utf8" in abiType) {
925
+ if (typeof value !== "string") {
926
+ throw new Error(
927
+ `@satoshai/kit: Expected string (string-utf8), got ${typeof value}`
928
+ );
929
+ }
930
+ return transactions.stringUtf8CV(value);
931
+ }
932
+ if ("optional" in abiType) {
933
+ if (value === null || value === void 0) return transactions.noneCV();
934
+ return transactions.someCV(toClarityValue(value, abiType.optional));
935
+ }
936
+ if ("list" in abiType) {
937
+ if (!Array.isArray(value)) {
938
+ throw new Error(
939
+ `@satoshai/kit: Expected array (list), got ${typeof value}`
940
+ );
941
+ }
942
+ const listType = abiType.list;
943
+ return transactions.listCV(
944
+ value.map((item) => toClarityValue(item, listType.type))
945
+ );
946
+ }
947
+ if ("tuple" in abiType) {
948
+ if (typeof value !== "object" || value === null) {
949
+ throw new Error(
950
+ `@satoshai/kit: Expected object (tuple), got ${value === null ? "null" : typeof value}`
951
+ );
952
+ }
953
+ const entries = abiType.tuple;
954
+ const obj = value;
955
+ const result = {};
956
+ for (const entry of entries) {
957
+ result[entry.name] = toClarityValue(obj[entry.name], entry.type);
958
+ }
959
+ return transactions.tupleCV(result);
960
+ }
961
+ throw new Error(
962
+ `@satoshai/kit: Unsupported ABI type: ${JSON.stringify(abiType)}`
963
+ );
964
+ }
965
+ function namedArgsToClarityValues(args, abiArgs) {
966
+ return abiArgs.map((abiArg) => {
967
+ if (!(abiArg.name in args)) {
968
+ throw new Error(
969
+ `@satoshai/kit: Missing argument "${abiArg.name}"`
970
+ );
971
+ }
972
+ return toClarityValue(
973
+ args[abiArg.name],
974
+ abiArg.type
975
+ );
976
+ });
977
+ }
762
978
  var preparePostConditionsForOKX = (postConditions) => postConditions.map((pc) => transactions.postConditionToHex(pc));
763
979
  var prepareArgsForOKX = (args) => args.map((arg) => transactions.cvToHex(arg));
764
980
 
765
981
  // src/hooks/use-write-contract/use-write-contract.ts
982
+ function resolveArgs(variables) {
983
+ if (!variables.abi) {
984
+ return variables.args;
985
+ }
986
+ const fn = variables.abi.functions.find(
987
+ (f) => f.name === variables.functionName && f.access === "public"
988
+ );
989
+ if (!fn) {
990
+ throw new Error(
991
+ `@satoshai/kit: Public function "${variables.functionName}" not found in ABI`
992
+ );
993
+ }
994
+ return namedArgsToClarityValues(
995
+ variables.args,
996
+ fn.args
997
+ );
998
+ }
766
999
  var useWriteContract = () => {
767
1000
  const { isConnected, address, provider } = useAddress();
768
1001
  const [data, setData] = react.useState(void 0);
@@ -776,6 +1009,7 @@ var useWriteContract = () => {
776
1009
  setStatus("pending");
777
1010
  setError(null);
778
1011
  setData(void 0);
1012
+ const resolvedArgs = resolveArgs(variables);
779
1013
  try {
780
1014
  if (provider === "okx") {
781
1015
  if (!window.okxwallet) {
@@ -785,7 +1019,7 @@ var useWriteContract = () => {
785
1019
  contractAddress: variables.address,
786
1020
  contractName: variables.contract,
787
1021
  functionName: variables.functionName,
788
- functionArgs: prepareArgsForOKX(variables.args),
1022
+ functionArgs: prepareArgsForOKX(resolvedArgs),
789
1023
  postConditions: preparePostConditionsForOKX(
790
1024
  variables.pc.postConditions
791
1025
  ),
@@ -802,7 +1036,7 @@ var useWriteContract = () => {
802
1036
  address,
803
1037
  contract: `${variables.address}.${variables.contract}`,
804
1038
  functionName: variables.functionName,
805
- functionArgs: variables.args,
1039
+ functionArgs: resolvedArgs,
806
1040
  postConditions: variables.pc.postConditions,
807
1041
  postConditionMode: variables.pc.mode === transactions.PostConditionMode.Allow ? "allow" : "deny",
808
1042
  network: getNetworkFromAddress(address)
@@ -864,20 +1098,28 @@ var useBnsName = (address) => {
864
1098
  setIsLoading(false);
865
1099
  return;
866
1100
  }
1101
+ let cancelled = false;
867
1102
  const fetchBnsName = async () => {
868
1103
  setIsLoading(true);
869
1104
  try {
870
1105
  const network = getNetworkFromAddress(address);
871
1106
  const result = await bnsV2Sdk.getPrimaryName({ address, network });
1107
+ if (cancelled) return;
872
1108
  const fullName = result ? `${result.name}.${result.namespace}` : null;
873
1109
  setBnsName(fullName);
874
1110
  } catch {
1111
+ if (cancelled) return;
875
1112
  setBnsName(null);
876
1113
  } finally {
877
- setIsLoading(false);
1114
+ if (!cancelled) {
1115
+ setIsLoading(false);
1116
+ }
878
1117
  }
879
1118
  };
880
1119
  void fetchBnsName();
1120
+ return () => {
1121
+ cancelled = true;
1122
+ };
881
1123
  }, [address]);
882
1124
  return { bnsName, isLoading };
883
1125
  };
@@ -886,8 +1128,14 @@ var useWallets = () => {
886
1128
  return react.useMemo(() => ({ wallets }), [wallets]);
887
1129
  };
888
1130
 
1131
+ // src/utils/create-contract-config.ts
1132
+ function createContractConfig(config) {
1133
+ return config;
1134
+ }
1135
+
889
1136
  exports.SUPPORTED_STACKS_WALLETS = SUPPORTED_STACKS_WALLETS;
890
1137
  exports.StacksWalletProvider = StacksWalletProvider;
1138
+ exports.createContractConfig = createContractConfig;
891
1139
  exports.getLocalStorageWallet = getLocalStorageWallet;
892
1140
  exports.getNetworkFromAddress = getNetworkFromAddress;
893
1141
  exports.getStacksWallets = getStacksWallets;
@@ -896,6 +1144,7 @@ exports.useBnsName = useBnsName;
896
1144
  exports.useConnect = useConnect;
897
1145
  exports.useDisconnect = useDisconnect;
898
1146
  exports.useSignMessage = useSignMessage;
1147
+ exports.useTransferSTX = useTransferSTX;
899
1148
  exports.useWallets = useWallets;
900
1149
  exports.useWriteContract = useWriteContract;
901
1150
  //# sourceMappingURL=index.cjs.map