Let's say I create an account foo.near on NEAR with a single full access public key ed25519:publiccc which is matched to a private key ed25519:secrettt and stored locally in the .near-credentials/mainnet/foo.near.json file.
I can now send myself 1 NEAR using near send foo.near foo.near 1 to verify the keys work. If I modify the private key in that keypair at all, the send fails. Good.
Then I replace the public key in that keypair with a nonsense string like ed25519:giggle.
I can still send myself 1 NEAR using the same command. Why?
It appears the public key is totally irrelevant in this keypair. You can use a nonsense string or a public key that's supposed to have limited access only and the private key gives full access to transfer funds.
Good question! I had to do some digging. So let's start with what the CLI is doing behind the scenes. The code is found here:
exports.sendMoney = async function (options) {
await checkCredentials(options.sender, options.networkId, options.keyStore);
console.log(`Sending ${options.amount} NEAR to ${options.receiver} from ${options.sender}`);
const near = await connect(options);
const account = await near.account(options.sender);
const result = await account.sendMoney(options.receiver, utils.format.parseNearAmount(options.amount));
inspectResponse.prettyPrintResponse(result, options);
};
Essentially it connects to the network, creates an account object, and then calls the sendMoney method for that account object which is through near-api-js. The way it's instantiated is through near.account() which behind the scenes does the following (found here):
async account(accountId: string): Promise<Account> {
const account = new Account(this.connection, accountId);
return account;
}
This is just calling the class' constructor passing in the connection and account ID. Up until this point, we have no reference to the private or public key other than the connection which has a keystore indicating WHERE the keypair will be found. Ok moving on.
After the account class is initialized, the sendMoney method is called and the only action is a simple transfer with the amount. The code for this method can be seen here.
async sendMoney(receiverId: string, amount: BN): Promise<FinalExecutionOutcome> {
return this.signAndSendTransaction({
receiverId,
actions: [transfer(amount)]
});
}
You'll notice that it calls the signAndSendTransaction method that is part of the Account Class (found here).
async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise<FinalExecutionOutcome> {
let txHash, signedTx;
// TODO: TX_NONCE (different constants for different uses of exponentialBackoff?)
const result = await exponentialBackoff(TX_NONCE_RETRY_WAIT, TX_NONCE_RETRY_NUMBER, TX_NONCE_RETRY_WAIT_BACKOFF, async () => {
[txHash, signedTx] = await this.signTransaction(receiverId, actions);
const publicKey = signedTx.transaction.publicKey;
try {
return await this.connection.provider.sendTransaction(signedTx);
}
...
...
Out of this method, the important thing is this.signTransaction whose method is found here:
protected async signTransaction(receiverId: string, actions: Action[]): Promise<[Uint8Array, SignedTransaction]> {
const accessKeyInfo = await this.findAccessKey(receiverId, actions);
if (!accessKeyInfo) {
throw new TypedError(`Can not sign transactions for account ${this.accountId} on network ${this.connection.networkId}, no matching key pair exists for this account`, 'KeyNotFound');
}
const { accessKey } = accessKeyInfo;
const block = await this.connection.provider.block({ finality: 'final' });
const blockHash = block.header.hash;
const nonce = accessKey.nonce.add(new BN(1));
return await signTransaction(
receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId
);
}
In this method, there's a sneaky this.findAccessKey being done whose code can be found here.
async findAccessKey(receiverId: string, actions: Action[]): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> {
// TODO: Find matching access key based on transaction (i.e. receiverId and actions)
const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId);
if (!publicKey) {
throw new TypedError(`no matching key pair found in ${this.connection.signer}`, 'PublicKeyNotFound');
}
const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()];
if (cachedAccessKey !== undefined) {
return { publicKey, accessKey: cachedAccessKey };
}
try {
const rawAccessKey = await this.connection.provider.query<AccessKeyViewRaw>({
request_type: 'view_access_key',
account_id: this.accountId,
public_key: publicKey.toString(),
finality: 'optimistic'
});
// store nonce as BN to preserve precision on big number
const accessKey = {
...rawAccessKey,
nonce: new BN(rawAccessKey.nonce),
};
// this function can be called multiple times and retrieve the same access key
// this checks to see if the access key was already retrieved and cached while
// the above network call was in flight. To keep nonce values in line, we return
// the cached access key.
if (this.accessKeyByPublicKeyCache[publicKey.toString()]) {
return { publicKey, accessKey: this.accessKeyByPublicKeyCache[publicKey.toString()] };
}
this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey;
return { publicKey, accessKey };
} catch (e) {
if (e.type == 'AccessKeyDoesNotExist') {
return null;
}
throw e;
}
}
If you take a look at this code, you'll notice a really important line:
const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId);
What is this returning? The short answer is that if you're using the NEAR-CLI, the connection.signer comes from UnencryptedFileSystemKeyStore since your keys are stored in .near-credentials. Now let's look at this getPublicKey method found here:
async getPublicKey(accountId?: string, networkId?: string): Promise<PublicKey> {
const keyPair = await this.keyStore.getKey(networkId, accountId);
if (keyPair === null) {
return null;
}
return keyPair.getPublicKey();
}
It does this.keyStore.getKey where the keyStore is the UnencryptedFileSystemKeyStore I mentioned earlier. The getKey method is then found here:
async getKey(networkId: string, accountId: string): Promise<KeyPair> {
// Find key / account id.
if (!await exists(this.getKeyFilePath(networkId, accountId))) {
return null;
}
const accountKeyPair = await readKeyFile(this.getKeyFilePath(networkId, accountId));
return accountKeyPair[1];
}
In this function, the final culprit is the readKeyFile where it returns the key pair (both public and private). This can be found here:
export async function readKeyFile(filename: string): Promise<[string, KeyPair]> {
const accountInfo = await loadJsonFile(filename);
// The private key might be in private_key or secret_key field.
let privateKey = accountInfo.private_key;
if (!privateKey && accountInfo.secret_key) {
privateKey = accountInfo.secret_key;
}
return [accountInfo.account_id, KeyPair.fromString(privateKey)];
}
Notice that it returns a KeyPair that is generated from the private key. This means that no matter what the public key is, it will overwrite it. I did a quick test where I console logged the accountInfo and it indeed returned the incorrect public key that I had set manually but when it generates the KeyPair from the private key, that information is overwritten.
TLDR:
When the your account signs a transaction, the public key is re-generated based on the private key. It doesn't matter what public key you have in your .near-credentials since it is overwritten when the KeyPair is created based on the private key.
I'm trying to update the status of a job object. I get the "success" message return but the value is not updating. Do I miss something?
#nearBindgen
export class Contract {
private jobs: PersistentVector<Job> = new PersistentVector<Job>('jobs');
......
#mutateState()
cancelJob(jobTitle: string): string {
for (let i = 0; i < this.jobs.length; i++) {
if (this.jobs[i].title == jobTitle) {
this.jobs[i].status = "Cancelled";
return "success"
}
}
return "not found";
}
And I'm calling it like that:
near call apptwo.msaudi.testnet cancelJob '{\"jobTitle\":\"title2\"}' --account-id=msaudi.testnet
It’s not enough to update entry when you fetch it. You need to update the storage on the contract as well. Write it back in so to speak.
This isn’t enough
this.jobs[i].status = "Cancelled";
You need to add it back in:
if (this.jobs[i].title == jobTitle) {
const job: Job = this.jobs[i]; // Need an intermediate object in memory
job.status = "Cancelled";
this.jobs.replace(i, job); // Update storage with the new job.
return "success"
}
I'm trying to test the Chainlink alarm oracle following the documentation steps with hardhat on a kovan fork network.
https://docs.chain.link/docs/chainlink-alarm-clock/#:~:text=ala
When I test the contract with npx hardhat test it looks like it hits the request function to the oracle but never calling back the fulfill_alarm().
I wonder if that might be an issue with the fork network or with my testing code.
I'm using Alchemy api as url for the fork network on kovan.
Also, I'm using a timeout with mocha so it can wait for the callback to execute, but never hits.
The first it statement executes correctly, but then on the second one which is suposed to increment lotteryId + 1 once the lottery is done (fulfill_alarm() called) doesn't pass. It looks like the function never gets executed by the chainlink oracle.
oracle: 0xAA1DC356dc4B18f30C347798FD5379F3D77ABC5b
jobId: '982105d690504c5d9ce374d040c08654'
Solidity code:
//Starting Oracle Alarm
function start_new_lottery(uint256 duration) public {
require(lottery_state == LOTTERY_STATE.CLOSED, "can't start a n
lottery_state = LOTTERY_STATE.OPEN;
Chainlink.Request memory req = buildChainlinkRequest(jobId, add
req.addUint("until", block.timestamp + duration);
sendChainlinkRequestTo(oracle, req, oraclePayment);
console.log('LINK REQ SEND');
}
//Callback Function after Oracle Alarm is Fulfilled
function fulfill_alarm(bytes32 _requestId) public recordChainlinkFu
require(lottery_state == LOTTERY_STATE.OPEN, "The lottery hasn'
lotteryId = lotteryId + 1;
console.log('ALARM_FULFILLED');
pickWinner();
}
test code:
const {expect} = require("chai");
const { time } = require("#openzeppelin/test-helpers");
describe("Lottery Contract", function() {
//Defining global variables for testing.
const price_lottery = ethers.BigNumber.from(1);
let owner;
let Lottery;
let lottery;
before(async function () {
Lottery = await ethers.getContractFactory("Lottery");
[owner] = await ethers.getSigners();
lottery = await Lottery.deploy(
price_lottery,
);
//Starting new Lottery
await lottery.start_new_lottery(60);
});
it("should start a chainlink alarm to init new lottery", async func
let lottery_state = await lottery.lottery_state();
expect(lottery_state).to.equal(0);
//Initial value
let lotteryId = await lottery.lotteryId();
expect(lotteryId).to.equal(1);
})
it("increments lotteryID + 1, when chainlink alarm is fulfilled aft
//Wait until alarm is fulfilled
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await timeout(120000);
//Expected value
let lotteryId = await lottery.lotteryId();
expect(lotteryId).to.equal(2);
})
})
Hope doing well
I want to do the following activities in solidity code below but dont know how, please advise.
Thanks with regards
Syed
QUERY:_
Emit an event ScoreChanged with two arguments: int amount (equal to the value that was sent) and bool direction (true for up function and false for down)
Emit an event GameEnded after gameOver is switched to true
CODE:
pragma solidity ^0.4.17;
contract TugOfWar {
int public score = 0;
int constant endAt = 1 ether;
bool public gameOver = false;
function up() external payable {
require(msg.value > 0);
require(!gameOver);
int value = int(msg.value);
score += value;
checkIfGameOver();
}
function down() external payable {
require(msg.value > 0);
require(!gameOver);
int value = int(msg.value);
score -= value;
checkIfGameOver();
}
function checkIfGameOver() internal {
if(score >= endAt || score <= endAt * -1) {
gameOver = true;
}
}
}
You can define events in the contract by:
event ScoreChanged(uint amount, bool up);
event GameEnded(bool up); // where 'up' indicates which side won.
to emit the events you write:
emit ScoreChanged(msg.value, true); // where the second argument indicates the direction
you could to this right after you increment or decrement the score.
and when the game has ended:
emit GameEnded(true); // where the argument indicates which side has won.
I'm trying to scrape a website & put the value in cache so I don't hit the daily limit of UrlFetchApp
Here is the script I did:
/**
* Scrape URL, return whatever you choose with jquery-style selectors.
Dependency: cheeriogs, see https://github.com/fgborges/cheeriogs
*
* #param {url} valid start-url
* #return result (array values)
*
* #customfunction
*/
function scrapercache(url) {
var result = [];
var description;
var options = {
'muteHttpExceptions': true,
'followRedirects': false,
};
var cache = CacheService.getScriptCache();
var properties = PropertiesService.getScriptProperties();
try {
let res = cache.get(url);
if (!res) {
// trim url to prevent (rare) errors
url.toString().trim();
var r = UrlFetchApp.fetch(url, options);
var c = r.getResponseCode();
// check for meta refresh if 200 ok
if (c == 200) {
var html = r.getContentText();
cache.put(url, "cached", 21600);
properties.setProperty(url, html);
var $ = Cheerio.load(html); // make sure this lib is added to your project!
// meta description
if ($('meta[name=description]').attr("content")) {
description = $('meta[name=description]').attr("content").trim();
}
}
result.push([description]);
}
}
catch (error) {
result.push(error.toString());
}
finally {
return result;
}
}
but when I call the function like that:
=scrapercache("https://www.gurufocus.com/term/total_freecashflow/nyse:ABBV/Free-Cash-Flow")
I get the error message:
Exception: argument too large: value
Anyone can help me please?
Thank you :)
Gabriel
As written in the official documentation,
The maximum length of a key is 250 characters. The maximum amount of data that can be stored per key is 100KB.
If the size of the data put in cache exceeds any of the above limitations, the error
Exception: argument too large
is shown. In your case, value exceeds 100KB. Solution would be to cache only necessary data or don't cache at all depending on your specific needs.