Skip to content

Commit 52ef245

Browse files
committed
feat: various fixes and improvements
- Use launchpad API to resolve scripts. This helps reduce RPC calls and increase perfomance when localStorage cache is not available (TLink). - Improve WaterfallFallbackProvider to prevent fallback for eth_call exceptions. - When ts:contract interface is missing, default it to erc721. - Cleanup old RegistryScriptURI & ScriptURI code.
1 parent 67639da commit 52ef245

File tree

9 files changed

+79
-192
lines changed

9 files changed

+79
-192
lines changed

javascript/engine-js/src/AbstractEngine.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,42 +69,6 @@ export abstract class AbstractTokenScriptEngine implements ITokenScriptEngine {
6969
return uri;
7070
}
7171

72-
public async getScriptUris(chain: string | number, contractAddr: string) {
73-
// Direct RPC gets too hammered by opensea view (that doesn't allow localStorage to cache XML)
74-
/*const provider = await this.getWalletAdapter();
75-
let uri: string|string[]|null;
76-
77-
try {
78-
uri = Array.from(await provider.call(parseInt(chain), contractAddr, "scriptURI", [], ["string[]"])) as string[];
79-
} catch (e) {
80-
uri = await provider.call(parseInt(chain), contractAddr, "scriptURI", [], ["string"]);
81-
}
82-
83-
if (uri && Array.isArray(uri))
84-
uri = uri.length ? uri[0] : null
85-
86-
return <string>uri;*/
87-
88-
// TODO: Add support for selecting a specific index or URL?
89-
// const res = await fetch(`https://api.token-discovery.tokenscript.org/script-uri?chain=${chain}&contract=${contractAddr}`);
90-
// const scriptUris = await res.json();
91-
//return <string>scriptUris[0];
92-
93-
// i.e. https://store-backend.smartlayer.network/tokenscript/0xD5cA946AC1c1F24Eb26dae9e1A53ba6a02bd97Fe/chain/137/script-uri
94-
const res = await fetch(`https://store-backend.smartlayer.network/tokenscript/${contractAddr.toLowerCase()}/chain/${chain}/script-uri`);
95-
const data = await res.json();
96-
97-
if (!data.scriptURI) return null;
98-
99-
let uris: string[] = [];
100-
101-
if (data.scriptURI.erc5169?.length) uris.push(...data.scriptURI.erc5169);
102-
103-
if (data.scriptURI.offchain?.length) uris.push(...data.scriptURI.offchain);
104-
105-
return uris.length ? uris : null;
106-
}
107-
10872
public abstract getTokenScriptFromUrl(url: string): Promise<ITokenScript>;
10973
public abstract loadTokenScript(xml: string): Promise<ITokenScript>;
11074
public abstract getTokenDiscoveryAdapter?: () => Promise<any>;

