@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/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PostCondition, PostConditionMode, ClarityValue } from '@stacks/transactions';
3
+ import { ClarityAbi, ExtractAbiFunctionNames, Pretty, ExtractAbiFunction, ClarityAbiArgToPrimitiveTypeValue } from 'clarity-abitype';
4
+ export { ClarityAbi } from 'clarity-abitype';
3
5
 
4
6
  declare const SUPPORTED_STACKS_WALLETS: readonly ["xverse", "leather", "asigna", "fordefi", "wallet-connect", "okx"];
5
7
  type SupportedStacksWallet = (typeof SUPPORTED_STACKS_WALLETS)[number];
@@ -122,26 +124,82 @@ declare const useSignMessage: () => {
122
124
  status: MutationStatus;
123
125
  };
124
126
 
127
+ interface TransferSTXVariables {
128
+ recipient: string;
129
+ amount: bigint | number | string;
130
+ memo?: string;
131
+ fee?: bigint | number | string;
132
+ nonce?: bigint | number | string;
133
+ }
134
+ interface TransferSTXOptions {
135
+ onSuccess?: (txid: string) => void;
136
+ onError?: (error: Error) => void;
137
+ onSettled?: (txid: string | undefined, error: Error | null) => void;
138
+ }
139
+ declare const useTransferSTX: () => {
140
+ transferSTX: (variables: TransferSTXVariables, options?: TransferSTXOptions) => void;
141
+ transferSTXAsync: (variables: TransferSTXVariables) => Promise<string>;
142
+ reset: () => void;
143
+ data: string | undefined;
144
+ error: Error | null;
145
+ isError: boolean;
146
+ isIdle: boolean;
147
+ isPending: boolean;
148
+ isSuccess: boolean;
149
+ status: MutationStatus;
150
+ };
151
+
152
+ /** Contract principal string in the format `address.contract-name` */
153
+ type TraitReference = `${string}.${string}`;
154
+ /** Union of public function names from a Clarity ABI */
155
+ type PublicFunctionName<TAbi extends ClarityAbi> = ExtractAbiFunctionNames<TAbi, 'public'>;
156
+ /** Named args object for a specific public function, derived from ABI */
157
+ type PublicFunctionArgs<TAbi extends ClarityAbi, TFn extends ExtractAbiFunctionNames<TAbi, 'public'>> = Pretty<{
158
+ [K in ExtractAbiFunction<TAbi, TFn, 'public'>['args'][number] as K['name']]: ClarityAbiArgToPrimitiveTypeValue<K>;
159
+ }>;
160
+
125
161
  interface PostConditionConfig {
126
162
  postConditions: PostCondition[];
127
163
  mode: PostConditionMode;
128
164
  }
