Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agoric offer signer endo maker #64

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f0b8cd9
build(fincaps): cosmjs, @agoric/cosmic-proto
dckc Nov 19, 2023
ad08f4d
lock packages with cosmjs
dckc Nov 19, 2023
3fdbf2a
feat: offerSigner endo plug-in
dckc Nov 19, 2023
9a2d21d
feat: hdWallet endo plugin / module
dckc Nov 19, 2023
e5ab30f
feat: smartWallet endo component
dckc Nov 19, 2023
7c75850
feat: RpcClient using explicit access to the network
dckc Nov 19, 2023
99828cc
test: replay fetch() utility
dckc Nov 19, 2023
ba233b4
test: regen fixture
dckc Nov 19, 2023
07b7ae0
docs: send-tokens with endo (Makefile target)
dckc Nov 19, 2023
681ad63
chore: borrow marshal.ts from ui-kit
dckc Nov 19, 2023
eb5dc48
fix: static mutable state in marshalTables
dckc Nov 19, 2023
41c0c51
chore: copy batchQuery.ts from ui-kit
dckc Nov 19, 2023
0ecd58c
chore: port batchQuery to .js
dckc Nov 19, 2023
31e1f84
test: regenerate snapshots
dckc Nov 19, 2023
cc7f90e
feat!: add makeQueryTool to smartwallet (rename makeTxTool)
dckc Nov 19, 2023
ee82368
docs: lookup IST brand from agoricNames
dckc Nov 19, 2023
849a0c1
feat: E(queryTool).lookup('agoricNames', 'brand')
dckc Nov 20, 2023
ada33c5
build: @agoric/smart-wallet devDependencies (types)
dckc Nov 20, 2023
d779eba
feat: read vstorage history (for offer status)
dckc Nov 20, 2023
fbf6c33
feat: executeOffer
dckc Nov 20, 2023
f1e725b
fixup! feat!: add makeQueryTool to smartwallet (rename makeTxTool)
dckc Nov 20, 2023
cec183f
test: test query (WIP)
dckc Nov 20, 2023
36addf3
docs: reserve-add demo using smartWallet.js
dckc Nov 20, 2023
aec750b
SQUASHME: move agoricZone
dckc Nov 22, 2023
e60d8e6
SQUASHME: move batchQuery
dckc Nov 22, 2023
acb38e0
SQUASHME: move hdWallet
dckc Nov 22, 2023
076b12c
SQUASHME: move httpClient
dckc Nov 22, 2023
69358cc
SQUASHME: move marshalTables
dckc Nov 22, 2023
1620a84
SQUASHME: move offerSigner
dckc Nov 22, 2023
4eb9a9c
SQUASHME: move snapshots
dckc Nov 22, 2023
539da67
feat: add to reserve
dckc Nov 20, 2023
b46dba0
SQUASHME: move smartWallet
dckc Nov 22, 2023
39dc004
SQUASHME: move replayFetch
dckc Nov 22, 2023
c7fda60
SQUASHME: move test-hdWallet
dckc Nov 22, 2023
90071da
SQUASHME: move test-query
dckc Nov 22, 2023
792deb7
SQUASHME: move test-sign
dckc Nov 22, 2023
2fdb4c0
SQUASHME: move test-smartWallet
dckc Nov 22, 2023
3b0b25b
SQUASHME: move web-fixture
dckc Nov 22, 2023
cb51fc5
Revert "docs: reserve-add demo using smartWallet.js"
dckc Nov 22, 2023
e802ef6
Revert "docs: lookup IST brand from agoricNames"
dckc Nov 22, 2023
c3192fa
Revert "docs: send-tokens with endo (Makefile target)"
dckc Nov 22, 2023
ee277ac
SQUASHME: rever send-tokens Makefile target (docs)
dckc Nov 22, 2023
93df2c6
Revert "build: @agoric/smart-wallet devDependencies (types)"
dckc Nov 22, 2023
6538c9c
Revert "build(fincaps): cosmjs, @agoric/cosmic-proto"
dckc Nov 22, 2023
bd9d2e6
WIP: inter bid
dckc Nov 22, 2023
0c9dc2a
WIP: copy makefile
dckc Nov 22, 2023
bc80aa3
WIP: copy packages
dckc Nov 22, 2023
328528f
WIP: lock packages
dckc Nov 22, 2023
eb36d07
SQUASHME: batchQuery: make mapHistory more robust
dckc Nov 22, 2023
2dfae78
feat(smartWallet): walletView
dckc Nov 22, 2023
f8b9a5c
feat: separate fetch worker from smartWallet worker
dckc Nov 25, 2023
0d52ec4
SQUASHME: prune offerSigner design alternative
dckc Nov 25, 2023
d1ce327
SQUASHME: prune test-sign snapshots
dckc Nov 25, 2023
8ae16d6
fixup! test: replay fetch() utility
dckc Nov 25, 2023
c92d442
SQUASHME: finish smartWallet recording test
dckc Nov 25, 2023
b5e06d4
CHORE: regenearte test-smartWallet snapshots
dckc Nov 25, 2023
2221c11
fixup! test: regen fixture
dckc Nov 25, 2023
8b41ed0
SQUASHME: test-query
dckc Nov 25, 2023
57db64c
SQUASME: web fixture
dckc Nov 25, 2023
1b74a11
XXX recording should be at RPC/LCD level, not fetch
dckc Nov 26, 2023
831f365
SQUASHME smartWallet.js export WalletView type
dckc Nov 26, 2023
e1c5592
WIP: refactor `bid list` to take an ERef<WalletView>
dckc Nov 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions packages/ag-trade/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
STATE=$(shell endo where state)
PET=$(STATE)/pet-store

