odin-connect 1.3.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +19 -3
- package/dist/index.js +2 -2
- package/package.json +5 -4
- package/readme.md +60 -4
package/dist/index.d.ts
CHANGED
|
@@ -194,7 +194,7 @@ declare class OdinApiClient {
|
|
|
194
194
|
followers: number;
|
|
195
195
|
following: number;
|
|
196
196
|
}>;
|
|
197
|
-
set apiKey(key: string);
|
|
197
|
+
set apiKey(key: string | null);
|
|
198
198
|
get apiKey(): string | null;
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -362,6 +362,7 @@ interface AppInitOptions {
|
|
|
362
362
|
name: string;
|
|
363
363
|
icon?: string;
|
|
364
364
|
env?: Environment;
|
|
365
|
+
slug?: string;
|
|
365
366
|
}
|
|
366
367
|
interface BaseConnectOptions {
|
|
367
368
|
open?: WindowClientSettings;
|
|
@@ -382,25 +383,40 @@ declare class Connect {
|
|
|
382
383
|
private _api;
|
|
383
384
|
private _window;
|
|
384
385
|
private _odin;
|
|
386
|
+
private _storage;
|
|
385
387
|
constructor(appInfo?: Partial<AppInitOptions>);
|
|
386
388
|
private createUrl;
|
|
387
389
|
get origin(): string;
|
|
388
390
|
get appInfo(): AppInitOptions | null;
|
|
391
|
+
get slug(): string;
|
|
389
392
|
connect({ open, requires_api, requires_delegation, targets, }?: ConnectOptions | undefined): Promise<ConnectedUser>;
|
|
390
393
|
get api(): OdinApiClient;
|
|
391
394
|
get odin(): OdinCanisterClient;
|
|
392
395
|
get currentEnv(): "prod" | "dev" | "local" | "_preview";
|
|
396
|
+
restoreSession(): ConnectedUser | null;
|
|
397
|
+
disconnect(): void;
|
|
398
|
+
isSessionValid(): boolean;
|
|
393
399
|
hello(): void;
|
|
394
400
|
}
|
|
395
401
|
|
|
402
|
+
declare function isDelegationValid(chainJson: string): boolean;
|
|
403
|
+
|
|
396
404
|
declare function convertToOdinAmount(numberStr: string | number, token?: Pick<BaseToken, "decimals" | "divisibility">): bigint;
|
|
397
405
|
type TokenValue = string | File | null | bigint;
|
|
398
406
|
declare const createTokenValidators: Partial<Record<keyof Token, (value: TokenValue) => string | undefined>>;
|
|
399
407
|
|
|
400
408
|
declare const index_convertToOdinAmount: typeof convertToOdinAmount;
|
|
401
409
|
declare const index_createTokenValidators: typeof createTokenValidators;
|
|
410
|
+
declare const index_isDelegationValid: typeof isDelegationValid;
|
|
402
411
|
declare namespace index {
|
|
403
|
-
export { index_convertToOdinAmount as convertToOdinAmount, index_createTokenValidators as createTokenValidators };
|
|
412
|
+
export { index_convertToOdinAmount as convertToOdinAmount, index_createTokenValidators as createTokenValidators, index_isDelegationValid as isDelegationValid };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
interface SessionData {
|
|
416
|
+
principal: string;
|
|
417
|
+
sessionKey: string;
|
|
418
|
+
delegationChain: string | null;
|
|
419
|
+
jwt: string | null;
|
|
404
420
|
}
|
|
405
421
|
|
|
406
|
-
export { type Achievement as OdinAchievement, type AchievementCategory as OdinAchievementCategory, type Activity as OdinActivity, type Balance as OdinBalance, type BaseToken as OdinBaseToken, Connect as OdinConnect, ConnectedUser as OdinConnectedUser, type Token as OdinToken, type TokenWithBalance as OdinTokenWithBalance, type Transaction as OdinTransaction, type User as OdinUser, index as OdinUtils };
|
|
422
|
+
export { type Achievement as OdinAchievement, type AchievementCategory as OdinAchievementCategory, type Activity as OdinActivity, type Balance as OdinBalance, type BaseToken as OdinBaseToken, Connect as OdinConnect, ConnectedUser as OdinConnectedUser, type Token as OdinToken, type TokenWithBalance as OdinTokenWithBalance, type Transaction as OdinTransaction, type User as OdinUser, index as OdinUtils, type SessionData };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import U from'axios';import E from'@apimatic/json-bigint';import {Ed25519KeyIdentity,DelegationIdentity,DelegationChain}from'@dfinity/identity';var C=Object.defineProperty;var R=(n,e)=>{for(var t in e)C(n,t,{get:e[t],enumerable:true});};var x=n=>{try{return E.parse(n)}catch(e){return console.error("Error parsing response:",e),n}},u=class{async get(e,t){return this.fetch(e,{method:"GET",...t})}async fetch(e,t){let i={transformResponse:[x],...t};return (await U(e,i)).data}async post(e,t,i){return this.fetch(e,{method:"POST",data:t,...i})}async put(e,t,i){return this.fetch(e,{method:"PUT",data:t,...i})}async delete(e,t){return this.fetch(e,{method:"DELETE",...t})}};var b={};R(b,{convertToOdinAmount:()=>I,createTokenValidators:()=>c});function I(n,e={decimals:3,divisibility:8}){let t=e.decimals+e.divisibility;if(n=typeof n=="string"?n.trim():String(n),isNaN(+n))return 0n;let[i,r=""]=n.split(".");return r=r.slice(0,t),r=r.padEnd(t,"0"),i=i.replace(/^0+(?=\d)/,""),i===""&&(i="0"),BigInt(i+r)}var c={name:P,image:L,ticker:B,description:S,twitter:$,website:q,telegram:D};function P(n){if(typeof n!="string")return "Name must be a string.";if(!n||n.trim()==="")return "Name is required.";if(n.length<3||n.length>30)return "Name must be between 3 and 30 characters."}function S(n){if(n){if(typeof n!="string")return "Description must be a string.";if(n&&n.length>100)return "Description must not exceed 100 characters."}}function B(n){if(typeof n!="string")return "Ticker must be a string.";if(!n||n.trim()==="")return "Ticker is required.";if(n.length<3||n.length>10)return "Ticker must be between 3 and 10 characters.";if(!/^[A-Z0-9]+$/.test(n))return "Ticker must be alphanumeric characters in uppercase only.";if(!/(?=(.*[A-Z].*[A-Z]))/.test(n))return "Ticker must have at least 2 alpha characters."}function L(n){if(!(n instanceof File))return "Image must be a file.";let e=["image/png","image/jpeg","image/webp","image/svg","image/gif","image/avif"],t=200*1024;if(!n)return "Image file is required.";if(!e.includes(n.type))return `Invalid file type. Allowed types are: ${e.join(", ")}`;if(n.size>t)return `File size exceeds the maximum limit of ${t/1024}KB.`}function $(n){if(n){if(typeof n!="string")return "Twitter handle must be a string.";if(n&&!/^https?:\/\/(www\.)?(twitter\.com|x\.com)\/[A-Za-z0-9_]{1,15}\/?$/.test(n))return "Twitter must be a valid twitter URL."}}function q(n){if(n){if(typeof n!="string")return "Website must be a string.";if(n&&!/^(https?:\/\/)?([\w-]+(\.[\w-]+)+)(\/[\w-./?%&=]*)?$/.test(n))return "Website must be a valid URL."}}function D(n){if(n){if(typeof n!="string")return "Telegram must be a string.";if(n&&!/^(https?:\/\/)?(t\.me|telegram\.me|telegram\.org)\/[A-Za-z0-9_]{5,32}\/?$/.test(n))return "Telegram must be a valid telegram URL."}}var F={dev:"https://api.odin.fun/dev",prod:"https://api.odin.fun/v1",local:"https://api.odin.fun/dev"},m=class{_apiKey=null;_httpClient;BASE_URL;constructor(e="prod"){this._httpClient=new u,this.BASE_URL=F[e];}getUser(e){return this._httpClient.get(`${this.BASE_URL}/user/${e}`)}async getBalances(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/balances`,{params:{...t}})).data}async getBalance(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/balances`,{params:{token_in:t}})).data.find(r=>r.id===t)??null}getTokens(e,t={field:"marketcap",direction:"desc"},i={}){return this._httpClient.get(`${this.BASE_URL}/tokens`,{params:{...e,sort:`${t.field}:${t.direction}`,...i}})}getToken(e){return this._httpClient.get(`${this.BASE_URL}/token/${e}`)}async uploadImage(e){if(!this._apiKey)throw new Error("API key is not set");try{let t=c.image?.(e);if(t)throw new Error(t);let i=new FormData;return i.append("file",e),(await this._httpClient.post(`${this.BASE_URL}/upload`,i,{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${this._apiKey}`}})).data.upload}catch(t){if(t instanceof Error)if(t.name==="AxiosError"){let i=t;throw new Error(i.response?.data?.message||i.message)}else throw new Error(t.message);else throw new Error("Image upload failed")}}updateTokenImage(e,t){if(!this._apiKey)throw new Error("API key is not set");try{return this._httpClient.post(`${this.BASE_URL}/token/${e}/image`,{image:t},{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${this._apiKey}`}})}catch(i){if(i instanceof Error&&i.name==="AxiosError"){let r=i;throw new Error(r.response?.data?.message||r.message)}else throw new Error("Image update failed")}}getUserActivity(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/activity`,{params:{...t}})}async getUserTokens(e,t){let i=await this._httpClient.get(`${this.BASE_URL}/user/${e}/tokens`,{params:{...t}});return {...i,data:i.data.map(r=>({...r,balance:BigInt(r.balance)}))}}async getUserLiquidity(e,t){let i=await this._httpClient.get(`${this.BASE_URL}/user/${e}/liquidity`,{params:{...t}});return {...i,data:i.data.map(r=>({...r,balance:BigInt(r.balance)}))}}async getUserAchievements(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/achievements`,{params:{...t}})).data}getUserTransactions(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/transactions`,{params:{...t}})}getUserCreatedTokens(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/created`,{params:{...t}})}getUserStats(e){return this._httpClient.get(`${this.BASE_URL}/user/${e}/stats`)}set apiKey(e){this._apiKey=e;}get apiKey(){return this._apiKey}};var g=class{_identity;_api;_principal;_odin;constructor(e,t,i,r){this._principal=e,this._identity=t,this._api=i,this._odin=r;}get principal(){return this._principal}set principal(e){this._principal=e;}getIdentity(){return this._identity}getUser(){return this._api.getUser(this.principal)}getBalances(e){return this._api.getBalances(this.principal,e)}getBalance(e){return this._api.getBalance(this.principal,e)}getTokens(e){return this._api.getUserTokens(this.principal,e)}getCreatedTokens(e){return this._api.getUserCreatedTokens(this.principal,e)}getActivity(e){return this._api.getUserActivity(this.principal,e)}getLiquidity(e){return this._api.getUserLiquidity(this.principal,e)}getAchievements(e){return this._api.getUserAchievements(this.principal,e)}getTransactions(e){return this._api.getUserTransactions(this.principal,e)}getStats(){return this._api.getUserStats(this.principal)}sell(e){return this._odin.sell({...e,principal:this.principal})}buy(e){return this._odin.buy({...e,principal:this.principal})}addLiquidity(e){return this._odin.addLiquidity({...e,principal:this.principal})}removeLiquidity(e){return this._odin.removeLiquidity({...e,principal:this.principal})}transfer(e){return this._odin.transfer({...e,principal:this.principal})}createToken(e){return this._odin.createToken({...e,principal:this.principal})}icrcApprove(e){return this._odin.icrcApprove({...e,principal:this.principal})}swap(e){return this._odin.swap({...e,principal:this.principal})}};var w={local:"http://localhost:5173",prod:"https://odin.fun",dev:"https://dev.odin.fun",_preview:"https://deploy-preview-1368--dev-odin-toniq.netlify.app"};var h=class{_settings;constructor(e){this._settings={target:e?.target||"_blank",settings:e?.settings||""};}get settings(){return this._settings}set settings(e){this._settings={...this._settings,...e};}open(e){return window.open(e,this._settings?.target||"_blank",this._settings?.settings)}};var f=class{_window;_appInfo;_api;origin;constructor(e,t,i,r){this._window=e,this._api=t,this._appInfo=i,this.origin=r;}get appInfo(){return this._appInfo}createUrl(e){let t=new URL(`${this.origin}/${e}`);return this._appInfo?.name&&t.searchParams.append("app_name",this._appInfo.name),t.searchParams.append("referrer",window.location.origin),t}baseAction({params:e,odinPath:t,receivedMessageFromOrigin:i,resolve:r}){return new Promise((o,l)=>{let p=async s=>{s.origin===this.origin&&s.data.path==="/"+t&&(window.removeEventListener("message",p),(typeof i=="function"?i(s.data.message):i===s.data.message)?o(r.success(s.data.message)):l(new Error(r.failure)));},d=this.createUrl(t);for(let s in e)e[s]&&d.searchParams.append(s,e[s]);let a=this._window.open(d);if(!a||a.closed||typeof a.closed>"u"){l(new Error(r.didnotopen??`Failed to open ${t} window, please always allow popups and try again`));return}window.addEventListener("message",p);})}sell({token:e,tokenAmount:t,principal:i}){return this.baseAction({params:{principal:i,token:e,amount:t.toString()},odinPath:"authorize/sell",receivedMessageFromOrigin:"sold",resolve:{success:()=>true,failure:"Sell failed or was cancelled",close:"User closed the window"}})}buy({principal:e,token:t,btcAmount:i}){return this.baseAction({params:{principal:e,token:t,amount:i.toString()},odinPath:"authorize/buy",receivedMessageFromOrigin:"purchased",resolve:{success:()=>true,failure:"Purchase failed or was cancelled",close:"User closed the window"}})}transfer({principal:e,token:t,amount:i,destination:r}){return this.baseAction({params:{principal:e,token:t,amount:i.toString(),destination:r},odinPath:"authorize/transfer",receivedMessageFromOrigin:"transferred",resolve:{success:()=>true,failure:"Transfer failed or was cancelled",close:"User closed the window"}})}addLiquidity({principal:e,btcAmount:t,token:i}){return this.baseAction({params:{principal:e,amount:t.toString(),token:i},odinPath:"authorize/add_liquidity",receivedMessageFromOrigin:"addedLiquidity",resolve:{success:()=>true,failure:"Add liquidity failed or was cancelled",close:"User closed the window"}})}removeLiquidity({principal:e,lpAmount:t,token:i}){return this.baseAction({params:{principal:e,amount:t.toString(),token:i},odinPath:"authorize/remove_liquidity",receivedMessageFromOrigin:"removedLiquidity",resolve:{success:()=>true,failure:"Remove liquidity failed or was cancelled",close:"User closed the window"}})}swap({principal:e,fromToken:t,toToken:i,fromAmount:r}){return this.baseAction({params:{principal:e,from:t,to:i,amount:r.toString()},odinPath:"authorize/swap",receivedMessageFromOrigin:"swapped",resolve:{success:()=>true,failure:"Swap failed or was cancelled",close:"User closed the window"}})}icrcApprove({principal:e,token:t,spender:i,amount:r}){return this.baseAction({params:{principal:e,token:t,spender:i,amount:r.toString()},odinPath:"authorize/icrc_approve",receivedMessageFromOrigin:"approved",resolve:{success:()=>true,failure:"ICRC approve failed or was cancelled",close:"User closed the window"}})}async createToken({image:e,...t}){for(let o in c)if(o in t){let p=c[o]?.(t[o]||null);if(p)throw new Error(p)}if(t.discount&&!/^[A-Za-z0-9]{10}$/.test(t.discount))throw new Error("Discount code must be alphanumeric and exactly 10 characters long.");let i=await this._api.uploadImage(e);if(!await this.baseAction({params:{...t,image:i,buy:t.buy?.toString()},odinPath:"authorize/create_token",receivedMessageFromOrigin:"tokenCreated",resolve:{success:()=>true,failure:"Token creation failed or was cancelled",close:"User closed the window"}}))throw new Error("Token creation failed. Please try again.");return true}};var _=class{_appInfo=null;_api;_window;_odin;constructor(e){this._appInfo={env:"prod",name:"app_name",...e},this._api=new m(this._appInfo.env==="prod"?"prod":"dev"),this._window=new h,this._odin=new f(this._window,this._api,this._appInfo,w[this._appInfo.env||"prod"]);}createUrl(e){let t=new URL(`${this.origin}/${e}`);return this._appInfo?.name&&t.searchParams.append("app_name",this._appInfo.name),t.searchParams.append("referrer",window.location.origin),t}get origin(){return w[this._appInfo?.env||"prod"]}get appInfo(){return this._appInfo}connect({open:e,requires_api:t,requires_delegation:i,targets:r}={requires_delegation:false,requires_api:false}){return new Promise((o,l)=>{e&&(this._window.settings=e);let p=Ed25519KeyIdentity.generate(),d=async s=>{if(s.origin===this.origin&&s.data.path==="/authorize/connect")if(window.removeEventListener("message",d),s.data.message!="rejected"){let y;try{let T=s.data.message,{principal:v,jwt:O,delegationChain:A}=T;if(t&&(this._api.apiKey=O),i){if(!A)throw new Error("Delegation chain is missing");let k=DelegationIdentity.fromDelegation(p,DelegationChain.fromJSON(A));y=new g(v,k,this.api,this._odin);}else y=new g(v,null,this.api,this._odin);o(y);}catch{l(new Error("Failed to fetch user data"));}}else l(new Error("User rejected the connection"));},a=this.createUrl("authorize/connect");if(a.searchParams.append("requires_api",t?"1":"0"),i){a.searchParams.append("requires_delegation","1");let s=btoa(JSON.stringify(p.toJSON()));a.searchParams.append("session_key",s),a.searchParams.append("targets",r?.join(",")||"");}this._window.open(a),window.addEventListener("message",d);})}get api(){return this._api}get odin(){return this._odin}get currentEnv(){return this._appInfo?.env||"prod"}hello(){console.log("Hello from Odin Connect!");}};
|
|
2
|
-
export{
|
|
1
|
+
import P from'axios';import B from'@apimatic/json-bigint';import {DelegationChain,Ed25519KeyIdentity,DelegationIdentity}from'@dfinity/identity';var x=Object.defineProperty;var E=(i,e)=>{for(var t in e)x(i,t,{get:e[t],enumerable:true});};var L=i=>{try{return B.parse(i)}catch(e){return console.error("Error parsing response:",e),i}},m=class{async get(e,t){return this.fetch(e,{method:"GET",...t})}async fetch(e,t){let n={transformResponse:[L],...t};return (await P(e,n)).data}async post(e,t,n){return this.fetch(e,{method:"POST",data:t,...n})}async put(e,t,n){return this.fetch(e,{method:"PUT",data:t,...n})}async delete(e,t){return this.fetch(e,{method:"DELETE",...t})}};var k={};E(k,{convertToOdinAmount:()=>D,createTokenValidators:()=>c,isDelegationValid:()=>u});function u(i){try{let e=DelegationChain.fromJSON(JSON.parse(i)),t=BigInt(Date.now())*1000000n;return e.delegations.every(n=>n.delegation.expiration==null||n.delegation.expiration>t)}catch{return false}}function D(i,e={decimals:3,divisibility:8}){let t=e.decimals+e.divisibility;if(i=typeof i=="string"?i.trim():String(i),isNaN(+i))return 0n;let[n,r=""]=i.split(".");return r=r.slice(0,t),r=r.padEnd(t,"0"),n=n.replace(/^0+(?=\d)/,""),n===""&&(n="0"),BigInt(n+r)}var c={name:q,image:z,ticker:N,description:F,twitter:K,website:W,telegram:V};function q(i){if(typeof i!="string")return "Name must be a string.";if(!i||i.trim()==="")return "Name is required.";if(i.length<3||i.length>30)return "Name must be between 3 and 30 characters."}function F(i){if(i){if(typeof i!="string")return "Description must be a string.";if(i&&i.length>100)return "Description must not exceed 100 characters."}}function N(i){if(typeof i!="string")return "Ticker must be a string.";if(!i||i.trim()==="")return "Ticker is required.";if(i.length<3||i.length>10)return "Ticker must be between 3 and 10 characters.";if(!/^[A-Z0-9]+$/.test(i))return "Ticker must be alphanumeric characters in uppercase only.";if(!/(?=(.*[A-Z].*[A-Z]))/.test(i))return "Ticker must have at least 2 alpha characters."}function z(i){if(!(i instanceof File))return "Image must be a file.";let e=["image/png","image/jpeg","image/webp","image/svg","image/gif","image/avif"],t=200*1024;if(!i)return "Image file is required.";if(!e.includes(i.type))return `Invalid file type. Allowed types are: ${e.join(", ")}`;if(i.size>t)return `File size exceeds the maximum limit of ${t/1024}KB.`}function K(i){if(i){if(typeof i!="string")return "Twitter handle must be a string.";if(i&&!/^https?:\/\/(www\.)?(twitter\.com|x\.com)\/[A-Za-z0-9_]{1,15}\/?$/.test(i))return "Twitter must be a valid twitter URL."}}function W(i){if(i){if(typeof i!="string")return "Website must be a string.";if(i&&!/^(https?:\/\/)?([\w-]+(\.[\w-]+)+)(\/[\w-./?%&=]*)?$/.test(i))return "Website must be a valid URL."}}function V(i){if(i){if(typeof i!="string")return "Telegram must be a string.";if(i&&!/^(https?:\/\/)?(t\.me|telegram\.me|telegram\.org)\/[A-Za-z0-9_]{5,32}\/?$/.test(i))return "Telegram must be a valid telegram URL."}}var J={dev:"https://api.odin.fun/dev",prod:"https://api.odin.fun/v1",local:"https://api.odin.fun/dev"},f=class{_apiKey=null;_httpClient;BASE_URL;constructor(e="prod"){this._httpClient=new m,this.BASE_URL=J[e];}getUser(e){return this._httpClient.get(`${this.BASE_URL}/user/${e}`)}async getBalances(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/balances`,{params:{...t}})).data}async getBalance(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/balances`,{params:{token_in:t}})).data.find(r=>r.id===t)??null}getTokens(e,t={field:"marketcap",direction:"desc"},n={}){return this._httpClient.get(`${this.BASE_URL}/tokens`,{params:{...e,sort:`${t.field}:${t.direction}`,...n}})}getToken(e){return this._httpClient.get(`${this.BASE_URL}/token/${e}`)}async uploadImage(e){if(!this._apiKey)throw new Error("API key is not set");try{let t=c.image?.(e);if(t)throw new Error(t);let n=new FormData;return n.append("file",e),(await this._httpClient.post(`${this.BASE_URL}/upload`,n,{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${this._apiKey}`}})).data.upload}catch(t){if(t instanceof Error)if(t.name==="AxiosError"){let n=t;throw new Error(n.response?.data?.message||n.message)}else throw new Error(t.message);else throw new Error("Image upload failed")}}updateTokenImage(e,t){if(!this._apiKey)throw new Error("API key is not set");try{return this._httpClient.post(`${this.BASE_URL}/token/${e}/image`,{image:t},{headers:{"Content-Type":"multipart/form-data",Authorization:`Bearer ${this._apiKey}`}})}catch(n){if(n instanceof Error&&n.name==="AxiosError"){let r=n;throw new Error(r.response?.data?.message||r.message)}else throw new Error("Image update failed")}}getUserActivity(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/activity`,{params:{...t}})}async getUserTokens(e,t){let n=await this._httpClient.get(`${this.BASE_URL}/user/${e}/tokens`,{params:{...t}});return {...n,data:n.data.map(r=>({...r,balance:BigInt(r.balance)}))}}async getUserLiquidity(e,t){let n=await this._httpClient.get(`${this.BASE_URL}/user/${e}/liquidity`,{params:{...t}});return {...n,data:n.data.map(r=>({...r,balance:BigInt(r.balance)}))}}async getUserAchievements(e,t){return (await this._httpClient.get(`${this.BASE_URL}/user/${e}/achievements`,{params:{...t}})).data}getUserTransactions(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/transactions`,{params:{...t}})}getUserCreatedTokens(e,t){return this._httpClient.get(`${this.BASE_URL}/user/${e}/created`,{params:{...t}})}getUserStats(e){return this._httpClient.get(`${this.BASE_URL}/user/${e}/stats`)}set apiKey(e){this._apiKey=e;}get apiKey(){return this._apiKey}};var g=class{_identity;_api;_principal;_odin;constructor(e,t,n,r){this._principal=e,this._identity=t,this._api=n,this._odin=r;}get principal(){return this._principal}set principal(e){this._principal=e;}getIdentity(){return this._identity}getUser(){return this._api.getUser(this.principal)}getBalances(e){return this._api.getBalances(this.principal,e)}getBalance(e){return this._api.getBalance(this.principal,e)}getTokens(e){return this._api.getUserTokens(this.principal,e)}getCreatedTokens(e){return this._api.getUserCreatedTokens(this.principal,e)}getActivity(e){return this._api.getUserActivity(this.principal,e)}getLiquidity(e){return this._api.getUserLiquidity(this.principal,e)}getAchievements(e){return this._api.getUserAchievements(this.principal,e)}getTransactions(e){return this._api.getUserTransactions(this.principal,e)}getStats(){return this._api.getUserStats(this.principal)}sell(e){return this._odin.sell({...e,principal:this.principal})}buy(e){return this._odin.buy({...e,principal:this.principal})}addLiquidity(e){return this._odin.addLiquidity({...e,principal:this.principal})}removeLiquidity(e){return this._odin.removeLiquidity({...e,principal:this.principal})}transfer(e){return this._odin.transfer({...e,principal:this.principal})}createToken(e){return this._odin.createToken({...e,principal:this.principal})}icrcApprove(e){return this._odin.icrcApprove({...e,principal:this.principal})}swap(e){return this._odin.swap({...e,principal:this.principal})}};var b={local:"http://localhost:5173",prod:"https://odin.fun",dev:"https://dev.odin.fun",_preview:"https://deploy-preview-1368--dev-odin-toniq.netlify.app"};var y=class{_settings;constructor(e){this._settings={target:e?.target||"_blank",settings:e?.settings||""};}get settings(){return this._settings}set settings(e){this._settings={...this._settings,...e};}open(e){return window.open(e,this._settings?.target||"_blank",this._settings?.settings)}};var w=class{_window;_appInfo;_api;origin;constructor(e,t,n,r){this._window=e,this._api=t,this._appInfo=n,this.origin=r;}get appInfo(){return this._appInfo}createUrl(e){let t=new URL(`${this.origin}/${e}`);return this._appInfo?.name&&t.searchParams.append("app_name",this._appInfo.name),t.searchParams.append("referrer",window.location.origin),t}baseAction({params:e,odinPath:t,receivedMessageFromOrigin:n,resolve:r}){return new Promise((p,l)=>{let a=async s=>{s.origin===this.origin&&s.data.path==="/"+t&&(window.removeEventListener("message",a),(typeof n=="function"?n(s.data.message):n===s.data.message)?p(r.success(s.data.message)):l(new Error(r.failure)));},d=this.createUrl(t);for(let s in e)e[s]&&d.searchParams.append(s,e[s]);let o=this._window.open(d);if(!o||o.closed||typeof o.closed>"u"){l(new Error(r.didnotopen??`Failed to open ${t} window, please always allow popups and try again`));return}window.addEventListener("message",a);})}sell({token:e,tokenAmount:t,principal:n}){return this.baseAction({params:{principal:n,token:e,amount:t.toString()},odinPath:"authorize/sell",receivedMessageFromOrigin:"sold",resolve:{success:()=>true,failure:"Sell failed or was cancelled",close:"User closed the window"}})}buy({principal:e,token:t,btcAmount:n}){return this.baseAction({params:{principal:e,token:t,amount:n.toString()},odinPath:"authorize/buy",receivedMessageFromOrigin:"purchased",resolve:{success:()=>true,failure:"Purchase failed or was cancelled",close:"User closed the window"}})}transfer({principal:e,token:t,amount:n,destination:r}){return this.baseAction({params:{principal:e,token:t,amount:n.toString(),destination:r},odinPath:"authorize/transfer",receivedMessageFromOrigin:"transferred",resolve:{success:()=>true,failure:"Transfer failed or was cancelled",close:"User closed the window"}})}addLiquidity({principal:e,btcAmount:t,token:n}){return this.baseAction({params:{principal:e,amount:t.toString(),token:n},odinPath:"authorize/add_liquidity",receivedMessageFromOrigin:"addedLiquidity",resolve:{success:()=>true,failure:"Add liquidity failed or was cancelled",close:"User closed the window"}})}removeLiquidity({principal:e,lpAmount:t,token:n}){return this.baseAction({params:{principal:e,amount:t.toString(),token:n},odinPath:"authorize/remove_liquidity",receivedMessageFromOrigin:"removedLiquidity",resolve:{success:()=>true,failure:"Remove liquidity failed or was cancelled",close:"User closed the window"}})}swap({principal:e,fromToken:t,toToken:n,fromAmount:r}){return this.baseAction({params:{principal:e,from:t,to:n,amount:r.toString()},odinPath:"authorize/swap",receivedMessageFromOrigin:"swapped",resolve:{success:()=>true,failure:"Swap failed or was cancelled",close:"User closed the window"}})}icrcApprove({principal:e,token:t,spender:n,amount:r}){return this.baseAction({params:{principal:e,token:t,spender:n,amount:r.toString()},odinPath:"authorize/icrc_approve",receivedMessageFromOrigin:"approved",resolve:{success:()=>true,failure:"ICRC approve failed or was cancelled",close:"User closed the window"}})}async createToken({image:e,...t}){for(let p in c)if(p in t){let a=c[p]?.(t[p]||null);if(a)throw new Error(a)}if(t.discount&&!/^[A-Za-z0-9]{10}$/.test(t.discount))throw new Error("Discount code must be alphanumeric and exactly 10 characters long.");let n=await this._api.uploadImage(e);if(!await this.baseAction({params:{...t,image:n,buy:t.buy?.toString()},odinPath:"authorize/create_token",receivedMessageFromOrigin:"tokenCreated",resolve:{success:()=>true,failure:"Token creation failed or was cancelled",close:"User closed the window"}}))throw new Error("Token creation failed. Please try again.");return true}};var _=class{_key;constructor(e,t){this._key=`odin_connect:${e}:${t}:session`;}save(e){try{localStorage.setItem(this._key,JSON.stringify(e));}catch{}}load(){try{let e=localStorage.getItem(this._key);if(!e)return null;let t=JSON.parse(e);return this.isValidSessionData(t)?t:null}catch{return null}}isValidSessionData(e){if(typeof e!="object"||e===null)return false;let t=e;return typeof t.principal=="string"&&typeof t.sessionKey=="string"&&(t.delegationChain===null||typeof t.delegationChain=="string")&&(t.jwt===null||typeof t.jwt=="string")}clear(){try{localStorage.removeItem(this._key);}catch{}}};function j(i){let e=0;for(let t=0;t<i.length;t++)e=e*31+i.charCodeAt(t)|0;return (e>>>0).toString(36).slice(0,3).padStart(3,"0")}function M(i){return `${i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}-${j(i)}`}var O=class{_appInfo=null;_api;_window;_odin;_storage;constructor(e){this._appInfo={env:"prod",name:"app_name",...e},this._appInfo.slug=this._appInfo.slug||M(this._appInfo.name),this._api=new f(this._appInfo.env==="prod"?"prod":"dev"),this._window=new y,this._odin=new w(this._window,this._api,this._appInfo,b[this._appInfo.env||"prod"]),this._storage=new _(this._appInfo.slug,this._appInfo.env||"prod");}createUrl(e){let t=new URL(`${this.origin}/${e}`);return this._appInfo?.name&&t.searchParams.append("app_name",this._appInfo.name),t.searchParams.append("referrer",window.location.origin),t}get origin(){return b[this._appInfo?.env||"prod"]}get appInfo(){return this._appInfo}get slug(){return this._appInfo?.slug||""}connect({open:e,requires_api:t,requires_delegation:n,targets:r}={requires_delegation:false,requires_api:false}){return new Promise((p,l)=>{e&&(this._window.settings=e);let a=Ed25519KeyIdentity.generate(),d=async s=>{if(s.origin===this.origin&&s.data.path==="/authorize/connect")if(window.removeEventListener("message",d),s.data.message!="rejected"){let v;try{let A=s.data.message,{principal:T,jwt:C,delegationChain:h}=A;if(t&&(this._api.apiKey=C),n){if(!h)throw new Error("Delegation chain is missing");let I=DelegationIdentity.fromDelegation(a,DelegationChain.fromJSON(h));v=new g(T,I,this.api,this._odin);}else v=new g(T,null,this.api,this._odin);(t||n)&&this._storage.save({principal:T,sessionKey:JSON.stringify(a.toJSON()),delegationChain:h?JSON.stringify(h):null,jwt:C||null}),p(v);}catch{l(new Error("Failed to fetch user data"));}}else l(new Error("User rejected the connection"));},o=this.createUrl("authorize/connect");if(o.searchParams.append("requires_api",t?"1":"0"),n){o.searchParams.append("requires_delegation","1");let s=btoa(JSON.stringify(a.toJSON()));o.searchParams.append("session_key",s),o.searchParams.append("targets",r?.join(",")||"");}this._window.open(o),window.addEventListener("message",d);})}get api(){return this._api}get odin(){return this._odin}get currentEnv(){return this._appInfo?.env||"prod"}restoreSession(){try{let e=this._storage.load();if(!e)return null;if(e.delegationChain&&!u(e.delegationChain))return this._storage.clear(),null;e.jwt&&(this._api.apiKey=e.jwt);let t=null;if(e.delegationChain&&e.sessionKey){let n=Ed25519KeyIdentity.fromJSON(e.sessionKey),r=DelegationChain.fromJSON(JSON.parse(e.delegationChain));t=DelegationIdentity.fromDelegation(n,r);}return new g(e.principal,t,this._api,this._odin)}catch{return this._storage.clear(),null}}disconnect(){this._storage.clear(),this._api.apiKey=null;}isSessionValid(){let e=this._storage.load();return e?e.delegationChain?u(e.delegationChain):true:false}hello(){console.log("Hello from Odin Connect!");}};
|
|
2
|
+
export{O as OdinConnect,k as OdinUtils};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odin-connect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"homepage": "https://github.com/Toniq-Labs/odin-connect#readme",
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^24.3.1",
|
|
43
|
-
"@vitest/ui": "^3.2.
|
|
43
|
+
"@vitest/ui": "^3.2.6",
|
|
44
44
|
"jsdom": "^27.0.0",
|
|
45
45
|
"release-it": "^19.0.5",
|
|
46
46
|
"tsup": "^8.5.1",
|
|
47
47
|
"typescript": "^5.9.2",
|
|
48
|
-
"vitest": "^3.2.
|
|
48
|
+
"vitest": "^3.2.6"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@apimatic/json-bigint": "^1.2.0",
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
"axios": "^1.12.0"
|
|
54
54
|
},
|
|
55
55
|
"overrides": {
|
|
56
|
-
"undici": "6.24.1"
|
|
56
|
+
"undici": "6.24.1",
|
|
57
|
+
"esbuild": "0.28.1"
|
|
57
58
|
},
|
|
58
59
|
"release-it": {
|
|
59
60
|
"npm": {
|
package/readme.md
CHANGED
|
@@ -12,6 +12,7 @@ A TypeScript SDK for integrating with the [Odin](https://odin.fun) decentralized
|
|
|
12
12
|
- [API Request Flow](#api-request-flow)
|
|
13
13
|
- [Getting Started](#getting-started)
|
|
14
14
|
- [Authentication](#authentication)
|
|
15
|
+
- [Session Restoration](#session-restoration)
|
|
15
16
|
- [Connected User Operations](#connected-user-operations)
|
|
16
17
|
- [Fetching User Data](#fetching-user-data)
|
|
17
18
|
- [Trading](#trading)
|
|
@@ -91,6 +92,7 @@ sequenceDiagram
|
|
|
91
92
|
User->>Popup: Authenticates
|
|
92
93
|
Popup->>SDK: postMessage({ principal, jwt, delegationChain? })
|
|
93
94
|
SDK->>SDK: Create ConnectedUser instance
|
|
95
|
+
SDK->>SDK: Persist session to localStorage
|
|
94
96
|
SDK->>App: Returns ConnectedUser
|
|
95
97
|
```
|
|
96
98
|
|
|
@@ -151,10 +153,11 @@ const odinConnect = new OdinConnect({
|
|
|
151
153
|
env: "prod",
|
|
152
154
|
});
|
|
153
155
|
|
|
154
|
-
// 2.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
});
|
|
156
|
+
// 2. Restore existing session or authenticate
|
|
157
|
+
let user = odinConnect.restoreSession();
|
|
158
|
+
if (!user) {
|
|
159
|
+
user = await odinConnect.connect({ requires_api: true });
|
|
160
|
+
}
|
|
158
161
|
|
|
159
162
|
// 3. Fetch data
|
|
160
163
|
const balances = await user.getBalances({ page: 1, limit: 10 });
|
|
@@ -204,6 +207,58 @@ const identity = user.getIdentity();
|
|
|
204
207
|
// Use identity with @dfinity/agent
|
|
205
208
|
```
|
|
206
209
|
|
|
210
|
+
## Session Restoration
|
|
211
|
+
|
|
212
|
+
OdinConnect automatically persists session data to `localStorage` after a successful `connect()`. This allows you to restore sessions on page load without requiring user action.
|
|
213
|
+
|
|
214
|
+
### Restoring a session
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const odinConnect = new OdinConnect({ name: "My App", env: "prod" });
|
|
218
|
+
|
|
219
|
+
// Attempt to restore a previous session (synchronous, no popup)
|
|
220
|
+
const user = odinConnect.restoreSession();
|
|
221
|
+
if (user) {
|
|
222
|
+
// Session restored — user is ready
|
|
223
|
+
const balances = await user.getBalances({ page: 1, limit: 10 });
|
|
224
|
+
} else {
|
|
225
|
+
// No valid session — prompt the user to connect
|
|
226
|
+
const user = await odinConnect.connect({ requires_api: true });
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Checking session validity
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
if (odinConnect.isSessionValid()) {
|
|
234
|
+
// A non-expired session exists in storage
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Disconnecting
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// Clears persisted session data and resets the API key
|
|
242
|
+
odinConnect.disconnect();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Custom app slug
|
|
246
|
+
|
|
247
|
+
Storage keys are scoped by a slug derived from your app name (e.g. `"My App"` becomes `"my-app"`). You can provide a custom slug to control the storage key:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const odinConnect = new OdinConnect({
|
|
251
|
+
name: "My App",
|
|
252
|
+
slug: "myapp-v2", // Storage key: odin_connect:myapp-v2:prod:session
|
|
253
|
+
env: "prod",
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
> **Notes:**
|
|
258
|
+
> - Sessions with a delegation chain are automatically invalidated when the delegation expires.
|
|
259
|
+
> - Calling `disconnect()` in one tab clears the session for all tabs on the same origin.
|
|
260
|
+
> - In environments where `localStorage` is unavailable (SSR, strict privacy mode), session persistence is silently skipped.
|
|
261
|
+
|
|
207
262
|
## Connected User Operations
|
|
208
263
|
|
|
209
264
|
After calling `connect()`, you receive a `ConnectedUser` with the following capabilities:
|
|
@@ -451,6 +506,7 @@ import type {
|
|
|
451
506
|
OdinAchievement,
|
|
452
507
|
OdinAchievementCategory,
|
|
453
508
|
OdinConnectedUser,
|
|
509
|
+
SessionData,
|
|
454
510
|
} from "odin-connect";
|
|
455
511
|
```
|
|
456
512
|
|