feat: first version
This commit is contained in:
1 parent
f907277998
commit
855b07f707
18 files changed
+7276
-1
No files matched your search
@@ -0,0 +1,6 @@
|
||||
import { CompilerConfig } from '@ton/blueprint';
|
||||
|
||||
export const compile: CompilerConfig = {
|
||||
lang: 'func',
|
||||
targets: ['contracts/universal_wallet_contract.fc'],
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
|
||||
|
||||
export type UniversalWalletContractConfig = {
|
||||
subwalletId: number,
|
||||
publicKey: Buffer,
|
||||
admin: Address
|
||||
};
|
||||
|
||||
export function universalWalletContractConfigToCell(config: UniversalWalletContractConfig): Cell {
|
||||
if (config.publicKey.length !== 32) throw new Error('Public key must be 32 bytes long');
|
||||
return beginCell()
|
||||
.storeUint(config.subwalletId, 32)
|
||||
.storeAddress(config.admin)
|
||||
.storeRef(
|
||||
beginCell()
|
||||
.storeBuffer(config.publicKey)
|
||||
.storeUint(0, 64)
|
||||
.storeUint(0, 1)
|
||||
.endCell()
|
||||
)
|
||||
.endCell();
|
||||
}
|
||||
|
||||
export class UniversalWalletContract implements Contract {
|
||||
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}
|
||||
|
||||
static createFromAddress(address: Address) {
|
||||
return new UniversalWalletContract(address);
|
||||
}
|
||||
|
||||
static createFromConfig(config: UniversalWalletContractConfig, code: Cell, workchain = 0) {
|
||||
const data = universalWalletContractConfigToCell(config);
|
||||
const init = { code, data };
|
||||
return new UniversalWalletContract(contractAddress(workchain, init), init);
|
||||
}
|
||||
|
||||
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
|
||||
await provider.internal(via, {
|
||||
value,
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
||||
body: beginCell()
|
||||
.endCell(),
|
||||
});
|
||||
}
|
||||
|
||||
async sendAdminProvideMessage(provider: ContractProvider, via: Sender, value: bigint, messages: {rawMsg: Cell, msgMode: number}[], queryId: number = 0) {
|
||||
let messagesPacked = beginCell();
|
||||
for (const {rawMsg, msgMode} of messages) {
|
||||
messagesPacked = messagesPacked.storeUint(msgMode, 8).storeRef(rawMsg);
|
||||
console.log(msgMode, rawMsg);
|
||||
}
|
||||
|
||||
await provider.internal(via, {
|
||||
value,
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
||||
body: beginCell()
|
||||
.storeUint(0xA173, 32)
|
||||
.storeUint(queryId, 64)
|
||||
.storeSlice(messagesPacked.endCell().beginParse())
|
||||
.endCell(),
|
||||
});
|
||||
}
|
||||
|
||||
async sendSignedRequest(provider: ContractProvider, via: Sender, value: bigint, signedRequest: Cell, query_id: number = 0) {
|
||||
await provider.internal(via, {
|
||||
value,
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
||||
body: beginCell()
|
||||
.storeUint(0xA174, 32)
|
||||
.storeSlice(signedRequest.beginParse())
|
||||
.endCell(),
|
||||
});
|
||||
}
|
||||
|
||||
async sendEditCode(provider: ContractProvider, via: Sender, value: bigint, newCode: Cell, newData: Cell) {
|
||||
await provider.internal(via, {
|
||||
value,
|
||||
sendMode: SendMode.PAY_GAS_SEPARATELY,
|
||||
body: beginCell()
|
||||
.storeUint(0xA183, 32)
|
||||
.storeUint(0, 64)
|
||||
.storeRef(newCode)
|
||||
.storeRef(newData)
|
||||
.endCell(),
|
||||
});
|
||||
}
|
||||
|
||||
async getWalletData(provider: ContractProvider) {
|
||||
let stack = (await provider.get('get_wallet_data', [])).stack;
|
||||
return {
|
||||
subwalletId: stack.readNumber(),
|
||||
admin: stack.readAddress()
|
||||
}
|
||||
}
|
||||
|
||||
async getMessageStates(provider: ContractProvider) {
|
||||
let stack = (await provider.get('get_message_states', [])).stack;
|
||||
return {
|
||||
publicKey: stack.readBigNumber(),
|
||||
stateCleaned: stack.readNumber(),
|
||||
oldQueries: stack.readCellOpt()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user