where:
@echo state: $(STATE)

bid-list: $(PET)/gov1-view

$(PET)/gov1-view: $(PET)/query-tool
endo eval "E(hub).walletView('agoric1acfcen6peh9ed9tyrj5wyqtfrf7hthrh5smddy')" \
hub:query-tool -n gov1-view

reserve-add: $(PET)/reserve-tool $(PET)/user1-kit $(PET)/u1-ist
@echo ++ Add to reserve
endo eval "E(reserve).addToReserve(kit.smartWallet, \
{ brand, value: 7_000_000n }, 'add2')" \
reserve:reserve-tool kit:user1-kit brand:u1-ist

$(PET)/u1-ist: $(PET)/user1-kit
@echo ++ look up IST in user1 marshal context
endo eval "E(kit.query).lookup('agoricNames', 'brand', 'IST')" -n u1-ist kit:user1-kit

$(PET)/reserve-tool: src/reserve-add.js
@echo ++ instantiate reserve add tool
endo make src/reserve-add.js -n reserve-tool

kit: $(PET)/user1-kit

$(PET)/user1-kit: $(PET)/desktop-secrets $(PET)/wallet-factory $(PET)/cosmos-fetch
@echo ++ Instantiate SigningStargetClient for user1
endo eval "\
(async () => { \
const passKey = E(secrets).makePassKey({ username: 'user1' }); \
const mnemonic = await E(passKey).get(); \
const rpc = E(net).makeRPCClient('http://localhost:26657'); \
const lcd = E(net).makeLCDClient('http://localhost:1317'); \
return E(wf).makeWalletKit(mnemonic, rpc, lcd); \
})()" \
secrets:desktop-secrets wf:wallet-factory net:cosmos-fetch \
-n user1-kit

$(PET)/wallet-factory: src/smartWallet.js
@echo ++ start smart wallet caplet -- TODO: should not need UNSAFE
endo make --UNSAFE src/smartWallet.js -n wallet-factory

$(PET)/cosmos-fetch: src/cosmosFetch.js
@echo ++ install cosmos fetch plugin
endo make --UNSAFE src/cosmosFetch.js -n cosmos-fetch


get-brand: $(PET)/ist-brand

$(PET)/ist-brand: $(PET)/query-tool
@echo ++ Get IST brand
endo eval "E(hub).lookup('agoricNames', 'brand', 'IST')" hub:query-tool -n ist-brand

