-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
44ac8a5
commit 0e155cb
Showing
16 changed files
with
847 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
{ | ||
"appAccount": "createit.near", | ||
"appAccount": "create.near", | ||
"aliases": { | ||
"nui": "nearui.near" | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"appAccount": "editor.near" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
function get({ path }) { | ||
return fetch(`https://raw.githubusercontent.com/${path}`); | ||
} | ||
|
||
return { get }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
// Sourced from | ||
// https://github.com/petersalomonsen/near-openai/blob/main/boswidgets/askchatgpt/main.js | ||
|
||
const NETWORK_ID = "mainnet"; | ||
|
||
// what does near-api-js use these for? | ||
// and how can people discover other options | ||
const NODE_URL = "https://rpc.mainnet.near.org"; | ||
const WALLET_URL = `https://wallet.${NETWORK_ID}.near.org`; // what should this be defaulting to? | ||
const HELPER_URL = `https://helper.${NETWORK_ID}.near.org`; | ||
const EXPLORER_URL = `https://explorer.${NETWORK_ID}.near.org`; // and this? | ||
|
||
const API_URL = "https://near-openai.vercel.app/api/openai"; | ||
|
||
const code = ` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<meta charset="UTF-8"> | ||
</head> | ||
<body> | ||
</body> | ||
<script type="module"> | ||
import 'https://cdn.jsdelivr.net/npm/near-api-js@2.1.3/dist/near-api-js.min.js'; | ||
import 'https://cdn.jsdelivr.net/npm/js-sha256@0.9.0/src/sha256.min.js'; | ||
const keyStore = new nearApi.keyStores.InMemoryKeyStore(); | ||
let account; | ||
const networkId = "mainnet"; | ||
const config = { | ||
keyStore, // instance of UnencryptedFileSystemKeyStore | ||
networkId: networkId, | ||
nodeUrl: "https://rpc.mainnet.near.org", | ||
walletUrl: "https://wallet.mainnet.near.org", | ||
helperUrl: "https://helper.mainnet.near.org", | ||
explorerUrl: "https://explorer.mainnet.near.org" | ||
}; | ||
async function createAccount() { | ||
const keypair = nearApi.utils.KeyPairEd25519.fromRandom(); | ||
const accountId = Buffer.from(keypair.publicKey.data).toString('hex'); | ||
await keyStore.setKey(networkId, accountId, keypair); | ||
const near = await nearApi.connect(config); | ||
account = await near.account(accountId); | ||
return { secretKey: keypair.secretKey, accountId }; | ||
} | ||
async function useAccount(secretKey) { | ||
const keypair = nearApi.utils.KeyPair.fromString(secretKey); | ||
const accountId = Buffer.from(keypair.publicKey.data).toString('hex'); | ||
await keyStore.setKey(networkId, accountId, keypair); | ||
const near = await nearApi.connect(config); | ||
account = await near.account(accountId); | ||
return accountId; | ||
} | ||
async function create_ask_ai_request_body(messages, model) { | ||
const accountId = account.accountId; | ||
const messagesStringified = JSON.stringify(messages); | ||
const deposit = 50_00000_00000_00000_00000n; | ||
const message_hash = sha256(messagesStringified); | ||
const receiverId = 'jsinrust.near'; | ||
const method_name = 'ask_ai'; | ||
const gas = '30000000000000'; | ||
const publicKey = await account.connection.signer.getPublicKey(account.accountId, account.connection.networkId); | ||
let accessKey; | ||
try { | ||
accessKey = (await account.findAccessKey()).accessKey; | ||
} catch (e) { | ||
throw new Error(JSON.stringify("Balance is empty.", null, 1)); | ||
} | ||
const nonce = ++accessKey.nonce; | ||
const recentBlockHash = nearApi.utils.serialize.base_decode( | ||
accessKey.block_hash | ||
); | ||
const transaction = nearApi.transactions.createTransaction( | ||
account.accountId, | ||
publicKey, | ||
receiverId, | ||
nonce, | ||
[nearApi.transactions.functionCall(method_name, { | ||
message_hash | ||
}, gas, deposit)], | ||
recentBlockHash | ||
); | ||
const [txHash, signedTx] = await nearApi.transactions.signTransaction(transaction, account.connection.signer, account.accountId, account.connection.networkId); | ||
return JSON.stringify({ | ||
signed_transaction: Buffer.from(signedTx.encode()).toString('base64'), | ||
transaction_hash: nearApi.utils.serialize.base_encode(txHash), | ||
sender_account_id: accountId, | ||
messages: messages, | ||
model: model | ||
}); | ||
} | ||
async function create_and_send_ask_ai_request(messages, model) { | ||
console.log("model", model); | ||
try { | ||
const requestbody = await create_ask_ai_request_body(messages, model); | ||
const airesponse = await fetch( | ||
"https://near-openai-50jjawxtf-petersalomonsen.vercel.app/api/openai", | ||
{ | ||
method: 'POST', | ||
body: requestbody | ||
}).then(r => r.json()); | ||
if (airesponse.error) { | ||
throw new Error(JSON.stringify(airesponse.error, null, 1)); | ||
} | ||
return airesponse.choices[0].message.content; | ||
} catch (e) { | ||
console.log(e.message) | ||
window.parent.postMessage({ command: "error", error: e.message }, '*'); | ||
} | ||
} | ||
window.onmessage = async (msg) => { | ||
globalThis.parentOrigin = msg.origin; | ||
console.log('iframe got message', msg.data); | ||
switch (msg.data.command) { | ||
case 'createaccount': | ||
const { secretKey, accountId } = await createAccount(); | ||
window.parent.postMessage({ command: 'accountcreated', secretKey, accountId }, globalThis.parentOrigin); | ||
break; | ||
case 'useaccount': | ||
window.parent.postMessage({ command: 'usingaccount', accountId: await useAccount(msg.data.secretKey) }, globalThis.parentOrigin); | ||
break; | ||
case 'ask_ai': | ||
const response = await create_and_send_ask_ai_request([{ role: 'user', content: msg.data.aiquestion }], msg.data.model); | ||
window.parent.postMessage({ command: 'airesponse', airesponse: response }, globalThis.parentOrigin); | ||
break; | ||
} | ||
}; | ||
window.parent.postMessage({ command: 'ready' }, '*'); | ||
</script> | ||
</html> | ||
`; | ||
|
||
const SECRET_KEY_STORAGE_KEY = "secretKey"; | ||
Storage.privateGet(SECRET_KEY_STORAGE_KEY); | ||
|
||
State.init({ | ||
secretKey: null, | ||
airesponse: "", | ||
aiquestion: "", | ||
accountId: "", | ||
iframeMessage: null, | ||
usingAccount: false, | ||
}); | ||
|
||
function init_iframe() { | ||
const secretKey = Storage.privateGet(SECRET_KEY_STORAGE_KEY); | ||
|
||
State.update({ | ||
secretKey, | ||
iframeMessage: secretKey | ||
? { | ||
command: "useaccount", | ||
secretKey: secretKey, | ||
} | ||
: { | ||
command: "createaccount", | ||
}, | ||
}); | ||
} | ||
|
||
function ask_ai() { | ||
State.update({ | ||
iframeMessage: { | ||
command: "ask_ai", | ||
aiquestion: state.aiquestion, | ||
model: "gpt-4", | ||
ts: new Date().getTime(), | ||
}, | ||
progress: true, | ||
}); | ||
console.log("state updated", state.iframeMessage); | ||
} | ||
|
||
function changeSecretKey(secretKey) { | ||
State.update({ secretKey }); | ||
Storage.privateSet(SECRET_KEY_STORAGE_KEY, secretKey); | ||
init_iframe(); | ||
} | ||
|
||
function handleMessage(msg) { | ||
switch (msg.command) { | ||
case "accountcreated": | ||
Storage.privateSet(SECRET_KEY_STORAGE_KEY, msg.secretKey); | ||
State.update({ | ||
accountId: msg.accountId, | ||
secretKey: msg.secretKey, | ||
}); | ||
break; | ||
case "airesponse": | ||
State.update({ airesponse: msg.airesponse, progress: false }); | ||
break; | ||
case "usingaccount": | ||
State.update({ accountId: msg.accountId }); | ||
break; | ||
case "error": | ||
console.log("error received in parent", msg.error); | ||
break; | ||
case "ready": | ||
console.log("ready"); | ||
init_iframe(); | ||
break; | ||
} | ||
} | ||
|
||
const iframe = ( | ||
<iframe | ||
message={state.iframeMessage} | ||
onMessage={handleMessage} | ||
srcDoc={code} | ||
style={{ width: "0px", height: "0px", border: "none" }} | ||
></iframe> | ||
); | ||
|
||
const secretKeyToggle = state.showSecretKey ? ( | ||
<> | ||
<button onClick={() => State.update({ showSecretKey: false })}>Hide</button> | ||
<input | ||
type="text" | ||
value={state.secretKey} | ||
onChange={(e) => changeSecretKey(e.target.value)} | ||
></input> | ||
</> | ||
) : ( | ||
<button onClick={() => State.update({ showSecretKey: true })}>Show</button> | ||
); | ||
|
||
return ( | ||
<> | ||
<p> | ||
<b>NOTE:</b> Each request costs about 0.005 NEAR. Make sure the spending | ||
account below is funded, and you can also get full access to that account | ||
by using the secret key. Only you have the key to this account, so don't | ||
loose it. | ||
</p> | ||
{iframe} | ||
<textarea | ||
style={{ width: "100%" }} | ||
onChange={(e) => State.update({ aiquestion: e.target.value })} | ||
value={state.aiquestion} | ||
></textarea> | ||
{state.progress ? ( | ||
<Progress.Root> | ||
<Progress.Indicator state="indeterminate" /> | ||
</Progress.Root> | ||
) : ( | ||
<button onClick={ask_ai}>Ask ChatGPT</button> | ||
)} | ||
|
||
<div | ||
style={{ marginTop: "20px", padding: "20px", backgroundColor: "#f5f5f5" }} | ||
> | ||
<Markdown text={state.airesponse} /> | ||
</div> | ||
|
||
<p> | ||
<br /> | ||
</p> | ||
|
||
<p></p> | ||
<p> | ||
Spending account ID: <pre>{state.accountId}</pre> | ||
</p> | ||
<p>Spending account secret key: {secretKeyToggle}</p> | ||
</> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
const call = ({ daoId, methodName, args, deposit }) => { | ||
Near.call([ | ||
{ | ||
contractName: daoId, | ||
methodName, | ||
args, | ||
deposit: deposit, | ||
gas: "219000000000000", | ||
}, | ||
]); | ||
}; | ||
|
||
const addProposal = ({ daoId, proposal }) => { | ||
const policy = Near.view(daoId, "get_policy"); | ||
|
||
if (policy === null) { | ||
return "Loading..."; // lol what does this do... | ||
} | ||
|
||
const deposit = policy.proposal_bond; | ||
|
||
call({ | ||
daoId, | ||
methodName: "add_proposal", | ||
args: { | ||
proposal, | ||
}, | ||
deposit, | ||
}); | ||
}; | ||
|
||
const createFunctionCallProposal = ({ | ||
daoId, | ||
receiver_id, | ||
method_name, | ||
args, | ||
}) => { | ||
const proposal_args = Buffer.from(JSON.stringify(args), "utf-8").toString("base64"); | ||
addProposal({ | ||
daoId, | ||
proposal: { | ||
description: `call ${method_name} to ${receiver_id}`, | ||
kind: { | ||
FunctionCall: { | ||
receiver_id, | ||
actions: [ | ||
{ | ||
method_name, | ||
args: proposal_args, | ||
deposit: "100000000000000000000000", | ||
gas: "219000000000000", | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}); | ||
}; | ||
|
||
return { createFunctionCallProposal }; |
Oops, something went wrong.