uvd-x402-sdk 2.10.1 → 2.12.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/adapters/index.d.mts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/index.mjs.map +1 -1
- package/dist/backend/index.d.mts +1 -1
- package/dist/backend/index.d.ts +1 -1
- package/dist/backend/index.js.map +1 -1
- package/dist/backend/index.mjs.map +1 -1
- package/dist/{index-C60c_e5z.d.mts → index-C6Vxnneo.d.mts} +1 -1
- package/dist/{index-VIOUicmO.d.ts → index-DmJGKD9r.d.ts} +1 -1
- package/dist/{index-D-dO_FoP.d.mts → index-fIhvHqCQ.d.mts} +18 -22
- package/dist/{index-D-dO_FoP.d.ts → index-fIhvHqCQ.d.ts} +18 -22
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/providers/algorand/index.d.mts +42 -15
- package/dist/providers/algorand/index.d.ts +42 -15
- package/dist/providers/algorand/index.js +198 -32
- package/dist/providers/algorand/index.js.map +1 -1
- package/dist/providers/algorand/index.mjs +198 -32
- package/dist/providers/algorand/index.mjs.map +1 -1
- package/dist/providers/evm/index.d.mts +1 -1
- package/dist/providers/evm/index.d.ts +1 -1
- package/dist/providers/evm/index.js.map +1 -1
- package/dist/providers/evm/index.mjs.map +1 -1
- package/dist/providers/near/index.d.mts +1 -1
- package/dist/providers/near/index.d.ts +1 -1
- package/dist/providers/near/index.js.map +1 -1
- package/dist/providers/near/index.mjs.map +1 -1
- package/dist/providers/solana/index.d.mts +1 -1
- package/dist/providers/solana/index.d.ts +1 -1
- package/dist/providers/solana/index.js.map +1 -1
- package/dist/providers/solana/index.mjs.map +1 -1
- package/dist/providers/stellar/index.d.mts +1 -1
- package/dist/providers/stellar/index.d.ts +1 -1
- package/dist/providers/stellar/index.js.map +1 -1
- package/dist/providers/stellar/index.mjs.map +1 -1
- package/dist/react/index.d.mts +3 -3
- package/dist/react/index.d.ts +3 -3
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +6 -1
- package/src/providers/algorand/index.ts +261 -40
- package/src/types/index.ts +18 -22
|
@@ -684,36 +684,113 @@ function uint8ArrayToBase64(bytes) {
|
|
|
684
684
|
}
|
|
685
685
|
var algosdk = null;
|
|
686
686
|
var PeraWalletConnect = null;
|
|
687
|
+
var LuteConnect = null;
|
|
687
688
|
async function loadAlgorandDeps() {
|
|
688
689
|
if (!algosdk) {
|
|
689
690
|
algosdk = await import('algosdk');
|
|
690
691
|
}
|
|
692
|
+
}
|
|
693
|
+
async function loadPeraWallet() {
|
|
691
694
|
if (!PeraWalletConnect) {
|
|
692
695
|
const peraModule = await import('@perawallet/connect');
|
|
693
696
|
PeraWalletConnect = peraModule.PeraWalletConnect;
|
|
694
697
|
}
|
|
695
698
|
}
|
|
699
|
+
async function loadLuteWallet() {
|
|
700
|
+
if (!LuteConnect) {
|
|
701
|
+
try {
|
|
702
|
+
const luteModule = await import('lute-connect');
|
|
703
|
+
LuteConnect = luteModule.default;
|
|
704
|
+
} catch {
|
|
705
|
+
LuteConnect = null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function isLuteAvailable() {
|
|
710
|
+
if (typeof window === "undefined") return false;
|
|
711
|
+
const win = window;
|
|
712
|
+
return !!(win.algorand || win.lute);
|
|
713
|
+
}
|
|
696
714
|
var AlgorandProvider = class {
|
|
697
|
-
id = "
|
|
698
|
-
name = "
|
|
715
|
+
id = "algorand";
|
|
716
|
+
name = "Algorand Wallet";
|
|
699
717
|
networkType = "algorand";
|
|
718
|
+
// Active wallet type
|
|
719
|
+
walletType = null;
|
|
720
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
721
|
+
luteWallet = null;
|
|
700
722
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
701
723
|
peraWallet = null;
|
|
702
724
|
address = null;
|
|
703
725
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
704
726
|
algodClients = /* @__PURE__ */ new Map();
|
|
705
727
|
/**
|
|
706
|
-
* Check if
|
|
707
|
-
*
|
|
728
|
+
* Check if any Algorand wallet is available
|
|
729
|
+
* Returns true if Lute extension is installed OR we can use Pera (always available via WalletConnect)
|
|
708
730
|
*/
|
|
709
731
|
isAvailable() {
|
|
710
732
|
return typeof window !== "undefined";
|
|
711
733
|
}
|
|
712
734
|
/**
|
|
713
|
-
*
|
|
735
|
+
* Get the name of the currently connected wallet
|
|
736
|
+
*/
|
|
737
|
+
getWalletName() {
|
|
738
|
+
if (this.walletType === "lute") return "Lute Wallet";
|
|
739
|
+
if (this.walletType === "pera") return "Pera Wallet";
|
|
740
|
+
return "Algorand Wallet";
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Connect to Algorand wallet
|
|
744
|
+
* Priority: Lute (desktop extension) > Pera (mobile via WalletConnect)
|
|
714
745
|
*/
|
|
715
746
|
async connect(_chainName) {
|
|
716
747
|
await loadAlgorandDeps();
|
|
748
|
+
if (isLuteAvailable()) {
|
|
749
|
+
try {
|
|
750
|
+
return await this.connectLute();
|
|
751
|
+
} catch (error) {
|
|
752
|
+
console.warn("Lute connection failed, falling back to Pera:", error);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return await this.connectPera();
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Connect to Lute Wallet (desktop browser extension)
|
|
759
|
+
*/
|
|
760
|
+
async connectLute() {
|
|
761
|
+
await loadLuteWallet();
|
|
762
|
+
if (!LuteConnect) {
|
|
763
|
+
throw new X402Error("Lute Wallet SDK not available", "WALLET_NOT_FOUND");
|
|
764
|
+
}
|
|
765
|
+
try {
|
|
766
|
+
this.luteWallet = new LuteConnect("402milly");
|
|
767
|
+
const genesisId = "mainnet-v1.0";
|
|
768
|
+
const accounts = await this.luteWallet.connect(genesisId);
|
|
769
|
+
if (!accounts || accounts.length === 0) {
|
|
770
|
+
throw new X402Error("No accounts returned from Lute Wallet", "WALLET_CONNECTION_REJECTED");
|
|
771
|
+
}
|
|
772
|
+
this.address = accounts[0];
|
|
773
|
+
this.walletType = "lute";
|
|
774
|
+
return accounts[0];
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (error instanceof X402Error) throw error;
|
|
777
|
+
if (error instanceof Error) {
|
|
778
|
+
if (error.message.includes("rejected") || error.message.includes("cancelled")) {
|
|
779
|
+
throw new X402Error("Connection rejected by user", "WALLET_CONNECTION_REJECTED");
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
throw new X402Error(
|
|
783
|
+
`Failed to connect Lute Wallet: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
784
|
+
"UNKNOWN_ERROR",
|
|
785
|
+
error
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Connect to Pera Wallet (mobile via WalletConnect)
|
|
791
|
+
*/
|
|
792
|
+
async connectPera() {
|
|
793
|
+
await loadPeraWallet();
|
|
717
794
|
if (!PeraWalletConnect) {
|
|
718
795
|
throw new X402Error("Failed to load Pera Wallet SDK", "WALLET_NOT_FOUND");
|
|
719
796
|
}
|
|
@@ -722,6 +799,7 @@ var AlgorandProvider = class {
|
|
|
722
799
|
const accounts = await this.peraWallet.reconnectSession();
|
|
723
800
|
if (accounts.length > 0) {
|
|
724
801
|
this.address = accounts[0];
|
|
802
|
+
this.walletType = "pera";
|
|
725
803
|
return accounts[0];
|
|
726
804
|
}
|
|
727
805
|
const newAccounts = await this.peraWallet.connect();
|
|
@@ -729,8 +807,10 @@ var AlgorandProvider = class {
|
|
|
729
807
|
throw new X402Error("No accounts returned from Pera Wallet", "WALLET_CONNECTION_REJECTED");
|
|
730
808
|
}
|
|
731
809
|
this.address = newAccounts[0];
|
|
810
|
+
this.walletType = "pera";
|
|
732
811
|
this.peraWallet.connector?.on("disconnect", () => {
|
|
733
812
|
this.address = null;
|
|
813
|
+
this.walletType = null;
|
|
734
814
|
});
|
|
735
815
|
return newAccounts[0];
|
|
736
816
|
} catch (error) {
|
|
@@ -747,17 +827,21 @@ var AlgorandProvider = class {
|
|
|
747
827
|
}
|
|
748
828
|
}
|
|
749
829
|
/**
|
|
750
|
-
* Disconnect from
|
|
830
|
+
* Disconnect from wallet
|
|
751
831
|
*/
|
|
752
832
|
async disconnect() {
|
|
753
|
-
if (this.
|
|
833
|
+
if (this.walletType === "lute" && this.luteWallet) {
|
|
834
|
+
this.luteWallet = null;
|
|
835
|
+
}
|
|
836
|
+
if (this.walletType === "pera" && this.peraWallet) {
|
|
754
837
|
try {
|
|
755
838
|
await this.peraWallet.disconnect();
|
|
756
839
|
} catch {
|
|
757
840
|
}
|
|
841
|
+
this.peraWallet = null;
|
|
758
842
|
}
|
|
759
|
-
this.peraWallet = null;
|
|
760
843
|
this.address = null;
|
|
844
|
+
this.walletType = null;
|
|
761
845
|
this.algodClients.clear();
|
|
762
846
|
}
|
|
763
847
|
/**
|
|
@@ -793,15 +877,17 @@ var AlgorandProvider = class {
|
|
|
793
877
|
}
|
|
794
878
|
}
|
|
795
879
|
/**
|
|
796
|
-
* Create Algorand
|
|
880
|
+
* Create Algorand atomic group payment (GoPlausible x402-avm spec)
|
|
881
|
+
*
|
|
882
|
+
* Transaction structure (atomic group):
|
|
883
|
+
* - Transaction 0: Fee payment (UNSIGNED) - facilitator -> facilitator, covers all fees
|
|
884
|
+
* - Transaction 1: ASA transfer (SIGNED) - client -> merchant
|
|
797
885
|
*
|
|
798
|
-
*
|
|
799
|
-
* 1. ASA Transfer from user to recipient
|
|
800
|
-
* 2. Facilitator pays transaction fees
|
|
886
|
+
* The facilitator signs transaction 0 and submits the complete atomic group.
|
|
801
887
|
*/
|
|
802
888
|
async signPayment(paymentInfo, chainConfig) {
|
|
803
889
|
await loadAlgorandDeps();
|
|
804
|
-
if (!this.
|
|
890
|
+
if (!this.address || !this.walletType) {
|
|
805
891
|
throw new X402Error("Wallet not connected", "WALLET_NOT_CONNECTED");
|
|
806
892
|
}
|
|
807
893
|
if (!algosdk) {
|
|
@@ -810,28 +896,85 @@ var AlgorandProvider = class {
|
|
|
810
896
|
const algodClient = await this.getAlgodClient(chainConfig);
|
|
811
897
|
const recipient = paymentInfo.recipients?.algorand || paymentInfo.recipient;
|
|
812
898
|
const assetId = parseInt(chainConfig.usdc.address, 10);
|
|
899
|
+
const facilitatorAddress = paymentInfo.facilitator;
|
|
900
|
+
if (!facilitatorAddress) {
|
|
901
|
+
throw new X402Error(
|
|
902
|
+
"Facilitator address required for Algorand payments. Set paymentInfo.facilitator",
|
|
903
|
+
"PAYMENT_FAILED"
|
|
904
|
+
);
|
|
905
|
+
}
|
|
813
906
|
const amount = Math.floor(parseFloat(paymentInfo.amount) * 1e6);
|
|
814
907
|
try {
|
|
815
908
|
const suggestedParams = await algodClient.getTransactionParams().do();
|
|
816
|
-
const
|
|
909
|
+
const feeTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
|
|
910
|
+
sender: facilitatorAddress,
|
|
911
|
+
receiver: facilitatorAddress,
|
|
912
|
+
// self-transfer
|
|
913
|
+
amount: 0,
|
|
914
|
+
suggestedParams: {
|
|
915
|
+
...suggestedParams,
|
|
916
|
+
fee: 2e3,
|
|
917
|
+
// Covers both transactions (1000 each)
|
|
918
|
+
flatFee: true
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
const paymentTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
817
922
|
sender: this.address,
|
|
818
923
|
receiver: recipient,
|
|
819
924
|
amount: BigInt(amount),
|
|
820
925
|
assetIndex: assetId,
|
|
821
|
-
suggestedParams
|
|
926
|
+
suggestedParams: {
|
|
927
|
+
...suggestedParams,
|
|
928
|
+
fee: 0,
|
|
929
|
+
// Fee paid by transaction 0
|
|
930
|
+
flatFee: true
|
|
931
|
+
},
|
|
822
932
|
note: new TextEncoder().encode("x402 payment via uvd-x402-sdk")
|
|
823
933
|
});
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
934
|
+
const txnGroup = algosdk.assignGroupID([feeTxn, paymentTxn]);
|
|
935
|
+
const unsignedFeeTxnBytes = algosdk.encodeUnsignedTransaction(txnGroup[0]);
|
|
936
|
+
const unsignedFeeTxnBase64 = uint8ArrayToBase64(unsignedFeeTxnBytes);
|
|
937
|
+
let signedPaymentTxnBytes;
|
|
938
|
+
if (this.walletType === "lute" && this.luteWallet) {
|
|
939
|
+
const feeTxnBase64 = uint8ArrayToBase64(txnGroup[0].toByte());
|
|
940
|
+
const paymentTxnBase64 = uint8ArrayToBase64(txnGroup[1].toByte());
|
|
941
|
+
const signedTxns = await this.luteWallet.signTxns([
|
|
942
|
+
{ txn: feeTxnBase64, signers: [] },
|
|
943
|
+
// Don't sign - facilitator will
|
|
944
|
+
{ txn: paymentTxnBase64 }
|
|
945
|
+
// Sign this one
|
|
946
|
+
]);
|
|
947
|
+
if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
|
|
948
|
+
throw new X402Error("No signed transaction returned", "SIGNATURE_REJECTED");
|
|
949
|
+
}
|
|
950
|
+
const signedResult = signedTxns[1];
|
|
951
|
+
signedPaymentTxnBytes = this.decodeSignedTxn(signedResult);
|
|
952
|
+
} else if (this.walletType === "pera" && this.peraWallet) {
|
|
953
|
+
const signedTxns = await this.peraWallet.signTransaction([
|
|
954
|
+
[
|
|
955
|
+
{ txn: txnGroup[0], signers: [] },
|
|
956
|
+
// Don't sign - facilitator will
|
|
957
|
+
{ txn: txnGroup[1] }
|
|
958
|
+
// Sign this one
|
|
959
|
+
]
|
|
960
|
+
]);
|
|
961
|
+
if (!signedTxns || signedTxns.length < 2 || !signedTxns[1]) {
|
|
962
|
+
throw new X402Error("No signed transaction returned", "SIGNATURE_REJECTED");
|
|
963
|
+
}
|
|
964
|
+
signedPaymentTxnBytes = signedTxns[1];
|
|
965
|
+
} else {
|
|
966
|
+
throw new X402Error("No wallet available for signing", "WALLET_NOT_CONNECTED");
|
|
827
967
|
}
|
|
828
|
-
const
|
|
968
|
+
const signedPaymentTxnBase64 = uint8ArrayToBase64(signedPaymentTxnBytes);
|
|
829
969
|
const payload = {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
970
|
+
paymentIndex: 1,
|
|
971
|
+
// Index of the payment transaction in the group
|
|
972
|
+
paymentGroup: [
|
|
973
|
+
unsignedFeeTxnBase64,
|
|
974
|
+
// Transaction 0: unsigned fee tx
|
|
975
|
+
signedPaymentTxnBase64
|
|
976
|
+
// Transaction 1: signed payment tx
|
|
977
|
+
]
|
|
835
978
|
};
|
|
836
979
|
return JSON.stringify(payload);
|
|
837
980
|
} catch (error) {
|
|
@@ -850,6 +993,29 @@ var AlgorandProvider = class {
|
|
|
850
993
|
);
|
|
851
994
|
}
|
|
852
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Decode signed transaction from wallet response (handles various formats)
|
|
998
|
+
*/
|
|
999
|
+
decodeSignedTxn(signedResult) {
|
|
1000
|
+
if (signedResult instanceof Uint8Array) {
|
|
1001
|
+
return signedResult;
|
|
1002
|
+
} else if (typeof signedResult === "string") {
|
|
1003
|
+
try {
|
|
1004
|
+
return Uint8Array.from(atob(signedResult), (c) => c.charCodeAt(0));
|
|
1005
|
+
} catch {
|
|
1006
|
+
const standardBase64 = signedResult.replace(/-/g, "+").replace(/_/g, "/");
|
|
1007
|
+
return Uint8Array.from(atob(standardBase64), (c) => c.charCodeAt(0));
|
|
1008
|
+
}
|
|
1009
|
+
} else if (ArrayBuffer.isView(signedResult)) {
|
|
1010
|
+
return new Uint8Array(
|
|
1011
|
+
signedResult.buffer,
|
|
1012
|
+
signedResult.byteOffset,
|
|
1013
|
+
signedResult.byteLength
|
|
1014
|
+
);
|
|
1015
|
+
} else {
|
|
1016
|
+
throw new X402Error("Unexpected signed transaction format", "PAYMENT_FAILED");
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
853
1019
|
/**
|
|
854
1020
|
* Encode Algorand payment as X-PAYMENT header
|
|
855
1021
|
*
|
|
@@ -860,14 +1026,15 @@ var AlgorandProvider = class {
|
|
|
860
1026
|
*/
|
|
861
1027
|
encodePaymentHeader(paymentPayload, chainConfig, version = 1) {
|
|
862
1028
|
const payload = JSON.parse(paymentPayload);
|
|
863
|
-
|
|
1029
|
+
let networkName;
|
|
1030
|
+
if (chainConfig?.name === "algorand-testnet") {
|
|
1031
|
+
networkName = "algorand-testnet";
|
|
1032
|
+
} else {
|
|
1033
|
+
networkName = "algorand-mainnet";
|
|
1034
|
+
}
|
|
864
1035
|
const payloadData = {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
amount: payload.amount,
|
|
868
|
-
assetId: payload.assetId,
|
|
869
|
-
signedTxn: payload.signedTxn,
|
|
870
|
-
...payload.note && { note: payload.note }
|
|
1036
|
+
paymentIndex: payload.paymentIndex,
|
|
1037
|
+
paymentGroup: payload.paymentGroup
|
|
871
1038
|
};
|
|
872
1039
|
const x402Payload = version === 2 ? {
|
|
873
1040
|
x402Version: 2,
|
|
@@ -879,7 +1046,6 @@ var AlgorandProvider = class {
|
|
|
879
1046
|
x402Version: 1,
|
|
880
1047
|
scheme: "exact",
|
|
881
1048
|
network: networkName,
|
|
882
|
-
// Plain chain name for v1
|
|
883
1049
|
payload: payloadData
|
|
884
1050
|
};
|
|
885
1051
|
return btoa(JSON.stringify(x402Payload));
|