javascript/engine-js/src/AbstractTokenScript.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ export abstract class AbstractTokenScript implements ITokenScript {
374374
initialTokenDetails.push({
375375
originId: i, // TODO: ensure that this is unique
376376
blockChain: 'eth',
377-
tokenType: contract.getInterface(),
377+
tokenType: contract.getInterface() ?? "erc721",
378378
chainId: addresses[key].chain,
379379
contractAddress: addresses[key].address,
380380
});

javascript/engine-js/src/IEngine.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export interface ITokenScriptEngine {
3030
readonly config?: IEngineConfig;
3131

3232
processIpfsUrl(uri: string): string;
33-
getScriptUris(chain: string | number, contractAddr: string): Promise<string[] | null>;
3433
getTokenScriptFromUrl(url: string): Promise<ITokenScript>;
3534
loadTokenScript(xml: string): Promise<ITokenScript>;
3635

javascript/engine-js/src/repo/Repo.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {RegistryScriptURI} from "./sources/RegistryScriptURI";
33
import {ScriptURI} from "./sources/ScriptURI";
44
import {ResolvedScriptData, ScriptInfo, SourceInterfaceConstructor} from "./sources/SourceInterface";
55
import {TokenScriptRepo} from "./sources/TokenScriptRepo";
6+
import {LaunchpadAPI} from "./sources/LaunchpadAPI";
67

78
type ScriptLookupCache = {[chainAndContract: string]: {scripts: ScriptInfo[], timestamp: number}};
89

@@ -13,11 +14,11 @@ export class Repo {
1314

1415
/**
1516
* Any number of sources can be defined and their order changed.
16-
* The first source should be the most reliable to avoid fallback lookups.
1717
*/
1818
static REPO_SOURCES: SourceInterfaceConstructor[] = [
19-
ScriptURI,
20-
RegistryScriptURI,
19+
//ScriptURI,
20+
//RegistryScriptURI,
21+
LaunchpadAPI,
2122
TokenScriptRepo
2223
];
2324

@@ -70,7 +71,7 @@ export class Repo {
7071
try {
7172
scripts.push(...await (new resolver(this.context)).resolveAllScripts(tsPath));
7273
} catch (e) {
73-
console.error("Failed to resolve tokenscripts using resolver: " + resolver.name, e);
74+
console.warn("Failed to resolve tokenscripts using resolver: " + resolver.name, e);
7475
}
7576
}
7677

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {ScriptInfo, SourceInterface} from "./SourceInterface";
2+
import {ITokenScriptEngine} from "../../IEngine";
3+
4+
const LAUNCHPAD_API_URL = "https://store-backend.smartlayer.network/";
5+
6+
export class LaunchpadAPI implements SourceInterface {
7+
8+
constructor(private context: ITokenScriptEngine) {
9+
10+
}
11+
12+
async resolveAllScripts(tsPath: string): Promise<Omit<ScriptInfo, "timestamp">[]> {
13+
14+
const [chain, contractAddr] = tsPath.split("-");
15+
16+
if (!contractAddr || contractAddr.indexOf("0x") !== 0)
17+
throw new Error("Not a EIP-5169 or EIP-7738 path");
18+
19+
const res = await fetch(`${LAUNCHPAD_API_URL}tokenscript/${contractAddr.toLowerCase()}/chain/${chain}/script-info`);
20+
return await res.json() as ScriptInfo[];
21+
}
22+
23+
}

javascript/engine-js/src/repo/sources/RegistryScriptURI.ts

Lines changed: 0 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,13 @@ import {ITokenScriptEngine, ScriptSourceType} from "../../IEngine";
22
import {ScriptInfo, SourceInterface} from "./SourceInterface";
33

44
const REGISTRY_7738 = "0x0077380bCDb2717C9640e892B9d5Ee02Bb5e0682";
5-
const HOLESKY_ID = 17000; // TODO: Source this from engine
6-
const cacheTimeout = 60 * 1000; // 1 minute cache validity
7-
8-
//cache entries
9-
export interface ScriptEntry {
10-
scriptURIs: string[];
11-
timeStamp: number;
12-
}
13-
14-
export interface RegistryMetaData {
15-
scriptData: ScriptInfo[];
16-
timeStamp: number;
17-
}
18-
19-
const cachedResults = new Map<string, ScriptEntry>();
20-
const cachedMetaDataResults = new Map<string, RegistryMetaData>();
215

226
/**
237
* The ScriptURI source implement ethereum EIP-5169
248
* https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5169.md
259
*/
2610
export class RegistryScriptURI implements SourceInterface {
2711

28-
// Development deployment of 7738 on Holesky only
29-
// TODO: Replace with multichain address once available
30-
31-
32-
3312
constructor(private context: ITokenScriptEngine) {
3413
}
3514

@@ -51,56 +30,10 @@ export class RegistryScriptURI implements SourceInterface {
5130
return registryScripts;
5231
}
5332

54-
// This returns all entries but the 7738 calling function currently selects the first entry
55-
// TODO: Create selector that displays icon and name for each entry, in order returned
56-
// TODO: Use global deployed address for 7738
57-
public async get7738Entry(chain: string, contractAddr: string): Promise<string[]> {
58-
59-
// use 1 minute persistence fetch cache
60-
let cachedResult = this.checkCachedResult(chain, contractAddr);
61-
62-
if (cachedResult.length > 0) {
63-
return cachedResult;
64-
}
65-
66-
const chainId: number = parseInt(chain);
67-
68-
const provider = await this.context.getWalletAdapter();
69-
let uri: string|string[]|null;
70-
71-
try {
72-
uri = Array.from(await provider.call(
73-
chainId, REGISTRY_7738, "scriptURI", [
74-
{
75-
internalType: "address",
76-
name: "",
77-
type: "address",
78-
value: contractAddr
79-
}], ["string[]"]
80-
)) as string[];
81-
} catch (e) {
82-
uri = "";
83-
}
84-
85-
if (uri && Array.isArray(uri) && uri.length > 0) {
86-
this.storeResult(chain, contractAddr, uri);
87-
return uri;
88-
} else {
89-
return [];
90-
}
91-
}
92-
9333
public async get7738Metadata(chain: string, contractAddr: string): Promise<ScriptInfo[]> {
9434

9535
const chainId: number = parseInt(chain);
9636

97-
// use 1 minute persistence fetch cache
98-
let cachedResult = this.checkCachedMetaData(chain, contractAddr);
99-
100-
if (cachedResult.length > 0) {
101-
return cachedResult;
102-
}
103-
10437
const provider = await this.context.getWalletAdapter();
10538
let scriptSourceData: any;
10639

@@ -176,85 +109,4 @@ export class RegistryScriptURI implements SourceInterface {
176109
return sourceElements;
177110
}
178111

179-
private storeResult(chain: string, contractAddr: string, uris: string[]) {
180-
// remove out of date entries
181-
cachedResults.forEach((value, key) => {
182-
if (value.timeStamp < (Date.now() - cacheTimeout)) {
183-
cachedResults.delete(key);
184-
}
185-
});
186-
187-
cachedResults.set(chain + "-" + contractAddr, {
188-
scriptURIs: uris,
189-
timeStamp: Date.now()
190-
});
191-
}
192-
193-
private checkCachedResult(chain: string, contractAddress: string): string[] {
194-
const key = chain + "-" + contractAddress;
195-
const mapping = cachedResults.get(key);
196-
if (mapping) {
197-
if (mapping.timeStamp < (Date.now() - cacheTimeout)) {
198-
//out of date result, remove key
199-
cachedResults.delete(key);
200-
return []
201-
} else {
202-
//consoleLog("Can use cache");
203-
return mapping.scriptURIs;
204-
}
205-
} else {
206-
return [];
207-
}
208-
}
209-
210-
private checkCachedMetaData(chain: string, contractAddress: string): ScriptInfo[] {
211-
const key = chain + "-" + contractAddress;
212-
this.removeOutOfDateEntries();
213-
const mapping = cachedMetaDataResults.get(key);
214-
if (mapping) {
215-
return mapping.scriptData;
216-
} else {
217-
return [];
218-
}
219-
}
220-
221-
//ensure memory usage is kept to a minimum
222-
private removeOutOfDateEntries() {
223-
const currentTime = Date.now();
224-
for (const [key, value] of cachedMetaDataResults) {
225-
if (currentTime - value.timeStamp > cacheTimeout) {
226-
cachedMetaDataResults.delete(key);
227-
}
228-
}
229-
}
230-
231-
/*public async getAuthenticationStatus(contractAddress: string, order: number) {
232-
const wallet = await this.engine.getWalletAdapter();
233-
const chain = this.context.getCurrentTokenContext()?.chainId ?? await wallet.getChain();
234-
235-
let isAuthorised: boolean = false;
236-
237-
try {
238-
isAuthorised = await wallet.call(
239-
chain, HOLESKY_DEV_7738, "isAuthenticated", [
240-
{
241-
internalType: "address",
242-
name: "",
243-
type: "address",
244-
value: contractAddress
245-
},
246-
{
247-
internalType: "uint256",
248-
name: "",
249-
type: "uint256",
250-
value: order
251-
}], ["bool"]
252-
);
253-
} catch (e) {
254-
255-
}
256-
257-
return isAuthorised;
258-
}*/
259-
260112
}

javascript/engine-js/src/repo/sources/ScriptURI.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class ScriptURI implements SourceInterface {
2020

2121
const scripts: ScriptInfo[] = [];
2222

23-
let uris = await this.context.getScriptUris(chain, contractAddr);
23+
let uris = await this.getScriptUris(chain, contractAddr);
2424

2525
if (!uris)
2626
throw new Error("ScriptURI is not set on this contract or chain");
@@ -45,4 +45,40 @@ export class ScriptURI implements SourceInterface {
4545
return scripts;
4646
}
4747

48+
public async getScriptUris(chain: string | number, contractAddr: string) {
49+
// Direct RPC gets too hammered by opensea view (that doesn't allow localStorage to cache XML)
50+
/*const provider = await this.getWalletAdapter();
51+
let uri: string|string[]|null;
52+
53+
try {
54+
uri = Array.from(await provider.call(parseInt(chain), contractAddr, "scriptURI", [], ["string[]"])) as string[];
55+
} catch (e) {
56+
uri = await provider.call(parseInt(chain), contractAddr, "scriptURI", [], ["string"]);
57+
}
58+
59+
if (uri && Array.isArray(uri))
60+
uri = uri.length ? uri[0] : null
61+
62+
return <string>uri;*/
63+
64+
// TODO: Add support for selecting a specific index or URL?
65+
// const res = await fetch(`https://api.token-discovery.tokenscript.org/script-uri?chain=${chain}&contract=${contractAddr}`);
66+
// const scriptUris = await res.json();
67+
//return <string>scriptUris[0];
68+
69+
// i.e. https://store-backend.smartlayer.network/tokenscript/0xD5cA946AC1c1F24Eb26dae9e1A53ba6a02bd97Fe/chain/137/script-uri
70+
const res = await fetch(`https://store-backend.smartlayer.network/tokenscript/${contractAddr.toLowerCase()}/chain/${chain}/script-uri`);
71+
const data = await res.json();
72+
73+
if (!data.scriptURI) return null;
74+
75+
let uris: string[] = [];
76+
77+
if (data.scriptURI.erc5169?.length) uris.push(...data.scriptURI.erc5169);
78+
79+
if (data.scriptURI.offchain?.length) uris.push(...data.scriptURI.offchain);
80+
81+
return uris.length ? uris : null;
82+
}
83+
4884
}

javascript/engine-js/src/wallet/WaterfallFallbackProvider.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@ export class WaterfallFallbackProvider extends AbstractProvider {
1212

1313
async _perform<T = any>(req: PerformActionRequest): Promise<T> {
1414

15+
let errors = []
16+
1517
for (const provider of this.providers){
1618
try {
1719
return await provider._perform(req);
1820
} catch (e) {
19-
console.error("Provider error, falling back to next provider ", e);
21+
// For non connection errors we can throw
22+
if (e?.code === "CALL_EXCEPTION"){
23+
throw e;
24+
}
25+
errors.push(e)
26+
console.error("Provider error, falling back to next provider ", e)
2027
}
2128
}
29+
30+
throw errors[0];
2231
}
2332

2433
async destroy(): Promise<void> {

javascript/tokenscript-viewer/src/components/viewers/util/getSingleTokenMetadata.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export const getSingleTokenMetadata = async (chain: number, contract: string, to
2525
name: meta.title
2626
};
2727

28+
if (meta.contractType)
29+
selectedOrigin.tokenType = meta.contractType.toLowerCase();
30+
2831
if (selectedOrigin.tokenType !== "erc20") {
2932

3033
selectedOrigin = await discoveryAdapter.getTokenById(selectedOrigin, tokenId);

0 commit comments

Comments
 (0)