@kirkelabs/walletless-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LEGAL.md +79 -0
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/bin/cli.js +189 -0
- package/examples/raffle.js +101 -0
- package/package.json +73 -0
- package/src/audit.js +183 -0
- package/src/draw.js +197 -0
- package/src/identity.js +110 -0
- package/src/index.js +64 -0
- package/src/ledger.js +112 -0
- package/src/onboarding.js +120 -0
- package/src/privacy.js +98 -0
- package/src/receipt.js +138 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. Format: [Keep a Changelog](https://keepachangelog.com/);
|
|
4
|
+
versioning: [SemVer](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] — 2026-06-20
|
|
7
|
+
|
|
8
|
+
- Initial release. A walletless web-architecture toolkit built on
|
|
9
|
+
`@kirkelabs/open-agent-access-core` and `@kirkelabs/oaa-agent-kit`.
|
|
10
|
+
- **Onboarding** (`createEphemeralAccount`, `rotateAccount`, `expireAccount`,
|
|
11
|
+
`isExpired`) — tightly-scoped, round-relative auto-expiring custodial accounts,
|
|
12
|
+
authority-bounded via an oaa-agent-kit mandate.
|
|
13
|
+
- **Identity** (`OtpIdentity`) — email/SMS OTP adapter: CSPRNG codes, single-use,
|
|
14
|
+
expiring, rate-limited, lockout, constant-time compare; stores only keyed
|
|
15
|
+
(peppered) pseudonymous contact references.
|
|
16
|
+
- **Receipts** (`buildOrderReceipt`, `deterministicOrderId`, `attestOnChain`) —
|
|
17
|
+
hash-chained, signed, non-PII receipts with compact, size- and network-bound
|
|
18
|
+
on-chain attestation; x402-charged actions reuse oaa-agent-kit `payAndFetch`.
|
|
19
|
+
- **Audit** (`createTrail`, `append`, `merkleRoot`, `anchor`, `verifyTrail`) —
|
|
20
|
+
append-only hash-chained events + an RFC 6962-style domain-separated Merkle root,
|
|
21
|
+
periodically anchored on-chain; tamper/removal is detectable.
|
|
22
|
+
- **Ledger** (`createLedger`, `reconciliationSheet`) — three segregated append-only
|
|
23
|
+
books (inflow/charity/escrow), integer-only money, immutable snapshots, and a
|
|
24
|
+
human-readable per-draw reconciliation sheet.
|
|
25
|
+
- **Draw** (`runDraw`, `publishDrawProof`, `blockHashSeed`/`vrfSeed`/`beaconSeed`) —
|
|
26
|
+
deterministic, recomputable winner selection (seeded Fisher–Yates, rejection
|
|
27
|
+
sampling, no `Math.random`), with a commit step and honest seed-manipulability docs.
|
|
28
|
+
Ships a frozen test vector.
|
|
29
|
+
- **Privacy** (`hashPii`, `pseudonymRef`, `eraseSubject`) — keyed hashing and
|
|
30
|
+
random, erasable references; PII off-chain only.
|
|
31
|
+
- CLI (`walletless init|keygen|draw|reconcile|verify|help`), a TestNet raffle
|
|
32
|
+
example, README, SECURITY, and LEGAL (gambling + AML + data-protection). MIT.
|
|
33
|
+
|
|
34
|
+
> ⚠ Some modules are **EXPERIMENTAL · UNAUDITED**. TestNet by default; obtain an
|
|
35
|
+
> independent audit before holding material value or processing real personal data.
|
package/LEGAL.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Legal, regulatory & acceptable-use notice
|
|
2
|
+
|
|
3
|
+
`@kirkelabs/walletless-kit` is free, open-source developer software (MIT). It provides
|
|
4
|
+
**transparency and bookkeeping tooling** for walletless commerce and prize-draws — it
|
|
5
|
+
does **not** provide legal, regulatory, financial, or compliance services, and using it
|
|
6
|
+
does not make your activity lawful. This notice is **not legal advice**; if you operate
|
|
7
|
+
in any regulated context, take your own advice. You, the operator, are solely
|
|
8
|
+
responsible for compliance with all applicable laws in every jurisdiction you serve.
|
|
9
|
+
|
|
10
|
+
## Nature of the software
|
|
11
|
+
|
|
12
|
+
A toolkit of independent modules: ephemeral custodial onboarding, receipt-only on-chain
|
|
13
|
+
proofs, a tamper-evident audit trail, segregated money ledgers, a verifiable draw, and
|
|
14
|
+
privacy/erasure helpers. Kirke Labs operates no servers in your flow, holds no funds, and
|
|
15
|
+
holds no keys or personal data. Everything runs in **your** infrastructure under **your**
|
|
16
|
+
control.
|
|
17
|
+
|
|
18
|
+
## 1. Prize draws, raffles & lotteries (gambling law)
|
|
19
|
+
|
|
20
|
+
Prize draws, raffles, sweepstakes, and lotteries are **heavily regulated** and the rules
|
|
21
|
+
differ by country, state, and province (e.g. the UK Gambling Act 2005 and the Gambling
|
|
22
|
+
Commission; US state lottery/raffle statutes; varied EU regimes). **This package ships
|
|
23
|
+
transparency tooling, not legal compliance.** In particular, **you** are responsible for:
|
|
24
|
+
|
|
25
|
+
- obtaining any **licence/registration/exempt-lottery** status you need;
|
|
26
|
+
- providing a genuine **free-entry route** of equal standing where required;
|
|
27
|
+
- **age and geographic gating** (excluding minors and prohibited jurisdictions);
|
|
28
|
+
- prize fulfilment, terms & conditions, advertising rules, and record-keeping;
|
|
29
|
+
- handling and protecting entrant funds appropriately.
|
|
30
|
+
|
|
31
|
+
A "verifiable" or "recomputable" draw means the winner can be re-derived from a published
|
|
32
|
+
seed — it is **not** a representation that your draw is lawful, nor a substitute for
|
|
33
|
+
licensing. **Draw fairness is exactly as strong as the public seed you choose** (see the
|
|
34
|
+
README): block-hash seeds are validator-manipulable; use the commit step and prefer a VRF
|
|
35
|
+
or randomness beacon for anything of value. Do not describe a draw as "provably fair"
|
|
36
|
+
beyond what the chosen seed actually guarantees.
|
|
37
|
+
|
|
38
|
+
## 2. Money handling, custody & AML
|
|
39
|
+
|
|
40
|
+
The kit can hold value in **ephemeral custodial accounts** and **segregated escrow/charity
|
|
41
|
+
ledgers**. Holding or transmitting other people's money may make you a regulated entity
|
|
42
|
+
(money transmission / e-money / payment services) and typically triggers **AML/CFT and
|
|
43
|
+
sanctions** obligations (KYC, screening, record-keeping). The custodial keys are
|
|
44
|
+
**dev/TestNet-grade**: production custody requires your own KMS/HSM, segregated trust
|
|
45
|
+
accounts, reconciliation controls, and an independent audit. The kit performs **no** KYC,
|
|
46
|
+
sanctions screening, or licensing checks — those are yours. It transacts in the native
|
|
47
|
+
Algorand coin (ALGO) only and is **TestNet by default**; MainNet is an explicit,
|
|
48
|
+
cautioned opt-in.
|
|
49
|
+
|
|
50
|
+
## 3. Personal data & privacy (GDPR / data protection)
|
|
51
|
+
|
|
52
|
+
If you process entrants' contact details or other personal data, **you are the data
|
|
53
|
+
controller**. The kit keeps personal data **off-chain** (you encrypt and can erase it) and
|
|
54
|
+
places only **non-identifying references** on-chain. Two honest limits:
|
|
55
|
+
|
|
56
|
+
- **Hashed contact references are pseudonymous, not anonymous** — they remain *personal
|
|
57
|
+
data*. Use the keyed (peppered) hashing provided, never bare hashes of low-entropy
|
|
58
|
+
values like emails or phone numbers.
|
|
59
|
+
- **The blockchain is immutable.** "Erasure" works only because on-chain data is designed
|
|
60
|
+
to be non-identifying and unlinkable once the off-chain record (and any random reference
|
|
61
|
+
mapping) is deleted. Do **not** put personal data on-chain; if you do, it cannot be
|
|
62
|
+
erased. You remain responsible for lawful basis, retention, subject-access, and erasure.
|
|
63
|
+
|
|
64
|
+
## No warranty; experimental
|
|
65
|
+
|
|
66
|
+
Provided **"as is", without warranty** (see [LICENSE](./LICENSE)). Modules — and especially
|
|
67
|
+
anything labelled **EXPERIMENTAL · UNAUDITED** — are not guaranteed fit for any purpose.
|
|
68
|
+
Crypto-assets are volatile and largely unregulated. **You may lose funds and you bear all
|
|
69
|
+
compliance risk.** Nothing here is financial, legal, or investment advice.
|
|
70
|
+
|
|
71
|
+
## Jurisdiction
|
|
72
|
+
|
|
73
|
+
Published globally as open source; **not targeted at any jurisdiction** and not an offer to
|
|
74
|
+
provide any regulated service. You determine whether your use is lawful where you operate.
|
|
75
|
+
|
|
76
|
+
## Reporting
|
|
77
|
+
|
|
78
|
+
Security issues: **security@kirkelabs.com** (please do not open public issues for
|
|
79
|
+
vulnerabilities affecting funds or personal data).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kirke Labs — Soleman El Gelawi and Steve Kirton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @kirkelabs/walletless-kit
|
|
2
|
+
|
|
3
|
+
[](./LICENSE)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://algorand.co)
|
|
6
|
+
|
|
7
|
+
**Walletless web architecture: onboard users with no wallet, prove what happened with receipts (not personal data), keep a tamper-evident audit trail and clean money trails, and run a draw anyone can recompute.** Charity prize-draws are the flagship example; every module is reusable for any walletless commerce flow.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @kirkelabs/walletless-kit
|
|
11
|
+
npx walletless init my-raffle
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> ⚠️ **Experimental developer tooling, provided "as is."** It handles **custodial keys, money, and personal data**, and prize draws are **regulated**. This is *transparency tooling, not legal compliance*. Read **[Guardrails](#guardrails)** and **[LEGAL.md](./LEGAL.md)** before using real funds or real personal data. Nothing here is financial or legal advice. TestNet by default.
|
|
15
|
+
|
|
16
|
+
Built on [`@kirkelabs/open-agent-access-core`](https://www.npmjs.com/package/@kirkelabs/open-agent-access-core) (proof/receipt spine) and [`@kirkelabs/oaa-agent-kit`](https://www.npmjs.com/package/@kirkelabs/oaa-agent-kit) (Algorand spend/identity/x402). The only new cryptographic primitive here is the Merkle root in `audit.js`.
|
|
17
|
+
|
|
18
|
+
## What's in the box
|
|
19
|
+
|
|
20
|
+
| Module | What it does |
|
|
21
|
+
|--------|--------------|
|
|
22
|
+
| **onboarding** | `createEphemeralAccount` / `rotateAccount` / `expireAccount` / `isExpired` — tightly-scoped, **round-relative auto-expiring** custodial accounts; authority bounded by an oaa-agent-kit mandate. |
|
|
23
|
+
| **identity** | `OtpIdentity` — email/SMS OTP: CSPRNG codes, single-use, expiring, rate-limited, lockout, constant-time compare; stores only **keyed (peppered) pseudonymous** contact refs. |
|
|
24
|
+
| **receipt** | `buildOrderReceipt` / `deterministicOrderId` / `signReceipt` / `verifyReceiptChain` / `attestOnChain` — hash-chained, signed, **non-PII** receipts; only the receipt *hash* goes on-chain. x402 actions via `chargeForAction`. |
|
|
25
|
+
| **audit** | `createTrail` / `append` / `merkleRoot` / `merkleProof` / `verifyMerkleProof` / `anchor` / `verifyTrail` — append-only hash-chained events + an **RFC 6962** Merkle root, periodically anchored on-chain. |
|
|
26
|
+
| **ledger** | `createLedger` — three segregated append-only books (inflow / charity / escrow), **integer-only money**, immutable snapshots, and a per-draw `reconciliationSheet`. |
|
|
27
|
+
| **draw** | `runDraw` / `publishDrawProof` / `verifyDraw` + `commitSeedSource` / `blockHashSeed` / `vrfSeed` / `beaconSeed` — deterministic, recomputable winner selection (no `Math.random`). |
|
|
28
|
+
| **privacy** | `hashPii` / `pseudonymRef` / `eraseSubject` / `assertNoPii` — keyed hashing and random, **erasable** references; PII stays off-chain. |
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import {
|
|
34
|
+
createLedger, OtpIdentity, buildOrderReceipt, createTrail, append,
|
|
35
|
+
runDraw, publishDrawProof, verifyDraw,
|
|
36
|
+
} from '@kirkelabs/walletless-kit';
|
|
37
|
+
|
|
38
|
+
// 1) Identity-lite: OTP, storing only a keyed pseudonymous ref (never the email).
|
|
39
|
+
const id = new OtpIdentity({ pepper: process.env.PEPPER, send: sendEmail });
|
|
40
|
+
await id.issueChallenge('alice@example.com');
|
|
41
|
+
const { ok, contactRef } = await id.verifyChallenge('alice@example.com', code);
|
|
42
|
+
|
|
43
|
+
// 2) A non-PII order receipt, hash-chained and (optionally) signed.
|
|
44
|
+
const receipt = buildOrderReceipt({ orderId: 'o1', action: 'buy_ticket', quantity: 1, price: '1000000', agent: contactRef });
|
|
45
|
+
|
|
46
|
+
// 3) A tamper-evident audit trail.
|
|
47
|
+
let trail = createTrail();
|
|
48
|
+
trail = append(trail, { type: 'ticket_sold', orderId: 'o1' });
|
|
49
|
+
|
|
50
|
+
// 4) Segregated money books.
|
|
51
|
+
const ledger = createLedger();
|
|
52
|
+
ledger.post({ book: 'inflow', amountMicro: 1_000_000, kind: 'ticket', txRef: 'tx1' });
|
|
53
|
+
|
|
54
|
+
// 5) A verifiable draw — anyone can recompute the winner from the proof.
|
|
55
|
+
const entries = [contactRef /* … */];
|
|
56
|
+
const proof = publishDrawProof({ entries, seed: committedSeed, winners: 1 });
|
|
57
|
+
console.log(verifyDraw(proof, entries).ok); // true
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Run the full on-chain flow on TestNet: [`examples/raffle.js`](./examples/raffle.js).
|
|
61
|
+
|
|
62
|
+
## CLI
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
walletless init [dir] # scaffold a starter raffle (ships a .gitignore)
|
|
66
|
+
walletless keygen --out owner.json # dev account, written 0600 (never printed)
|
|
67
|
+
walletless draw --entries entries.json --seed <s> # run + print a recomputable draw proof
|
|
68
|
+
walletless reconcile --ledger ledger.json # print a reconciliation sheet
|
|
69
|
+
walletless verify --proof proof.json --entries entries.json # recompute a draw / audit trail
|
|
70
|
+
walletless help
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Guardrails
|
|
74
|
+
|
|
75
|
+
These are non-negotiable; the modules enforce or document them.
|
|
76
|
+
|
|
77
|
+
- **TestNet by default.** MainNet is an explicit, cautioned opt-in.
|
|
78
|
+
- **Custodial keys are sensitive.** Ephemeral account keys are server-held, tightly-scoped, and auto-expiring — and **dev/TestNet-grade**. JS cannot wipe key memory; production custody needs your own **KMS/HSM** and an independent audit. Secret keys are never logged, serialized into receipts/events/snapshots, or written on-chain.
|
|
79
|
+
- **Personal data stays off-chain.** The chain holds only non-identifying references. **Hashed contact refs are pseudonymous, not anonymous** — still personal data; use the keyed (peppered) hashing provided, never bare hashes. For erasable references, `pseudonymRef` gives a **random, deletable** on-chain ref so `eraseSubject` truly unlinks the subject.
|
|
80
|
+
- **Draw fairness equals the seed — no more.** A block-hash seed is **manipulable** (a block producer can withhold/grind a block). Use `commitSeedSource` to announce the exact future round **before entries close**, and prefer a **VRF / drand beacon** for anything of value. Winner selection is a deterministic, recomputable function of `(seed, entries)` with rejection sampling — there is **no `Math.random`**. Don't call a draw "provably fair" beyond what your seed guarantees.
|
|
81
|
+
- **Money is integer microALGO.** Never floats (they drift and break reconciliation). Ledger books are append-only with immutable snapshots.
|
|
82
|
+
- **Regulated use.** Prize draws/lotteries are regulated; custodial money handling implies AML/custody duties; processing entrant data makes you a **GDPR data controller**. This package ships transparency tooling, **not** legal compliance — you own licensing, the **free-entry route**, and **age/geo gating**. See **[LEGAL.md](./LEGAL.md)**.
|
|
83
|
+
- **Anything experimental is labelled EXPERIMENTAL · UNAUDITED.** Get an independent audit before holding material value or processing real personal data.
|
|
84
|
+
|
|
85
|
+
## Supply chain
|
|
86
|
+
|
|
87
|
+
Pinned dependency floors + committed lockfile; CI runs lint, tests, and `npm audit` on Node 20 & 22 with least-privilege permissions and SHA-pinned actions; `release.yml` publishes with **npm provenance** via OIDC. Report security issues to **security@kirkelabs.com** ([SECURITY.md](./SECURITY.md)).
|
|
88
|
+
|
|
89
|
+
## Licence
|
|
90
|
+
|
|
91
|
+
[MIT](./LICENSE) © 2026 Kirke Labs — [www.kirkelabs.com](https://www.kirkelabs.com)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* walletless-kit CLI
|
|
4
|
+
*
|
|
5
|
+
* walletless init [dir] scaffold a starter raffle project
|
|
6
|
+
* walletless keygen [--out <file>] generate a dev Algorand account
|
|
7
|
+
* walletless draw --entries <f> --seed <s> [--winners N] run + print a draw proof
|
|
8
|
+
* walletless reconcile --ledger <f> [--draw id] print a reconciliation sheet
|
|
9
|
+
* walletless verify --proof <f> [--entries <f>] recompute a draw proof / audit trail
|
|
10
|
+
* walletless help
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { writeFile, mkdir, readFile } from 'node:fs/promises';
|
|
14
|
+
import { resolve, join } from 'node:path';
|
|
15
|
+
import {
|
|
16
|
+
algosdk,
|
|
17
|
+
publishDrawProof,
|
|
18
|
+
verifyDraw,
|
|
19
|
+
verifyTrail,
|
|
20
|
+
createLedger,
|
|
21
|
+
} from '../src/index.js';
|
|
22
|
+
|
|
23
|
+
// Copied from @kirkelabs/oaa-agent-kit — same flag-parsing convention.
|
|
24
|
+
function parse(argv) {
|
|
25
|
+
const o = { _: [] };
|
|
26
|
+
for (let i = 3; i < argv.length; i++) {
|
|
27
|
+
const a = argv[i];
|
|
28
|
+
if (a.startsWith('--')) o[a.slice(2)] = argv[i + 1]?.startsWith('--') ? true : argv[++i];
|
|
29
|
+
else o._.push(a);
|
|
30
|
+
}
|
|
31
|
+
return o;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(msg) {
|
|
35
|
+
console.error(`✗ ${msg}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const cmd = process.argv[2];
|
|
41
|
+
const o = parse(process.argv);
|
|
42
|
+
|
|
43
|
+
if (cmd === 'keygen') {
|
|
44
|
+
const a = algosdk.generateAccount();
|
|
45
|
+
const account = { address: String(a.addr), mnemonic: algosdk.secretKeyToMnemonic(a.sk) };
|
|
46
|
+
if (o.out) {
|
|
47
|
+
await writeFile(resolve(o.out), JSON.stringify(account, null, 2), { mode: 0o600 });
|
|
48
|
+
console.log(`Wrote account to ${resolve(o.out)} (keep the mnemonic secret).`);
|
|
49
|
+
} else {
|
|
50
|
+
console.log(JSON.stringify(account, null, 2));
|
|
51
|
+
console.log(
|
|
52
|
+
'\n⚠ The mnemonic above is a SECRET (printed to your terminal). Anyone with it' +
|
|
53
|
+
'\n controls the account. Prefer: walletless keygen --out owner.json' +
|
|
54
|
+
'\n⚠ Dev only. Fund on TestNet via https://bank.testnet.algorand.network/',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (cmd === 'draw') {
|
|
61
|
+
if (!o.entries || !o.seed) return fail('draw requires --entries <file.json> and --seed <seed>');
|
|
62
|
+
const entries = JSON.parse(await readFile(resolve(o.entries), 'utf8'));
|
|
63
|
+
const proof = publishDrawProof({
|
|
64
|
+
entries,
|
|
65
|
+
seed: String(o.seed),
|
|
66
|
+
winners: parseInt(o.winners || '1', 10),
|
|
67
|
+
});
|
|
68
|
+
console.log(JSON.stringify(proof, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (cmd === 'reconcile') {
|
|
73
|
+
if (!o.ledger) return fail('reconcile requires --ledger <file.json> (an array of ledger entries or {drawId,entries})');
|
|
74
|
+
const data = JSON.parse(await readFile(resolve(o.ledger), 'utf8'));
|
|
75
|
+
const entries = Array.isArray(data) ? data : data.entries || [];
|
|
76
|
+
const l = createLedger();
|
|
77
|
+
for (const e of entries) l.post(e);
|
|
78
|
+
const drawId = o.draw || data.drawId || 'draw';
|
|
79
|
+
console.log(JSON.stringify(l.reconciliationSheet(drawId, { winnerProofLink: o.proof || null }), null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (cmd === 'verify') {
|
|
84
|
+
const path = o.proof || o.file || o._[0];
|
|
85
|
+
if (!path) return fail('verify requires --proof <file.json> (a draw proof or an audit trail)');
|
|
86
|
+
const obj = JSON.parse(await readFile(resolve(path), 'utf8'));
|
|
87
|
+
if (obj && obj.entriesRoot) {
|
|
88
|
+
if (!o.entries) return fail('verifying a draw proof also requires --entries <file.json>');
|
|
89
|
+
const entries = JSON.parse(await readFile(resolve(o.entries), 'utf8'));
|
|
90
|
+
console.log(JSON.stringify(verifyDraw(obj, entries), null, 2));
|
|
91
|
+
} else {
|
|
92
|
+
console.log(JSON.stringify(verifyTrail(obj), null, 2));
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (cmd === 'init') {
|
|
98
|
+
const dir = resolve(o._[0] || 'my-raffle');
|
|
99
|
+
await mkdir(dir, { recursive: true });
|
|
100
|
+
await writeFile(join(dir, 'package.json'), STARTER_PKG);
|
|
101
|
+
await writeFile(join(dir, 'raffle.js'), STARTER_RAFFLE);
|
|
102
|
+
await writeFile(join(dir, '.env.example'), STARTER_ENV);
|
|
103
|
+
await writeFile(join(dir, '.gitignore'), STARTER_GITIGNORE);
|
|
104
|
+
await writeFile(join(dir, 'README.md'), STARTER_README);
|
|
105
|
+
console.log(
|
|
106
|
+
`Scaffolded a raffle in ${dir}\n cd ${dir} && npm install && cp .env.example .env && node raffle.js`,
|
|
107
|
+
);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
help();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function help() {
|
|
115
|
+
console.log(`
|
|
116
|
+
walletless-kit — walletless web architecture (onboarding · proofs · audit · ledger · draw)
|
|
117
|
+
|
|
118
|
+
Commands
|
|
119
|
+
init [dir] scaffold a starter raffle project
|
|
120
|
+
keygen [--out <file>] generate a dev Algorand account (mnemonic)
|
|
121
|
+
draw --entries <f> --seed <s> [--winners N]
|
|
122
|
+
run a draw and print its recomputable proof
|
|
123
|
+
reconcile --ledger <f> [--draw id] print a per-draw reconciliation sheet
|
|
124
|
+
verify --proof <f> [--entries <f>] recompute a draw proof or an audit trail
|
|
125
|
+
help
|
|
126
|
+
|
|
127
|
+
TestNet by default. Prize draws/lotteries are regulated — see LEGAL.md.
|
|
128
|
+
MIT · Kirke Labs · free & open source
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const STARTER_PKG = `{
|
|
133
|
+
"name": "my-raffle",
|
|
134
|
+
"private": true,
|
|
135
|
+
"type": "module",
|
|
136
|
+
"dependencies": { "@kirkelabs/walletless-kit": "^0.1.0" }
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
const STARTER_ENV = `# Operator account (dev). Generate with: npx walletless keygen --out owner.json
|
|
141
|
+
# SECRET — never commit your real .env. The 25 words control all funds.
|
|
142
|
+
OPERATOR_MNEMONIC="word1 word2 ... word25"
|
|
143
|
+
NETWORK=algorand-testnet
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
const STARTER_GITIGNORE = `# Secrets — NEVER commit these. The mnemonic controls all funds.
|
|
147
|
+
.env
|
|
148
|
+
*.key
|
|
149
|
+
owner.json
|
|
150
|
+
node_modules/
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const STARTER_RAFFLE = `import {
|
|
154
|
+
getAlgod, runDraw, publishDrawProof, verifyDraw,
|
|
155
|
+
createLedger, OtpIdentity, deterministicOrderId,
|
|
156
|
+
} from '@kirkelabs/walletless-kit';
|
|
157
|
+
|
|
158
|
+
// A minimal, OFFLINE walletless raffle: entries -> draw -> proof -> verify.
|
|
159
|
+
// See the package's examples/raffle.js for the full on-chain TestNet flow.
|
|
160
|
+
const entries = ['ref_alice', 'ref_bob', 'ref_carol', 'ref_dave'];
|
|
161
|
+
const seed = 'demo-seed-replace-with-a-committed-block-hash';
|
|
162
|
+
|
|
163
|
+
const proof = publishDrawProof({ entries, seed, winners: 1 });
|
|
164
|
+
console.log('Winner:', proof.winners[0]);
|
|
165
|
+
console.log('Proof verifies:', verifyDraw(proof, entries).ok);
|
|
166
|
+
|
|
167
|
+
// NOTE: a real draw must use a COMMITTED public seed (announce the future block
|
|
168
|
+
// round before entries close) — see the README guardrails. Prize draws are
|
|
169
|
+
// regulated; you own licensing, the free-entry route, and age/geo gating.
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
const STARTER_README = `# my-raffle
|
|
173
|
+
|
|
174
|
+
A starter walletless raffle built with @kirkelabs/walletless-kit.
|
|
175
|
+
|
|
176
|
+
1. \`npx walletless keygen --out owner.json\` (TestNet operator account)
|
|
177
|
+
2. Fund it on TestNet: https://bank.testnet.algorand.network/
|
|
178
|
+
3. \`npm install && node raffle.js\`
|
|
179
|
+
|
|
180
|
+
⚠ Prize draws/lotteries are REGULATED. This is transparency tooling, not legal
|
|
181
|
+
compliance: you are responsible for licensing, a genuine free-entry route, and
|
|
182
|
+
age/geo gating. Draw fairness equals the public seed you choose — use a committed
|
|
183
|
+
block hash or a VRF/beacon. See the package LEGAL.md.
|
|
184
|
+
`;
|
|
185
|
+
|
|
186
|
+
main().catch((e) => {
|
|
187
|
+
console.error(e.message || e);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* examples/raffle.js — a full walletless charity prize-draw on Algorand TestNet.
|
|
3
|
+
*
|
|
4
|
+
* Flow: ephemeral guest account -> OTP (stub) -> non-PII order receipts +
|
|
5
|
+
* on-chain attestation -> audit trail + on-chain anchor -> a draw seeded by a
|
|
6
|
+
* TestNet block hash -> reconciliation sheet -> verify recomputes the winner.
|
|
7
|
+
*
|
|
8
|
+
* Run: OPERATOR_MNEMONIC="..." node examples/raffle.js
|
|
9
|
+
* (Fund the operator on TestNet: https://bank.testnet.algorand.network/)
|
|
10
|
+
*
|
|
11
|
+
* ⚠ Demo only. Prize draws are regulated and a block-hash seed is manipulable —
|
|
12
|
+
* see the README guardrails and LEGAL.md. TestNet by default.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getAlgod,
|
|
17
|
+
createEphemeralAccount,
|
|
18
|
+
isExpired,
|
|
19
|
+
OtpIdentity,
|
|
20
|
+
buildOrderReceipt,
|
|
21
|
+
deterministicOrderId,
|
|
22
|
+
attestOnChain,
|
|
23
|
+
createTrail,
|
|
24
|
+
append,
|
|
25
|
+
trailRoot,
|
|
26
|
+
anchor,
|
|
27
|
+
createLedger,
|
|
28
|
+
publishDrawProof,
|
|
29
|
+
verifyDraw,
|
|
30
|
+
blockHashSeed,
|
|
31
|
+
} from '@kirkelabs/walletless-kit';
|
|
32
|
+
import { LocalOwnerSigner } from '@kirkelabs/oaa-agent-kit';
|
|
33
|
+
|
|
34
|
+
const mnemonic = process.env.OPERATOR_MNEMONIC;
|
|
35
|
+
if (!mnemonic) throw new Error('Set OPERATOR_MNEMONIC (a funded TestNet account).');
|
|
36
|
+
|
|
37
|
+
const algod = getAlgod({ network: 'algorand-testnet' });
|
|
38
|
+
const operator = new LocalOwnerSigner({ mnemonic });
|
|
39
|
+
const PEPPER = process.env.PEPPER || 'demo-pepper-at-least-16-chars';
|
|
40
|
+
const log = (...a) => console.log(...a);
|
|
41
|
+
|
|
42
|
+
log('Operator:', operator.address);
|
|
43
|
+
const sp = await algod.getTransactionParams().do();
|
|
44
|
+
log('Current round ~', Number(sp.firstValid), '\n');
|
|
45
|
+
|
|
46
|
+
// 1) Ephemeral custodial guest account (round-relative expiry).
|
|
47
|
+
const guest = await createEphemeralAccount({ algod, ttlRounds: 10_000, scope: { perTxMicroAlgos: 1_000_000 } });
|
|
48
|
+
log('[1] Ephemeral guest account:', guest.address);
|
|
49
|
+
log(' expiresRound', guest.expiryRound, '| expired now?', isExpired(guest, Number(sp.firstValid)));
|
|
50
|
+
|
|
51
|
+
// 2) Identity-lite: OTP (we capture the code instead of emailing it).
|
|
52
|
+
const codes = {};
|
|
53
|
+
const id = new OtpIdentity({ pepper: PEPPER, send: async (c, code) => (codes[c] = code) });
|
|
54
|
+
const entrants = ['alice@example.com', 'bob@example.com', 'carol@example.com'];
|
|
55
|
+
const refs = [];
|
|
56
|
+
for (const e of entrants) {
|
|
57
|
+
await id.issueChallenge(e);
|
|
58
|
+
const v = await id.verifyChallenge(e, codes[e]);
|
|
59
|
+
refs.push(v.contactRef);
|
|
60
|
+
}
|
|
61
|
+
log('\n[2] OTP-verified', refs.length, 'entrants (stored as keyed refs, not emails)');
|
|
62
|
+
|
|
63
|
+
// 3) Non-PII order receipts (hash-chained) + attest the latest hash on-chain.
|
|
64
|
+
const ledger = createLedger();
|
|
65
|
+
let trail = createTrail();
|
|
66
|
+
let prevHash = null;
|
|
67
|
+
let lastReceipt = null;
|
|
68
|
+
refs.forEach((ref, i) => {
|
|
69
|
+
const orderId = deterministicOrderId({ ref, item: 'raffle-ticket', n: i });
|
|
70
|
+
const receipt = buildOrderReceipt({ orderId, action: 'buy_ticket', quantity: 1, price: '1000000', agent: ref, previousHash: prevHash });
|
|
71
|
+
prevHash = receipt.receiptHash;
|
|
72
|
+
lastReceipt = receipt;
|
|
73
|
+
ledger.post({ book: 'inflow', amountMicro: 1_000_000, kind: 'ticket', ref: orderId });
|
|
74
|
+
trail = append(trail, { type: 'ticket_sold', orderId, eventId: `evt_${i}` });
|
|
75
|
+
});
|
|
76
|
+
const att = await attestOnChain(algod, operator, lastReceipt);
|
|
77
|
+
log('\n[3] Receipts chained; latest hash attested on-chain in round', att.confirmedRound);
|
|
78
|
+
log(' txid', att.txid);
|
|
79
|
+
|
|
80
|
+
// 4) Audit trail -> Merkle root -> on-chain anchor.
|
|
81
|
+
const root = trailRoot(trail);
|
|
82
|
+
const anc = await anchor(algod, operator, root);
|
|
83
|
+
log('\n[4] Audit Merkle root', root.slice(0, 16) + '… anchored in round', anc.confirmedRound);
|
|
84
|
+
|
|
85
|
+
// 5) Allocate fee + charity in segregated books.
|
|
86
|
+
ledger.post({ book: 'inflow', amountMicro: 100_000, kind: 'fee' });
|
|
87
|
+
ledger.post({ book: 'charity', amountMicro: 2_500_000, ref: operator.address, kind: 'charity_payout', txRef: att.txid });
|
|
88
|
+
|
|
89
|
+
// 6) Draw, seeded by a recent TestNet block hash (committed in advance in production).
|
|
90
|
+
const seedRound = Number(sp.firstValid) - 5; // a confirmed round
|
|
91
|
+
const seed = await blockHashSeed(algod, seedRound);
|
|
92
|
+
log('\n[5] Seed from TestNet block', seedRound, '(⚠ manipulable — commit in advance / use VRF for value)');
|
|
93
|
+
const proof = publishDrawProof({ entries: refs, seed: seed.value, winners: 1 });
|
|
94
|
+
log(' Winner ref:', proof.winners[0]);
|
|
95
|
+
|
|
96
|
+
// 7) Reconciliation sheet + independent verification.
|
|
97
|
+
const sheet = ledger.reconciliationSheet('demo-draw-1', { winnerProofLink: `algo:${anc.txid}` });
|
|
98
|
+
log('\n[6] Reconciliation:', JSON.stringify(sheet.charity), '| net', sheet.netMicroAlgos, 'µALGO');
|
|
99
|
+
log('[7] verifyDraw recomputes the winner:', verifyDraw(proof, refs).ok);
|
|
100
|
+
|
|
101
|
+
log('\n✅ Walletless raffle complete on TestNet: ephemeral onboarding → OTP → receipts+attestation → audit anchor → verifiable draw → reconciliation.');
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kirkelabs/walletless-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Walletless web architecture toolkit: low-friction onboarding (ephemeral custodial accounts), receipt-only on-chain proofs, a tamper-evident hash-chained + Merkle audit trail, segregated money ledgers, and a verifiable, recomputable draw. Charity prize-draws are the flagship example; every module is reusable for any walletless commerce flow. Built on @kirkelabs/open-agent-access-core and @kirkelabs/oaa-agent-kit. Free & open source from Kirke Labs.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"walletless": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"examples/",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"LEGAL.md",
|
|
18
|
+
"README.md",
|
|
19
|
+
"CHANGELOG.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test",
|
|
26
|
+
"lint": "eslint . --ext .js",
|
|
27
|
+
"format": "prettier --write \"**/*.{js,json,md}\"",
|
|
28
|
+
"prepublishOnly": "npm run lint && npm test"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"walletless",
|
|
32
|
+
"algorand",
|
|
33
|
+
"custodial",
|
|
34
|
+
"onboarding",
|
|
35
|
+
"audit-trail",
|
|
36
|
+
"merkle",
|
|
37
|
+
"verifiable-draw",
|
|
38
|
+
"raffle",
|
|
39
|
+
"charity",
|
|
40
|
+
"receipts",
|
|
41
|
+
"x402",
|
|
42
|
+
"oaa",
|
|
43
|
+
"privacy",
|
|
44
|
+
"gdpr",
|
|
45
|
+
"cli",
|
|
46
|
+
"nodejs"
|
|
47
|
+
],
|
|
48
|
+
"author": "Soleman El Gelawi <soleman@kirkelabs.com> (https://www.kirkelabs.com)",
|
|
49
|
+
"contributors": [
|
|
50
|
+
"Steve Kirton <steve@kirkelabs.com> (https://www.kirkelabs.com)"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"homepage": "https://github.com/KirkeLabs/walletless-kit",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/KirkeLabs/walletless-kit.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/KirkeLabs/walletless-kit/issues"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=20"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"algosdk": "^3.6.0",
|
|
66
|
+
"@kirkelabs/open-agent-access-core": "^0.1.0",
|
|
67
|
+
"@kirkelabs/oaa-agent-kit": "^0.7.1"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"eslint": "^9.0.0",
|
|
71
|
+
"prettier": "^3.0.0"
|
|
72
|
+
}
|
|
73
|
+
}
|