Comparing version 0.1.1 to 0.2.0
@@ -0,1 +1,4 @@ | ||
const bs58 = require('bs58'); | ||
const { CreateAccountTransaction, SignedTransaction } = require('./protos'); | ||
const KeyPair = require('./signing/key_pair'); | ||
@@ -18,12 +21,28 @@ | ||
*/ | ||
async createAccount (newAccountId, publicKey, amount, originatorAccountId) { | ||
const createAccountParams = { | ||
originator: originatorAccountId, | ||
new_account_id: newAccountId, | ||
amount: amount, | ||
public_key: publicKey, | ||
}; | ||
async createAccount (newAccountId, publicKey, amount, originator) { | ||
const nonce = await this.nearClient.getNonce(originator); | ||
publicKey = bs58.decode(publicKey); | ||
const createAccount = CreateAccountTransaction.create({ | ||
originator, | ||
newAccountId, | ||
amount, | ||
publicKey, | ||
}); | ||
// Integers with value of 0 must be omitted | ||
// https://github.com/dcodeIO/protobuf.js/issues/1138 | ||
if (nonce !== 0) { | ||
createAccount.nonce = nonce; | ||
} | ||
const transactionResponse = await this.nearClient.submitTransaction('create_account', createAccountParams); | ||
return transactionResponse; | ||
const buffer = CreateAccountTransaction.encode(createAccount).finish(); | ||
const signature = await this.nearClient.signer.signTransactionBody( | ||
buffer, | ||
originator, | ||
); | ||
const signedTransaction = SignedTransaction.create({ | ||
createAccount, | ||
signature, | ||
}); | ||
return await this.nearClient.submitTransaction(signedTransaction); | ||
} | ||
@@ -41,4 +60,8 @@ | ||
const createAccountResult = await this.createAccount( | ||
newAccountId, keyWithRandomSeed.getPublicKey(), amount, originatorAccountId); | ||
return { key: keyWithRandomSeed, ...createAccountResult }; | ||
newAccountId, | ||
keyWithRandomSeed.getPublicKey(), | ||
amount, | ||
originatorAccountId, | ||
); | ||
return { key: keyWithRandomSeed, ...createAccountResult }; | ||
} | ||
@@ -45,0 +68,0 @@ |
21
API.md
@@ -32,6 +32,8 @@ # API | ||
- [Parameters](#parameters-10) | ||
- [waitForTransactionResult](#waitfortransactionresult) | ||
- [Parameters](#parameters-11) | ||
- [loadContract](#loadcontract) | ||
- [Parameters](#parameters-11) | ||
- [Parameters](#parameters-12) | ||
- [createDefaultConfig](#createdefaultconfig) | ||
- [Parameters](#parameters-12) | ||
- [Parameters](#parameters-13) | ||
@@ -118,3 +120,2 @@ ## KeyPair | ||
- `sender` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** account id of the sender | ||
- `contractAccountId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** account id of the contract | ||
@@ -160,2 +161,16 @@ - `methodName` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** method to call | ||
### waitForTransactionResult | ||
Wait until transaction is completed or failed. | ||
Automatically sends logs from contract to `console.log`. | ||
[MAX_STATUS_POLL_ATTEMPTS](MAX_STATUS_POLL_ATTEMPTS) defines how many attempts are made. | ||
[STATUS_POLL_PERIOD_MS](STATUS_POLL_PERIOD_MS) defines delay between subsequent [getTransactionStatus](getTransactionStatus) calls. | ||
#### Parameters | ||
- `transactionResponseOrHash` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** hash of transaction or object returned from [submitTransaction](submitTransaction) | ||
- `options` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** object used to pass named parameters (optional, default `{}`) | ||
- `options.contractAccountId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** specifies contract ID for better logs and error messages | ||
### loadContract | ||
@@ -162,0 +177,0 @@ |
let fetch = (typeof window === 'undefined' || window.name == 'nodejs') ? require('node-fetch') : window.fetch; | ||
const createError = require('http-errors'); | ||
module.exports = async function sendJson(method, url, json) { | ||
@@ -9,3 +12,3 @@ const response = await fetch(url, { | ||
if (!response.ok) { | ||
throw new Error(await response.text()); | ||
throw createError(response.status, await response.text()); | ||
} | ||
@@ -17,2 +20,2 @@ if (response.status === 204) { | ||
return await response.json(); | ||
}; | ||
}; |
140
near.js
@@ -0,1 +1,4 @@ | ||
const bs58 = require('bs58'); | ||
const createError = require('http-errors'); | ||
const NearClient = require('./nearclient'); | ||
@@ -5,5 +8,8 @@ const BrowserLocalStorageKeystore = require('./signing/browser_local_storage_key_store'); | ||
const LocalNodeConnection = require('./local_node_connection'); | ||
const { | ||
DeployContractTransaction, FunctionCallTransaction, SignedTransaction | ||
} = require('./protos'); | ||
const MAX_STATUS_POLL_ATTEMPTS = 3; | ||
const STATUS_POLL_PERIOD_MS = 750; | ||
const MAX_STATUS_POLL_ATTEMPTS = 5; | ||
const STATUS_POLL_PERIOD_MS = 1000; | ||
@@ -36,3 +42,2 @@ /** | ||
* Calls a view function. Returns the same value that the function returns. | ||
* @param {string} sender account id of the sender | ||
* @param {string} contractAccountId account id of the contract | ||
@@ -42,3 +47,3 @@ * @param {string} methodName method to call | ||
*/ | ||
async callViewFunction(sender, contractAccountId, methodName, args) { | ||
async callViewFunction(contractAccountId, methodName, args) { | ||
if (!args) { | ||
@@ -49,3 +54,2 @@ args = {}; | ||
const response = await this.nearClient.request('call_view_function', { | ||
originator: sender, | ||
contract_account_id: contractAccountId, | ||
@@ -68,14 +72,35 @@ method_name: methodName, | ||
*/ | ||
async scheduleFunctionCall(amount, sender, contractAccountId, methodName, args) { | ||
async scheduleFunctionCall(amount, originator, contractId, methodName, args) { | ||
if (!args) { | ||
args = {}; | ||
} | ||
const serializedArgs = Array.from(Buffer.from(JSON.stringify(args))); | ||
return await this.nearClient.submitTransaction('schedule_function_call', { | ||
amount: amount, | ||
originator: sender, | ||
contract_account_id: contractAccountId, | ||
method_name: methodName, | ||
args: serializedArgs | ||
methodName = new Uint8Array(Buffer.from(methodName)); | ||
args = new Uint8Array(Buffer.from(JSON.stringify(args))); | ||
const nonce = await this.nearClient.getNonce(originator); | ||
const functionCall = FunctionCallTransaction.create({ | ||
originator, | ||
contractId, | ||
methodName, | ||
args, | ||
}); | ||
// Integers with value of 0 must be omitted | ||
// https://github.com/dcodeIO/protobuf.js/issues/1138 | ||
if (nonce !== 0) { | ||
functionCall.nonce = nonce; | ||
} | ||
if (amount !== 0) { | ||
functionCall.amount = amount; | ||
} | ||
const buffer = FunctionCallTransaction.encode(functionCall).finish(); | ||
const signature = await this.nearClient.signer.signTransactionBody( | ||
buffer, | ||
originator, | ||
); | ||
const signedTransaction = SignedTransaction.create({ | ||
functionCall, | ||
signature, | ||
}); | ||
return await this.nearClient.submitTransaction(signedTransaction); | ||
} | ||
@@ -89,13 +114,35 @@ | ||
*/ | ||
async deployContract(sender, contractAccountId, wasmArray) { | ||
return await this.nearClient.submitTransaction('deploy_contract', { | ||
originator: sender, | ||
contract_account_id: contractAccountId, | ||
wasm_byte_array: wasmArray, | ||
public_key: '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE' // This parameter is not working properly yet. Use some fake value | ||
async deployContract(originator, contractId, wasmByteArray) { | ||
const nonce = await this.nearClient.getNonce(originator); | ||
// This parameter is not working properly yet. Use some fake value | ||
var publicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE'; | ||
publicKey = bs58.decode(publicKey); | ||
const deployContract = DeployContractTransaction.create({ | ||
originator, | ||
contractId, | ||
wasmByteArray, | ||
publicKey, | ||
}); | ||
// Integers with value of 0 must be omitted | ||
// https://github.com/dcodeIO/protobuf.js/issues/1138 | ||
if (nonce !== 0) { | ||
deployContract.nonce = nonce; | ||
} | ||
const buffer = DeployContractTransaction.encode(deployContract).finish(); | ||
const signature = await this.nearClient.signer.signTransactionBody( | ||
buffer, | ||
originator, | ||
); | ||
const signedTransaction = SignedTransaction.create({ | ||
deployContract, | ||
signature, | ||
}); | ||
return await this.nearClient.submitTransaction(signedTransaction); | ||
} | ||
/** | ||
* Get a status of a single transaction identified by the transaction hash. | ||
* Get a status of a single transaction identified by the transaction hash. | ||
* @param {string} transactionHash unique identifier of the transaction | ||
@@ -111,2 +158,36 @@ */ | ||
/** | ||
* Wait until transaction is completed or failed. | ||
* Automatically sends logs from contract to `console.log`. | ||
* | ||
* {@link MAX_STATUS_POLL_ATTEMPTS} defines how many attempts are made. | ||
* {@link STATUS_POLL_PERIOD_MS} defines delay between subsequent {@link getTransactionStatus} calls. | ||
* | ||
* @param {string | object} transactionResponseOrHash hash of transaction or object returned from {@link submitTransaction} | ||
* @param {object} options object used to pass named parameters | ||
* @param {string} options.contractAccountId specifies contract ID for better logs and error messages | ||
*/ | ||
async waitForTransactionResult(transactionResponseOrHash, options = {}) { | ||
const transactionHash = transactionResponseOrHash.hasOwnProperty('hash') ? transactionResponseOrHash.hash : transactionResponseOrHash; | ||
const contractAccountId = options.contractAccountId || 'unknown contract'; | ||
let result; | ||
for (let i = 0; i < MAX_STATUS_POLL_ATTEMPTS; i++) { | ||
await sleep(STATUS_POLL_PERIOD_MS); | ||
result = (await this.getTransactionStatus(transactionHash)).result; | ||
const flatLog = result.logs.reduce((acc, it) => acc.concat(it.lines), []); | ||
flatLog.forEach(line => { | ||
console.log(`[${contractAccountId}]: ${line}`); | ||
}); | ||
if (result.status == 'Completed') { | ||
return result; | ||
} | ||
if (result.status == 'Failed') { | ||
const errorMessage = flatLog.find(it => it.startsWith('ABORT:')) || ''; | ||
throw createError(400, `Transaction ${transactionHash} on ${contractAccountId} failed. ${errorMessage}`); | ||
} | ||
} | ||
throw createError(408, `Exceeded ${MAX_STATUS_POLL_ATTEMPTS} status check attempts ` + | ||
`for transaction ${transactionHash} on ${contractAccountId} with status: ${result.status}`); | ||
} | ||
/** | ||
* Load given contract and expose it's methods. | ||
@@ -144,20 +225,3 @@ * | ||
const response = await near.scheduleFunctionCall(0, options.sender, contractAccountId, methodName, args); | ||
let result; | ||
for (let i = 0; i < MAX_STATUS_POLL_ATTEMPTS; i++) { | ||
await sleep(STATUS_POLL_PERIOD_MS); | ||
result = (await near.getTransactionStatus(response.hash)).result; | ||
const flatLog = result.logs.reduce((acc, it) => acc.concat(it.lines), []); | ||
flatLog.forEach(line => { | ||
console.log(`[${contractAccountId}]: ${line}`); | ||
}); | ||
if (result.status == 'Completed') { | ||
return result; | ||
} | ||
if (result.status == 'Failed') { | ||
const errorMessage = flatLog.find(it => it.startsWith('ABORT:')) || ''; | ||
throw new Error(`Transaction ${response.hash} failed. ${errorMessage}`); | ||
} | ||
} | ||
throw new Error(`Exceeded ${MAX_STATUS_POLL_ATTEMPTS} status check attempts ` + | ||
`for transaction ${response.hash} with status: ${result.status}`); | ||
return near.waitForTransactionResult(response.hash, { contractAccountId }); | ||
}; | ||
@@ -164,0 +228,0 @@ }); |
@@ -0,1 +1,3 @@ | ||
const { SignedTransaction } = require('./protos'); | ||
/** | ||
@@ -5,2 +7,6 @@ * Client for communicating with near blockchain. | ||
function _arrayBufferToBase64(buffer) { | ||
return Buffer.from(buffer).toString('base64'); | ||
} | ||
class NearClient { | ||
@@ -19,21 +25,14 @@ constructor (signer, nearConnection) { | ||
async submitTransaction (method, args) { | ||
const senderKey = 'originator'; | ||
const sender = args[senderKey]; | ||
const nonce = await this.getNonce(sender); | ||
const tx_args = Object.assign({}, args, { nonce }); | ||
const response = await this.request(method, tx_args); | ||
const signature = await this.signer.signTransaction(response, sender); | ||
const signedTransaction = { | ||
body: response.body, | ||
signature: signature | ||
}; | ||
var submitResponse; | ||
async submitTransaction (signedTransaction) { | ||
const buffer = SignedTransaction.encode(signedTransaction).finish(); | ||
const transaction = _arrayBufferToBase64(buffer); | ||
const data = { transaction }; | ||
try { | ||
submitResponse = await this.request('submit_transaction', signedTransaction); | ||
return await this.request('submit_transaction', data); | ||
} catch(e) { | ||
console.log(e.response.text); | ||
throw (e); | ||
if (e.response) { | ||
throw new Error(e.response.text); | ||
} | ||
throw e; | ||
} | ||
return submitResponse; | ||
} | ||
@@ -50,2 +49,2 @@ | ||
module.exports = NearClient; | ||
module.exports = NearClient; |
{ | ||
"name": "nearlib", | ||
"description": "Javascript library to interact with near blockchain", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"repository": { | ||
@@ -13,3 +13,6 @@ "type": "git", | ||
"bs58": "^4.0.1", | ||
"http-errors": "^1.7.1", | ||
"js-sha256": "^0.9.0", | ||
"node-fetch": "^2.3.0", | ||
"protobufjs": "^6.8.8", | ||
"tweetnacl": "^1.0.0" | ||
@@ -16,0 +19,0 @@ }, |
@@ -6,2 +6,3 @@ /** | ||
const nacl = require('tweetnacl'); | ||
const { sha256 } = require('js-sha256'); | ||
@@ -14,10 +15,13 @@ class SimpleKeyStoreSigner { | ||
/** | ||
* Sign a given hash. If the key for senderAccountId is not present, this operation | ||
* will fail. | ||
* @param {Buffer} hash | ||
* Sign a transaction body. If the key for senderAccountId is not present, | ||
* this operation will fail. | ||
* @param {object} body | ||
* @param {string} senderAccountId | ||
*/ | ||
async signHash(hash, senderAccountId) { | ||
async signTransactionBody(body, senderAccountId) { | ||
const encodedKey = await this.keyStore.getKey(senderAccountId); | ||
const message = bs58.decode(hash); | ||
if (!encodedKey) { | ||
throw new Error(`Cannot find key for sender ${senderAccountId}`); | ||
} | ||
const message = new Uint8Array(sha256.array(body)); | ||
const key = bs58.decode(encodedKey.getSecretKey()); | ||
@@ -27,15 +31,4 @@ const signature = [...nacl.sign.detached(message, key)]; | ||
} | ||
/** | ||
* Sign a transaction. If the key for senderAccountId is not present, this operation | ||
* will fail. | ||
* @param {object} tx Transaction details | ||
* @param {string} senderAccountId | ||
*/ | ||
signTransaction(tx, senderAccountId) { | ||
return this.signHash(tx.hash, senderAccountId); | ||
} | ||
} | ||
module.exports = SimpleKeyStoreSigner; | ||
module.exports = SimpleKeyStoreSigner; |
@@ -16,4 +16,2 @@ const { Account, SimpleKeyStoreSigner, InMemoryKeyStore, KeyPair, LocalNodeConnection, NearClient, Near } = require('../'); | ||
const nearjs = new Near(nearClient); | ||
const TEST_MAX_RETRIES = 10; | ||
const TRANSACTION_COMPLETE_MAX_RETRIES = 100; | ||
@@ -36,3 +34,3 @@ test('test creating default config', async () => { | ||
const createAccountResponse = await account.createAccount(newAccountName, newAccountPublicKey, 1, aliceAccountName); | ||
await waitForTransactionToComplete(createAccountResponse); | ||
await nearjs.waitForTransactionResult(createAccountResponse); | ||
const expctedAccount = { | ||
@@ -57,3 +55,3 @@ nonce: 0, | ||
aliceAccountName); | ||
await waitForTransactionToComplete(createAccountResponse); | ||
await nearjs.waitForTransactionResult(createAccountResponse); | ||
expect(createAccountResponse['key']).not.toBeFalsy(); | ||
@@ -77,11 +75,10 @@ const expctedAccount = { | ||
let logs; | ||
let contractName = 'test_contract_' + Date.now(); | ||
beforeAll(async () => { | ||
// See README.md for details about this contract source code location. | ||
const data = [...fs.readFileSync('../tests/hello.wasm')]; | ||
const deployResult = await nearjs.deployContract( | ||
aliceAccountName, | ||
'test_contract', | ||
data); | ||
await waitForContractToDeploy(deployResult); | ||
contract = await nearjs.loadContract('test_contract', { | ||
await nearjs.waitForTransactionResult( | ||
await nearjs.deployContract(aliceAccountName, contractName, data)); | ||
contract = await nearjs.loadContract(contractName, { | ||
sender: aliceAccountName, | ||
@@ -110,4 +107,3 @@ viewMethods: ['getAllKeys'], | ||
const viewFunctionResult = await nearjs.callViewFunction( | ||
aliceAccountName, | ||
'test_contract', | ||
contractName, | ||
'hello', // this is the function defined in hello.wasm file that we are calling | ||
@@ -124,10 +120,9 @@ args); | ||
aliceAccountName, | ||
'test_contract', | ||
contractName, | ||
'setValue', // this is the function defined in hello.wasm file that we are calling | ||
setArgs); | ||
expect(scheduleResult.hash).not.toBeFalsy(); | ||
await waitForTransactionToComplete(scheduleResult); | ||
await nearjs.waitForTransactionResult(scheduleResult); | ||
const secondViewFunctionResult = await nearjs.callViewFunction( | ||
aliceAccountName, | ||
'test_contract', | ||
contractName, | ||
'getValue', // this is the function defined in hello.wasm file that we are calling | ||
@@ -140,3 +135,3 @@ {}); | ||
await contract.generateLogs(); | ||
expect(logs).toEqual(['[test_contract]: LOG: log1', '[test_contract]: LOG: log2']); | ||
expect(logs).toEqual([`[${contractName}]: LOG: log1`, `[${contractName}]: LOG: log2`]); | ||
}); | ||
@@ -146,58 +141,8 @@ | ||
await expect(contract.triggerAssert()).rejects.toThrow(/Transaction .+ failed.+expected to fail/); | ||
expect(logs).toEqual(["[test_contract]: LOG: log before assert", | ||
"[test_contract]: ABORT: \"expected to fail\" filename: \"main.ts\" line: 35 col: 2"]); | ||
expect(logs).toEqual([`[${contractName}]: LOG: log before assert`, | ||
`[${contractName}]: ABORT: "expected to fail" filename: "main.ts" line: 44 col: 2`, | ||
`[${contractName}]: Runtime error: wasm async call execution failed with error: Interpreter(Trap(Trap { kind: Host(AssertFailed) }))`]); | ||
}); | ||
}); | ||
const callUntilConditionIsMet = async (functToPoll, condition, description, maxRetries = TEST_MAX_RETRIES) => { | ||
for (let i = 0; i < maxRetries; i++) { | ||
try { | ||
const response = await functToPoll(); | ||
if (condition(response)) { | ||
console.log('Success ' + description + ' in ' + (i + 1) + ' attempts.'); | ||
return response; | ||
} | ||
} catch (e) { | ||
if (i == TEST_MAX_RETRIES - 1) { | ||
fail('exceeded number of retries for ' + description + '. Last error ' + e.toString()); | ||
} | ||
} | ||
await sleep(500); | ||
} | ||
fail('exceeded number of retries for ' + description); | ||
}; | ||
const waitForTransactionToComplete = async (submitTransactionResult) => { | ||
expect(submitTransactionResult.hash).not.toBeFalsy(); | ||
console.log('Waiting for transaction', submitTransactionResult.hash); | ||
await callUntilConditionIsMet( | ||
async () => { return await nearjs.getTransactionStatus(submitTransactionResult.hash); }, | ||
(response) => { | ||
if (response.result.status == 'Completed') { | ||
console.log('Transaction ' + submitTransactionResult.hash + ' completed'); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}, | ||
'Call get transaction status until transaction is completed', | ||
TRANSACTION_COMPLETE_MAX_RETRIES | ||
); | ||
}; | ||
const waitForContractToDeploy = async (deployResult) => { | ||
await callUntilConditionIsMet( | ||
async () => { return await nearjs.getTransactionStatus(deployResult.hash); }, | ||
(response) => { return response['result']['status'] == 'Completed'; }, | ||
'Call account status until contract is deployed', | ||
TRANSACTION_COMPLETE_MAX_RETRIES | ||
); | ||
}; | ||
function sleep(time) { | ||
return new Promise(function (resolve) { | ||
setTimeout(resolve, time); | ||
}); | ||
} | ||
// Generate some unique string with a given prefix using the alice nonce. | ||
@@ -204,0 +149,0 @@ const generateUniqueString = async (prefix) => { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
732687
25
13859
6
7
15
+ Addedhttp-errors@^1.7.1
+ Addedjs-sha256@^0.9.0
+ Addedprotobufjs@^6.8.8
+ Added@protobufjs/aspromise@1.1.2(transitive)
+ Added@protobufjs/base64@1.1.2(transitive)
+ Added@protobufjs/codegen@2.0.4(transitive)
+ Added@protobufjs/eventemitter@1.1.0(transitive)
+ Added@protobufjs/fetch@1.1.0(transitive)
+ Added@protobufjs/float@1.0.2(transitive)
+ Added@protobufjs/inquire@1.1.0(transitive)
+ Added@protobufjs/path@1.1.2(transitive)
+ Added@protobufjs/pool@1.1.0(transitive)
+ Added@protobufjs/utf8@1.1.0(transitive)
+ Added@types/long@4.0.2(transitive)
+ Added@types/node@22.13.4(transitive)
+ Addeddepd@1.1.2(transitive)
+ Addedhttp-errors@1.8.1(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedjs-sha256@0.9.0(transitive)
+ Addedlong@4.0.0(transitive)
+ Addedprotobufjs@6.11.4(transitive)
+ Addedsetprototypeof@1.2.0(transitive)
+ Addedstatuses@1.5.0(transitive)
+ Addedtoidentifier@1.0.1(transitive)
+ Addedundici-types@6.20.0(transitive)