129
- interface WriteContractVariables {
165
+ /** Typed mode: ABI present, args is a named object with autocomplete. */
166
+ interface TypedWriteContractVariables<TAbi extends ClarityAbi, TFn extends ExtractAbiFunctionNames<TAbi, 'public'>> {
167
+ abi: TAbi;
168
+ address: string;
169
+ contract: string;
170
+ functionName: TFn;
171
+ args: PublicFunctionArgs<TAbi, TFn>;
172
+ pc: PostConditionConfig;
173
+ }
174
+ /** Untyped mode: no ABI, args is ClarityValue[] (original behavior). */
175
+ interface UntypedWriteContractVariables {
130
176
  address: string;
131
177
  contract: string;
132
178
  functionName: string;
133
179
  args: ClarityValue[];
134
180
  pc: PostConditionConfig;
135
181
  }
182
+ /** Backward-compatible alias for the untyped variant. */
183
+ type WriteContractVariables = UntypedWriteContractVariables;
136
184
  interface WriteContractOptions {
137
185
  onSuccess?: (txHash: string) => void;
138
186
  onError?: (error: Error) => void;
139
187
  onSettled?: (txHash: string | undefined, error: Error | null) => void;
140
188
  }
189
+ /** Overloaded async function: typed mode (with ABI) or untyped mode. */
190
+ interface WriteContractAsyncFn {
191
+ <const TAbi extends ClarityAbi, TFn extends ExtractAbiFunctionNames<TAbi, 'public'>>(variables: TypedWriteContractVariables<TAbi, TFn>): Promise<string>;
192
+ (variables: UntypedWriteContractVariables): Promise<string>;
193
+ }
194
+ /** Overloaded fire-and-forget function: typed mode or untyped mode. */
195
+ interface WriteContractFn {
196
+ <const TAbi extends ClarityAbi, TFn extends ExtractAbiFunctionNames<TAbi, 'public'>>(variables: TypedWriteContractVariables<TAbi, TFn>, options?: WriteContractOptions): void;
197
+ (variables: UntypedWriteContractVariables, options?: WriteContractOptions): void;
198
+ }
141
199
 
142
200
  declare const useWriteContract: () => {
143
- writeContract: (variables: WriteContractVariables, options?: WriteContractOptions) => void;
144
- writeContractAsync: (variables: WriteContractVariables) => Promise<string>;
201
+ writeContract: WriteContractFn;
202
+ writeContractAsync: WriteContractAsyncFn;
145
203
  reset: () => void;
146
204
  data: string | undefined;
147
205
  error: Error | null;
@@ -174,4 +232,15 @@ interface StacksWallets {
174
232
  }
175
233
  declare const getStacksWallets: () => StacksWallets;
176
234
 
177
- export { type ConnectOptions, type MutationStatus, type PostConditionConfig, SUPPORTED_STACKS_WALLETS, type SignMessageData, type SignMessageOptions, type SignMessageVariables, type StacksChain, StacksWalletProvider, type StacksWallets, type SupportedStacksWallet, type WalletConnectMetadata, type WalletContextValue, type WalletInfo, type WalletState, type WriteContractOptions, type WriteContractVariables, getLocalStorageWallet, getNetworkFromAddress, getStacksWallets, useAddress, useBnsName, useConnect, useDisconnect, useSignMessage, useWallets, useWriteContract };
235
+ /** Pre-bind ABI + address + contract for reuse with useWriteContract. */
236
+ declare function createContractConfig<const TAbi extends ClarityAbi>(config: {
237
+ abi: TAbi;
238
+ address: string;
239
+ contract: string;
240
+ }): {
241
+ abi: TAbi;
242
+ address: string;
243
+ contract: string;
244
+ };
245
+
246
+ export { type ConnectOptions, type MutationStatus, type PostConditionConfig, type PublicFunctionArgs, type PublicFunctionName, SUPPORTED_STACKS_WALLETS, type SignMessageData, type SignMessageOptions, type SignMessageVariables, type StacksChain, StacksWalletProvider, type StacksWallets, type SupportedStacksWallet, type TraitReference, type TransferSTXOptions, type TransferSTXVariables, type TypedWriteContractVariables, type UntypedWriteContractVariables, type WalletConnectMetadata, type WalletContextValue, type WalletInfo, type WalletState, type WriteContractOptions, type WriteContractVariables, createContractConfig, getLocalStorageWallet, getNetworkFromAddress, getStacksWallets, useAddress, useBnsName, useConnect, useDisconnect, useSignMessage, useTransferSTX, useWallets, useWriteContract };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { WalletConnect, DEFAULT_PROVIDERS, WALLET_CONNECT_PROVIDER, clearSelectedProviderId, request, getSelectedProviderId, getSelectedProvider, setSelectedProviderId } from '@stacks/connect';
2
2
  import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
- import { PostConditionMode, postConditionToHex, cvToHex } from '@stacks/transactions';
4
+ import { PostConditionMode, postConditionToHex, cvToHex, noneCV, contractPrincipalCV, standardPrincipalCV, boolCV, intCV, uintCV, bufferCV, stringAsciiCV, stringUtf8CV, someCV, listCV, tupleCV } from '@stacks/transactions';
5
5
  import { getPrimaryName } from 'bns-v2-sdk';
6
6
 
7
7
  // src/constants/stacks-provider-mapping.ts
@@ -284,6 +284,7 @@ var StacksWalletProvider = ({
284
284
  const [provider, setProvider] = useState();
285
285
  const [isConnecting, setIsConnecting] = useState(false);
286
286
  const connectGenRef = useRef(0);
287
+ const wasConnectedRef = useRef(false);
287
288
  const wcInitRef = useRef(null);
288
289
  const walletConnectRef = useRef(walletConnect);
289
290
  walletConnectRef.current = walletConnect;
@@ -507,8 +508,11 @@ var StacksWalletProvider = ({
507
508
  );
508
509
  }, [address, provider]);
509
510
  useEffect(() => {
510
- if (!address || !provider || !onConnect) return;
511
- onConnect(provider, address);
511
+ const isConnected = !!address && !!provider;
512
+ if (isConnected && !wasConnectedRef.current) {
513
+ onConnect?.(provider, address);
514
+ }
515
+ wasConnectedRef.current = isConnected;
512
516
  }, [address, provider, onConnect]);
513
517
  const handleAddressChange = useCallback(
514
518
  (newAddress) => {
@@ -757,10 +761,239 @@ var getNetworkFromAddress = (address) => {
757
761
  }
758
762
  throw new Error(`Invalid Stacks address: ${address}`);
759
763
  };
764
+
765
+ // src/hooks/use-transfer-stx.ts
766
+ var useTransferSTX = () => {
767
+ const { isConnected, address, provider } = useAddress();
768
+ const [data, setData] = useState(void 0);
769
+ const [error, setError] = useState(null);
770
+ const [status, setStatus] = useState("idle");
771
+ const transferSTXAsync = useCallback(
772
+ async (variables) => {
773
+ if (!isConnected || !address) {
774
+ throw new Error("Wallet is not connected");
775
+ }
776
+ setStatus("pending");
777
+ setError(null);
778
+ setData(void 0);
779
+ try {
780
+ if (provider === "okx") {
781
+ if (!window.okxwallet) {
782
+ throw new Error("OKX wallet not found");
783
+ }
784
+ const response2 = await window.okxwallet.stacks.signTransaction({
785
+ txType: "token_transfer",
786
+ recipient: variables.recipient,
787
+ amount: String(variables.amount),
788
+ memo: variables.memo ?? "",
789
+ stxAddress: address,
790
+ anchorMode: 3
791
+ });
792
+ setData(response2.txHash);
793
+ setStatus("success");
794
+ return response2.txHash;
795
+ }
796
+ const response = await request("stx_transferStx", {
797
+ recipient: variables.recipient,
798
+ amount: variables.amount,
799
+ ...variables.memo !== void 0 && {
800
+ memo: variables.memo
801
+ },
802
+ ...variables.fee !== void 0 && {
803
+ fee: variables.fee
804
+ },
805
+ ...variables.nonce !== void 0 && {
806
+ nonce: variables.nonce
807
+ },
808
+ network: getNetworkFromAddress(address)
809
+ });
810
+ if (!response.txid) {
811
+ throw new Error("No transaction ID returned");
812
+ }
813
+ setData(response.txid);
814
+ setStatus("success");
815
+ return response.txid;
816
+ } catch (err) {
817
+ const error2 = err instanceof Error ? err : new Error(String(err));
818
+ setError(error2);
819
+ setStatus("error");
820
+ throw error2;
821
+ }
822
+ },
823
+ [isConnected, address, provider]
824
+ );
825
+ const transferSTX = useCallback(
826
+ (variables, options) => {
827
+ transferSTXAsync(variables).then((txid) => {
828
+ options?.onSuccess?.(txid);
829
+ options?.onSettled?.(txid, null);
830
+ }).catch((error2) => {
831
+ options?.onError?.(error2);
832
+ options?.onSettled?.(void 0, error2);
833
+ });
834
+ },
835
+ [transferSTXAsync]
836
+ );
837
+ const reset = useCallback(() => {
838
+ setData(void 0);
839
+ setError(null);
840
+ setStatus("idle");
841
+ }, []);
842
+ return useMemo(
843
+ () => ({
844
+ transferSTX,
845
+ transferSTXAsync,
846
+ reset,
847
+ data,
848
+ error,
849
+ isError: status === "error",
850
+ isIdle: status === "idle",
851
+ isPending: status === "pending",
852
+ isSuccess: status === "success",
853
+ status
854
+ }),
855
+ [transferSTX, transferSTXAsync, reset, data, error, status]
856
+ );
857
+ };
858
+ function toClarityValue(value, abiType) {
859
+ if (typeof abiType === "string") {
860
+ switch (abiType) {
861
+ case "uint128": {
862
+ if (typeof value !== "bigint") {
863
+ throw new Error(
864
+ `@satoshai/kit: Expected bigint (uint128), got ${typeof value}`
865
+ );
866
+ }
867
+ return uintCV(value);
868
+ }
869
+ case "int128": {
870
+ if (typeof value !== "bigint") {
871
+ throw new Error(
872
+ `@satoshai/kit: Expected bigint (int128), got ${typeof value}`
873
+ );
874
+ }
875
+ return intCV(value);
876
+ }
877
+ case "bool": {
878
+ if (typeof value !== "boolean") {
879
+ throw new Error(
880
+ `@satoshai/kit: Expected boolean (bool), got ${typeof value}`
881
+ );
882
+ }
883
+ return boolCV(value);
884
+ }
885
+ case "principal":
886
+ case "trait_reference": {
887
+ if (typeof value !== "string") {
888
+ throw new Error(
889
+ `@satoshai/kit: Expected string (${abiType}), got ${typeof value}`
890
+ );
891
+ }
892
+ if (value.includes(".")) {
893
+ const [addr, ...rest] = value.split(".");
894
+ return contractPrincipalCV(addr, rest.join("."));
895
+ }
896
+ return standardPrincipalCV(value);
897
+ }
898
+ case "none":
899
+ return noneCV();
900
+ default:
901
+ throw new Error(
902
+ `@satoshai/kit: Unsupported ABI type "${abiType}"`
903
+ );
904
+ }
905
+ }
906
+ if ("buffer" in abiType) {
907
+ if (!(value instanceof Uint8Array)) {
908
+ throw new Error(
909
+ `@satoshai/kit: Expected Uint8Array (buffer), got ${typeof value}`
910
+ );
911
+ }
912
+ return bufferCV(value);
913
+ }
914
+ if ("string-ascii" in abiType) {
915
+ if (typeof value !== "string") {
916
+ throw new Error(
917
+ `@satoshai/kit: Expected string (string-ascii), got ${typeof value}`
918
+ );
919
+ }
920
+ return stringAsciiCV(value);
921
+ }
922
+ if ("string-utf8" in abiType) {
923
+ if (typeof value !== "string") {
924
+ throw new Error(
925
+ `@satoshai/kit: Expected string (string-utf8), got ${typeof value}`
926
+ );
927
+ }
928
+ return stringUtf8CV(value);
929
+ }
930
+ if ("optional" in abiType) {
931
+ if (value === null || value === void 0) return noneCV();
932
+ return someCV(toClarityValue(value, abiType.optional));
933
+ }
934
+ if ("list" in abiType) {
935
+ if (!Array.isArray(value)) {
936
+ throw new Error(
937
+ `@satoshai/kit: Expected array (list), got ${typeof value}`
938
+ );
939
+ }
940
+ const listType = abiType.list;
941
+ return listCV(
942
+ value.map((item) => toClarityValue(item, listType.type))
943
+ );
944
+ }
945
+ if ("tuple" in abiType) {
946
+ if (typeof value !== "object" || value === null) {
947
+ throw new Error(
948
+ `@satoshai/kit: Expected object (tuple), got ${value === null ? "null" : typeof value}`
949
+ );
950
+ }
951
+ const entries = abiType.tuple;
952
+ const obj = value;
953
+ const result = {};
954
+ for (const entry of entries) {
955
+ result[entry.name] = toClarityValue(obj[entry.name], entry.type);
956
+ }
957
+ return tupleCV(result);
958
+ }
959
+ throw new Error(
960
+ `@satoshai/kit: Unsupported ABI type: ${JSON.stringify(abiType)}`
961
+ );
962
+ }
963
+ function namedArgsToClarityValues(args, abiArgs) {
964
+ return abiArgs.map((abiArg) => {
965
+ if (!(abiArg.name in args)) {
966
+ throw new Error(
967
+ `@satoshai/kit: Missing argument "${abiArg.name}"`
968
+ );
969
+ }
970
+ return toClarityValue(
971
+ args[abiArg.name],
972
+ abiArg.type
973
+ );
974
+ });
975
+ }
760
976
  var preparePostConditionsForOKX = (postConditions) => postConditions.map((pc) => postConditionToHex(pc));
761
977
  var prepareArgsForOKX = (args) => args.map((arg) => cvToHex(arg));
762
978
 
763
979
  // src/hooks/use-write-contract/use-write-contract.ts
980
+ function resolveArgs(variables) {
981
+ if (!variables.abi) {
982
+ return variables.args;
983
+ }
984
+ const fn = variables.abi.functions.find(
985
+ (f) => f.name === variables.functionName && f.access === "public"
986
+ );
987
+ if (!fn) {
988
+ throw new Error(
989
+ `@satoshai/kit: Public function "${variables.functionName}" not found in ABI`
990
+ );
991
+ }
992
+ return namedArgsToClarityValues(
993
+ variables.args,
994
+ fn.args
995
+ );
996
+ }
764
997
  var useWriteContract = () => {
765
998
  const { isConnected, address, provider } = useAddress();
766
999
  const [data, setData] = useState(void 0);
@@ -774,6 +1007,7 @@ var useWriteContract = () => {
774
1007
  setStatus("pending");
775
1008
  setError(null);
776
1009
  setData(void 0);
1010
+ const resolvedArgs = resolveArgs(variables);
777
1011
  try {
778
1012
  if (provider === "okx") {
779
1013
  if (!window.okxwallet) {
@@ -783,7 +1017,7 @@ var useWriteContract = () => {
783
1017
  contractAddress: variables.address,
784
1018
  contractName: variables.contract,
785
1019
  functionName: variables.functionName,
786
- functionArgs: prepareArgsForOKX(variables.args),
1020
+ functionArgs: prepareArgsForOKX(resolvedArgs),
787
1021
  postConditions: preparePostConditionsForOKX(
788
1022
  variables.pc.postConditions
789
1023
  ),
@@ -800,7 +1034,7 @@ var useWriteContract = () => {
800
1034
  address,
801
1035
  contract: `${variables.address}.${variables.contract}`,
802
1036
  functionName: variables.functionName,
803
- functionArgs: variables.args,
1037
+ functionArgs: resolvedArgs,
804
1038
  postConditions: variables.pc.postConditions,
805
1039
  postConditionMode: variables.pc.mode === PostConditionMode.Allow ? "allow" : "deny",
806
1040
  network: getNetworkFromAddress(address)
@@ -862,20 +1096,28 @@ var useBnsName = (address) => {
862
1096
  setIsLoading(false);
863
1097
  return;
864
1098
  }
1099
+ let cancelled = false;
865
1100
  const fetchBnsName = async () => {
866
1101
  setIsLoading(true);
867
1102
  try {
868
1103
  const network = getNetworkFromAddress(address);
869
1104
  const result = await getPrimaryName({ address, network });
1105
+ if (cancelled) return;
870
1106
  const fullName = result ? `${result.name}.${result.namespace}` : null;
871
1107
  setBnsName(fullName);
872
1108
  } catch {
1109
+ if (cancelled) return;
873
1110
  setBnsName(null);
874
1111
  } finally {
875
- setIsLoading(false);
1112
+ if (!cancelled) {
1113
+ setIsLoading(false);
1114
+ }
876
1115
  }
877
1116
  };
878
1117
  void fetchBnsName();
1118
+ return () => {
1119
+ cancelled = true;
1120
+ };
879
1121
  }, [address]);
880
1122
  return { bnsName, isLoading };
881
1123
  };
@@ -884,6 +1126,11 @@ var useWallets = () => {
884
1126
  return useMemo(() => ({ wallets }), [wallets]);
885
1127
  };
886
1128
 
887
- export { SUPPORTED_STACKS_WALLETS, StacksWalletProvider, getLocalStorageWallet, getNetworkFromAddress, getStacksWallets, useAddress, useBnsName, useConnect, useDisconnect, useSignMessage, useWallets, useWriteContract };
1129
+ // src/utils/create-contract-config.ts
1130
+ function createContractConfig(config) {
1131
+ return config;
1132
+ }
1133
+
1134
+ export { SUPPORTED_STACKS_WALLETS, StacksWalletProvider, createContractConfig, getLocalStorageWallet, getNetworkFromAddress, getStacksWallets, useAddress, useBnsName, useConnect, useDisconnect, useSignMessage, useTransferSTX, useWallets, useWriteContract };
888
1135
  //# sourceMappingURL=index.js.map
889
1136
  //# sourceMappingURL=index.js.map