@pollar/core 0.4.4 → 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.mjs CHANGED
@@ -650,249 +650,118 @@ function createApiClient(baseUrl) {
650
650
  return createClient({ baseUrl });
651
651
  }
652
652
 
653
- // src/constants.ts
654
- var StateStatus = {
655
- NONE: "NONE",
656
- LOADING: "LOADING",
657
- SUCCESS: "SUCCESS",
658
- ERROR: "ERROR"
659
- };
660
- var PollarStateVar = {
661
- NETWORK: "network",
662
- AUTHENTICATION: "authentication",
663
- TRANSACTION: "transaction"
664
- };
665
- var STATE_VAR_CODES = {
666
- authentication: {
667
- NONE: "NONE",
668
- LOGOUT: "LOGOUT",
669
- CREATE_SESSION_START: "CREATE_SESSION_START",
670
- CREATE_SESSION_ERROR: "CREATE_SESSION_ERROR",
671
- CREATE_SESSION_SUCCESS: "CREATE_SESSION_SUCCESS",
672
- EMAIL_AUTH_START: "EMAIL_AUTH_START",
673
- EMAIL_AUTH_START_ERROR: "EMAIL_AUTH_START_ERROR",
674
- EMAIL_AUTH_START_SUCCESS: "EMAIL_AUTH_START_SUCCESS",
675
- EMAIL_AUTH_CODE_ERROR: "EMAIL_AUTH_CODE_ERROR",
676
- EMAIL_AUTH_CODE_SUCCESS: "EMAIL_AUTH_CODE_SUCCESS",
677
- WALLET_AUTH_START: "WALLET_AUTH_START",
678
- WALLET_AUTH_FREIGHTER_NOT_INSTALLED: "WALLET_AUTH_FREIGHTER_NOT_INSTALLED",
679
- WALLET_AUTH_ALBEDO_NOT_INSTALLED: "WALLET_AUTH_ALBEDO_NOT_INSTALLED",
680
- WALLET_AUTH_CONNECTED: "WALLET_AUTH_CONNECTED",
681
- WALLET_AUTH_LOGIN_START: "WALLET_AUTH_LOGIN_START",
682
- WALLET_AUTH_LOGIN_START_SUCCESS: "WALLET_AUTH_LOGIN_START_SUCCESS",
683
- WALLET_AUTH_LOGIN_START_ERROR: "WALLET_AUTH_LOGIN_START_ERROR",
684
- WALLET_AUTH_ERROR: "WALLET_AUTH_ERROR",
685
- STREAM_POLL_START: "STREAM_POLL_START",
686
- STREAM_POLL_EVENT: "STREAM_POLL_EVENT",
687
- STREAM_POLL_READY: "STREAM_POLL_READY",
688
- FETCH_SESSION_START: "FETCH_SESSION_START",
689
- FETCH_SESSION_SUCCESS: "FETCH_SESSION_SUCCESS",
690
- FETCH_SESSION_ERROR: "FETCH_SESSION_ERROR",
691
- NO_RESTORED_SESSION: "NO_RESTORED_SESSION",
692
- RESTORED_SESSION_SUCCESS: "RESTORED_SESSION_SUCCESS",
693
- RESTORED_SESSION_ERROR: "RESTORED_SESSION_ERROR",
694
- SESSION_STORED: "SESSION_STORED",
695
- ERROR_ABORTED: "ABORTED",
696
- ERROR_UNKNOWN: "ERROR_UNKNOWN"
697
- },
698
- walletAddress: {
699
- NONE: "NONE",
700
- REMOVED_ADDRESS: "REMOVED_ADDRESS",
701
- UPDATED_ADDRESS: "UPDATED_ADDRESS"
702
- },
703
- transaction: {
704
- NONE: "NONE",
705
- BUILD_TRANSACTION_ERROR_NO_WALLET: "BUILD_TRANSACTION_ERROR_NO_WALLET",
706
- BUILD_TRANSACTION_START: "BUILD_TRANSACTION_START",
707
- BUILD_TRANSACTION_SUCCESS: "BUILD_TRANSACTION_SUCCESS",
708
- BUILD_TRANSACTION_ERROR: "BUILD_TRANSACTION_ERROR",
709
- SIGN_SEND_TRANSACTION_START: "SIGN_SEND_TRANSACTION_START",
710
- SIGN_SEND_TRANSACTION_SUCCESS: "SIGN_SEND_TRANSACTION_SUCCESS",
711
- SIGN_SEND_TRANSACTION_ERROR: "SIGN_SEND_TRANSACTION_ERROR"
712
- },
713
- network: {
714
- NONE: "NONE",
715
- NETWORK_UPDATED: "NETWORK_UPDATED"
653
+ // src/api/endpoints/kyc.ts
654
+ async function getKycStatus(api, providerId) {
655
+ const { data, error } = await api.GET("/kyc/status", {
656
+ params: { query: providerId ? { providerId } : {} }
657
+ });
658
+ if (!data?.content || error) {
659
+ throw new Error(error?.error ?? "Failed to get KYC status");
716
660
  }
717
- };
718
-
719
- // src/client/helpers.ts
720
- var emitResponse = (state, response, success, errorCode, emitLog) => {
721
- const isSuccess = !response.error && !!response.data && !!response.data?.success;
722
- emitLog(
723
- state,
724
- isSuccess ? success.code : errorCode,
725
- isSuccess ? "info" : "error",
726
- isSuccess ? success.status || StateStatus.LOADING : StateStatus.ERROR,
727
- isSuccess ? response.data : response.error
728
- );
729
- return isSuccess;
730
- };
731
-
732
- // src/wallets/FreighterAdapter.ts
733
- var import_freighter_api = __toESM(require_index_min());
661
+ return data.content;
662
+ }
663
+ async function getKycProviders(api, country) {
664
+ const { data, error } = await api.GET("/kyc/providers", { params: { query: { country } } });
665
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get KYC providers");
666
+ return data.content;
667
+ }
668
+ async function startKyc(api, body) {
669
+ const { data, error } = await api.POST("/kyc/start", { body });
670
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to start KYC");
671
+ return data.content;
672
+ }
673
+ async function resolveKyc(api, providerId, level = "basic") {
674
+ const { status } = await getKycStatus(api, providerId);
675
+ if (status === "approved") return { alreadyApproved: true };
676
+ const started = await startKyc(api, { providerId, level });
677
+ return { alreadyApproved: false, ...started };
678
+ }
679
+ async function pollKycStatus(api, providerId, { intervalMs = 3e3, timeoutMs = 3e5 } = {}) {
680
+ const deadline = Date.now() + timeoutMs;
681
+ while (Date.now() < deadline) {
682
+ const { status } = await getKycStatus(api, providerId);
683
+ if (status === "approved" || status === "rejected") return status;
684
+ await new Promise((r) => setTimeout(r, intervalMs));
685
+ }
686
+ throw new Error("KYC polling timed out");
687
+ }
734
688
 
735
- // src/wallets/types.ts
736
- var WalletType = /* @__PURE__ */ ((WalletType2) => {
737
- WalletType2["FREIGHTER"] = "freighter";
738
- WalletType2["ALBEDO"] = "albedo";
739
- return WalletType2;
740
- })(WalletType || {});
689
+ // src/api/endpoints/ramps.ts
690
+ async function getRampsQuote(api, query) {
691
+ const { data, error } = await api.GET("/ramps/quote", { params: { query } });
692
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get ramp quotes");
693
+ return data.content;
694
+ }
695
+ async function createOnRamp(api, body) {
696
+ const { data, error } = await api.POST("/ramps/onramp", { body });
697
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to create onramp");
698
+ return data.content;
699
+ }
700
+ async function createOffRamp(api, body) {
701
+ const { data, error } = await api.POST("/ramps/offramp", { body });
702
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to create offramp");
703
+ return data.content;
704
+ }
705
+ async function getRampTransaction(api, txId) {
706
+ const { data, error } = await api.GET("/ramps/transaction/{txId}", { params: { path: { txId } } });
707
+ if (!data?.content || error) throw new Error(error?.error ?? "Failed to get transaction");
708
+ return data.content;
709
+ }
710
+ async function pollRampTransaction(api, txId, { intervalMs = 5e3, timeoutMs = 6e5 } = {}) {
711
+ const deadline = Date.now() + timeoutMs;
712
+ while (Date.now() < deadline) {
713
+ const { status } = await getRampTransaction(api, txId);
714
+ if (status === "completed" || status === "failed") return status;
715
+ await new Promise((r) => setTimeout(r, intervalMs));
716
+ }
717
+ throw new Error("Ramp transaction polling timed out");
718
+ }
741
719
 
742
- // src/wallets/FreighterAdapter.ts
743
- var FreighterAdapter = class {
744
- constructor() {
745
- this.type = "freighter" /* FREIGHTER */;
746
- }
747
- async isAvailable() {
748
- try {
749
- return await (0, import_freighter_api.isConnected)();
750
- } catch {
751
- return false;
752
- }
753
- }
754
- async connect() {
755
- const connected = await (0, import_freighter_api.isConnected)();
756
- if (!connected) {
757
- throw new Error("Freighter wallet is not installed");
758
- }
759
- const allowed = await (0, import_freighter_api.isAllowed)();
760
- if (!allowed) {
761
- await (0, import_freighter_api.setAllowed)();
762
- }
763
- const userInfo = await (0, import_freighter_api.getUserInfo)();
764
- if (!userInfo?.publicKey) {
765
- throw new Error("Failed to get user information from Freighter");
766
- }
767
- return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
768
- }
769
- async disconnect() {
720
+ // src/stellar/StellarClient.ts
721
+ var HORIZON_URLS = {
722
+ mainnet: "https://horizon.stellar.org",
723
+ testnet: "https://horizon-testnet.stellar.org"
724
+ };
725
+ var StellarClient = class {
726
+ constructor(config) {
727
+ this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
770
728
  }
771
- async getPublicKey() {
729
+ async submitTransaction(signedXdr) {
772
730
  try {
773
- const allowed = await (0, import_freighter_api.isAllowed)();
774
- if (!allowed) return null;
775
- const userInfo = await (0, import_freighter_api.getUserInfo)();
776
- return userInfo?.publicKey ?? null;
731
+ const response = await fetch(`${this.horizonUrl}/transactions`, {
732
+ method: "POST",
733
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
734
+ body: new URLSearchParams({ tx: signedXdr })
735
+ });
736
+ if (!response.ok) {
737
+ const body = await response.json().catch(() => ({}));
738
+ return { success: false, errorCode: body.extras?.result_codes?.transaction ?? "HORIZON_ERROR" };
739
+ }
740
+ const data = await response.json();
741
+ return { success: true, hash: data.hash };
777
742
  } catch {
778
- return null;
779
- }
780
- }
781
- async getNetwork() {
782
- return (0, import_freighter_api.getNetwork)();
783
- }
784
- async signTransaction(xdr, options) {
785
- const result = await (0, import_freighter_api.signTransaction)(xdr, {
786
- network: options?.network,
787
- networkPassphrase: options?.networkPassphrase,
788
- accountToSign: options?.accountToSign
789
- });
790
- if (!result || typeof result !== "string") {
791
- throw new Error("Invalid response from Freighter");
792
- }
793
- return { signedTxXdr: result };
794
- }
795
- async signAuthEntry(entryXdr, options) {
796
- const result = await (0, import_freighter_api.signAuthEntry)(entryXdr, { accountToSign: options?.accountToSign });
797
- if (!result || typeof result !== "string") {
798
- throw new Error("Invalid response from Freighter");
743
+ return { success: false, errorCode: "NETWORK_ERROR" };
799
744
  }
800
- return { signedAuthEntry: result };
801
745
  }
802
746
  };
803
747
 
804
- // src/wallets/AlbedoAdapter.ts
805
- function openAlbedoPopup(url) {
806
- const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
807
- if (!popup) {
808
- throw new Error("Failed to open Albedo popup (blocked by browser)");
809
- }
810
- return popup;
811
- }
812
- function waitForAlbedoPopup() {
813
- return new Promise((resolve, reject) => {
814
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
815
- function handler(event) {
816
- if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
817
- clearTimeout(timeout);
818
- window.removeEventListener("message", handler);
819
- resolve(event.data.payload);
820
- }
821
- window.addEventListener("message", handler);
822
- });
823
- }
824
- function waitForAlbedoResult() {
825
- return new Promise((resolve, reject) => {
826
- const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
827
- const parseResult = () => {
828
- const params = new URLSearchParams(window.location.search);
829
- if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
830
- clearTimeout(timeout);
831
- const result = {};
832
- params.forEach((value, key) => {
833
- result[key] = value;
834
- });
835
- window.history.replaceState({}, document.title, window.location.pathname);
836
- resolve(result);
837
- };
838
- parseResult();
839
- window.addEventListener("popstate", parseResult);
840
- });
841
- }
842
- var AlbedoAdapter = class {
843
- constructor() {
844
- this.type = "albedo" /* ALBEDO */;
845
- }
846
- async isAvailable() {
847
- return typeof window !== "undefined";
848
- }
849
- async connect() {
850
- const url = new URL("https://albedo.link");
851
- url.searchParams.set("intent", "public-key");
852
- url.searchParams.set("app_name", "Pollar");
853
- url.searchParams.set("network", "testnet");
854
- url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
855
- url.searchParams.set("origin", window.location.origin);
856
- openAlbedoPopup(url.toString());
857
- const result = await waitForAlbedoPopup();
858
- if (!result.pubkey) {
859
- throw new Error("Albedo connection rejected");
860
- }
861
- return { address: result.pubkey, publicKey: result.pubkey };
862
- }
863
- async disconnect() {
864
- }
865
- async getPublicKey() {
866
- return null;
867
- }
868
- async getNetwork() {
869
- throw new Error("Albedo does not expose network");
870
- }
871
- async signTransaction(xdr, _options) {
872
- const url = new URL("https://albedo.link");
873
- url.searchParams.set("intent", "tx");
874
- url.searchParams.set("xdr", xdr);
875
- url.searchParams.set("app_name", "Pollar");
876
- url.searchParams.set("network", "testnet");
877
- url.searchParams.set("callback", window.location.href);
878
- url.searchParams.set("origin", window.location.origin);
879
- window.location.href = url.toString();
880
- const result = await waitForAlbedoResult();
881
- if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
882
- return { signedTxXdr: result.signed_envelope_xdr };
883
- }
884
- async signAuthEntry(entryXdr, _options) {
885
- const url = new URL("https://albedo.link");
886
- url.searchParams.set("intent", "sign-auth-entry");
887
- url.searchParams.set("xdr", entryXdr);
888
- url.searchParams.set("app_name", "Pollar");
889
- url.searchParams.set("network", "testnet");
890
- url.searchParams.set("callback", window.location.href);
891
- url.searchParams.set("origin", window.location.origin);
892
- window.location.href = url.toString();
893
- const result = await waitForAlbedoResult();
894
- if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
895
- return { signedAuthEntry: result.signed_xdr };
748
+ // src/types.ts
749
+ var AUTH_ERROR_CODES = {
750
+ SESSION_CREATE_FAILED: "SESSION_CREATE_FAILED",
751
+ EMAIL_SEND_FAILED: "EMAIL_SEND_FAILED",
752
+ EMAIL_VERIFY_FAILED: "EMAIL_VERIFY_FAILED",
753
+ EMAIL_CODE_EXPIRED: "EMAIL_CODE_EXPIRED",
754
+ EMAIL_CODE_INVALID: "EMAIL_CODE_INVALID",
755
+ AUTH_FAILED: "AUTH_FAILED",
756
+ WALLET_CONNECT_FAILED: "WALLET_CONNECT_FAILED",
757
+ WALLET_AUTH_FAILED: "WALLET_AUTH_FAILED",
758
+ UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
759
+ };
760
+ var PollarFlowError = class extends Error {
761
+ constructor(message) {
762
+ super(message);
763
+ this.code = "INVALID_FLOW";
764
+ this.name = "PollarFlowError";
896
765
  }
897
766
  };
898
767
 
@@ -1107,432 +976,749 @@ async function streamUntilFound(api, clientSessionId, check, retryDelayMs = 200,
1107
976
  }
1108
977
  }
1109
978
 
1110
- // src/client/login.ts
1111
- function withSignal(promise, signal) {
1112
- return Promise.race([
1113
- promise,
1114
- new Promise((_, reject) => {
1115
- if (signal.aborted) {
1116
- reject(new DOMException("Aborted", "AbortError"));
1117
- return;
1118
- }
1119
- signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1120
- })
1121
- ]);
1122
- }
1123
- async function login(options, deps) {
1124
- const { api, basePath, apiKey, signal, emitState, storeSession, clearSession } = deps;
1125
- const oauthPopup = options.provider === "google" || options.provider === "github" ? window.open("about:blank", "_blank") : null;
1126
- emitState("authentication", STATE_VAR_CODES.authentication.CREATE_SESSION_START, "info", StateStatus.LOADING);
1127
- const createSessionResponse = await api.POST("/auth/session", { signal });
1128
- if (!emitResponse(
1129
- PollarStateVar.AUTHENTICATION,
1130
- createSessionResponse,
1131
- { code: STATE_VAR_CODES.authentication.CREATE_SESSION_SUCCESS },
1132
- STATE_VAR_CODES.authentication.CREATE_SESSION_ERROR,
1133
- emitState
1134
- )) {
1135
- oauthPopup?.close();
1136
- return;
1137
- }
1138
- const clientSessionId = createSessionResponse.data.content.clientSessionId;
1139
- switch (options.provider) {
1140
- case "email": {
1141
- emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_START, "info", StateStatus.LOADING, {
1142
- email: options.email
1143
- });
1144
- const emailRes = await api.POST(`/auth/email`, {
1145
- body: { clientSessionId, email: options.email },
1146
- signal
1147
- });
1148
- if (!emitResponse(
1149
- PollarStateVar.AUTHENTICATION,
1150
- emailRes,
1151
- { code: STATE_VAR_CODES.authentication.EMAIL_AUTH_START_SUCCESS },
1152
- STATE_VAR_CODES.authentication.EMAIL_AUTH_START_ERROR,
1153
- emitState
1154
- )) {
1155
- return;
1156
- }
1157
- break;
1158
- }
1159
- case "google":
1160
- case "github": {
1161
- const url = new URL(`${basePath}/auth/${options.provider}`);
1162
- url.searchParams.set("api_key", apiKey);
1163
- url.searchParams.set("client_session_id", clientSessionId);
1164
- url.searchParams.set("redirect_uri", window.location.origin);
1165
- if (oauthPopup) {
1166
- oauthPopup.location.href = url.toString();
1167
- } else {
1168
- window.open(url.toString(), "_blank");
1169
- }
1170
- break;
1171
- }
1172
- case "wallet": {
1173
- try {
1174
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_START, "info", StateStatus.LOADING, {
1175
- adapter: options.type
1176
- });
1177
- const adapter = options.type === "freighter" /* FREIGHTER */ ? new FreighterAdapter() : new AlbedoAdapter();
1178
- const available = await withSignal(adapter.isAvailable(), signal);
1179
- if (!available) {
1180
- emitState(
1181
- "authentication",
1182
- options.type === "freighter" /* FREIGHTER */ ? STATE_VAR_CODES.authentication.WALLET_AUTH_FREIGHTER_NOT_INSTALLED : STATE_VAR_CODES.authentication.WALLET_AUTH_ALBEDO_NOT_INSTALLED,
1183
- "info",
1184
- StateStatus.LOADING,
1185
- {
1186
- type: options.type
1187
- }
1188
- );
1189
- }
1190
- const { publicKey } = await withSignal(adapter.connect(), signal);
1191
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_CONNECTED, "info", StateStatus.LOADING, {
1192
- adapter: options.type,
1193
- publicKey
1194
- });
1195
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START, "info", StateStatus.LOADING, {
1196
- adapter: options.type,
1197
- publicKey
1198
- });
1199
- const emailRes = await api.POST(`/auth/wallet`, {
1200
- body: { clientSessionId, walletAddress: publicKey },
1201
- signal
1202
- });
1203
- if (!emitResponse(
1204
- PollarStateVar.AUTHENTICATION,
1205
- emailRes,
1206
- { code: STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_SUCCESS },
1207
- STATE_VAR_CODES.authentication.WALLET_AUTH_LOGIN_START_ERROR,
1208
- emitState
1209
- )) {
1210
- return;
1211
- }
1212
- } catch (error2) {
1213
- emitState("authentication", STATE_VAR_CODES.authentication.WALLET_AUTH_ERROR, "error", StateStatus.ERROR, {
1214
- clientSessionId
1215
- });
1216
- }
1217
- break;
1218
- }
1219
- }
1220
- emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_START, "info", StateStatus.LOADING, {
1221
- clientSessionId
1222
- });
1223
- await streamUntilFound(
1224
- api,
1225
- clientSessionId,
1226
- (data2) => {
1227
- const status = data2?.status;
1228
- if (status === "READY") {
1229
- emitState("authentication", STATE_VAR_CODES.authentication.STREAM_POLL_READY, "info", StateStatus.LOADING);
1230
- return true;
1231
- }
1232
- emitState(
1233
- "authentication",
1234
- STATE_VAR_CODES.authentication.STREAM_POLL_EVENT + (status ? `/${status}` : ""),
1235
- "info",
1236
- StateStatus.LOADING,
1237
- data2
1238
- );
1239
- return false;
1240
- },
1241
- 200,
1242
- signal
1243
- );
1244
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_START, "info", StateStatus.LOADING);
1245
- const { data, error } = await api.POST(`/auth/login`, {
979
+ // src/client/auth/authenticate.ts
980
+ async function authenticate(clientSessionId, deps) {
981
+ const { api, signal, setAuthState, storeSession, clearSession } = deps;
982
+ setAuthState({ step: "authenticating" });
983
+ await streamUntilFound(api, clientSessionId, (data2) => data2?.status === "READY", 200, signal);
984
+ const { data, error } = await api.POST("/auth/login", {
1246
985
  body: { clientSessionId },
1247
986
  signal
1248
987
  });
1249
988
  if (data?.code === "SDK_LOGIN_SUCCESS" && isValidSession(data?.content)) {
1250
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_SUCCESS, "info", StateStatus.SUCCESS);
1251
989
  storeSession(data.content);
1252
990
  } else {
1253
- emitState("authentication", STATE_VAR_CODES.authentication.FETCH_SESSION_ERROR, "error", StateStatus.ERROR, {
1254
- error,
1255
- data
991
+ setAuthState({
992
+ step: "error",
993
+ previousStep: "authenticating",
994
+ message: "Failed to load session",
995
+ errorCode: AUTH_ERROR_CODES.AUTH_FAILED
1256
996
  });
1257
997
  clearSession();
1258
998
  }
1259
999
  }
1260
1000
 
1261
- // src/client/client.ts
1262
- var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1263
- function warnServerSide(method) {
1264
- console.warn(
1265
- `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
1266
- );
1001
+ // src/client/auth/deps.ts
1002
+ async function createAuthSession(deps) {
1003
+ const { api, signal, setAuthState } = deps;
1004
+ setAuthState({ step: "creating_session" });
1005
+ const { data, error } = await api.POST("/auth/session", { signal });
1006
+ if (error || !data?.success) {
1007
+ setAuthState({
1008
+ step: "error",
1009
+ previousStep: "creating_session",
1010
+ message: "Failed to create session",
1011
+ errorCode: AUTH_ERROR_CODES.SESSION_CREATE_FAILED
1012
+ });
1013
+ return null;
1014
+ }
1015
+ return data.content.clientSessionId;
1267
1016
  }
1268
- var PollarClient = class {
1269
- constructor(config) {
1270
- this._session = null;
1271
- this._stateListeners = /* @__PURE__ */ new Set();
1272
- this._state = {
1273
- network: [],
1274
- authentication: [],
1275
- transaction: []
1276
- };
1277
- this.apiKey = config.apiKey;
1278
- this.id = crypto.randomUUID();
1279
- this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1280
- this._api = createApiClient(this.basePath);
1281
- this._api.use({
1282
- onRequest({ request }) {
1283
- request.headers.set("x-pollar-api-key", config.apiKey);
1284
- return request;
1285
- }
1017
+
1018
+ // src/client/auth/emailFlow.ts
1019
+ async function initEmailSession(deps) {
1020
+ const clientSessionId = await createAuthSession(deps);
1021
+ if (!clientSessionId) return;
1022
+ deps.setAuthState({ step: "entering_email", clientSessionId });
1023
+ }
1024
+ async function sendEmailCode(email, clientSessionId, deps) {
1025
+ const { api, signal, setAuthState } = deps;
1026
+ setAuthState({ step: "sending_email", email });
1027
+ const { data, error } = await api.POST("/auth/email", {
1028
+ body: { clientSessionId, email },
1029
+ signal
1030
+ });
1031
+ if (error || !data?.success) {
1032
+ setAuthState({
1033
+ step: "error",
1034
+ previousStep: "sending_email",
1035
+ message: "Failed to send code",
1036
+ errorCode: AUTH_ERROR_CODES.EMAIL_SEND_FAILED
1286
1037
  });
1287
- if (!isBrowser) {
1288
- warnServerSide("constructor");
1289
- this._session = null;
1290
- return;
1291
- }
1292
- console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}`);
1293
- this._readStore();
1294
- window.addEventListener("storage", (e) => {
1038
+ return;
1039
+ }
1040
+ setAuthState({ step: "entering_code", clientSessionId, email });
1041
+ }
1042
+ async function verifyAndAuthenticate(code, clientSessionId, email, deps) {
1043
+ const { api, signal, setAuthState } = deps;
1044
+ setAuthState({ step: "verifying_email_code", clientSessionId, email });
1045
+ const { data, error } = await api.POST("/auth/email/verify-code", {
1046
+ body: { clientSessionId, code },
1047
+ signal
1048
+ });
1049
+ if (data?.code === "SDK_EMAIL_CODE_VERIFIED") {
1050
+ await authenticate(clientSessionId, deps);
1051
+ return;
1052
+ }
1053
+ const errCode = error?.error ?? data?.code;
1054
+ if (errCode === "SDK_EMAIL_CODE_EXPIRED") {
1055
+ setAuthState({
1056
+ step: "error",
1057
+ previousStep: "verifying_email_code",
1058
+ message: "Code expired \u2014 request a new one",
1059
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED,
1060
+ clientSessionId,
1061
+ email
1062
+ });
1063
+ return;
1064
+ }
1065
+ if (errCode === "INVALID_EMAIL_CODE" || errCode === "SDK_EMAIL_CODE_INVALID") {
1066
+ setAuthState({
1067
+ step: "error",
1068
+ previousStep: "verifying_email_code",
1069
+ message: "Invalid code \u2014 try again",
1070
+ errorCode: AUTH_ERROR_CODES.EMAIL_CODE_INVALID,
1071
+ clientSessionId,
1072
+ email
1073
+ });
1074
+ return;
1075
+ }
1076
+ setAuthState({
1077
+ step: "error",
1078
+ previousStep: "verifying_email_code",
1079
+ message: "Failed to verify code \u2014 try again",
1080
+ errorCode: AUTH_ERROR_CODES.EMAIL_VERIFY_FAILED
1081
+ });
1082
+ }
1083
+
1084
+ // src/client/auth/oauthFlow.ts
1085
+ async function loginOAuth(provider, deps) {
1086
+ const { setAuthState, basePath, apiKey } = deps;
1087
+ const popup = window.open("about:blank", "_blank");
1088
+ const clientSessionId = await createAuthSession(deps);
1089
+ if (!clientSessionId) {
1090
+ popup?.close();
1091
+ return;
1092
+ }
1093
+ setAuthState({ step: "opening_oauth", provider });
1094
+ const url = new URL(`${basePath}/auth/${provider}`);
1095
+ url.searchParams.set("api_key", apiKey);
1096
+ url.searchParams.set("client_session_id", clientSessionId);
1097
+ url.searchParams.set("redirect_uri", window.location.origin);
1098
+ if (popup) {
1099
+ popup.location.href = url.toString();
1100
+ } else {
1101
+ window.open(url.toString(), "_blank");
1102
+ }
1103
+ await authenticate(clientSessionId, deps);
1104
+ }
1105
+
1106
+ // src/wallets/FreighterAdapter.ts
1107
+ var import_freighter_api = __toESM(require_index_min());
1108
+
1109
+ // src/wallets/types.ts
1110
+ var WalletType = /* @__PURE__ */ ((WalletType2) => {
1111
+ WalletType2["FREIGHTER"] = "freighter";
1112
+ WalletType2["ALBEDO"] = "albedo";
1113
+ return WalletType2;
1114
+ })(WalletType || {});
1115
+
1116
+ // src/wallets/FreighterAdapter.ts
1117
+ var FreighterAdapter = class {
1118
+ constructor() {
1119
+ this.type = "freighter" /* FREIGHTER */;
1120
+ }
1121
+ async isAvailable() {
1122
+ try {
1123
+ return await (0, import_freighter_api.isConnected)();
1124
+ } catch {
1125
+ return false;
1126
+ }
1127
+ }
1128
+ async connect() {
1129
+ const connected = await (0, import_freighter_api.isConnected)();
1130
+ if (!connected) {
1131
+ throw new Error("Freighter wallet is not installed");
1132
+ }
1133
+ const allowed = await (0, import_freighter_api.isAllowed)();
1134
+ if (!allowed) {
1135
+ await (0, import_freighter_api.setAllowed)();
1136
+ }
1137
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1138
+ if (!userInfo?.publicKey) {
1139
+ throw new Error("Failed to get user information from Freighter");
1140
+ }
1141
+ return { address: userInfo.publicKey, publicKey: userInfo.publicKey };
1142
+ }
1143
+ async disconnect() {
1144
+ }
1145
+ async getPublicKey() {
1146
+ try {
1147
+ const allowed = await (0, import_freighter_api.isAllowed)();
1148
+ if (!allowed) return null;
1149
+ const userInfo = await (0, import_freighter_api.getUserInfo)();
1150
+ return userInfo?.publicKey ?? null;
1151
+ } catch {
1152
+ return null;
1153
+ }
1154
+ }
1155
+ async getNetwork() {
1156
+ return (0, import_freighter_api.getNetwork)();
1157
+ }
1158
+ async signTransaction(xdr, options) {
1159
+ const result = await (0, import_freighter_api.signTransaction)(xdr, {
1160
+ network: options?.network,
1161
+ networkPassphrase: options?.networkPassphrase,
1162
+ accountToSign: options?.accountToSign
1163
+ });
1164
+ if (!result || typeof result !== "string") {
1165
+ throw new Error("Invalid response from Freighter");
1166
+ }
1167
+ return { signedTxXdr: result };
1168
+ }
1169
+ async signAuthEntry(entryXdr, options) {
1170
+ const result = await (0, import_freighter_api.signAuthEntry)(entryXdr, { accountToSign: options?.accountToSign });
1171
+ if (!result || typeof result !== "string") {
1172
+ throw new Error("Invalid response from Freighter");
1173
+ }
1174
+ return { signedAuthEntry: result };
1175
+ }
1176
+ };
1177
+
1178
+ // src/wallets/AlbedoAdapter.ts
1179
+ function openAlbedoPopup(url) {
1180
+ const popup = window.open(url, "albedo", "width=420,height=720,resizable=yes,scrollbars=yes");
1181
+ if (!popup) {
1182
+ throw new Error("Failed to open Albedo popup (blocked by browser)");
1183
+ }
1184
+ return popup;
1185
+ }
1186
+ function waitForAlbedoPopup() {
1187
+ return new Promise((resolve, reject) => {
1188
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1189
+ function handler(event) {
1190
+ if (event.origin !== window.location.origin || event.data?.type !== "ALBEDO_RESULT") return;
1191
+ clearTimeout(timeout);
1192
+ window.removeEventListener("message", handler);
1193
+ resolve(event.data.payload);
1194
+ }
1195
+ window.addEventListener("message", handler);
1196
+ });
1197
+ }
1198
+ function waitForAlbedoResult() {
1199
+ return new Promise((resolve, reject) => {
1200
+ const timeout = setTimeout(() => reject(new Error("Albedo response timeout")), 2 * 60 * 1e3);
1201
+ const parseResult = () => {
1202
+ const params = new URLSearchParams(window.location.search);
1203
+ if (!params.has("pubkey") && !params.has("signed_envelope_xdr") && !params.has("signed_xdr")) return;
1204
+ clearTimeout(timeout);
1205
+ const result = {};
1206
+ params.forEach((value, key) => {
1207
+ result[key] = value;
1208
+ });
1209
+ window.history.replaceState({}, document.title, window.location.pathname);
1210
+ resolve(result);
1211
+ };
1212
+ parseResult();
1213
+ window.addEventListener("popstate", parseResult);
1214
+ });
1215
+ }
1216
+ var AlbedoAdapter = class {
1217
+ constructor() {
1218
+ this.type = "albedo" /* ALBEDO */;
1219
+ }
1220
+ async isAvailable() {
1221
+ return typeof window !== "undefined";
1222
+ }
1223
+ async connect() {
1224
+ const url = new URL("https://albedo.link");
1225
+ url.searchParams.set("intent", "public-key");
1226
+ url.searchParams.set("app_name", "Pollar");
1227
+ url.searchParams.set("network", "testnet");
1228
+ url.searchParams.set("callback", `${window.location.origin}/albedo-callback`);
1229
+ url.searchParams.set("origin", window.location.origin);
1230
+ openAlbedoPopup(url.toString());
1231
+ const result = await waitForAlbedoPopup();
1232
+ if (!result.pubkey) {
1233
+ throw new Error("Albedo connection rejected");
1234
+ }
1235
+ return { address: result.pubkey, publicKey: result.pubkey };
1236
+ }
1237
+ async disconnect() {
1238
+ }
1239
+ async getPublicKey() {
1240
+ return null;
1241
+ }
1242
+ async getNetwork() {
1243
+ throw new Error("Albedo does not expose network");
1244
+ }
1245
+ async signTransaction(xdr, _options) {
1246
+ const url = new URL("https://albedo.link");
1247
+ url.searchParams.set("intent", "tx");
1248
+ url.searchParams.set("xdr", xdr);
1249
+ url.searchParams.set("app_name", "Pollar");
1250
+ url.searchParams.set("network", "testnet");
1251
+ url.searchParams.set("callback", window.location.href);
1252
+ url.searchParams.set("origin", window.location.origin);
1253
+ window.location.href = url.toString();
1254
+ const result = await waitForAlbedoResult();
1255
+ if (!result.signed_envelope_xdr) throw new Error("Albedo signing rejected");
1256
+ return { signedTxXdr: result.signed_envelope_xdr };
1257
+ }
1258
+ async signAuthEntry(entryXdr, _options) {
1259
+ const url = new URL("https://albedo.link");
1260
+ url.searchParams.set("intent", "sign-auth-entry");
1261
+ url.searchParams.set("xdr", entryXdr);
1262
+ url.searchParams.set("app_name", "Pollar");
1263
+ url.searchParams.set("network", "testnet");
1264
+ url.searchParams.set("callback", window.location.href);
1265
+ url.searchParams.set("origin", window.location.origin);
1266
+ window.location.href = url.toString();
1267
+ const result = await waitForAlbedoResult();
1268
+ if (!result.signed_xdr) throw new Error("Albedo auth entry signing rejected");
1269
+ return { signedAuthEntry: result.signed_xdr };
1270
+ }
1271
+ };
1272
+
1273
+ // src/client/auth/walletFlow.ts
1274
+ function withSignal(promise, signal) {
1275
+ return Promise.race([
1276
+ promise,
1277
+ new Promise((_, reject) => {
1278
+ if (signal.aborted) {
1279
+ reject(new DOMException("Aborted", "AbortError"));
1280
+ return;
1281
+ }
1282
+ signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
1283
+ })
1284
+ ]);
1285
+ }
1286
+ async function loginWallet(type, deps) {
1287
+ const { api, signal, setAuthState } = deps;
1288
+ const clientSessionId = await createAuthSession(deps);
1289
+ if (!clientSessionId) return;
1290
+ try {
1291
+ setAuthState({ step: "connecting_wallet", walletType: type });
1292
+ const adapter = type === "freighter" /* FREIGHTER */ ? new FreighterAdapter() : new AlbedoAdapter();
1293
+ const available = await withSignal(adapter.isAvailable(), signal);
1294
+ if (!available) {
1295
+ setAuthState({ step: "wallet_not_installed", walletType: type });
1296
+ return;
1297
+ }
1298
+ const { publicKey } = await withSignal(adapter.connect(), signal);
1299
+ deps.storeWalletAdapter(adapter);
1300
+ setAuthState({ step: "authenticating_wallet" });
1301
+ const { data: walletData, error: walletError } = await api.POST("/auth/wallet", {
1302
+ body: { clientSessionId, walletAddress: publicKey },
1303
+ signal
1304
+ });
1305
+ if (walletError || !walletData?.success) {
1306
+ setAuthState({
1307
+ step: "error",
1308
+ previousStep: "authenticating_wallet",
1309
+ message: "Wallet authentication failed",
1310
+ errorCode: AUTH_ERROR_CODES.WALLET_AUTH_FAILED
1311
+ });
1312
+ return;
1313
+ }
1314
+ } catch {
1315
+ setAuthState({
1316
+ step: "error",
1317
+ previousStep: "connecting_wallet",
1318
+ message: "Wallet connection failed",
1319
+ errorCode: AUTH_ERROR_CODES.WALLET_CONNECT_FAILED
1320
+ });
1321
+ return;
1322
+ }
1323
+ await authenticate(clientSessionId, deps);
1324
+ }
1325
+
1326
+ // src/client/client.ts
1327
+ var isBrowser = typeof window !== "undefined" && typeof localStorage !== "undefined";
1328
+ function warnServerSide(method) {
1329
+ console.warn(
1330
+ `[PollarClient] ${method}() called server-side \u2014 browser APIs unavailable. Use PollarClient only in Client Components.`
1331
+ );
1332
+ }
1333
+ var PollarClient = class {
1334
+ constructor(config) {
1335
+ this._session = null;
1336
+ this._transactionState = null;
1337
+ this._transactionStateListeners = /* @__PURE__ */ new Set();
1338
+ this._txHistoryState = { step: "idle" };
1339
+ this._txHistoryStateListeners = /* @__PURE__ */ new Set();
1340
+ this._authState = { step: "idle" };
1341
+ this._authStateListeners = /* @__PURE__ */ new Set();
1342
+ this._networkState = { step: "idle" };
1343
+ this._networkStateListeners = /* @__PURE__ */ new Set();
1344
+ this._walletAdapter = null;
1345
+ this._loginController = null;
1346
+ this.apiKey = config.apiKey;
1347
+ this.id = crypto.randomUUID();
1348
+ this.basePath = `${config.baseUrl || "https://sdk.api.pollar.xyz"}/v1`;
1349
+ this._api = createApiClient(this.basePath);
1350
+ const self = this;
1351
+ this._api.use({
1352
+ onRequest({ request }) {
1353
+ request.headers.set("x-pollar-api-key", config.apiKey);
1354
+ const accessToken = self._session?.token?.accessToken;
1355
+ if (accessToken) {
1356
+ request.headers.set("Authorization", `Bearer ${accessToken}`);
1357
+ }
1358
+ return request;
1359
+ }
1360
+ });
1361
+ this._networkState = { step: "connected", network: config.stellarNetwork ?? "testnet" };
1362
+ if (!isBrowser) {
1363
+ warnServerSide("constructor");
1364
+ this._session = null;
1365
+ return;
1366
+ }
1367
+ console.info(`[PollarClient] Initialized \u2014 endpoint: ${this.basePath}, network: ${this._networkState.network}`);
1368
+ this._restoreSession();
1369
+ window.addEventListener("storage", (e) => {
1295
1370
  if (e.key === STORAGE_KEY) {
1296
1371
  const prev = this._session;
1297
1372
  console.info(`[PollarClient] Storage event \u2014 session ${this._session ? "updated" : prev ? "cleared" : "unchanged"}`);
1298
- this._readStore();
1373
+ this._restoreSession();
1299
1374
  }
1300
1375
  });
1301
- this._emitState("network", STATE_VAR_CODES.network.NETWORK_UPDATED, "info", StateStatus.SUCCESS, {
1302
- network: "testnet"
1303
- });
1304
1376
  }
1305
- isAuthenticated() {
1306
- return !!this._session?.wallet?.publicKey;
1377
+ // ─── Auth state ──────────────────────────────────────────────────────────────
1378
+ getAuthState() {
1379
+ return this._authState;
1307
1380
  }
1308
- getState() {
1309
- return { session: this._session };
1310
- }
1311
- getApi() {
1312
- return this._api;
1381
+ onAuthStateChange(cb) {
1382
+ this._authStateListeners.add(cb);
1383
+ cb(this._authState);
1384
+ return () => this._authStateListeners.delete(cb);
1313
1385
  }
1386
+ // ─── Login (unified entry point) ─────────────────────────────────────────
1314
1387
  login(options) {
1315
1388
  if (!isBrowser) {
1316
1389
  warnServerSide("login");
1317
- return {
1318
- cancelLogin: () => {
1319
- }
1320
- };
1390
+ return;
1321
1391
  }
1322
- const controller = new AbortController();
1323
- console.info(`[PollarClient] Login started \u2014 provider: ${options.provider}`);
1324
- login(options, {
1325
- api: this._api,
1326
- basePath: this.basePath,
1327
- apiKey: this.apiKey,
1328
- signal: controller.signal,
1329
- emitState: this._emitState.bind(this),
1330
- storeSession: this._storeSession.bind(this),
1331
- clearSession: this._clearSession.bind(this)
1332
- }).catch((error) => {
1333
- if (error instanceof Error && error.name === "AbortError") {
1334
- console.info("[PollarClient] Login aborted by user");
1335
- this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_ABORTED, "error", StateStatus.ERROR);
1336
- return;
1392
+ if (options.provider === "google" || options.provider === "github" || options.provider === "email") {
1393
+ const controller = this._newController();
1394
+ const deps = this._flowDeps(controller.signal);
1395
+ if (options.provider === "google" || options.provider === "github") {
1396
+ loginOAuth(options.provider, {
1397
+ ...deps,
1398
+ basePath: this.basePath,
1399
+ apiKey: this.apiKey
1400
+ }).catch((err) => this._handleFlowError(err));
1401
+ } else if (options.provider === "email") {
1402
+ const { email } = options;
1403
+ initEmailSession(deps).then(() => {
1404
+ if (this._authState.step === "entering_email") {
1405
+ return sendEmailCode(email, this._authState.clientSessionId, deps);
1406
+ }
1407
+ }).catch((err) => this._handleFlowError(err));
1337
1408
  }
1338
- console.error("[PollarClient] Login failed with unexpected error", error);
1339
- this._emitState("authentication", STATE_VAR_CODES.authentication.ERROR_UNKNOWN, "error", StateStatus.ERROR, {
1340
- error
1341
- });
1342
- });
1343
- return { cancelLogin: () => controller.abort() };
1409
+ } else if (options.provider === "wallet") {
1410
+ this.loginWallet(options.type);
1411
+ }
1344
1412
  }
1345
- onStateChange(cb) {
1346
- this._stateListeners.add(cb);
1347
- for (const [, stateEntry] of Object.entries(this._state)) {
1348
- if (stateEntry.length >= 1) {
1349
- cb(stateEntry.at(-1));
1350
- }
1413
+ // ─── Email OTP flow (3 steps) ─────────────────────────────────────────────
1414
+ beginEmailLogin() {
1415
+ if (!isBrowser) {
1416
+ warnServerSide("beginEmailLogin");
1417
+ return;
1418
+ }
1419
+ const controller = this._newController();
1420
+ initEmailSession(this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
1421
+ }
1422
+ sendEmailCode(email) {
1423
+ if (!isBrowser) {
1424
+ warnServerSide("sendEmailCode");
1425
+ return;
1426
+ }
1427
+ if (this._authState.step !== "entering_email") {
1428
+ throw new PollarFlowError(`sendEmailCode() requires step 'entering_email', current step is '${this._authState.step}'`);
1351
1429
  }
1352
- return () => this._stateListeners.delete(cb);
1430
+ const { clientSessionId } = this._authState;
1431
+ const signal = this._loginController.signal;
1432
+ sendEmailCode(email, clientSessionId, this._flowDeps(signal)).catch((err) => this._handleFlowError(err));
1353
1433
  }
1354
- async verifyEmailCode(clientSessionId, code) {
1434
+ verifyEmailCode(code) {
1355
1435
  if (!isBrowser) {
1356
1436
  warnServerSide("verifyEmailCode");
1357
1437
  return;
1358
1438
  }
1439
+ const isRetryableError = this._authState.step === "error" && this._authState.clientSessionId != null && (this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_INVALID || this._authState.errorCode === AUTH_ERROR_CODES.EMAIL_CODE_EXPIRED);
1440
+ if (this._authState.step !== "entering_code" && !isRetryableError) {
1441
+ throw new PollarFlowError(`verifyEmailCode() requires step 'entering_code', current step is '${this._authState.step}'`);
1442
+ }
1443
+ const state = this._authState;
1444
+ const clientSessionId = state.step === "entering_code" ? state.clientSessionId : state.clientSessionId;
1445
+ const email = state.step === "entering_code" ? state.email : state.email ?? "";
1446
+ const controller = this._newController();
1447
+ verifyAndAuthenticate(code, clientSessionId, email, this._flowDeps(controller.signal)).catch(
1448
+ (err) => this._handleFlowError(err)
1449
+ );
1450
+ }
1451
+ // ─── Wallet flow (single call) ────────────────────────────────────────────
1452
+ loginWallet(type) {
1453
+ if (!isBrowser) {
1454
+ warnServerSide("loginWallet");
1455
+ return;
1456
+ }
1457
+ const controller = this._newController();
1458
+ loginWallet(type, this._flowDeps(controller.signal)).catch((err) => this._handleFlowError(err));
1459
+ }
1460
+ // ─── Cancel ───────────────────────────────────────────────────────────────
1461
+ cancelLogin() {
1462
+ this._loginController?.abort();
1463
+ this._loginController = null;
1464
+ this._setAuthState({ step: "idle" });
1465
+ }
1466
+ // ─── Logout ───────────────────────────────────────────────────────────────
1467
+ logout() {
1468
+ if (!isBrowser) {
1469
+ warnServerSide("logout");
1470
+ return;
1471
+ }
1472
+ console.info("[PollarClient] Logout requested");
1473
+ this._clearSession();
1474
+ }
1475
+ // ─── Network ──────────────────────────────────────────────────────────────
1476
+ getNetwork() {
1477
+ return this._networkState.step === "connected" ? this._networkState.network : "testnet";
1478
+ }
1479
+ getNetworkState() {
1480
+ return this._networkState;
1481
+ }
1482
+ setNetwork(network) {
1483
+ this._setNetworkState({ step: "connected", network });
1484
+ }
1485
+ onNetworkStateChange(cb) {
1486
+ this._networkStateListeners.add(cb);
1487
+ cb(this._networkState);
1488
+ return () => this._networkStateListeners.delete(cb);
1489
+ }
1490
+ // ─── Transaction state ────────────────────────────────────────────────────
1491
+ getTransactionState() {
1492
+ return this._transactionState;
1493
+ }
1494
+ onTransactionStateChange(cb) {
1495
+ this._transactionStateListeners.add(cb);
1496
+ if (this._transactionState) cb(this._transactionState);
1497
+ return () => this._transactionStateListeners.delete(cb);
1498
+ }
1499
+ // ─── Tx history ──────────────────────────────────────────────────────────
1500
+ _setTxHistoryState(next) {
1501
+ this._txHistoryState = next;
1502
+ for (const cb of this._txHistoryStateListeners) cb(next);
1503
+ }
1504
+ getTxHistoryState() {
1505
+ return this._txHistoryState;
1506
+ }
1507
+ onTxHistoryStateChange(cb) {
1508
+ this._txHistoryStateListeners.add(cb);
1509
+ cb(this._txHistoryState);
1510
+ return () => this._txHistoryStateListeners.delete(cb);
1511
+ }
1512
+ async fetchTxHistory(params = {}) {
1513
+ this._setTxHistoryState({ step: "loading", params });
1359
1514
  try {
1360
- const { error, data } = await this._api.POST("/auth/email/verify-code", {
1361
- body: { clientSessionId, code }
1362
- });
1363
- if (error || !data || data?.code !== "SDK_EMAIL_CODE_VERIFIED") {
1364
- this._emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_CODE_ERROR, "error", StateStatus.ERROR, {
1365
- data,
1366
- error
1367
- });
1368
- return;
1515
+ const { data, error } = await this._api.GET("/tx/history", { params: { query: params } });
1516
+ if (!error && data?.success && data.content) {
1517
+ this._setTxHistoryState({ step: "loaded", params, data: data.content });
1518
+ } else {
1519
+ const message = error?.message ?? "Failed to load history";
1520
+ this._setTxHistoryState({ step: "error", params, message });
1369
1521
  }
1370
- this._emitState("authentication", STATE_VAR_CODES.authentication.EMAIL_AUTH_CODE_SUCCESS, "info", StateStatus.SUCCESS, {
1371
- data,
1372
- error
1373
- });
1374
- } catch (error) {
1375
- this._emitState(
1376
- "authentication",
1377
- STATE_VAR_CODES.authentication.WALLET_AUTH_ALBEDO_NOT_INSTALLED,
1378
- "error",
1379
- StateStatus.ERROR
1380
- );
1522
+ } catch {
1523
+ this._setTxHistoryState({ step: "error", params, message: "Failed to load history" });
1381
1524
  }
1382
1525
  }
1383
- getNetwork() {
1384
- return this._state.network.at(-1)?.data?.network === "public" ? "public" : "testnet";
1526
+ // ─── Wallet balance ───────────────────────────────────────────────────────
1527
+ async getWalletBalance(publicKey) {
1528
+ const pk = publicKey ?? this._session?.wallet?.publicKey;
1529
+ if (!pk) return null;
1530
+ const network = this.getNetwork();
1531
+ const { data, error } = await this._api.GET("/wallet/balance", { params: { query: { publicKey: pk, network } } });
1532
+ if (!error && data?.success && data.content) return data.content;
1533
+ return null;
1385
1534
  }
1535
+ // ─── Transactions ─────────────────────────────────────────────────────────
1386
1536
  async buildTx(operation, params, options) {
1387
1537
  if (!this._session?.wallet?.publicKey) {
1388
- this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR_NO_WALLET, "error", StateStatus.ERROR);
1538
+ this._setTransactionState({ step: "error", details: "No wallet connected" });
1389
1539
  return;
1390
1540
  }
1391
1541
  const body = {
1392
1542
  network: this.getNetwork(),
1393
- publicKey: this._session?.wallet?.publicKey,
1543
+ publicKey: this._session.wallet.publicKey,
1394
1544
  operation,
1395
1545
  params,
1396
- options: options || {}
1546
+ options: options ?? {}
1397
1547
  };
1398
1548
  try {
1399
- this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_START, "info", StateStatus.LOADING);
1400
- const response = await this._api.POST("/tx/build", { body });
1401
- if (!emitResponse(
1402
- PollarStateVar.TRANSACTION,
1403
- response,
1404
- { code: STATE_VAR_CODES.transaction.BUILD_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
1405
- STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR,
1406
- this._emitState.bind(this)
1407
- )) {
1408
- return;
1549
+ this._setTransactionState({ step: "building" });
1550
+ const { data, error } = await this._api.POST("/tx/build", { body });
1551
+ if (!error && data?.success && data.content) {
1552
+ this._setTransactionState({ step: "built", buildData: data.content });
1553
+ } else {
1554
+ const details = error?.details;
1555
+ this._setTransactionState({ step: "error", ...details && { details } });
1409
1556
  }
1410
- } catch (error) {
1411
- this._emitState("transaction", STATE_VAR_CODES.transaction.BUILD_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
1412
- body,
1413
- error
1414
- });
1415
- return;
1557
+ } catch {
1558
+ this._setTransactionState({ step: "error" });
1416
1559
  }
1417
1560
  }
1418
- async submitTx(signedXdr) {
1561
+ async signAndSubmitTx(unsignedXdr) {
1562
+ if (this._transactionState?.step !== "built") {
1563
+ throw new PollarFlowError(
1564
+ `signAndSubmitTx() requires step 'built', current step is '${this._transactionState?.step ?? "none"}'`
1565
+ );
1566
+ }
1567
+ const buildData = this._transactionState.buildData;
1568
+ this._setTransactionState({ step: "signing", buildData });
1569
+ if (this._walletAdapter) {
1570
+ try {
1571
+ const signOpts = this._session?.wallet?.publicKey ? { networkPassphrase: this._networkPassphrase(), accountToSign: this._session.wallet.publicKey } : { networkPassphrase: this._networkPassphrase() };
1572
+ const { signedTxXdr } = await this._walletAdapter.signTransaction(unsignedXdr, signOpts);
1573
+ const stellarClient = new StellarClient(this.getNetwork());
1574
+ const result = await stellarClient.submitTransaction(signedTxXdr);
1575
+ if (result.success) {
1576
+ this._setTransactionState({ step: "success", buildData, hash: result.hash });
1577
+ } else {
1578
+ this._setTransactionState({ step: "error", ...buildData && { buildData }, details: result.errorCode });
1579
+ }
1580
+ } catch {
1581
+ this._setTransactionState({ step: "error", ...buildData && { buildData } });
1582
+ }
1583
+ return;
1584
+ }
1419
1585
  const body = {
1420
1586
  network: this.getNetwork(),
1421
- signedXdr
1587
+ publicKey: this._session?.wallet?.publicKey ?? "",
1588
+ unsignedXdr
1422
1589
  };
1423
1590
  try {
1424
- this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_START, "info", StateStatus.LOADING);
1425
- const response = await this._api.POST("/tx/sign-and-send", { body });
1426
- if (!emitResponse(
1427
- PollarStateVar.TRANSACTION,
1428
- response,
1429
- { code: STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_SUCCESS, status: StateStatus.SUCCESS },
1430
- STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR,
1431
- this._emitState.bind(this)
1432
- )) {
1433
- return;
1591
+ const { data, error } = await this._api.POST("/tx/sign-and-send", { body });
1592
+ if (!error && data?.success && data.content?.hash) {
1593
+ this._setTransactionState({ step: "success", buildData, hash: data.content.hash });
1594
+ } else {
1595
+ const details = error?.details;
1596
+ this._setTransactionState({ step: "error", ...buildData && { buildData }, ...details && { details } });
1434
1597
  }
1435
- } catch (error) {
1436
- this._emitState("transaction", STATE_VAR_CODES.transaction.SIGN_SEND_TRANSACTION_ERROR, "error", StateStatus.ERROR, {
1437
- body,
1438
- error
1439
- });
1440
- return;
1598
+ } catch {
1599
+ this._setTransactionState({ step: "error", ...buildData && { buildData } });
1441
1600
  }
1442
1601
  }
1443
- logout() {
1444
- if (!isBrowser) {
1445
- warnServerSide("logout");
1602
+ // ─── App config ───────────────────────────────────────────────────────────
1603
+ async getAppConfig() {
1604
+ try {
1605
+ const { data, error } = await this._api.GET("/applications/config");
1606
+ if (!data || error) return null;
1607
+ return data.content;
1608
+ } catch {
1609
+ return null;
1610
+ }
1611
+ }
1612
+ // ─── KYC ──────────────────────────────────────────────────────────────────
1613
+ getKycStatus(providerId) {
1614
+ return getKycStatus(this._api, providerId);
1615
+ }
1616
+ getKycProviders(country) {
1617
+ return getKycProviders(this._api, country);
1618
+ }
1619
+ startKyc(body) {
1620
+ return startKyc(this._api, body);
1621
+ }
1622
+ resolveKyc(providerId, level) {
1623
+ return resolveKyc(this._api, providerId, level);
1624
+ }
1625
+ pollKycStatus(providerId, opts) {
1626
+ return pollKycStatus(this._api, providerId, opts);
1627
+ }
1628
+ // ─── Ramps ────────────────────────────────────────────────────────────────
1629
+ getRampsQuote(query) {
1630
+ return getRampsQuote(this._api, query);
1631
+ }
1632
+ createOnRamp(body) {
1633
+ return createOnRamp(this._api, body);
1634
+ }
1635
+ createOffRamp(body) {
1636
+ return createOffRamp(this._api, body);
1637
+ }
1638
+ getRampTransaction(txId) {
1639
+ return getRampTransaction(this._api, txId);
1640
+ }
1641
+ pollRampTransaction(txId, opts) {
1642
+ return pollRampTransaction(this._api, txId, opts);
1643
+ }
1644
+ // ─── Private ──────────────────────────────────────────────────────────────
1645
+ /** Creates a new AbortController, cancelling any existing flow first. */
1646
+ _newController() {
1647
+ this._loginController?.abort();
1648
+ this._loginController = new AbortController();
1649
+ return this._loginController;
1650
+ }
1651
+ /** Builds the deps object passed to flow functions via bind pattern. */
1652
+ _flowDeps(signal) {
1653
+ return {
1654
+ api: this._api,
1655
+ signal,
1656
+ setAuthState: this._setAuthState.bind(this),
1657
+ storeSession: this._storeSession.bind(this),
1658
+ clearSession: this._clearSession.bind(this),
1659
+ storeWalletAdapter: (adapter) => {
1660
+ this._walletAdapter = adapter;
1661
+ }
1662
+ };
1663
+ }
1664
+ _handleFlowError(error) {
1665
+ if (error instanceof Error && error.name === "AbortError") {
1666
+ console.info("[PollarClient] Login cancelled");
1667
+ this._setAuthState({ step: "idle" });
1446
1668
  return;
1447
1669
  }
1448
- console.info("[PollarClient] Logout requested");
1449
- this._clearSession();
1670
+ console.error("[PollarClient] Unexpected error in auth flow", error);
1671
+ this._setAuthState({
1672
+ step: "error",
1673
+ previousStep: this._authState.step,
1674
+ message: "An unexpected error occurred",
1675
+ errorCode: AUTH_ERROR_CODES.UNEXPECTED_ERROR
1676
+ });
1450
1677
  }
1451
- _readStore() {
1678
+ _restoreSession() {
1452
1679
  this._session = readStorage();
1453
1680
  if (this._session) {
1454
- this._emitState(
1455
- "authentication",
1456
- STATE_VAR_CODES.authentication.RESTORED_SESSION_SUCCESS,
1457
- "info",
1458
- StateStatus.SUCCESS,
1459
- this._session
1460
- );
1681
+ this._authState = { step: "authenticated", session: this._session };
1461
1682
  console.info("[PollarClient] Session restored from storage");
1462
1683
  } else {
1463
- this._emitState("authentication", STATE_VAR_CODES.authentication.NO_RESTORED_SESSION, "info", StateStatus.NONE);
1464
- console.info("[PollarClient] Session NO restored from storage");
1684
+ console.info("[PollarClient] No session in storage");
1465
1685
  }
1466
1686
  }
1467
1687
  _storeSession(session) {
1468
1688
  console.info(`[PollarClient] Session stored \u2014 user: ${session.userId ?? "anonymous"}`);
1469
1689
  this._session = session;
1470
1690
  writeStorage(session);
1471
- this._emitState(
1472
- "authentication",
1473
- STATE_VAR_CODES.authentication.SESSION_STORED,
1474
- "info",
1475
- StateStatus.SUCCESS,
1476
- this._session
1477
- );
1691
+ this._setAuthState({ step: "authenticated", session });
1478
1692
  }
1479
1693
  _clearSession() {
1480
1694
  console.info("[PollarClient] Session cleared");
1481
1695
  this._session = null;
1696
+ this._walletAdapter = null;
1482
1697
  removeStorage();
1483
- this._state = {
1484
- network: [],
1485
- authentication: [],
1486
- transaction: []
1487
- };
1488
- this._emitState("authentication", STATE_VAR_CODES.authentication.LOGOUT, "info", StateStatus.NONE);
1489
- }
1490
- _emitState(fn, code, level, status, data) {
1491
- const stateEntry = { var: fn, code, level, data, status, ts: Date.now() };
1492
- this._state[fn].push(stateEntry);
1493
- console[level](`[PollarClient] ${fn}:${code} \u2014 ${status}`);
1494
- for (const cb of this._stateListeners) {
1495
- cb(stateEntry);
1496
- }
1698
+ this._transactionState = null;
1699
+ this._setAuthState({ step: "idle" });
1497
1700
  }
1498
- };
1499
-
1500
- // src/stellar/StellarClient.ts
1501
- var HORIZON_URLS = {
1502
- mainnet: "https://horizon.stellar.org",
1503
- testnet: "https://horizon-testnet.stellar.org"
1504
- };
1505
- var StellarClient = class {
1506
- constructor(config) {
1507
- this.horizonUrl = typeof config === "string" ? HORIZON_URLS[config] : config.horizonUrl;
1701
+ _networkPassphrase() {
1702
+ return this.getNetwork() === "mainnet" ? "Public Global Stellar Network ; September 2015" : "Test SDF Network ; September 2015";
1508
1703
  }
1509
- async getBalances(publicKey) {
1510
- try {
1511
- const response = await fetch(`${this.horizonUrl}/accounts/${publicKey}`);
1512
- if (!response.ok) {
1513
- if (response.status === 404) {
1514
- console.warn(`[StellarClient] Account not found: ${publicKey}`);
1515
- return { success: false, errorCode: "ACCOUNT_NOT_FOUND", balances: [] };
1516
- }
1517
- console.warn(`[StellarClient] Horizon API error: ${response.status}`);
1518
- return { success: false, errorCode: "HORIZON_ERROR", balances: [] };
1519
- }
1520
- const data = await response.json();
1521
- return {
1522
- success: true,
1523
- balances: data.balances.filter((b) => b.asset_type !== "liquidity_pool_shares").map((b) => ({
1524
- asset: b.asset_type === "native" ? "XLM" : b.asset_code ?? "",
1525
- balance: b.balance,
1526
- ...b.asset_type !== "native" && { assetIssuer: b.asset_issuer }
1527
- }))
1528
- };
1529
- } catch (error) {
1530
- console.warn("[StellarClient] Network error fetching balances", error);
1531
- return { success: false, errorCode: "NETWORK_ERROR", balances: [] };
1532
- }
1704
+ _setNetworkState(next) {
1705
+ this._networkState = next;
1706
+ const label = next.step === "connected" ? next.network : next.step;
1707
+ console.info(`[PollarClient] network:${label}`);
1708
+ for (const cb of this._networkStateListeners) cb(next);
1709
+ }
1710
+ _setAuthState(next) {
1711
+ this._authState = next;
1712
+ console.info(`[PollarClient] auth:${next.step}`);
1713
+ for (const cb of this._authStateListeners) cb(next);
1714
+ }
1715
+ _setTransactionState(next) {
1716
+ this._transactionState = next;
1717
+ console.info(`[PollarClient] transaction:${next.step}`);
1718
+ for (const cb of this._transactionStateListeners) cb(next);
1533
1719
  }
1534
1720
  };
1535
1721
 
1536
- export { AlbedoAdapter, FreighterAdapter, PollarClient, PollarStateVar, STATE_VAR_CODES, StateStatus, StellarClient, WalletType, isValidSession };
1722
+ export { AUTH_ERROR_CODES, AlbedoAdapter, FreighterAdapter, PollarClient, StellarClient, WalletType, createOffRamp, createOnRamp, getKycProviders, getKycStatus, getRampTransaction, getRampsQuote, isValidSession, pollKycStatus, pollRampTransaction, resolveKyc, startKyc };
1537
1723
  //# sourceMappingURL=index.mjs.map
1538
1724
  //# sourceMappingURL=index.mjs.map