$(PET)/query-tool: $(PET)/client-maker
@echo ++ Instantiate QueryTool
endo eval "E(mkClient).makeQueryTool('http://localhost:1317')" mkClient:client-maker -n query-tool

send-tokens: $(PET)/validator-client
@echo ++ Send tokens
endo eval "E(client).sendTokens(\
'agoric14pfrxg63jn6ha0cp6wxkm4nlvswtscrh2pymwm', \
'agoric1a3zu5aqw255q0tuxzy9aftvgheekw2wedz3xwq', \
[{ denom: 'ubld', amount: '12345' }], 'auto').then( \
tx => tx.code === 0 ? tx.transactionHash : assert.fail(tx.rawLog))" client:validator-client

$(PET)/validator-client: $(PET)/validator-passkey $(PET)/client-maker $(PET)/cosmos-fetch
@echo ++ Instantiate SigningStargetClient
endo eval "E(key).get().then( \
m => E(mkClient).makeTxTool( \
m, E(net).makeRPCClient('http://localhost:26657')))" \
mkClient:client-maker key:validator-passkey net:cosmos-fetch \
-n validator-client

$(PET)/validator-signer: $(PET)/signer-maker $(PET)/validator-passkey
endo eval "E(key).get().then(m => E(mkSign).fromMnemonic(m))" mkSign:signer-maker key:validator-passkey -n validator-signer

$(PET)/client-maker: src/smartWallet.js
@echo ++ make endo plugin for SigningStargetClient
endo make --UNSAFE src/smartWallet.js -n client-maker

$(PET)/signer-maker: src/hdWallet.js
@echo ++ make endo plugin for hd wallets
endo make --UNSAFE src/hdWallet.js -n signer-maker

$(PET)/validator-passkey: $(PET)/desktop-secrets
endo eval "E(secrets).makePassKey({ username: 'validator' })" -n validator-passkey secrets:desktop-secrets

sync: $(PET)/synctool $(PET)/gcdb $(PET)/finsync
@echo ++ Push GnuCash transation ids to Sheetsync
endo eval "E(synctool).pushTxIds(sc, gc)" synctool gc:gcdb sc:finsync

$(PET)/synctool: src/txSync.js
@echo ++ instantiate confined sync tool
endo make src/txSync.js -n synctool

db: $(PET)/gdcb

watchdl: $(PET)/watchdl

$(PET)/watchdl: $(PET)/fsevents
@echo ++ make watcher for ~/Downloads
endo eval "'$${HOME}/Downloads'" -n dl
endo eval "E(fsevents).subWatcher(dl)" -n watchdl dl fsevents

$(PET)/fsevents: src/fileWatcher.js
@echo ++ make endo plugin for file watcher
endo make --UNSAFE src/fileWatcher.js -n fsevents

$(PET)/gcdb: $(PET)/hub1
@echo ++ Choose a sqlite3 database file path
endo eval "'$${GNUCASH_DB}'" -n p1
@echo ++ look up the path in the DBHub
endo eval "E(hub).lookup(path)" hub:hub1 path:p1 -n gcdb

$(PET)/hub1: ./src/dbTool.js
@echo ++ Instantiate sqlite3 database hub plugin
endo make -n hub1 --UNSAFE ./src/dbTool.js

sheet: $(PET)/finsync

$(PET)/finsync: $(PET)/fin-sync-creds $(PET)/gsheets
@echo ++ Load a spreadsheet based on item from desktop secret store.
endo eval "E(gsheets).load(item)" -n finsync gsheets item:fin-sync-creds

$(PET)/gsheets: src/sheetsTool.js
@echo ++ make endo plugin for Google Sheets
endo make --UNSAFE src/sheetsTool.js -n gsheets

$(PET)/fin-sync-creds: $(PET)/desktop-secrets
@echo ++ make a PassKey to access finSync Google Sheet
@echo ++ Assumes: secret-tool store --label='finSync' id 1-66j... title finSync \<project-id-661....json
endo eval "'$${SHEET1_ID}'" -n finsync-id
endo eval "E(secrets).makePassKey({ title: 'finSync', id })" -n fin-sync-creds secrets:desktop-secrets id:finsync-id

