From a1024149dfe3ee22e55f3aa89b3b08b2f5d7d74b Mon Sep 17 00:00:00 2001 From: Denis Fadeev Date: Mon, 29 Jul 2024 11:34:48 +0300 Subject: [PATCH] feat: add Xverse to Connect Wallet component --- src/app/page.tsx | 37 ++++- src/components/ConnectBitcoin/index.tsx | 31 ++++ src/components/ConnectBitcoin/xverse.jpeg | Bin 0 -> 20731 bytes src/providers/BitcoinWalletProvider/index.tsx | 46 +++--- src/providers/BitcoinWalletProvider/okx.ts | 28 ++-- src/providers/BitcoinWalletProvider/unisat.ts | 28 ++-- src/providers/BitcoinWalletProvider/xdefi.ts | 28 ++-- .../BitcoinWalletProvider/xverse/index.ts | 43 ++++++ .../BitcoinWalletProvider/xverse/utils.ts | 133 ++++++++++++++++++ 9 files changed, 303 insertions(+), 71 deletions(-) create mode 100644 src/components/ConnectBitcoin/xverse.jpeg create mode 100644 src/providers/BitcoinWalletProvider/xverse/index.ts create mode 100644 src/providers/BitcoinWalletProvider/xverse/utils.ts diff --git a/src/app/page.tsx b/src/app/page.tsx index 0471d61..1eb2936 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,14 +1,43 @@ "use client"; import { ConnectButton } from "@rainbow-me/rainbowkit"; -import { ConnectBitcoin } from "@/index"; +import { + Swap, + useEthersSigner, + useZetaChainClient, + ConnectBitcoin, + useBitcoinWallet, +} from "@/index"; import { useAccount, useChainId, useWalletClient } from "wagmi"; +const contract = "0xb459F14260D1dc6484CE56EB0826be317171e91F"; // universal swap contract + const Page = () => { + const account = useAccount(); + const chainId = useChainId(); + const { data: walletClient } = useWalletClient({ chainId }); + const signer = useEthersSigner({ walletClient }); + const client = useZetaChainClient({ network: "testnet", signer }); + const { address: bitcoinAddress } = useBitcoinWallet(); + return ( -
- - +
+
+ + +
+
+
+ {client && ( + + )} +
+
); }; diff --git a/src/components/ConnectBitcoin/index.tsx b/src/components/ConnectBitcoin/index.tsx index 0455aca..f271c3b 100644 --- a/src/components/ConnectBitcoin/index.tsx +++ b/src/components/ConnectBitcoin/index.tsx @@ -17,6 +17,22 @@ import Image, { StaticImageData } from "next/image"; import okxIcon from "./okx.jpeg"; import xdefiIcon from "./xdefi.jpeg"; import unisatIcon from "./unisat.jpeg"; +import xverseIcon from "./xverse.jpeg"; +import { + Params, + Requests, + RpcResult, + SupportedWallet, + defaultAdapters, + getSupportedWallets, + SatsConnectAdapter, + setDefaultProvider, + getDefaultProvider, + removeDefaultProvider, + RpcErrorCode, + AddressPurpose, + BaseAdapter, +} from "@sats-connect/core"; const formatAddress = (str: string): string => { if (str.length <= 10) { @@ -130,6 +146,14 @@ const Details = React.memo(({ address, disconnect }: any) => { }); const Connect = React.memo(({ connectWallet, loading }: any) => { + const connectXverse = async () => { + const adapter = new BaseAdapter("XverseProviders.BitcoinProvider"); + const accounts = await adapter.request("getAccounts", { + purposes: [AddressPurpose.Stacks, AddressPurpose.Payment], + }); + console.log(accounts); + }; + return ( @@ -166,6 +190,13 @@ const Connect = React.memo(({ connectWallet, loading }: any) => { connectWallet={connectWallet} loading={loading} /> +
diff --git a/src/components/ConnectBitcoin/xverse.jpeg b/src/components/ConnectBitcoin/xverse.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..518c9788fee3e7449bd33f9a346ce306aa7667a0 GIT binary patch literal 20731 zcmeHvc_5VS*Z*zbO_U|u2t{PeT4a*!F+zm!NVcpgOj)LoHH6S+OOhnX8Zy~Si?SBk zMjOIRiHR}uyJkqs9+U6h9_ z1eusX(hvkOLyR=s5FKdIfIkq80K`CTLy#4X;A-1}Mrx%GEd}u@z-{4begyAvQ<= zVsmmn?x$^LwvQr#(wFl2>uau;q6rAG z&vX^Ew;lfJxZ6+d>0k*{-;CVnB zw1I8XYPtIB9RO`0ht}Oaz~FoP{3&-!YjB(!9Iy8DHKtt43)<~IhfR^7O_4L=d|U_U z6%vCWI!1RFL&|)MfVPysuQ{a;cpf^vBQAS&L3rkE(+>5UMsFM+8o&-Sqg^QOy<-Qd86MiSmR_;sB?&D=jxfhT_zuo0Hb-X|hJ^KlF zO9OBm+?U?=1enm}Yw5#WJ@gD$`scX&8&Hl@u6^X^MYU1zZ1gz)0CURN!MzxSTzn2t zbO>ZJXgT@o>aVnqfc5GGxj??qVesw@`9MpMC}a-lLwmrdALI|txI!L~7dYYq&bojj zF5uhuKC-{`F{7Se?*F}qDC9=H@*}v?1KdGJBp(U@*SkZXfIRnANurPr_!a<;xj}2o zS$=}?6*<22eDn(RO5mRc{XY6#^y>6GAW?b*{cd_K`n}-j4tkAM*=9eDgYrD%;4aH@ zj)6P6Q2SYd5jTL5oq(Lc`zYw)3mspT@QW_a3j|0l>nPmc(ZjhT3D*`<$N!^m4}JBa zF7~#NpLFH{WO=Uk_*HAj|4R1^Zy7!^^nmx*3~yFth%yW?ya9h+18x17rQfwqnJZ?%^wCm*LGrvaxVI3u_^J3p*h^cOvNKsG>U z-?g>&y(lZgV^tE+H4O)tXOACu{AGO{08i`z1p%4HLs^MOu+zCW+Cl@Za4&@0j# z{$|zc6kA`Fy}Ta2&&PL7{5l2~MqS3ejM|VWqcr0VMny&=O8fizW|Rcyc7S()S|?mk zz_&n9rh1rVddrH-zczo zi=@<6X=N2vHT4}kb@uA&=^GdtSy&#hvIc5)KH}o)=I(Ly_=$kPAk@iI5s_!3qGMv? zQc^FZrDt5cbopA|_52$JHw$mwD=V+4tg60W)7aG9(%SauaeHT1cTaEMtJnRb*s-_o z-hcQwK7s%IW%ldbJmK2{h`?6Wy*#3y#sdaULrYIjN6$nV4-IV)Ww2cI44V`gxwZE* zIUQRsw*3tAhCRu-r420NO6EA8!~QQ>c_oxbH{&ToqK@eA4JiEoYDB98S{)C$A7ZDY z0TV{Y1tB2P=8sSg?P*rvTK}^jF!GCkQ1;7zPOg%I^6GW%vGrvitr) z+5fNY8#a+jV%|iC=s5|xWQakHpo15mi(SekZ6-`MInd+!65kV!jgTS65cSl3T^07u zmR%=mWsMK@sSE~L-0Kc|;^q|~qp?fP^llkUqqwJ~r!yytmS%~G3^i{;;7kw;aI^=S zD6hGT3_VSQ;CN;vaXpe0J>2g|hS)k_=r5ahGZFW!2M^FihH~fUGRRQlm!@IjV>22u zv@VhiH7JvzkD*KXWC&M>nycFOo=2hA`&kj z?(L(J#6{%5&(G-@)bhDs z0gGj!RO3~#cRQZlY+M&tm%mj*s&%V<)sYmr%pE;fx0*-ZqIAeq*KI$3_3}9<<`%WB zhUIx1?hK~rPf31y2Q+yjqNSd(9PikMUWZC;JT-J*$oiCHq%!@zxGx4>*LUL{mvjWP zFRI9-ZF-)`D*wt^I)l?^U$PF-7t7a~eo`;UT6!b|BlpY?wbdwHMzyV`GcT@L_j9{C z|Ng7;(KmY1PO4314yt_Wc9EauYxK=kK0Ym&n}tff{Qk8FLNQWi-?NxMtVkfGS}G0XxN zZkWW@ivIlND}sT53G#5@-*s8$3T}3;|fJPFpjE z$ovxhjW&sJ#Z&{BHJpPCjnui3A$izuL)wIcPrY1ZaYvN#MQh5BmC&Jzto!PeZcj{& zPFW!F?12&HT@`$l>C>GP{qLV`O2oEf)sLLTqC!5pvJUibX$-@UL?@~bbAMs)ka&Kt zZVTP^hEZMwhogL&W6@!S_Ul^@{dgUK=m$yqQ{;D|w)9Y$Wuxxr@3$WX){^gDqtu=dL`@E=HdL)_UD^95BeSp6*(t5^I_(4IdjIn!v@Gw7WXCs3_F=?|lt7c7B2JN;me}YYMCpYM(JS6<$v|VmOTDF ziYD#m-cl_xRY#JS=))dn-xWf@ayzw@i5{JYv)e4%c;+MOgwfnjU=gFqbPYulIQ6ZP zu@ZV5-bW{_^(^jp4=2w#j-W*}QCXpy){HnGX|udM=gV?@WVd>@-;oub)w=&*b-2l>0P(#cVhqK_06%%gN!k`OXritfNa`wa1rp?hgJ$k2F^dC7$QwIJeEp+3^% zIwUFIsRNr-fFxdi8ck{^%9C=i9Unw6FUE*jzXyQiwIY?jV<=K|gkfM!5txx-8Du|e zSIcnahfn&WGM@0nO4WH=GSrHW*vWA_us55(Sa!@~hUh%9$j`GW0(+p&;LF72Lk7u& zPL-3nZ7~@>cWFo``Ti5Y{{IBgXJLypq=)B;TG57tE_{JOB3c-^!J5R03z#*O(1JxB zfH9U7JseFza_{5`wsU1T;UYbC(A*nTY3=veJIF1_TU_Uu=W*Zd*Y6(EmR7dV5!;sh==Qmsa4Fgc&<4ni zW;es~2e%G;6?coT?}pS4%~*DY#srCbPsHgHrSf*$zYW^7e*>@UCQI(!g;z&SxeLwr zpQX6Nj#DSg1i`32YL0|wn?DUJj@L`7vebM#B6M+u>vsrZ61(^MhS&MftkI^mJYkLd;>@C|Vy9FEp!8uanvxg4HHNA8>} zIG!}c9-2Xbw4W+QnT>ICC1Y&S5nR0Pmg{;U#s8t|Fcuy1O zU9P|*rf*||uLsBHgRJ=Gs(96U)z;e#y|!=KTrrqASf0SqQv9s0J*D9o{mqvq>@oNB z#qMnnXU<`rU9}ea^G&kNrFH`?5??yRbl4U047c5T)F2)w^@8uD@FPoIhD$LA=+8^L zsat>`@PZS?v%K$iV%BvqzmR#sdBdH7dxkuwS0|otDBN>En$m$1Mm{&uQ;0l#<-+y; z{c+YlkHr^Qmd+slFsK)4xlZ&TbX{*jBo@TU%rK#Lk~XKvGqs3CM_Bia_?aWQ4I+0H z7lmKmxo%ou3G)(thKRyrPtM{G2{u$0>PST9>!>~ZXxM6V``S0R$VOAuV^6R>w=8Y3 z{r8sI@aS&GJlM-9Aw_notvCT&*#m4vw7J1FOW*XEd45t`XUb8V+o|TcdyF$^1dR-_ zww4Ho$!`)Q{I71~KMO2t<%O`>VgMJ~cSKW3ETwqPp&i#wPGLOILZR0-BnuqWoZ&>7 zjDE>uIuUl113yD|iufdXH@qkFmsJjIk%s6C;>U(zoNq$G6q5%CS_gvW#Bm7~No}5j z!Nuq?L_-2?uZA53cwUw-M4!`yJJDWO0Kg-0@~es5hQ0V(GS9$*Xf*sh3Bf-kpL7c< zupjhTI17+xX&HIW8?GWxzz^h+rXWvZ?wJiVw##1of5I~vQaqE8^$^7~8A~|8qI9XA zX(W%3{35!t;ty**b72hDsig#mND|RRYGuhppwxJzj-0=6)Aic}7WF487aX_YE*iEz z-w|S4VHAJJ&dRZxB)$ITIH5!M4udCbhu(g28@|@x!#QDR2ObKAaY{tRD~xhAj00l)@f1Th$q*SZyh zGwL{J86QY3gZAC_eDZZ==#SOIe%ieh~>bt>R%`m|E=0W={ zfvR2aZHH}*M7s8O-AR!d+&Q=L(~M{ocB8l%4Ee)3udJwFkZx)j=^VJINaqcZ&J`e? zZJVzLK8}KP^Q|JBcd1BcwkJuhApc=JJV6|}lirbwk@N~8Lz=<08V|IlCmKn;*w%$s ztf%pSO!bkh2HX1#}LgI?_yoMhzL)Hr@fw7wnuLn_0*m0N6V4bfo zQFb-~AHO}_AeES_Hrf|R9>@g=vR>DMTK5Ge$ZqZJZ`X0Zzso+>y9i9_>`iMO!{-y8+_aH^(u_u{TE{%tM|4&&)>Ra#jz7*An)m6 zePOfyTo@)1HY@rk8WZ*~i6E^%WjN9SuS~{?KkQTV%*I6%V!vkdqoR5Z-gO=B^)9|~ zFJgC>>z*e;ve}t36V{5^VmlgbTc6|G_2)BQCN;5-i6+V?D%JQuBtxF>CYdg{d=(pu zN$1Qru*%ZlVoc2ws5_r1a5Y0S_f*P$Q;@aFlP=XOfozorNi$l-(Gzv}9((6O;un=X zsSXyQ#QUQshpry!8w)@?!XJ))4@g$rCj2j`RK!UT z@W*E3?BrvIamK@O>j+A%Ws?qCOXovEZ$BL7(h|Nsbv$$=ezG8uB}^H(>0JOEcIiFd4VmxU`*FPZ;(5Vw#Fw~@y|Aq^>{@UqNzpvt-U^cB_iopXrGjJTB zY#KmWyA_npWLydF@F2WX#D$(?J>P(r9-^YGgaOe*pzg(3vvJiyiVep<(Ri?PdJ@?! z>$A=Glh2m22lwj2jXi5MYCQx5giMmGu*nz+9hRm4W4AUS+ zaxlUOw_*Aw+Db=JNz?8V*i(6sM- z?fvLK4$I)>cH&@X2#Y(i&=;R5KRGTvJIZc<=+*;Pv(}ya-xioZ*ro^PVz5#$;ylqU z)Oo1y9wM(!X<?&ogOaNUt-B-0U_6ZERy6=gvDSjLO@EM+s1<)wX!2a&%0(F30c{^ zh>yAraDw&~GW2ES&)3}qG>Hjro)j%~fDE0r0lrQ{ha_3hlC;3(r%zhv3OE`9j1XKk z#YKkpxHY1Q8&Iia=<-p-?{|9v1dZdJNs)&@&&Z8{sL`S$iA*i-MZdU&%LW{zhy)pW ze>sV8VHk$~c&x>da3CE#QaJ*9D&!z|Y(9GQC?mLiN5riD9QRUnMMr;<89pe2=sOxJ zsErSbI#I2u<9%|p?9^qg!fTI%c{Ba)*-Y@MRq+P(T3f4z6u-SYsOxW}>*M_>_1kMPF4^F_7lox^W%;Y{zHtW%-dI?jzsA?6ThR3!E%w*W3T~;>fXTrkH}r;*Bp_teP!zW1D)5 zqkC1}nbt{H<{EZ}kfG};#P{e!-xp#W8LA>fQ=}T{NjP0S=h49EA-u{ac&P(8rd%j198e_K2k6JIQv zXIqm(zKn{t$2L^~2Iv-F7F=>9aGvnaiuCpNp zTZ@_8Vvf$@s~>6XKh!Oy<>OJ($m<&D7U&jq`1vPeB_*LUC82aziFAp#StjUX5-El& ze{ZK4?@f8R`I@abvDdqDfbzGta;UiStM>!DQN99 zl0rAghe>LWlx%!iji0Na3)xEuEPK#1A1;5#Ml=HpZ>lap%XL^+#Cv+4l5iE2 zUvIO!)JUv*Q(0Xk-I7r~#d|sd@2N=jo(|hzhETj`jmn)`;5|PA@9A{&;Gn2?@i}9U zP|q!k_By7uiGsj;ZoU1ty{A1TPjFZVyyukjiuW8{JUCUXrD$v0+BHmQYUmGPP1eTC zG4H?8qg;E!5`SycWqM%psX=oO|J_W1Y0{0zwMa6~Xf9dYtc0Gh8@)_2UjU2wF;^32 z&Ju9xJyu81$3p1=s~s8By$o`_#C3zgz!aTO6H97{s-_w0kOKc>og&67EE z)NX6Lr}k^CK%L^nexL3GAI&n8`FjTwOKOB~y?RpFI~7R!z=U zBITt-xuJV{3>i|nvTSxfQ&mO4=6tP&ieq>GZgs5Cnj9sS@S~f*LSnuIaHos_ppU+m zkjWRJ>7iOSHuIXTt^X+pe*I|w^Le!)=K7i=;mDY5PHYxZy34x!oXq4Yv5U9sjGejS zkm6=h?Q_86$pDXcWi)AuWyRwW+Vt=<6pvR~Gk>;jf9|2;XNkE^{`dP|HK$iqrbL%_ zD=%J;`+|HB6|7NxIEyb~_b#p9RtzijGff)4LX#4Vs5I#VK$B*Vv_^OeR}{1gQfSf{ zew0=#l_s@oPLeCkZ;t0n5VKI;q`4>3QxyIo2~ZaWgM$Yh`$0|PAho6u#QvkEq57ky zAx5cbv@?sHmU*#4X8hYm$Ara9k)cKOjdPbf9?qPpX|KG~=&jV2rP;NmecZkMa4i+- zG)%rRGziJb@w6~yD`T(%9zDHr-WJ3 z1SMc}(ZJ~18iqG&fiO##5@v~f?Cl3(mPO8>?6as`XR6UvW>lsyRRE*AIr^Y$M(lFu zG_zam*k9)g8q2K6l&0gN52-nftCE6tfE6K9SP|Pc&*fYp+YBEa@k!J&3Kr|PH%Y57 zzcD6DMfYT`Bf(J19&q!Y<+n}GPSzG9%qpr&F{Ra)pU<^_W|6(~X?p6qyN|3-@q5d3 zwwTR&aqcdU$o|qfc5M8KCvWO_K2&OZ>*@$3c4bVb|tWl$oG;8THkm_c(Ptq z+{mjNb2e*pkx{zSMe~%JoJg$cv?|keqq;*yaIa(>zaU1O0S2sIhwcY<%neeY1yC!L8$8I zOB5XQSk&%ERn5Iwim4O#b;kF$_D?#18#?ccxC^U9H^4D(?}r$HvWJBl zDZhSW$vgRc3^7?q1JDO1h@}Ir`ty7*5F}G;P#Jl>9{67ClRK6;t-le_x7vONy5TeZ zkG$>ESAfpSMAq~$HKfBz2!6f~T&t*;E$v)-$%pT*wa%geUnTg*)LB#htMOvZ4F$1B zJM%BPDql~#De2*Kx6JXuy`lsD{w-uE3t8dDyh+`7a{EBJzW8|6M!ucCj^(nJ3C5v*VZtn zAg#mHT!c#g(4$j#3 zqjtrIpAQ+AT|1;&c;jegRUhy9sLR2sq00RoZ)FUbGu>F92#SXZkIh8C?ua=g81?Y2 zNc#=9G}rPnqk~2j;)c#1w^b$C%+KyDE8lcLpKEH<^KQ4tk5NOD3&@Z6u_Z9nek`Yq z5;oh{c+2y9JHN?>1d3cJs;jwPdQsC`$i#NTjCnrOHp$Y$PuLF(`z?)(5reHDOC)jtv|d|7`nepK5)=X!-p_nTykRoZE+w=C5mD6AVTU zX|!i25|n2BTSksE=%v(j``1-l-dg#wbr}xykl>T%*{Nu&%L+NGC%6pRhNu+ibecZ3AQh4n#Y6=k9UQ& zN7ubmAw)iX{&f23^e3CaSXWo)bXVt;r_CwNR{_dS{m5a`QUK7}N|YP#MQ5($FjavA zJ;1S775Q4VcCGtS%~{FWzNc>FwdmxZtX_(4KKpxcnG5{b141TKc%2(HxMXVU?_LQm ztwC^k|46uF44M<0Nvz%F&?{S9{4x2WSDlFqZHrXAS=68WJmwca59pLS;O95@06%}? zOl75_Z2s5VSEd3uc22ANo|t@fqpU7mFy_dA8C`-Rd=Rw=zXwE@6)Q#fw&*G6%5p-B zho>MCJqBa)z)9;eHCJ!ayJFcn_0ppCW|aGS61U0l0AQ{Eo$zwCWcI6)86fnUZ0pZz z2I16r8Hoe12jLe&f|stMz2RpurBlW4^T&K&PtIM&-$FTB;W)Z8)M7^*`Hreub2#H+ zjdb!$DSxE*ZomiJl74k`r)bvaQQj}e^6K5N+1WpFr~iAx@W0U0UlDpgVg$MZoOO7e z9Uk5?B#48ZuG?`Ng=x)_@7<<}7)=6I(Z(^0-il|Q7S+af!Wt%bD-0#?->=TvyVu~w zn8p=W76?9mAJ{N2Q1UvIBZ;MFR8|9Q70bD;jNu7;U9bEiZaW=s?lmGaj#TQU)iXkd z*q_(t)Zx7c%d=hZL2;t(kn4+bL(_|LGXZ|E@xVXjIe=!R7H>I!6mLlbm{NG8_iak? zb`aDCHZcXAwX8b$!-d|{# z8q)t2eX+t3PiU?m$>s|&06Y|tZUoLTDI$+AiT@f8gP|N7Ng{WvyT29(*>+Ehmv_w$ zsv*l0jpK`oiekCd#}Zf`KW+@)&9nf9*}#Yyi3kJySU8HHW4r{Q!#z_*509R@f?X1} z=jk1`co|0fu_m)x31!__cGx&Ld*h)7$=5_{>=XY1rc1dW&2L8-&_E>5NUcXQ6ukgmXS+8BapUnF8AON*-$ByPa;J}LaZASB?Aj;@+2nQlNX$r<2v;XCUv$5?@yj}( zViZQ-dT&5wkFw+G%M=xpAy$HUh_UanF_|kTwVq)yymq{=*!xA!s&{D(oGdzx-lx1; zA767_E=O*RVA0g8W|s>uhi>tWymJWQ+unC7X3h;WY9groh>Y>U{$Mi1kJ3P%F_+TX z+`y{beXiFnmrOf+W^g9W*y^1o{qW&@r z%X4}H7%rf~Fu}P=1WtwF;{b-ad)rLAykyM{r+s`RlexH+)t|qog0L650hrWx5Jnjr zFI_{phsx^%48DY2+Gx+UjKS_T`FFn%w~pF8N*Mwu!TVXz7ofzzCN*TP`*E}@+mpdV zbPG;@3MQl-hhKSES?^3N2==%!-fQ-t<=xWKQ`JDLbo{x;yPowlwb8F@t*c9^5lb|r zJuTX)k_m4X2)I+4Gg0GHb=@I1<%wmdd~ty6&S{mva}SEAE|l4s#(jKbyVXXgNY4K{ z#3B7VF#Xs-Cy4Cl>;lPSO3_yQR*_$(q5mR=-kC&CkZc*|*0dkRK!jzK_oSly5FZW`eVc;v_5kH$ zrj(b|OuDATQ?L^g6WG0p>SKHSe}bVBg6ME08UX#eWf+n)qQVdXz>w|_q-P)E!QFmE zokIn~IW!O9-kZvX_wJ`Q6c(^eK3H=OcVurwJ!wR95+$+n!i1S-TSR1t;Ha?3q9a}{ zwqkKIR7v;6to`=EY2UB1Mx=LR;r+e+%&*@DIlYh$e`k3beJPvB7eQhh9Fr$V!Op43 zzy2_HwD;>UeOOx-N3Xp^eOcC|ejiC=@|0JHP}mkcU&@O?8^ZP>-R&s1#?M?I!fWcw zlTf{tm;IPJhH<(`g6vF75@8FXg+FA|*yPMQqTy09F}SF|q!|GrgSGMs2_h4227*|o zrreWCY`e_c<*(ShFgGYCLtL-*-#xlJvF`OyBMs?#He;Alh~iSN7C%aDOtJyP|8Yo* z;5|0Q+vk9*ic=Q6UQ^n=O)gbK(Bq4&Z}DcB$IbCRL!Rl(VD`D7^E$`alW#H=d?$=o z*DAaflm~?XvBOB{4|@@qCA(CGI4sXR@QN5vT3ORquN1=nLe{y8l4N)bB7;Gaad3Ab z52pF%CjAVH&Jo?D*5rarTKSj=v=@ZV+BKLtlpPe{g!Vk<9O`^bC4VaI&2~F`TYEc$ z7jNti4YU_-<6CExu;Im{IDF0Td#rvu)0$ZYXEw$A1vR623CABg(BOuC_I^>CiWKh$ zfLW#3bX3+$d2nYd0A}oa0L;pJblUve(Gb9M!o(|Rbt%r_zz zUpSDY(OA!OF;?_GKJ8gu)No6dOx38Gajv^R#}2ufiZz3`_aSa$2o~Z_Ty{)d&=`ix zoy3K6i#!oLg>D|&(l?XSrR)vU>kg#J^lnjoVxcV(?d-@rF~!r;Y@&7PHZi!j{7JYNUOv{zLTGM?tujkd z4LJT|q$;md&&#;#gYE3q>As}{RQvy%1^&infmMs6=Cw>~?z@1^bdfmNmSVLS9f?AO zj75J!Y7=kR7Q#DEyu3ZZV#MC_V3bC+mI%tz_@(ldOsmNz)y^Vaw-Q2}z0Fj~*9y2# zv{A&O>T}~@&j;E*4-u8^e{ukq@EPaoByls1VPg2*Wj}x7MSsIM!En zDci$zyJUyngWZ?iCDRV-vc=vx30eAq!n8N#4j3?f%Rn~(?zoO53-XdkRGBr%v)yFv ztZ_-7JV%9{u2omZ-cf$$IW|;w!_*4SO-L7Y&KhV4>}s1!Vk0>GQSUolk?E>dzbIRI zm4^6ah2CET=19K}2z*U61Mdz*(})HKr7Jf0`A-pJQ%4yA7dYh>Tr@-Pc<7$Cd@~~Y zm?_CVtHhJHw|Caysj9=4duI+`(7mbSc9KF!hfoOVCO}9#QwixhmumzHA$`KrKB#eM zD}|75GApgwJz&S5-nr0gGFKCo86>0z2x$wOe?drJlY#g zhlhsVJf|Th{)tb5uUQrU+dEi+DMz|vKWo2>Og>eO;L}*>SMW7<=$Qil;9_e+N8s6wPizs(srVaPirsZNJAu_|qR=o@Vjc>g1XL z!S+Pc_=YTH>g1jsn{r*5+$&&m?YC=7f|}Pcq-2^$QNJF~*9TnttQhvEK2<_B82{se$%6y}UWNZC%u>szI zCW<7HA#vm<@PoBK;GntSh^st}h+6p5@>w${{o6Bycot3K1iw=V#{j|_lt6WeW=m(Z z;Gn`sHy1!ykNq;5By2N2@2^uxJc92{;HhXCoR4*lY#qkMx?A77sEptg8t4e}3P>M_G_;o%CzaE@t=Lz_A z(})vQssd*d+CO+YbN4&+8;JCobxr3*%_HmLJdLY2Wl8>tW`vDkNHBdelw2?eHYD7I zwUMDEd4k<;cvI-_AtBA0sp;R>bDI*0O`HsEHCd{~e8LislcD@|W=!wXe!EZD7j(mV z#7HH2J!T{Wz1|6DeePXvNa*4w)S}H9ihgV^Pb@a@^~noygPkWq8vtL^GbPsKc}h&# zN^A4I7=5W|v+$LQKc)Ekz2dLML8}nRsJ?N%Jgv-BxMhtD_hGq`BAZn|b-pNGt2}r_qJT zh9po>VMn+-Mu~6`p5Q!NZPk+M1yPH76@4M{*aX&bIWMps&q6~8He~B)OEs}ODT0Gq za~q50M&eMJ8|b=!Y|8v^nb%r%s{g#cKtX8+LF59)=!VxJu>|9H!WdA`z1`Y|@4=EP zZVF;0n<_G=OX|2n{U6g~){%r;G`m&F0YY^Vb58sj34J+0$(-d>08j3;f>DBCFC_zjKs zm72Jnq*q(_Sm5-j)rE#;0040cK}W)4f|xL z%a?5XJ0CfW2le|-f2g#(Vq9(fY(owlL?Czp*^VWCO#~qnT%1_}eklAua^Ns3i3Hn? zKh!T$00X~0c9LanYW9EK!J8CX65>_gtsF1&AvV2#nhYWD;uy{jEF0pD$o=b{+lkBo>>SlMW&{`=~U0#E5>} zOsY8^-hdFC=)aMj|9YZ{O)W-~?aphq`ys+5_RWQyxl)~9y=BZ}*H88D?Nb=Aw>yp2 zrX#_0Yae!ef-(9jB_+Ym&9@Ql9I`GznEz|V?_Hfdw7MhCTAm;i)RIk*QEP?K6TY;G zFu$nhMXeuojQ)z`B&ra6b0&fW$k3dT=X`p`ka%xJ377rr@h_eNLshDo`7KJi1>+Yj2G4l`OKYFcX-{u+Ge@0R^Hy8QqDY2ugof=UCz*7VJPLz#c03v5kC t_BSs48(m<3NfZ9?A%8>6nqAfg$u+zFw-3VBWGMdIV%EIipSADT{|B=^=4t={ literal 0 HcmV?d00001 diff --git a/src/providers/BitcoinWalletProvider/index.tsx b/src/providers/BitcoinWalletProvider/index.tsx index 98a9f88..36673be 100644 --- a/src/providers/BitcoinWalletProvider/index.tsx +++ b/src/providers/BitcoinWalletProvider/index.tsx @@ -2,17 +2,20 @@ import React, { createContext, useContext, useState, ReactNode } from "react"; import unisatWallet from "./unisat"; import okxWallet from "./okx"; import xdefiWallet from "./xdefi"; +import xverseWallet from "./xverse"; export const walletTypes = { unisat: unisatWallet, okx: okxWallet, xdefi: xdefiWallet, + xverse: xverseWallet, }; export type WalletType = keyof typeof walletTypes; interface WalletContextProps { address: string | null; + publicKey: string | null; loading: { isLoading: boolean; walletType: WalletType | null }; connectedWalletType: WalletType | null; connectWallet: (walletType: WalletType) => void; @@ -34,6 +37,7 @@ export const BitcoinWalletProvider = ({ children: ReactNode; }) => { const [address, setAddress] = useState(null); + const [publicKey, setPublicKey] = useState(null); const [loading, setLoading] = useState<{ isLoading: boolean; walletType: WalletType | null; @@ -44,23 +48,19 @@ export const BitcoinWalletProvider = ({ const connectWallet = async (walletType: WalletType) => { setAddress(null); const walletConfig = walletTypes[walletType]; - const wallet = (window as any)[walletConfig.name]; - if (wallet) { - setLoading({ isLoading: true, walletType }); - try { - const address = await walletConfig.getAddress(wallet); - setAddress(address); - setConnectedWalletType(walletType); - } catch (error) { - console.error(`Connection to ${walletConfig.label} failed:`, error); - setLoading({ isLoading: false, walletType: null }); - return; - } - setLoading({ isLoading: false, walletType: null }); - } else { - console.error("Unsupported wallet type"); + + setLoading({ isLoading: true, walletType }); + try { + const { address, publicKey } = await walletConfig.getAddress(); + setAddress(address); + setPublicKey(publicKey); + setConnectedWalletType(walletType); + } catch (error) { + console.error(`Connection to ${walletConfig.label} failed:`, error); setLoading({ isLoading: false, walletType: null }); + return; } + setLoading({ isLoading: false, walletType: null }); }; const disconnect = () => { @@ -79,16 +79,11 @@ export const BitcoinWalletProvider = ({ } const walletConfig = walletTypes[connectedWalletType]; - const wallet = (window as any)[walletConfig.name]; - if (wallet) { - try { - const txHash = await walletConfig.sendTransaction(wallet, params); - console.log(`Broadcasted a transaction: ${txHash}`); - } catch (error) { - console.error(`Transaction with ${walletConfig.label} failed:`, error); - } - } else { - console.error("Unsupported wallet type"); + try { + const txHash = await walletConfig.sendTransaction(params); + console.log(`Broadcasted a transaction: ${txHash}`); + } catch (error) { + console.error(`Transaction with ${walletConfig.label} failed:`, error); } }; @@ -96,6 +91,7 @@ export const BitcoinWalletProvider = ({ { - return (await wallet.bitcoinTestnet.connect()).address; + getAddress: async () => { + const wallet = (window as any).okxwallet; + const address = (await wallet.bitcoinTestnet.connect()).address; + return { address, publicKey: null }; }, - sendTransaction: async ( - wallet: any, - { - to, - value, - memo, - }: { - to: string; - value: number; - memo?: string; - } - ) => { + sendTransaction: async ({ + to, + value, + memo, + }: { + to: string; + value: number; + memo?: string; + }) => { + const wallet = (window as any).okxwallet; const account = await wallet?.bitcoinTestnet?.connect(); if (!account) throw new Error("No account found"); const txHash = await wallet.bitcoinTestnet.send({ diff --git a/src/providers/BitcoinWalletProvider/unisat.ts b/src/providers/BitcoinWalletProvider/unisat.ts index 60bcfd8..df3e456 100644 --- a/src/providers/BitcoinWalletProvider/unisat.ts +++ b/src/providers/BitcoinWalletProvider/unisat.ts @@ -1,21 +1,21 @@ export default { label: "Unisat Wallet", name: "unisat", - getAddress: async (wallet: any) => { - return (await wallet.requestAccounts())[0]; + getAddress: async () => { + const wallet = (window as any).unisat; + const address = (await wallet.requestAccounts())[0]; + return { address, publicKey: null }; }, - sendTransaction: async ( - wallet: any, - { - to, - value, - memo, - }: { - to: string; - value: number; - memo?: string; - } - ) => { + sendTransaction: async ({ + to, + value, + memo, + }: { + to: string; + value: number; + memo?: string; + }) => { + const wallet = (window as any).unisat; await wallet.requestAccounts(); const memos = memo && [memo.toLowerCase()]; const tx = await wallet.sendBitcoin(to, value * 1e8, { memos }); diff --git a/src/providers/BitcoinWalletProvider/xdefi.ts b/src/providers/BitcoinWalletProvider/xdefi.ts index 080c531..60c4c55 100644 --- a/src/providers/BitcoinWalletProvider/xdefi.ts +++ b/src/providers/BitcoinWalletProvider/xdefi.ts @@ -1,22 +1,22 @@ export default { label: "XDEFI Wallet", name: "xfi", - getAddress: async (wallet: any) => { + getAddress: async () => { + const wallet = (window as any).xfi; wallet.bitcoin.changeNetwork("testnet"); - return (await wallet?.bitcoin?.getAccounts())[0]; + const address = (await wallet?.bitcoin?.getAccounts())[0]; + return { address, publicKey: null }; }, - sendTransaction: async ( - wallet: any, - { - to, - value, - memo, - }: { - to: string; - value: number; - memo?: string; - } - ) => { + sendTransaction: async ({ + to, + value, + memo, + }: { + to: string; + value: number; + memo?: string; + }) => { + const wallet = (window as any).unisat; wallet.bitcoin.changeNetwork("testnet"); const account = (await wallet?.bitcoin?.getAccounts())?.[0]; if (!account) throw new Error("No account found"); diff --git a/src/providers/BitcoinWalletProvider/xverse/index.ts b/src/providers/BitcoinWalletProvider/xverse/index.ts new file mode 100644 index 0000000..93aba9a --- /dev/null +++ b/src/providers/BitcoinWalletProvider/xverse/index.ts @@ -0,0 +1,43 @@ +import { AddressPurpose, BaseAdapter } from "@sats-connect/core"; +import { createTransaction, signPsbt } from "./utils"; + +export default { + label: "Xverse Wallet", + name: "xverse", + getAddress: async () => { + const adapter = new BaseAdapter("XverseProviders.BitcoinProvider"); + const accounts: any = await adapter.request("getAccounts", { + purposes: [AddressPurpose.Payment, AddressPurpose.Payment], + }); + console.log(accounts); + return { + address: accounts.result[0].address, + publicKey: accounts.result[0].publicKey, + }; + }, + sendTransaction: async ({ + to, + value, + memo, + }: { + to: string; + value: number; + memo?: string; + }) => { + const adapter = new BaseAdapter("XverseProviders.BitcoinProvider"); + const accounts: any = await adapter.request("getAccounts", { + purposes: [AddressPurpose.Payment, AddressPurpose.Payment], + }); + const address = accounts.result[0].address; + const publicKey = accounts.result[0].publicKey; + + const result = await createTransaction(publicKey, address, { + memo, + amount: value * 1e8, + to, + }); + + const res = await signPsbt(result.psbtB64, result.utxoCnt, address); + console.log(res); + }, +}; diff --git a/src/providers/BitcoinWalletProvider/xverse/utils.ts b/src/providers/BitcoinWalletProvider/xverse/utils.ts new file mode 100644 index 0000000..e46b826 --- /dev/null +++ b/src/providers/BitcoinWalletProvider/xverse/utils.ts @@ -0,0 +1,133 @@ +import { base64, hex } from "@scure/base"; +import * as btc from "micro-btc-signer"; +import { RpcErrorCode } from "sats-connect"; +import { BaseAdapter } from "@sats-connect/core"; + +const bitcoinTestnet = { + bech32: "tb", + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +}; + +async function fetchUtxo(address: string): Promise { + try { + const response = await fetch( + `https://mempool.space/testnet/api/address/${address}/utxo` + ); + if (!response.ok) { + throw new Error("Failed to fetch UTXO"); + } + const utxos: any[] = await response.json(); + + if (utxos.length === 0) { + throw new Error("0 Balance"); + } + return utxos; + } catch (error) { + console.error("Error fetching UTXO:", error); + throw error; + } +} + +async function createTransaction( + publickkey: string, + senderAddress: string, + params: any +): Promise<{ psbtB64: string; utxoCnt: number }> { + const publicKey = hex.decode(publickkey); + + const p2wpkh = btc.p2wpkh(publicKey, bitcoinTestnet); + const p2sh = btc.p2sh(p2wpkh, bitcoinTestnet); + + const recipientAddress = params.to; + if (!senderAddress) { + throw new Error("Error: no sender address"); + } + if (!recipientAddress) { + throw new Error("Error: no recipient address in ENV"); + } + + const output = await fetchUtxo(senderAddress); + + const tx = new btc.Transaction({ + allowUnknowOutput: true, + }); + + let utxoCnt = 0; + + output.forEach((utxo) => { + tx.addInput({ + txid: utxo.txid, + index: utxo.vout, + witnessUtxo: { + script: p2sh.script, + amount: BigInt(utxo.value), + }, + witnessScript: p2sh.witnessScript, + redeemScript: p2sh.redeemScript, + }); + utxoCnt += 1; + }); + + const changeAddress = senderAddress; + + const memo = params.memo.toLowerCase(); + + const opReturn = btc.Script.encode(["RETURN", Buffer.from(memo, "utf8")]); + tx.addOutputAddress(recipientAddress, BigInt(params.amount), bitcoinTestnet); + tx.addOutput({ + script: opReturn, + amount: BigInt(0), + }); + tx.addOutputAddress(changeAddress, BigInt(800), bitcoinTestnet); + + const psbt = tx.toPSBT(0); + + const psbtB64 = base64.encode(psbt); + + return { psbtB64, utxoCnt }; +} + +async function signPsbt( + psbtBase64: string, + utxoCnt: number, + senderAddress: string +) { + // Get the PSBT Base64 from the input + + if (!psbtBase64) { + alert("Please enter a valid PSBT Base64 string."); + return; + } + + const sigInputs = new Array(utxoCnt).fill(0, 0, utxoCnt).map((_, i) => i); + + try { + const adapter = new BaseAdapter("XverseProviders.BitcoinProvider"); + const response = await adapter.request("signPsbt", { + psbt: psbtBase64, + allowedSignHash: btc.SignatureHash.ALL, + broadcast: true, + signInputs: { + [senderAddress]: sigInputs, + }, + }); + + if (response.status === "success") { + alert("PSBT signed successfully!"); + } else { + if (response.error.code === RpcErrorCode.USER_REJECTION) { + alert("Request canceled by user"); + } else { + console.error("Error signing PSBT:", response.error); + alert("Error signing PSBT: " + response.error.message); + } + } + } catch (err) { + console.error("Unexpected error:", err); + alert("Error while signing"); + } +} + +export { createTransaction, signPsbt };