Skip to content

Commit

Permalink
Merge pull request #18 from near/fix-and-keys
Browse files Browse the repository at this point in the history
Fix and keys
  • Loading branch information
gagdiez authored Jun 19, 2023
2 parents 5d5c16a + 91953c2 commit 8660afc
Show file tree
Hide file tree
Showing 17 changed files with 176 additions and 410 deletions.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "near-discovery-ide",
"displayName": "NEAR BOS IDE",
"description": "Build a decentralized frontend in minutes. Known before as NEAR Social / Discovery",
"version": "2.0.0",
"version": "2.1.2",
"publisher": "near-protocol",
"icon": "readme/near-protocol-near-logo.png",
"homepage": "https://github.com/near/near-vscode",
Expand Down Expand Up @@ -85,6 +85,11 @@
"view": "near-discovery",
"contents": "[Fetch Account Widgets](command:near.openWidgetsFromAccount)",
"when": "BOS.enabled"
},
{
"view": "near-discovery",
"contents": "[Add Access Key](command:near.addKey)",
"when": "BOS.enabled"
}
],
"views": {
Expand Down
36 changes: 36 additions & 0 deletions src/commands/add-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as vscode from 'vscode';
import { APP_NAME } from '../config';
import { KeyPair } from 'near-api-js';
import { addToContext, getFromContext } from '../extension';

export const addKeyForContract = async (context: vscode.ExtensionContext, localWorkspace: string) => {
const contractId = await vscode.window.showInputBox({ placeHolder: 'Which contract do you want to call?' });
const accountId = getFromContext(localWorkspace, "accountId");

if(!accountId){
return vscode.window.showErrorMessage('Please login first');
}

if (contractId) {
const publisher = context.extension.packageJSON.publisher;
const name = context.extension.packageJSON.name;
const callback = `${vscode.env.uriScheme}://${publisher}.${name}`;

// Create a private key to interact with the social contract
const keyPair = KeyPair.fromRandom("ED25519");
const publicKey = keyPair.getPublicKey().toString();

// Save the private access key in context.json
await addToContext(localWorkspace, 'accessKey', keyPair.toString());

context.globalState.update('addKeyForContract', true);

// Create the login URL and redirect the user to login
const networkId = await getFromContext(localWorkspace, 'networkId') || "mainnet";

let url = `https://wallet.${networkId}.near.org/login/?title=${APP_NAME}&success_url=${callback}&contract_id=${contractId}&public_key=${publicKey}&account_id=${accountId}`;
vscode.env.openExternal(vscode.Uri.parse(url));
} else {
vscode.window.showErrorMessage('Invalid Contract ID');
}
};
17 changes: 11 additions & 6 deletions src/commands/callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import * as vscode from 'vscode';
import { getTransactionStatus } from '../modules/social';
import { addToContext } from '../extension';
import { addToContext, getFromContext } from '../extension';

export const handleTransactionCallback = async (uri: vscode.Uri, context: vscode.ExtensionContext, localWorkspace: string | undefined) => {
const queryParams = new URLSearchParams(uri.query);
const networkId = await getFromContext(localWorkspace, 'networkId') || "mainnet";

// Transaction callback
if (queryParams.has('transactionHashes')) {
const tHash = queryParams.get('transactionHashes') as string;

const result = await getTransactionStatus(tHash);
const result = await getTransactionStatus(tHash, networkId);
const explorerURL = `https://explorer.near.org/transactions/${tHash}`;
const action = (selection?: string) => { selection ? vscode.env.openExternal(vscode.Uri.parse(explorerURL)) : ""; };

Expand All @@ -20,20 +21,24 @@ export const handleTransactionCallback = async (uri: vscode.Uri, context: vscode
vscode.window.showErrorMessage(`Error: ${result.error}`, "View in Explorer")
.then(action);
}

return;
}

// Passing an AccountID
if (queryParams.has('account_id')) {
const accountId = queryParams.get('account_id') as string;

await addToContext(localWorkspace, 'accountId', accountId);
await addToContext(localWorkspace, 'networkId', "mainnet");

if(context.globalState.get('addKeyForContract') === true){
context.globalState.update('addKeyForContract', false);
vscode.window.showInformationMessage(`Successfully added key for account ${accountId}`);
return;
}

if (localWorkspace) {
vscode.commands.executeCommand("near.openWidgetsFromAccount", accountId);
} else {
context.workspaceState.update('openAccount', accountId);
vscode.commands.executeCommand("near.chooseLocalPath");
}
}
};
7 changes: 5 additions & 2 deletions src/commands/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ import path from 'path';
import * as vscode from 'vscode';

import * as social from '../modules/social';
import { getFromContext } from '../extension';

export const openAccountWidgets = async (localWorkspace:string, accountId?: string) => {
accountId = accountId || await vscode.window.showInputBox({ placeHolder: 'Mainnet AccountId [e.g. alice.near]' });

const networkId = await getFromContext(localWorkspace, 'networkId') || "mainnet";

if (accountId) {
vscode.window.showInformationMessage(`Loading widgets for: ${accountId}`);

const widgetNames = await social.getWidgetsNames(accountId);
const widgetNames = await social.getWidgetsNames(accountId, networkId);

if (!widgetNames.length) {
return vscode.window.showErrorMessage('No widgets found');
Expand All @@ -28,7 +31,7 @@ export const openAccountWidgets = async (localWorkspace:string, accountId?: stri
vscode.workspace.fs.createDirectory(vscode.Uri.parse(dir));
}

const widgetCode = await social.getWidgetCode(accountId, name);
const widgetCode = await social.getWidgetCode(accountId, name, networkId);
vscode.workspace.fs.writeFile(vscode.Uri.parse(path.join(dir, `${file[0]}.jsx`)), Buffer.from(widgetCode));
}
} else {
Expand Down
12 changes: 7 additions & 5 deletions src/commands/login.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import * as vscode from 'vscode';
import { APP_NAME } from '../config';
import { APP_NAME, contractAccountForNetwork } from '../config';
import { KeyPair } from 'near-api-js';
import { addToContext } from '../extension';
import { addToContext, getFromContext } from '../extension';

export const loginAccount = async (context: vscode.ExtensionContext, network: string, localWorkspace: string) => {
export const loginAccount = async (context: vscode.ExtensionContext, localWorkspace: string) => {
const publisher = context.extension.packageJSON.publisher;
const name = context.extension.packageJSON.name;
const callback = `${vscode.env.uriScheme}://${publisher}.${name}`;

// Create a private key to interact with the social contract
const contractId = "social.near";
const keyPair = KeyPair.fromRandom("ED25519");
const publicKey = keyPair.getPublicKey().toString();

// Save the private access key in context.json
await addToContext(localWorkspace, 'accessKey', keyPair.toString());

const networkId = await getFromContext(localWorkspace, 'networkId') || "mainnet";
const contractId = contractAccountForNetwork(networkId);

// Create the login URL and redirect the user to login
let url = `https://wallet.${network}.near.org/login/?title=${APP_NAME}&success_url=${callback}&contract_id=${contractId}&public_key=${publicKey}`;
let url = `https://wallet.${networkId}.near.org/login/?title=${APP_NAME}&success_url=${callback}&contract_id=${contractId}&public_key=${publicKey}&methodNames=set`;
vscode.env.openExternal(vscode.Uri.parse(url));
};
9 changes: 6 additions & 3 deletions src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ import * as vscode from 'vscode';
import { WIDGET_EXT } from '../config';
import { transactionForPublishingCode } from '../modules/social';
import path from 'path';
import { getFromContext } from '../extension';

export const publishCode = async (context: vscode.ExtensionContext, network:string, localWorkspace: string) => {
export const publishCode = async (context: vscode.ExtensionContext, localWorkspace: string) => {
// This will be called from an active panel
const code: string = vscode.window.activeTextEditor?.document?.getText() || "";
const uri: string = vscode.window.activeTextEditor?.document?.uri.path.toString() || "";
const networkId = await getFromContext(localWorkspace, 'networkId') || "mainnet";

const [accountId, ...widgetName] = path.relative(localWorkspace, uri).split('/');
let transaction = await transactionForPublishingCode(accountId, widgetName.join('.').replace(WIDGET_EXT, ''), code);
let transaction = await transactionForPublishingCode(accountId, widgetName.join('.').replace(WIDGET_EXT, ''), code, networkId);

const publisher = context.extension.packageJSON.publisher;
const name = context.extension.packageJSON.name;
const callback = `${vscode.env.uriScheme}://${publisher}.${name}`;

const publishUrl = new URL('sign', 'https://wallet.' + network + '.near.org/');

const publishUrl = new URL('sign', 'https://wallet.' + networkId + '.near.org/');
publishUrl.searchParams.set('transactions', transaction);
publishUrl.searchParams.set('callbackUrl', callback);

Expand Down
2 changes: 1 addition & 1 deletion src/commands/start-ide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const startIDE = async (localWorkspace: string) => {
vscode.commands.executeCommand('setContext', 'BOS.enabled', true);

const files = ["props.json", "context.json", "flags.json"];
const defaultValues = ["{}", JSON.stringify(defaultContext), "{}"];
const defaultValues = ["{}", JSON.stringify(defaultContext), `{"components":{}}`];

for (let i = 0; i < files.length; i++) {
const file = path.join(localWorkspace, files[i]);
Expand Down
13 changes: 11 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@ export const WIDGET_EXT = `.jsx`;
export const APP_NAME = 'vscode social';
export const COST_PER_BYTE = new BN("10000000000000000000");
export const DATA_OVERHEAD = 336; // TODO: Compute better
export const TGAS30 = new BN("30"+"0".repeat(12));
export const TGAS30 = new BN("30" + "0".repeat(12));

export function contractAccountForNetwork(network: string) {
return network === "mainnet" ? "social.near" : "v1.social08.testnet";
}

export function networkRPC(network: string) {
return network === "mainnet" ? "https://rpc.near.org" : "https://rpc.testnet.near.org";
}

export const defaultContext = {
wrapperSrc: "near/widget/DIG.Theme",
wrapperProps: {}
wrapperProps: {},
networkId: "mainnet"
};
21 changes: 19 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { WidgetPreviewPanel } from "./modules/preview-panel";
import { preview } from "./commands/preview";
import { startIDE } from "./commands/start-ide";
import { updateAllFlags, updateFlags } from "./flags";
import { addKeyForContract } from "./commands/add-key";

let localWorkspace: string = "";
const FS = vscode.workspace.fs;
Expand Down Expand Up @@ -48,7 +49,14 @@ export function activate(context: vscode.ExtensionContext) {
// Login Account
context.subscriptions.push(
vscode.commands.registerCommand("near.login", () =>
loginAccount(context, 'mainnet', localWorkspace)
loginAccount(context, localWorkspace)
)
);

// Login Account
context.subscriptions.push(
vscode.commands.registerCommand("near.addKey", () =>
addKeyForContract(context, localWorkspace)
)
);

Expand All @@ -63,7 +71,7 @@ export function activate(context: vscode.ExtensionContext) {
// Publish Code
context.subscriptions.push(
vscode.commands.registerCommand("near.publishWidget", () =>
publishCode(context, 'mainnet', localWorkspace)
publishCode(context, localWorkspace)
)
);

Expand Down Expand Up @@ -100,4 +108,13 @@ export async function addToContext(localWorkspace: string | undefined, key: stri
let contextData = JSON.parse(data?.toString() || "{}");
contextData[key] = value;
await FS.writeFile(contextUri, Buffer.from(JSON.stringify(contextData, null, 2)));
}

export async function getFromContext(localWorkspace: string | undefined, key: string): Promise<string | undefined> {
if (!localWorkspace) { return; }

const contextUri = vscode.Uri.parse(path.join(localWorkspace, `context.json`));
let data = await FS.readFile(contextUri);
let contextData = JSON.parse(data?.toString() || "{}");
return key in contextData? contextData[key] : undefined;
}
2 changes: 2 additions & 0 deletions src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export async function updateFlags(localWorkspace: string, uri: vscode.Uri, del:
let data = await FS.readFile(flagsUri);
let flagsData = JSON.parse(data?.toString() || `{"components": {}}`);

if (!("components" in flagsData)) { flagsData["components"] = {}; }

const [accountId, ...widgetName] = path.relative(localWorkspace, uri.path).split('/');

const socialPath = uriToSocialPath(accountId, widgetName);
Expand Down
30 changes: 17 additions & 13 deletions src/modules/social.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { providers, transactions } from "near-api-js";
import { window } from "vscode";
import { COST_PER_BYTE, DATA_OVERHEAD, SOCIAL_CONTRACT_ACCOUNT, TGAS30 } from "../config";
import { COST_PER_BYTE, DATA_OVERHEAD, SOCIAL_CONTRACT_ACCOUNT, TGAS30, contractAccountForNetwork, networkRPC } from "../config";
import BN from "bn.js";
import * as naj from "near-api-js";
import { FinalExecutionOutcome } from "near-api-js/lib/providers";

const provider = new providers.JsonRpcProvider({ url: "https://rpc.near.org" });

export const getWidgetsNames = async (accountId: AccountId): Promise<string[]> => {
export const getWidgetsNames = async (accountId: AccountId, networkId: string): Promise<string[]> => {
const args = { keys: [`${accountId}/widget/*`] };
let result = await socialViewMethod('keys', args);
let result = await socialViewMethod('keys', args, networkId);
let retObj = JSON.parse(Buffer.from(result.result).toString());
try {
return Object.keys(retObj[accountId]["widget"]);
Expand All @@ -19,9 +17,9 @@ export const getWidgetsNames = async (accountId: AccountId): Promise<string[]> =
};
};

export const getWidgetCode = async (accountId: AccountId, widgetName: string): Promise<string> => {
export const getWidgetCode = async (accountId: AccountId, widgetName: string, networkId: string): Promise<string> => {
const args = { keys: [`${accountId}/widget/${widgetName}`] };
let result = await socialViewMethod('get', args);
let result = await socialViewMethod('get', args, networkId);
let retObj = JSON.parse(Buffer.from(result.result).toString());

try {
Expand All @@ -32,16 +30,18 @@ export const getWidgetCode = async (accountId: AccountId, widgetName: string): P
};
};

export const transactionForPublishingCode = async (accountId: AccountId, widgetName: string, code: string): Promise<string> => {
export const transactionForPublishingCode = async (accountId: AccountId, widgetName: string, code: string, networkId: string): Promise<string> => {
// Data to store
const update = `{"${accountId}": {"widget": {"${widgetName}": {"": ${JSON.stringify(code)}}}}}`;
const data = { data: JSON.parse(update) };
const contractId = contractAccountForNetwork(networkId);

// To create a transaction, we need to fill the `publicKey` field, but that field is not used later
const keyPair = naj.utils.KeyPairEd25519.fromRandom();
const publicKey = keyPair.getPublicKey();

// To create a transaction we need a recent block
const provider = new providers.JsonRpcProvider({ url: networkRPC(networkId) });
const block = await provider.block({ finality: 'final' });
const blockHash = naj.utils.serialize.base_decode(block.header.hash);

Expand All @@ -51,17 +51,21 @@ export const transactionForPublishingCode = async (accountId: AccountId, widgetN

// Create the transaction
const actions = [transactions.functionCall('set', data, TGAS30, amount)];
const transaction = transactions.createTransaction(accountId, publicKey, SOCIAL_CONTRACT_ACCOUNT, 0, actions, blockHash);
const transaction = transactions.createTransaction(accountId, publicKey, contractId, 0, actions, blockHash);

//@ts-ignore
return transaction.encode().toString('base64');
};

// RPC Call
export const socialViewMethod = async (methodName: String, args: any): Promise<any> => {
export const socialViewMethod = async (methodName: String, args: any, networkId: string): Promise<any> => {

const contractId = contractAccountForNetwork(networkId);
const provider = new providers.JsonRpcProvider({ url: networkRPC(networkId) });

const promise = provider.query({
request_type: 'call_function',
account_id: SOCIAL_CONTRACT_ACCOUNT,
account_id: contractId,
method_name: methodName,
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
finality: 'optimistic',
Expand All @@ -74,13 +78,13 @@ export const socialViewMethod = async (methodName: String, args: any): Promise<a
return promise;
};

export const getTransactionStatus = async (txhash: string): Promise<TxStatus> => {
export const getTransactionStatus = async (txhash: string, networkId: string): Promise<TxStatus> => {

// Retrieve transaction result from the network
const provider = new providers.JsonRpcProvider({ url: networkRPC(networkId) });
const transaction = await provider.txStatus(txhash, 'unnused');

let status = new TxStatus();
console.log(transaction.status as object)
status.succeeded = Object.hasOwn(transaction.status as object, "SuccessValue");

if (!status.succeeded) {
Expand Down
9 changes: 0 additions & 9 deletions webview/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@
"homepage": "/",
"private": true,
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"@near-wallet-selector/core": "^7.9.0",
"@near-wallet-selector/here-wallet": "^7.9.0",
"@near-wallet-selector/meteor-wallet": "^7.9.0",
"@near-wallet-selector/modal-ui": "^7.9.0",
"@near-wallet-selector/my-near-wallet": "^7.9.0",
"@near-wallet-selector/near-wallet": "^7.9.0",
"@near-wallet-selector/neth": "^7.9.0",
"@near-wallet-selector/sender": "^7.9.0",
"big.js": "^6.1.1",
"bn.js": "^5.1.1",
"bootstrap": "^5.2.1",
Expand Down
Loading

0 comments on commit 8660afc

Please sign in to comment.