$(PET)/desktop-secrets: ../fincaps/src/secret-tool.js
@echo ++ make endo plugin for desktop secrets
endo make -n desktop-secrets --UNSAFE ../fincaps/src/secret-tool.js

clean:
endo reset
42 changes: 42 additions & 0 deletions packages/ag-trade/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "ag-trace",
"version": "0.1.0",
"type": "module",
"scripts": {
"lint-fix": "eslint --fix .",
"lint": "run-s --continue-on-error lint:*",
"lint:js": "eslint .",
"lint:types": "tsc -p jsconfig.json"
},
"devDependencies": {
"@agoric/smart-wallet": "^0.5.4-u12.0",
"@endo/cli": "/home/connolly/projects/endo/packages/cli",
"@fast-check/ava": "^1.1.5",
"@types/google-spreadsheet": "^4.0.0",
"ava": "^5.3.1",
"eslint": "^8.36.0",
"npm-run-all": "^4.1.5",
"typescript": "~5.1.6"
},
"ava": {
"files": [
"**/test-*.js"
]
},
"dependencies": {
"@agoric/cosmic-proto": "^0.3.0",
"@cosmjs/amino": "^0.31.3",
"@cosmjs/crypto": "^0.31.3",
"@cosmjs/encoding": "^0.31.3",
"@cosmjs/proto-signing": "^0.31.3",
"@cosmjs/stargate": "^0.31.3",
"@cosmjs/tendermint-rpc": "^0.31.3",
"@endo/far": "^0.2.21",
"@endo/patterns": "^0.2.5",
"better-sqlite3": "^8.5.2",
"google-auth-library": "^9.0.0",
"google-spreadsheet": "^4.0.2",
"minimatch": "^9.0.3",
"watcher": "^2.3.0"
}
}
32 changes: 32 additions & 0 deletions packages/ag-trade/src/agoricZone.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// @ts-check
import { MsgWalletSpendAction } from '@agoric/cosmic-proto/swingset/msgs.js';

import { Registry } from '@cosmjs/proto-signing';
import { GasPrice, defaultRegistryTypes } from '@cosmjs/stargate';

// https://github.com/Agoric/agoric-sdk/blob/master/golang/cosmos/daemon/main.go
export const Bech32MainPrefix = 'agoric';

export const CoinType = 564;

/**
* `/agoric.swingset.XXX` matches package agoric.swingset in swingset/msgs.proto
* aminoType taken from Type() in golang/cosmos/x/swingset/types/msgs.go
*/
export const SwingsetMsgs = {
MsgWalletSpendAction: {
typeUrl: '/agoric.swingset.MsgWalletSpendAction',
aminoType: 'swingset/WalletSpendAction',
},
};

export const SwingsetRegistry = new Registry([
...defaultRegistryTypes,
// XXX should this list be "upstreamed" to @agoric/cosmic-proto?
[SwingsetMsgs.MsgWalletSpendAction.typeUrl, MsgWalletSpendAction],
]);

// https://community.agoric.com/t/network-change-instituting-fees-on-the-agoric-chain-to-mitigate-spam-transactions/109/2
// minimum-gas-prices = "0.01ubld,0.005uist"
export const gasPriceStake = GasPrice.fromString('0.01ubld');
export const gasPriceStable = GasPrice.fromString('0.005uist');
175 changes: 175 additions & 0 deletions packages/ag-trade/src/batchQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// @ts-check
import { E } from '@endo/far';

/** @typedef {'children' | 'data'} AgoricChainStoragePathKind */
/** @template T @typedef {import('@endo/marshal').FromCapData<T>} FromCapData<T> */
/** @template T @typedef {import('@endo/eventual-send').ERef<T>} ERef<T> */

/**
* @param {[kind: AgoricChainStoragePathKind, item: string]} path
*/
export const pathToKey = path => path.join('.');

