140 lines
5.0 KiB
TypeScript
140 lines
5.0 KiB
TypeScript
import { sleep, NetworkProvider, UIProvider} from '@ton/blueprint';
|
|
import { Address, beginCell, Builder, Cell, Dictionary, DictionaryValue, Slice } from "@ton/core";
|
|
import { sha256 } from 'ton-crypto';
|
|
|
|
export const defaultJettonKeys = ["uri", "name", "description", "image", "image_data", "symbol", "decimals", "amount_style"];
|
|
export const defaultNftKeys = ["uri", "name", "description", "image", "image_data"];
|
|
|
|
export const promptBool = async (prompt:string, options:[string, string], ui:UIProvider, choice: boolean = false) => {
|
|
let yes = false;
|
|
let no = false;
|
|
let opts = options.map(o => o.toLowerCase());
|
|
|
|
do {
|
|
let res = (choice ? await ui.choose(prompt, options, (c: string) => c) : await ui.input(`${prompt}(${options[0]}/${options[1]})`)).toLowerCase();
|
|
yes = res == opts[0]
|
|
if(!yes)
|
|
no = res == opts[1];
|
|
} while(!(yes || no));
|
|
|
|
return yes;
|
|
}
|
|
|
|
export const promptAddress = async (prompt:string, provider:UIProvider, fallback?:Address) => {
|
|
let promptFinal = fallback ? prompt.replace(/:$/,'') + `(default:${fallback}):` : prompt ;
|
|
do {
|
|
let testAddr = (await provider.input(promptFinal)).replace(/^\s+|\s+$/g,'');
|
|
try{
|
|
return testAddr == "" && fallback ? fallback : Address.parse(testAddr);
|
|
}
|
|
catch(e) {
|
|
provider.write(testAddr + " is not valid!\n");
|
|
prompt = "Please try again:";
|
|
}
|
|
} while(true);
|
|
|
|
};
|
|
|
|
export const promptAmount = async (prompt:string, provider:UIProvider) => {
|
|
let resAmount:number;
|
|
do {
|
|
let inputAmount = await provider.input(prompt);
|
|
resAmount = Number(inputAmount);
|
|
if(isNaN(resAmount)) {
|
|
provider.write("Failed to convert " + inputAmount + " to float number");
|
|
}
|
|
else {
|
|
return resAmount.toFixed(9);
|
|
}
|
|
} while(true);
|
|
}
|
|
|
|
|
|
const keysToHashMap = async (keys: string[]) => {
|
|
let keyMap: {[key: string]: bigint} = {};
|
|
for (let i = 0; i < keys.length; i++) {
|
|
keyMap[keys[i]] = BigInt("0x" + (await sha256(keys[i])).toString('hex'));
|
|
}
|
|
}
|
|
|
|
const contentValue: DictionaryValue<string> = {
|
|
serialize: (src: string, builder:Builder) => {
|
|
builder.storeRef(beginCell().storeUint(0, 8).storeStringTail(src).endCell());
|
|
},
|
|
parse: (src: Slice) => {
|
|
const sc = src.loadRef().beginParse();
|
|
const prefix = sc.loadUint(8);
|
|
if(prefix == 0) {
|
|
return sc.loadStringTail();
|
|
}
|
|
else if(prefix == 1) {
|
|
// Not really tested, but feels like it should work
|
|
const chunkDict = Dictionary.loadDirect(Dictionary.Keys.Uint(32), Dictionary.Values.Cell(), sc);
|
|
return chunkDict.values().map(x => x.beginParse().loadStringTail()).join('');
|
|
}
|
|
else {
|
|
throw(Error(`Prefix ${prefix} is not supported yet`));
|
|
}
|
|
}
|
|
};
|
|
export const displayContentCell = async (content:Cell, ui:UIProvider, jetton:boolean = true, additional?: string[]) => {
|
|
const cs = content.beginParse();
|
|
const contentType = cs.loadUint(8);
|
|
if(contentType == 1) {
|
|
const noData = cs.remainingBits == 0;
|
|
if(noData && cs.remainingRefs == 0) {
|
|
ui.write("No data in content cell!\n");
|
|
}
|
|
else {
|
|
const contentUrl = noData ? cs.loadStringRefTail() : cs.loadStringTail();
|
|
ui.write(`Content metadata url:${contentUrl}\n`);
|
|
}
|
|
}
|
|
else if(contentType == 0) {
|
|
let contentKeys: string[];
|
|
const hasAdditional = additional !== undefined && additional.length > 0;
|
|
const contentDict = Dictionary.load(Dictionary.Keys.BigUint(256), contentValue, cs);
|
|
const contentMap : {[key: string]: string} = {};
|
|
|
|
if(jetton) {
|
|
contentKeys = hasAdditional ? [...defaultJettonKeys, ...additional] : defaultJettonKeys;
|
|
}
|
|
else {
|
|
contentKeys = hasAdditional ? [...defaultNftKeys, ...additional] : defaultNftKeys;
|
|
}
|
|
for (const name of contentKeys) {
|
|
// I know we should pre-compute hashed keys for known values... just not today.
|
|
const dictKey = BigInt("0x" + (await sha256(name)).toString('hex'))
|
|
const dictValue = contentDict.get(dictKey);
|
|
if(dictValue !== undefined) {
|
|
contentMap[name] = dictValue;
|
|
}
|
|
}
|
|
ui.write(`Content:${JSON.stringify(contentMap,null, 2)}`);
|
|
}
|
|
else {
|
|
ui.write(`Unknown content format indicator:${contentType}\n`);
|
|
}
|
|
}
|
|
|
|
export const promptUrl = async(prompt:string, ui:UIProvider) => {
|
|
let retry = false;
|
|
let input = "";
|
|
let res = "";
|
|
|
|
do {
|
|
input = await ui.input(prompt);
|
|
try{
|
|
let testUrl = new URL(input);
|
|
res = testUrl.toString();
|
|
retry = false;
|
|
}
|
|
catch(e) {
|
|
ui.write(input + " doesn't look like a valid url:\n" + e);
|
|
retry = !(await promptBool('Use anyway?(y/n)', ['y', 'n'], ui));
|
|
}
|
|
} while(retry);
|
|
return input;
|
|
}
|
|
|