From f6f423117b39fa43554df410806277ad354f2251 Mon Sep 17 00:00:00 2001 From: ennucore Date: Sun, 22 Jan 2023 10:42:15 +0100 Subject: [PATCH] Getting records --- bin/api.js | 33 +++++++++++--- build/_deploy.ts | 32 ++++++++------ contracts/main.ts | 43 ++++++++++++++----- contracts/utils.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 3 +- package.json | 2 +- test/item.spec.ts | 5 ++- 7 files changed, 190 insertions(+), 33 deletions(-) diff --git a/bin/api.js b/bin/api.js index 0ac4aa3..c269e3e 100644 --- a/bin/api.js +++ b/bin/api.js @@ -1,14 +1,22 @@ const main = require('../contracts/main'); -const subcommand = require('subcommand'); -const {Address} = require("ton"); -const {keyPairFromSeed, getSecureRandomBytes, keyPairFromSecretKey} = require("ton-crypto"); +const {Address, Cell, Slice} = require("ton"); const {Base64} = require('@tonconnect/protocol'); const BN = require("bn.js"); const express = require('express') +const {get_tonclient} = require("../contracts/utils"); +const {sha256} = require("ton-crypto"); +const {getRecords} = require("../contracts/main"); const app = express() const port = 5171 +const debug = process.env.DEBUG === '1'; +let tonclient = get_tonclient(debug); +app.use(function(err, req, res, next) { + if(!err) return next(); // you also need this line + console.log("error"); + res.send("error"); +}); app.get('/', (req, res) => { res.send('Agorata microservice for dealing with TON') }) @@ -27,12 +35,27 @@ app.get('/buy-message/:collection/:domain' /* the rest as get parameters: buyer, } }); -app.get('/content-message/:address/:zone/:domain', (req, res) => { - let msg = main.setContent({domain: req.params.domain, zone: req.params.zone}); +app.get('/content-message/:address/:zone/:domain', async (req, res) => { + let msg = await main.setContent({domain: req.params.domain, zone: req.params.zone}); let addr = main.getItemAddr(Address.parse(req.params.address), req.params.domain, 0); res.send(JSON.stringify([Base64.encode(msg.toBoc()), addr.toString()])); }); +app.get('/address/:collection/:domain', (req, res) => { + let addr = main.getItemAddr(Address.parse(req.params.collection), req.params.domain, 0); + res.send(JSON.stringify({"address": addr.toString()})); +}) + +app.get('/get-records/:address', async (req, res) => { + try { + let records = await getRecords(tonclient, Address.parse(req.params.address)); + res.send(JSON.stringify(records)); + } catch (e) { + console.log(e); + res.send(JSON.stringify({})); + } +}) + app.listen(port, () => { console.log(`Example app listening on port ${port}`) }) diff --git a/build/_deploy.ts b/build/_deploy.ts index 38348b7..05f5e52 100644 --- a/build/_deploy.ts +++ b/build/_deploy.ts @@ -6,19 +6,30 @@ import axios from "axios"; import axiosThrottle from "axios-request-throttle"; - -axiosThrottle.use(axios, {requestsPerSecond: 0.5}); // required since toncenter jsonRPC limits to 1 req/sec without API key - import dotenv from "dotenv"; - -dotenv.config(); - import fs from "fs"; import path from "path"; import glob from "fast-glob"; -import {Address, Cell, CellMessage, CommonMessageInfo, fromNano, InternalMessage, StateInit, toNano} from "ton"; -import {TonClient, WalletContract, WalletV3R2Source, contractAddress, SendMode} from "ton"; +import { + Address, + Cell, + CellMessage, + CommonMessageInfo, + contractAddress, + fromNano, + InternalMessage, + SendMode, + StateInit, + toNano, + WalletContract, + WalletV3R2Source +} from "ton"; import {mnemonicNew, mnemonicToWalletKey} from "ton-crypto"; +import {get_tonclient} from "../contracts/utils"; + +axiosThrottle.use(axios, {requestsPerSecond: 0.5}); // required since toncenter jsonRPC limits to 1 req/sec without API key + +dotenv.config(); async function main() { console.log(`=================================================================`); @@ -34,10 +45,7 @@ async function main() { } // initialize globals - const client = new TonClient({ - endpoint: `https://${isTestnet ? "testnet." : ""}toncenter.com/api/v2/jsonRPC`, - apiKey: isTestnet ? 'bed2b4589201ff3c575be653593f912a337c08eed416b60b02345763b9ee9c36' : 'a1e8a1055a387515158589dc7e9bad3035e7db2b9f9ea5cdad6b727f71e328db' - }); + const client = get_tonclient(isTestnet); const deployerWalletType = "org.ton.wallets.v3.r2"; // also see WalletV3R2Source class used below const newContractFunding = toNano(0.02); // this will be (almost in full) the balance of a new deployed contract and allow it to pay rent const workchain = 0; // normally 0, only special contracts should be deployed to masterchain (-1) diff --git a/contracts/main.ts b/contracts/main.ts index dd26109..141bf5a 100644 --- a/contracts/main.ts +++ b/contracts/main.ts @@ -1,9 +1,9 @@ import BN from "bn.js"; -import {Cell, beginCell, Address} from "ton"; +import {Cell, beginCell, Address, Slice, TonClient} from "ton"; import {SmartContract} from "ton-contract-executor"; -import {encodeOffChainContent, makeSnakeCell} from "./utils"; +import {AdnlAddress, encodeOffChainContent, encodeSemiChainContent, makeSnakeCell} from "./utils"; import {randomBytes} from "crypto"; -import {keyPairFromSeed, KeyPair, sign, keyPairFromSecretKey} from "ton-crypto"; +import {keyPairFromSeed, KeyPair, sign, keyPairFromSecretKey, sha256} from "ton-crypto"; import { hex as item_code } from "../build/nft-item.compiled.json"; import {randomAddress} from "../test/helpers"; import {hashCell} from "ton/dist/boc/boc"; @@ -83,10 +83,28 @@ export function itemData(params: { .storeUint(0, 64).endCell(); } -export function auctionWithWinner(winnerAddress: Address) { - return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64) +export async function getRecords(tonclient: TonClient, address: Address) { + let ans_wallet = (await tonclient.callGetMethod(address, 'dnsresolve', + [["tvm.Slice", "te6cckEBAQEAAwAAAgDTZ9xB"], + ["num", new BN(await sha256('wallet')).toString()]])).stack[1]; + let wallet = null; + if (ans_wallet[1].bytes !== undefined) { + wallet = Cell.fromBoc(Buffer.from(ans_wallet[1].bytes, 'base64'))[0]; + } + let ans_site = (await tonclient.callGetMethod(address, 'dnsresolve', + [["tvm.Slice", "te6cckEBAQEAAwAAAgDTZ9xB"], + ["num", new BN(await sha256('site')).toString()]])).stack[1]; + let site = null; + if (ans_site[1].bytes !== undefined) { + let site_data = ans_site[1].bytes; // object.data.b64; + // print site_data converted from base64 + let c = Cell.fromBoc(Buffer.from(site_data, 'base64'))[0]; + site = new AdnlAddress(c.bits.buffer.subarray(2, 2 + 32)).toHex(); + } + return {wallet, site}; } + export function setContractBalance(contract: SmartContract, balance: number, address?: Address) { if (address == undefined) { address = randomAddress("collection"); @@ -164,18 +182,23 @@ export function initializeItemMsg(params: { domain: String, ownerAddr: Address } .endCell(); } -function itemContent(domain: string, zone: string): Cell { - return encodeOffChainContent(`https://api.agorata.io/data/${zone}/${domain}.json`) +async function itemContent(domain: string, zone: string): Promise { + return await encodeSemiChainContent(`https://api.agorata.io/data/${zone}/${domain}.json`) } -export function setContent(params: { domain: string, zone: string }) { +export async function setContent(params: { domain: string, zone: string }) { return beginCell() - .storeUint(0x1a0b9d51 , 32) + .storeUint(0x1a0b9d51, 32) .storeUint(0, 64) - .storeRef(itemContent(params.domain, params.zone)) + .storeRef(await itemContent(params.domain, params.zone)) .endCell(); } +export function loadRecords(map_cell: Cell) { + let map_sl = map_cell.beginParse(); + map_sl +} + export function instantBuySignature(receiverAddress: Address, issuedCollectionAddr: Address, amount: number, domain: Cell, privateKey: Buffer): Buffer { let messageToSign = beginCell().storeAddress(receiverAddress).storeAddress(issuedCollectionAddr).storeUint(amount, 256).storeRef(domain).endCell(); let hash = messageToSign.hash(); diff --git a/contracts/utils.ts b/contracts/utils.ts index be49817..0b9dfd4 100644 --- a/contracts/utils.ts +++ b/contracts/utils.ts @@ -1,11 +1,13 @@ -import {Cell} from "ton"; +import {beginCell, beginDict, BitString, Cell, DictBuilder, TonClient} from "ton"; +import {sha256} from "ton-crypto"; // from https://github.com/getgems-io/nft-contracts/blob/main/packages/nft-content/nftContent.ts const OFF_CHAIN_CONTENT_PREFIX = 0x01 +const ON_CHAIN_CONTENT_PREFIX = 0x00 export function flattenSnakeCell(cell: Cell) { - let c: Cell|null = cell + let c: Cell | null = cell let res = Buffer.alloc(0) @@ -55,6 +57,15 @@ export function encodeOffChainContent(content: string) { return makeSnakeCell(data) } +export async function encodeSemiChainContent(uri: string) { + let db = beginDict(256); + db.storeCell(await sha256("uri"), beginCell().storeBuffer(Buffer.from(uri)).endCell()); + let data = db.endDict()?.toBoc() as Buffer; + let offChainPrefix = Buffer.from([ON_CHAIN_CONTENT_PREFIX]) + data = Buffer.concat([offChainPrefix, data]) + return makeSnakeCell(data) +} + export function decodeOffChainContent(content: Cell) { let data = flattenSnakeCell(content) @@ -63,4 +74,94 @@ export function decodeOffChainContent(content: Cell) { throw new Error(`Unknown content prefix: ${prefix.toString(16)}`) } return data.slice(1).toString() +} + +export function get_tonclient(isTestnet: boolean) { + return new TonClient({ + endpoint: `https://${isTestnet ? "testnet." : ""}toncenter.com/api/v2/jsonRPC`, + apiKey: isTestnet ? 'bed2b4589201ff3c575be653593f912a337c08eed416b60b02345763b9ee9c36' : 'a1e8a1055a387515158589dc7e9bad3035e7db2b9f9ea5cdad6b727f71e328db' + }); +} +// look up tables +const to_hex_array: string[] = []; +const to_byte_map: any = {}; +for (let ord = 0; ord <= 0xff; ord++) { + let s = ord.toString(16); + if (s.length < 2) { + s = "0" + s; + } + to_hex_array.push(s); + to_byte_map[s] = ord; +} + +/** + * @param buffer {Uint8Array} + * @return {string} + */ +function bytesToHex(buffer: Uint8Array) { + const hex_array = []; + //(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) }); + for (let i = 0; i < buffer.byteLength; i++) { + hex_array.push(to_hex_array[buffer[i]]); + } + return hex_array.join(""); +} + +// reverse conversion using lookups +/** + * @param s {string} + * @return {Uint8Array} + */ +function hexToBytes(s: string) { + s = s.toLowerCase(); + const length2 = s.length; + if (length2 % 2 !== 0) { + throw "hex string must have length a multiple of 2"; + } + const length = length2 / 2; + const result = new Uint8Array(length); + for (let i = 0; i < length; i++) { + const i2 = i * 2; + const b = s.substring(i2, i2 + 2); + if (!to_byte_map.hasOwnProperty(b)) throw new Error('invalid hex character ' + b); + result[i] = to_byte_map[b]; + } + return result; +} + +export class AdnlAddress { + bytes: Uint8Array; + + /** + * @param anyForm {string | Uint8Array | AdnlAddress} + */ + constructor(anyForm: AdnlAddress | string | Uint8Array | null) { + if (anyForm == null) { + throw "Invalid address"; + } + + if (anyForm instanceof AdnlAddress) { + this.bytes = anyForm.bytes; + } else if (anyForm instanceof Uint8Array) { + if (anyForm.length !== 32) { + throw new Error('invalid adnl bytes length'); + } + this.bytes = anyForm; + } else if (typeof anyForm === 'string') { + if (anyForm.length !== 64) { + throw new Error('invalid adnl hex length'); + } + this.bytes = hexToBytes(anyForm); + } else { + throw new Error('unsupported type') + } + } + + toHex() { + let hex = bytesToHex(this.bytes); + while (hex.length < 64) { + hex = '0' + hex; + } + return hex; + } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 98d8679..be0ca32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "agorata-contracts", "version": "0.0.0", - "hasInstallScript": true, "license": "MIT", "dependencies": { "@tonconnect/protocol": "^2.0.1", @@ -18,7 +17,7 @@ }, "devDependencies": { "@swc/core": "^1.3.25", - "@types/bn.js": "^5.1.0", + "@types/bn.js": "^5.1.1", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", "@types/semver": "^7.3.9", diff --git a/package.json b/package.json index 537fbee..cd8f0a5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@swc/core": "^1.3.25", - "@types/bn.js": "^5.1.0", + "@types/bn.js": "^5.1.1", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", "@types/semver": "^7.3.9", diff --git a/test/item.spec.ts b/test/item.spec.ts index c448a11..a64281c 100644 --- a/test/item.spec.ts +++ b/test/item.spec.ts @@ -46,7 +46,7 @@ describe("Creating items tests", () => { expect(res.exit_code).to.equal(0); const setDataMsg = internalMessage({ from: ownerAddr, - body: main.setContent({domain: "test", zone: "example.ton"}), + body: await main.setContent({domain: "test", zone: "example.ton"}), value: new BN(0), }) const res2 = await contract.sendInternalMessage(setDataMsg); @@ -54,5 +54,8 @@ describe("Creating items tests", () => { expect(res2.exit_code).to.equal(0); let nft_data = await contract.invokeGetMethod("get_nft_data", []); console.log(nft_data.result); + // @ts-ignore + let content = (nft_data.result[4] as Slice); + }); });