/** @param {string} key */
export const keyToPath = key => {
const [kind, ...rest] = key.split('.');
assert(kind === 'children' || kind === 'data');
/** @type {[kind: 'children' | 'data', item: string]} */
const out = [kind, rest.join('.')];
};

/**
* @template T
* @param {(value: string) => T} f
* @param {AsyncGenerator<string[], void, unknown>} chunks
*/
async function* mapHistory(f, chunks) {
for await (const chunk of chunks) {
if (chunk === undefined) continue;
for (const value of chunk.reverse()) {
yield f(value);
}
}
}

/**
* @param {ERef<import('./httpClient').LCD>} lcd
*/
export const makeVStorage = lcd => {
const getJSON = (href, options) => E(lcd).getJSON(href, options);

// height=0 is the same as omitting height and implies the highest block
const href = (path = 'published', { kind = 'data' } = {}) =>
`/agoric/vstorage/${kind}/${path}`;
const headers = height =>
height ? { 'x-cosmos-block-height': `${height}` } : undefined;

const readStorage = (
path = 'published',
{ kind = 'data', height = 0 } = {},
) =>
getJSON(href(path, { kind }), { headers: headers(height) }).catch(err => {
throw Error(`cannot read ${kind} of ${path}: ${err.message}`);
});
const readCell = (path, opts) =>
readStorage(path, opts)
.then(data => data.value)
.then(s => (s === '' ? {} : JSON.parse(s)));

/**
* Read values going back as far as available
*
* @param {string} path
* @param {number | string} [minHeight]
*/
async function* readHistory(path, minHeight = undefined) {
// undefined the first iteration, to query at the highest
let blockHeight;
await null;
do {
// console.debug('READING', { blockHeight });
/** @type {string[]} */
let values = [];
try {
({ blockHeight, values } = await readCell(path, {
kind: 'data',
height: blockHeight && Number(blockHeight) - 1,
}));
// console.debug('readAt returned', { blockHeight });
} catch (err) {
if (err.message.match(/unknown request/)) {
// XXX FIXME
// console.error(err);
break;
}
throw err;
}
yield values;
// console.debug('PUSHED', values);
// console.debug('NEW', { blockHeight, minHeight });
if (minHeight && Number(blockHeight) <= Number(minHeight)) break;
} while (blockHeight > 0);
}

/**
* @template T
* @param {(value: string) => T} f
* @param {string} path
* @param {number | string} [minHeight]
*/
const readHistoryBy = (f, path, minHeight) =>
mapHistory(f, readHistory(path, minHeight));

return {
lcd,
readStorage,
readCell,
readHistory,
readHistoryBy,
};
};

/** @typedef {ReturnType<typeof makeVStorage>} VStorage */

/** @param {string | unknown} d */
const parseIfJSON = d => {
if (typeof d !== 'string') return d;
try {
return JSON.parse(d);
} catch {
return d;
}
};

/**
* @param {ReturnType<makeVStorage>} vstorage
* @param {FromCapData<string>} unmarshal
* @param {[AgoricChainStoragePathKind, string][]} paths
*/
export const batchVstorageQuery = async (vstorage, unmarshal, paths) => {
const requests = paths.map(([kind, path]) =>
vstorage.readStorage(path, { kind }),
);

return Promise.all(requests).then(responses =>
responses.map((res, index) => {
if (paths[index][0] === 'children') {
return [
pathToKey(paths[index]),
{ value: res.children, blockHeight: undefined },
];
}

if (!res.value) {
return [
pathToKey(paths[index]),
{
error: `Cannot parse value of response for path [${
paths[index]
}]: ${JSON.stringify(res)}`,
},
];
}

const data = parseIfJSON(res.value);

const latestValue =
typeof data.values !== 'undefined'
? parseIfJSON(data.values[data.values.length - 1])
: parseIfJSON(data.value);

const unserialized =
typeof latestValue.slots !== 'undefined'
? unmarshal(latestValue)
: latestValue;

return [
pathToKey(paths[index]),
{
blockHeight: data.blockHeight,
value: unserialized,
},
];
}),
);
};
Loading