Skip to content

Commit

Permalink
Tokens (#273)
Browse files Browse the repository at this point in the history
* feat: Add transfer of custom tokens naively

* Mess around with Parties

* feat: Add token class

* this.token.transfer successfully changes balances with public key input

* feat: Brute force custom token transfer

* Change createUnsigned to use optional object parameter for tokens

* Remove old transfer

* Use Bool() for account creation fee for token accounts

* Add basic mint/burn methods

* wip: burn and transfer pass transaction logic

* Set lazy-signature for required parties in token transfers

* Fix spelling in sign function

* Add token id in Ledger.getAccount

* Remove newTokenAccount in favor of using Party.fundNewAccount()

* Change this.token to a function to take optional token id

* Add token in getAccount

* Refactor this.token().transfer to this.token().send()

* Use getBalance for fetching balance from local ledger

* Remove comment code

* Add tokenId to getAccount

* Refactor createUnsigned to take string instead of Field for caller and tokenId

* Add proper typing for snarkyjs binding token functions

* Add tokenSymbol to Party API

* Fix getCachedAccount by passing in tokenId

* Fix usage of fetchAccount

* Fix tokenSymbol setter

* Scaffold test file for tokens

* Add first section of token tests, refactor existing Party token code

* Add minting with precondition tests

* Refactor getAccount and getBalance parameters

* Implement burn and send tests

* Fix fetchAccount type parameters

* Use init method for zkApp state

* Add support for checked tokenId, convert tokenId type to Field, additional feedback

* Refactor checked computation check and error messages

* Update bindings

* PR feedback

* Fix parentTokenId parameter for new Token()

* Add getter for tokenId on Party structure

* Update bindings

* Fix CircuitValue.isConstant

* Update for isNew

* Fix intg tests

* Use createChildParty instead of createUnsigned

* Add formatted getAccount error

* Fix checked and unchecked condition in token constructor

* Fix GraphQL query parameter

* integrate `deploy()` with qanet

* fix integration test

Co-authored-by: Gregor <[email protected]>
  • Loading branch information
MartinMinkov and mitschabaude authored Jul 25, 2022
1 parent 211993a commit a4fae70
Show file tree
Hide file tree
Showing 18 changed files with 694 additions and 92 deletions.
2 changes: 1 addition & 1 deletion MINA_COMMIT
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
The mina commit used to generate the backends for node and chrome is
97ff72a1d43cda02225d972031cdb0639f0abf84
dea7b89becac85444610378dd579ea0216631349
2 changes: 1 addition & 1 deletion src/examples/deploy/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isReady, PrivateKey, deploy, shutdown, compile } from 'snarkyjs';
import { isReady, PrivateKey, deploy, shutdown } from 'snarkyjs';
import SimpleZkapp from './simple_zkapp';
import { readFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
Expand Down
9 changes: 7 additions & 2 deletions src/examples/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import {
setGraphqlEndpoint,
shutdown,
fetchLastBlock,
PublicKey,
} from 'snarkyjs';

await isReady;
setGraphqlEndpoint('https://proxy.berkeley.minaexplorer.com/graphql');

let zkappAddress = 'B62qpRzFVjd56FiHnNfxokVbcHMQLT119My1FEdSq8ss7KomLiSZcan';
let { account, error } = await fetchAccount(zkappAddress);
let zkappAddress = PublicKey.fromBase58(
'B62qpRzFVjd56FiHnNfxokVbcHMQLT119My1FEdSq8ss7KomLiSZcan'
);
let { account, error } = await fetchAccount({
publicKey: zkappAddress,
});
console.log('account', JSON.stringify(account, null, 2));
console.log('error', error);

Expand Down
2 changes: 1 addition & 1 deletion src/examples/simple_zkapp_berkeley.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Mina.setActiveInstance(Berkeley);
let feePayerKey = PrivateKey.fromBase58(
'EKEQc95PPQZnMY9d9p1vq1MWLeDJKtvKj4V75UDG3rjnf32BerWD'
);
let response = await fetchAccount(feePayerKey.toPublicKey());
let response = await fetchAccount({ publicKey: feePayerKey.toPublicKey() });
if (response.error) throw Error(response.error.statusText);
let { nonce, balance } = response.account;
console.log(`Using fee payer account with nonce ${nonce}, balance ${balance}`);
Expand Down
9 changes: 8 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ export {
} from './lib/zkapp';
export { state, State, declareState } from './lib/state';
export { Proof, SelfProof, ZkProgram, verify } from './lib/proof_system';
export { Party, Permissions, ZkappPublicInput } from './lib/party';
export {
Token,
Party,
Permissions,
ZkappPublicInput,
getDefaultTokenId,
partiesToJson,
} from './lib/party';
export {
fetchAccount,
fetchLastBlock,
Expand Down
82 changes: 63 additions & 19 deletions src/lib/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import 'isomorphic-fetch';
import { Bool, Field, Types } from '../snarky';
import { Bool, Field, Types, Ledger } from '../snarky';
import { UInt32, UInt64 } from './int';
import { Permission, Permissions, ZkappStateLength } from './party';
import {
getDefaultTokenId,
Permission,
Permissions,
ZkappStateLength,
} from './party';
import { PublicKey } from './signature';
import { NetworkValue } from './precondition';

Expand Down Expand Up @@ -41,18 +46,24 @@ function setGraphqlEndpoint(graphqlEndpoint: string) {
* @returns zkapp information on the specified account or an error is thrown
*/
async function fetchAccount(
publicKey: string | PublicKey,
accountInfo: { publicKey: string | PublicKey; tokenId?: string },
graphqlEndpoint = defaultGraphqlEndpoint,
{ timeout = defaultTimeout } = {}
): Promise<
| { account: Account; error: undefined }
| { account: undefined; error: FetchError }
> {
let publicKeyBase58 =
publicKey instanceof PublicKey ? publicKey.toBase58() : publicKey;
let response = await fetchAccountInternal(publicKeyBase58, graphqlEndpoint, {
timeout,
});
accountInfo.publicKey instanceof PublicKey
? accountInfo.publicKey.toBase58()
: accountInfo.publicKey;
let response = await fetchAccountInternal(
{ publicKey: publicKeyBase58, tokenId: accountInfo.tokenId },
graphqlEndpoint,
{
timeout,
}
);
return response.error === undefined
? {
account: parseFetchedAccount(response.account),
Expand All @@ -64,12 +75,16 @@ async function fetchAccount(
// internal version of fetchAccount which does the same, but returns the original JSON version
// of the account, to save some back-and-forth conversions when caching accounts
async function fetchAccountInternal(
publicKey: string,
accountInfo: { publicKey: string; tokenId?: string },
graphqlEndpoint = defaultGraphqlEndpoint,
config?: FetchConfig
) {
const { publicKey, tokenId } = accountInfo;
let [response, error] = await makeGraphqlRequest(
accountQuery(publicKey),
accountQuery(
publicKey,
tokenId ?? Ledger.fieldToBase58(getDefaultTokenId())
),
graphqlEndpoint,
config
);
Expand Down Expand Up @@ -123,6 +138,8 @@ function toPermission(p: AuthRequired): Permission {
type FetchedAccount = {
publicKey: string;
nonce: string;
tokenId: string;
tokenSymbol: string;
zkappUri?: string;
zkappState: string[] | null;
receiptChainHash?: string;
Expand All @@ -149,6 +166,8 @@ type Account = {
publicKey: PublicKey;
nonce: UInt32;
balance: UInt64;
tokenId: Field;
tokenSymbol: string;
zkapp?: { appState: Field[] };
permissions?: Permissions;
receiptChainHash?: Field;
Expand All @@ -160,13 +179,15 @@ type Account = {
type FlexibleAccount = {
publicKey: PublicKey | string;
nonce: UInt32 | string | number;
tokenId?: string;
tokenSymbol?: string;
balance?: UInt64 | string | number;
zkapp?: { appState: (Field | string | number)[] };
};

// TODO provedState
const accountQuery = (publicKey: string) => `{
account(publicKey: "${publicKey}") {
const accountQuery = (publicKey: string, tokenId: string) => `{
account(publicKey: "${publicKey}", tokenId: "${tokenId}") {
publicKey
nonce
zkappUri
Expand All @@ -188,6 +209,8 @@ const accountQuery = (publicKey: string) => `{
balance { total }
delegateAccount { publicKey }
sequenceEvents
tokenId
tokenSymbol
}
}
`;
Expand All @@ -206,6 +229,8 @@ function parseFetchedAccount({
delegateAccount,
receiptChainHash,
sequenceEvents,
tokenId,
tokenSymbol,
}: Partial<FetchedAccount>): Partial<Account> {
return {
publicKey:
Expand All @@ -228,11 +253,13 @@ function parseFetchedAccount({
// : undefined,
delegate:
delegateAccount && PublicKey.fromBase58(delegateAccount.publicKey),
tokenId: tokenId !== undefined ? Ledger.fieldOfBase58(tokenId) : undefined,
tokenSymbol: tokenSymbol !== undefined ? tokenSymbol : undefined,
};
}

function stringifyAccount(account: FlexibleAccount): FetchedAccount {
let { publicKey, nonce, balance, zkapp } = account;
let { publicKey, nonce, balance, zkapp, tokenId, tokenSymbol } = account;
return {
publicKey:
publicKey instanceof PublicKey ? publicKey.toBase58() : publicKey,
Expand All @@ -241,6 +268,8 @@ function stringifyAccount(account: FlexibleAccount): FetchedAccount {
zkapp?.appState.map((s) => s.toString()) ??
Array(ZkappStateLength).fill('0'),
balance: { total: balance?.toString() ?? '0' },
tokenId: tokenId ?? Ledger.fieldToBase58(getDefaultTokenId()),
tokenSymbol: tokenSymbol ?? '',
};
}

Expand All @@ -262,15 +291,21 @@ let networkCache = {} as Record<
>;
let accountsToFetch = {} as Record<
string,
{ publicKey: string; graphqlEndpoint: string }
{ publicKey: string; tokenId: string; graphqlEndpoint: string }
>;
let networksToFetch = {} as Record<string, { graphqlEndpoint: string }>;
let cacheExpiry = 10 * 60 * 1000; // 10 minutes

function markAccountToBeFetched(publicKey: PublicKey, graphqlEndpoint: string) {
function markAccountToBeFetched(
publicKey: PublicKey,
tokenId: Field,
graphqlEndpoint: string
) {
let publicKeyBase58 = publicKey.toBase58();
accountsToFetch[`${publicKeyBase58};${graphqlEndpoint}`] = {
let tokenBase58 = Ledger.fieldToBase58(tokenId);
accountsToFetch[`${publicKeyBase58};${tokenBase58};${graphqlEndpoint}`] = {
publicKey: publicKeyBase58,
tokenId: tokenBase58,
graphqlEndpoint,
};
}
Expand All @@ -285,8 +320,11 @@ async function fetchMissingData(graphqlEndpoint: string) {
let cachedAccount = accountCache[key];
return cachedAccount === undefined || cachedAccount.timestamp < expired;
});
let promises = accounts.map(async ([key, { publicKey }]) => {
let response = await fetchAccountInternal(publicKey, graphqlEndpoint);
let promises = accounts.map(async ([key, { publicKey, tokenId }]) => {
let response = await fetchAccountInternal(
{ publicKey, tokenId },
graphqlEndpoint
);
if (response.error === undefined) delete accountsToFetch[key];
});

Expand All @@ -311,10 +349,15 @@ async function fetchMissingData(graphqlEndpoint: string) {

function getCachedAccount(
publicKey: PublicKey,
tokenId: Field,
graphqlEndpoint = defaultGraphqlEndpoint
) {
let account =
accountCache[`${publicKey.toBase58()};${graphqlEndpoint}`]?.account;
accountCache[
`${publicKey.toBase58()};${Ledger.fieldToBase58(
tokenId
)};${graphqlEndpoint}`
]?.account;
if (account !== undefined) return parseFetchedAccount(account);
}
function getCachedNetwork(graphqlEndpoint = defaultGraphqlEndpoint) {
Expand All @@ -329,6 +372,7 @@ function addCachedAccount(
zkapp?: {
appState: (string | number | Field)[];
};
tokenId: string;
},
graphqlEndpoint = defaultGraphqlEndpoint
) {
Expand All @@ -339,7 +383,7 @@ function addCachedAccountInternal(
account: FetchedAccount,
graphqlEndpoint: string
) {
accountCache[`${account.publicKey};${graphqlEndpoint}`] = {
accountCache[`${account.publicKey};${account.tokenId};${graphqlEndpoint}`] = {
account,
graphqlEndpoint,
timestamp: Date.now(),
Expand Down
2 changes: 1 addition & 1 deletion src/lib/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { inCheckedComputation } from './proof_system';
export { Poseidon };

// internal API
export { prefixes, emptyHashWithPrefix, hashWithPrefix };
export { prefixes, emptyHashWithPrefix, hashWithPrefix, salt };

class Sponge {
private sponge: unknown;
Expand Down
Loading

0 comments on commit a4fae70

Please sign in to comment.