Browse Source

Getting records

master
Lev 2 years ago
parent
commit
f6f423117b
  1. 33
      bin/api.js
  2. 32
      build/_deploy.ts
  3. 43
      contracts/main.ts
  4. 105
      contracts/utils.ts
  5. 3
      package-lock.json
  6. 2
      package.json
  7. 5
      test/item.spec.ts

33
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}`)
})

32
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)

43
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<Cell> {
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();

105
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;
}
}

3
package-lock.json generated

@ -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",

2
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",

5
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);
});
});

Loading…
Cancel
Save