getcofund 0.1.0 → 0.2.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/README.md +57 -0
- package/dist/api-types.d.ts +201 -0
- package/dist/api-types.js +124 -5
- package/dist/cli/context.d.ts +1 -1
- package/dist/cli/context.js +5 -0
- package/dist/cli/invite.d.ts +10 -1
- package/dist/cli/invite.js +81 -3
- package/dist/cli/policies.d.ts +17 -0
- package/dist/cli/policies.js +100 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +25 -1
- package/dist/client.d.ts +63 -1
- package/dist/client.js +25 -1
- package/dist/eip712.d.ts +14 -0
- package/dist/eip712.js +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/invite.d.ts +28 -1
- package/dist/invite.js +40 -1
- package/dist/policies.d.ts +69 -0
- package/dist/policies.js +132 -0
- package/dist/requests.d.ts +1 -1
- package/dist/requests.js +21 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ With npx, put `--` first when passing flags, or npm takes flags such as `--help`
|
|
|
23
23
|
cofund keys init [--force]
|
|
24
24
|
cofund me
|
|
25
25
|
cofund invite accept [--invite <id>] [--company <name>]
|
|
26
|
+
cofund invite create --email <email> --first-name <name> --last-name <name> --position <title> [--admin] [--chains A,B] [--yes]
|
|
27
|
+
cofund policies create --vault <id> --name <name> --signers <id|email,...> --threshold <n>
|
|
28
|
+
(--token <address|BTC> --max-amount <base units> | --wrapper <address>) [--type <PolicyType>] [--yes]
|
|
26
29
|
cofund requests [list]
|
|
27
30
|
cofund requests show <id>
|
|
28
31
|
cofund requests sign <id> [--yes]
|
|
@@ -61,6 +64,11 @@ It rebuilds the EIP-712 message itself under the pinned domain, signs locally, a
|
|
|
61
64
|
|
|
62
65
|
What it takes from the server: which on-chain user id (EVM) or account xpub (BTC) belongs to which named policy signer, and an invite's `callerId` and `authId` (a wrong one only makes the chain refuse it). `approve` receives each signer with its id or xpub, so a script holding its own roster can check the pairing.
|
|
63
66
|
|
|
67
|
+
An admin creates policies and invites with keys of their own:
|
|
68
|
+
- `policies create` builds the activation from the flags: the signers by member id or email (an EVM activation names their `onchainId`, a BTC one their account xpubs), the threshold, and the token and cap per transfer in base units or the wrapper (function `entry`). A transfer policy is `Operational_Expense` and a call policy `Treasury_Management` unless `--type` says otherwise. It signs under the pinned domain and company, with the BTC key's Cofund leaf on a Bitcoin vault. The policy activates at once when one admin may activate it; otherwise it awaits the other admins as a `policy` request.
|
|
69
|
+
- `invite create` asks which user id the email's seat registers (an existing account keeps its own), signs `InviteUser` on each of the cofund's EVM vault chains (or `--chains`), 29 days out, and sends the invite. A member seat is claimable at once; an admin seat awaits the other admins' co-signatures where the chain asks for them, one `invite` request per chain.
|
|
70
|
+
- Both show what they will sign and ask, or take `--yes`.
|
|
71
|
+
|
|
64
72
|
`stocks buy` quotes the pool, sets the minimum output 1% under the quote, signs the swap and proposes it on the cofund's Stocks policy. The other signers approve it in the app or with `requests sign`.
|
|
65
73
|
|
|
66
74
|
## SDK
|
|
@@ -97,6 +105,55 @@ for (const request of await api.requests(cofundId)) {
|
|
|
97
105
|
|
|
98
106
|
`proposeTransfer` and `proposeCall` take an id the caller picks (`id`, `authId`); a retry with the same id returns the same request, so the client retries them on a lost response. `signTransferProposal` and `signCallProposal` sign the proposer's own intent.
|
|
99
107
|
|
|
108
|
+
An admin's writes work the same way: `signPolicyCreation` and `signInviteCreation` build the activation or each chain's `InviteUser` from your inputs and sign it locally, and `createPolicy` and `createInvite` send it under the id you picked.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { randomUUID } from "node:crypto";
|
|
112
|
+
import { signInviteCreation, signPolicyCreation } from "getcofund";
|
|
113
|
+
|
|
114
|
+
const company = keystore.cofunds[cofundId].company;
|
|
115
|
+
const signers = { evm: evmWallet(keystore), btc: btcMessageSigner(keystore) };
|
|
116
|
+
const [vault] = await api.vaults(cofundId);
|
|
117
|
+
const roster = await api.members(cofundId);
|
|
118
|
+
|
|
119
|
+
const policy = await signPolicyCreation(
|
|
120
|
+
{
|
|
121
|
+
id: randomUUID(),
|
|
122
|
+
vault,
|
|
123
|
+
name: "Payouts",
|
|
124
|
+
type: "Operational_Expense",
|
|
125
|
+
signers: roster.filter((m) => m.isAdmin),
|
|
126
|
+
threshold: 2,
|
|
127
|
+
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
128
|
+
maxAmount: "1000000000", // 1,000 USDC per transfer
|
|
129
|
+
},
|
|
130
|
+
company,
|
|
131
|
+
pins,
|
|
132
|
+
signers,
|
|
133
|
+
);
|
|
134
|
+
await api.createPolicy(cofundId, policy); // AWAITING_SIGNATURES, or active at once
|
|
135
|
+
|
|
136
|
+
const id = randomUUID();
|
|
137
|
+
const { userId } = await api.inviteUserId(cofundId, "nia@example.com");
|
|
138
|
+
const invite = await signInviteCreation(
|
|
139
|
+
{
|
|
140
|
+
id,
|
|
141
|
+
callerId: me.id,
|
|
142
|
+
userId: userId ?? id,
|
|
143
|
+
email: "nia@example.com",
|
|
144
|
+
firstName: "Nia",
|
|
145
|
+
lastName: "Okafor",
|
|
146
|
+
position: "Treasurer",
|
|
147
|
+
isAdmin: false,
|
|
148
|
+
chains: ["ETHEREUM", "ROBINHOOD"],
|
|
149
|
+
},
|
|
150
|
+
company,
|
|
151
|
+
pins,
|
|
152
|
+
signers.evm,
|
|
153
|
+
);
|
|
154
|
+
await api.createInvite(cofundId, invite);
|
|
155
|
+
```
|
|
156
|
+
|
|
100
157
|
## Webhooks
|
|
101
158
|
|
|
102
159
|
`cofund webhooks add https://bot.example.com/cofund` subscribes an endpoint for your key's agent, to every event or those named with `--events`, and prints its signing secret once. The URL must be https, on a host that resolves only to public addresses.
|
package/dist/api-types.d.ts
CHANGED
|
@@ -73,6 +73,13 @@ export declare const PolicyTypeSchema: z.ZodEnum<{
|
|
|
73
73
|
Treasury_Management: "Treasury_Management";
|
|
74
74
|
}>;
|
|
75
75
|
export type PolicyType = z.infer<typeof PolicyTypeSchema>;
|
|
76
|
+
/** The types the API lists and creates: not the app's own deposit and invoice policies. */
|
|
77
|
+
export declare const ApiPolicyTypeSchema: z.ZodEnum<{
|
|
78
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
79
|
+
Operational_Expense: "Operational_Expense";
|
|
80
|
+
Treasury_Management: "Treasury_Management";
|
|
81
|
+
}>;
|
|
82
|
+
export type ApiPolicyType = z.infer<typeof ApiPolicyTypeSchema>;
|
|
76
83
|
export declare const PolicyStatusSchema: z.ZodEnum<{
|
|
77
84
|
PENDING: "PENDING";
|
|
78
85
|
ACTIVE: "ACTIVE";
|
|
@@ -431,6 +438,7 @@ export declare const ApiPolicySchema: z.ZodObject<{
|
|
|
431
438
|
export type ApiPolicy = z.infer<typeof ApiPolicySchema>;
|
|
432
439
|
export declare const ApiMemberSchema: z.ZodObject<{
|
|
433
440
|
id: z.ZodUUID;
|
|
441
|
+
onchainId: z.ZodUUID;
|
|
434
442
|
email: z.ZodEmail;
|
|
435
443
|
name: z.ZodNullable<z.ZodString>;
|
|
436
444
|
isAdmin: z.ZodBoolean;
|
|
@@ -1455,6 +1463,199 @@ export declare const ProposeCallRequestSchema: z.ZodObject<{
|
|
|
1455
1463
|
name: z.ZodOptional<z.ZodString>;
|
|
1456
1464
|
}, z.core.$strip>;
|
|
1457
1465
|
export type ProposeCallRequest = z.infer<typeof ProposeCallRequestSchema>;
|
|
1466
|
+
/** An EVM transfer policy: the token and the cap per transfer, with the creator's `TransferPolicyActivation`. */
|
|
1467
|
+
export declare const CreateEvmTransferPolicyRequestSchema: z.ZodObject<{
|
|
1468
|
+
chain: z.ZodEnum<{
|
|
1469
|
+
ETHEREUM: "ETHEREUM";
|
|
1470
|
+
BASE: "BASE";
|
|
1471
|
+
ARBITRUM: "ARBITRUM";
|
|
1472
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1473
|
+
}>;
|
|
1474
|
+
token: z.ZodString;
|
|
1475
|
+
maxAmount: z.ZodString;
|
|
1476
|
+
signature: z.ZodString;
|
|
1477
|
+
id: z.ZodUUID;
|
|
1478
|
+
vaultId: z.ZodUUID;
|
|
1479
|
+
name: z.ZodString;
|
|
1480
|
+
type: z.ZodEnum<{
|
|
1481
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1482
|
+
Operational_Expense: "Operational_Expense";
|
|
1483
|
+
Treasury_Management: "Treasury_Management";
|
|
1484
|
+
}>;
|
|
1485
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1486
|
+
threshold: z.ZodNumber;
|
|
1487
|
+
}, z.core.$strict>;
|
|
1488
|
+
/** An EVM call policy: the wrapper it binds (function `entry`), with the creator's `TransactionPolicyActivation`. */
|
|
1489
|
+
export declare const CreateEvmCallPolicyRequestSchema: z.ZodObject<{
|
|
1490
|
+
chain: z.ZodEnum<{
|
|
1491
|
+
ETHEREUM: "ETHEREUM";
|
|
1492
|
+
BASE: "BASE";
|
|
1493
|
+
ARBITRUM: "ARBITRUM";
|
|
1494
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1495
|
+
}>;
|
|
1496
|
+
wrapper: z.ZodString;
|
|
1497
|
+
signature: z.ZodString;
|
|
1498
|
+
id: z.ZodUUID;
|
|
1499
|
+
vaultId: z.ZodUUID;
|
|
1500
|
+
name: z.ZodString;
|
|
1501
|
+
type: z.ZodEnum<{
|
|
1502
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1503
|
+
Operational_Expense: "Operational_Expense";
|
|
1504
|
+
Treasury_Management: "Treasury_Management";
|
|
1505
|
+
}>;
|
|
1506
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1507
|
+
threshold: z.ZodNumber;
|
|
1508
|
+
}, z.core.$strict>;
|
|
1509
|
+
/**
|
|
1510
|
+
* A BTC transfer policy, with the creator's approval: a Bitcoin Signed Message
|
|
1511
|
+
* over the activation digest (`btc-policy-activation.ts`) by their BTC key's
|
|
1512
|
+
* Cofund leaf.
|
|
1513
|
+
*/
|
|
1514
|
+
export declare const CreateBtcPolicyRequestSchema: z.ZodObject<{
|
|
1515
|
+
chain: z.ZodLiteral<"BTC">;
|
|
1516
|
+
token: z.ZodLiteral<"BTC">;
|
|
1517
|
+
maxAmount: z.ZodString;
|
|
1518
|
+
signature: z.ZodString;
|
|
1519
|
+
id: z.ZodUUID;
|
|
1520
|
+
vaultId: z.ZodUUID;
|
|
1521
|
+
name: z.ZodString;
|
|
1522
|
+
type: z.ZodEnum<{
|
|
1523
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1524
|
+
Operational_Expense: "Operational_Expense";
|
|
1525
|
+
Treasury_Management: "Treasury_Management";
|
|
1526
|
+
}>;
|
|
1527
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1528
|
+
threshold: z.ZodNumber;
|
|
1529
|
+
}, z.core.$strict>;
|
|
1530
|
+
/** POST /policies (admins). Retrying with the same `id` returns the same policy. */
|
|
1531
|
+
export declare const CreatePolicyRequestSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
1532
|
+
chain: z.ZodEnum<{
|
|
1533
|
+
ETHEREUM: "ETHEREUM";
|
|
1534
|
+
BASE: "BASE";
|
|
1535
|
+
ARBITRUM: "ARBITRUM";
|
|
1536
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1537
|
+
}>;
|
|
1538
|
+
token: z.ZodString;
|
|
1539
|
+
maxAmount: z.ZodString;
|
|
1540
|
+
signature: z.ZodString;
|
|
1541
|
+
id: z.ZodUUID;
|
|
1542
|
+
vaultId: z.ZodUUID;
|
|
1543
|
+
name: z.ZodString;
|
|
1544
|
+
type: z.ZodEnum<{
|
|
1545
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1546
|
+
Operational_Expense: "Operational_Expense";
|
|
1547
|
+
Treasury_Management: "Treasury_Management";
|
|
1548
|
+
}>;
|
|
1549
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1550
|
+
threshold: z.ZodNumber;
|
|
1551
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1552
|
+
chain: z.ZodEnum<{
|
|
1553
|
+
ETHEREUM: "ETHEREUM";
|
|
1554
|
+
BASE: "BASE";
|
|
1555
|
+
ARBITRUM: "ARBITRUM";
|
|
1556
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1557
|
+
}>;
|
|
1558
|
+
wrapper: z.ZodString;
|
|
1559
|
+
signature: z.ZodString;
|
|
1560
|
+
id: z.ZodUUID;
|
|
1561
|
+
vaultId: z.ZodUUID;
|
|
1562
|
+
name: z.ZodString;
|
|
1563
|
+
type: z.ZodEnum<{
|
|
1564
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1565
|
+
Operational_Expense: "Operational_Expense";
|
|
1566
|
+
Treasury_Management: "Treasury_Management";
|
|
1567
|
+
}>;
|
|
1568
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1569
|
+
threshold: z.ZodNumber;
|
|
1570
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1571
|
+
chain: z.ZodLiteral<"BTC">;
|
|
1572
|
+
token: z.ZodLiteral<"BTC">;
|
|
1573
|
+
maxAmount: z.ZodString;
|
|
1574
|
+
signature: z.ZodString;
|
|
1575
|
+
id: z.ZodUUID;
|
|
1576
|
+
vaultId: z.ZodUUID;
|
|
1577
|
+
name: z.ZodString;
|
|
1578
|
+
type: z.ZodEnum<{
|
|
1579
|
+
Contractor_Stipend: "Contractor_Stipend";
|
|
1580
|
+
Operational_Expense: "Operational_Expense";
|
|
1581
|
+
Treasury_Management: "Treasury_Management";
|
|
1582
|
+
}>;
|
|
1583
|
+
signers: z.ZodArray<z.ZodUUID>;
|
|
1584
|
+
threshold: z.ZodNumber;
|
|
1585
|
+
}, z.core.$strict>]>;
|
|
1586
|
+
export type CreatePolicyRequest = z.infer<typeof CreatePolicyRequestSchema>;
|
|
1587
|
+
/** POST /invites/user-id (admins). */
|
|
1588
|
+
export declare const InviteUserIdRequestSchema: z.ZodObject<{
|
|
1589
|
+
email: z.ZodEmail;
|
|
1590
|
+
}, z.core.$strip>;
|
|
1591
|
+
export type InviteUserIdRequest = z.infer<typeof InviteUserIdRequestSchema>;
|
|
1592
|
+
/**
|
|
1593
|
+
* The id an invite to that email must seat: the account's own when the email
|
|
1594
|
+
* has one (its identity in every cofund), else null, and the invite seats its
|
|
1595
|
+
* own `id`.
|
|
1596
|
+
*/
|
|
1597
|
+
export declare const InviteUserIdSchema: z.ZodObject<{
|
|
1598
|
+
userId: z.ZodNullable<z.ZodUUID>;
|
|
1599
|
+
}, z.core.$strip>;
|
|
1600
|
+
export type InviteUserId = z.infer<typeof InviteUserIdSchema>;
|
|
1601
|
+
/** POST /invites (admins). Retrying with the same `id` returns the same invite. */
|
|
1602
|
+
export declare const CreateInviteRequestSchema: z.ZodObject<{
|
|
1603
|
+
id: z.ZodUUID;
|
|
1604
|
+
email: z.ZodEmail;
|
|
1605
|
+
firstName: z.ZodString;
|
|
1606
|
+
lastName: z.ZodString;
|
|
1607
|
+
position: z.ZodString;
|
|
1608
|
+
isAdmin: z.ZodBoolean;
|
|
1609
|
+
userId: z.ZodUUID;
|
|
1610
|
+
chains: z.ZodArray<z.ZodObject<{
|
|
1611
|
+
chain: z.ZodEnum<{
|
|
1612
|
+
ETHEREUM: "ETHEREUM";
|
|
1613
|
+
BASE: "BASE";
|
|
1614
|
+
ARBITRUM: "ARBITRUM";
|
|
1615
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1616
|
+
}>;
|
|
1617
|
+
expiresAt: z.ZodString;
|
|
1618
|
+
signature: z.ZodString;
|
|
1619
|
+
}, z.core.$strip>>;
|
|
1620
|
+
}, z.core.$strip>;
|
|
1621
|
+
export type CreateInviteRequest = z.infer<typeof CreateInviteRequestSchema>;
|
|
1622
|
+
/**
|
|
1623
|
+
* An invite as created: the seat, and its row on each chain. A row is
|
|
1624
|
+
* claimable (AWAITING_SUBMISSION), or an admin seat awaiting the other admins'
|
|
1625
|
+
* co-signatures (AWAITING_SIGNATURES), when its id is the `invite` request
|
|
1626
|
+
* they sign.
|
|
1627
|
+
*/
|
|
1628
|
+
export declare const ApiCreatedInviteSchema: z.ZodObject<{
|
|
1629
|
+
id: z.ZodUUID;
|
|
1630
|
+
cofundId: z.ZodUUID;
|
|
1631
|
+
member: z.ZodObject<{
|
|
1632
|
+
id: z.ZodUUID;
|
|
1633
|
+
email: z.ZodEmail;
|
|
1634
|
+
isAdmin: z.ZodBoolean;
|
|
1635
|
+
position: z.ZodString;
|
|
1636
|
+
}, z.core.$strip>;
|
|
1637
|
+
chains: z.ZodArray<z.ZodObject<{
|
|
1638
|
+
chain: z.ZodEnum<{
|
|
1639
|
+
ETHEREUM: "ETHEREUM";
|
|
1640
|
+
BASE: "BASE";
|
|
1641
|
+
ARBITRUM: "ARBITRUM";
|
|
1642
|
+
ROBINHOOD: "ROBINHOOD";
|
|
1643
|
+
}>;
|
|
1644
|
+
id: z.ZodUUID;
|
|
1645
|
+
status: z.ZodEnum<{
|
|
1646
|
+
FAILED: "FAILED";
|
|
1647
|
+
AWAITING_SIGNATURES: "AWAITING_SIGNATURES";
|
|
1648
|
+
COMPLETED: "COMPLETED";
|
|
1649
|
+
AWAITING_SUBMISSION: "AWAITING_SUBMISSION";
|
|
1650
|
+
PROCESSING: "PROCESSING";
|
|
1651
|
+
EXPIRED: "EXPIRED";
|
|
1652
|
+
CANCELED: "CANCELED";
|
|
1653
|
+
REJECTED: "REJECTED";
|
|
1654
|
+
}>;
|
|
1655
|
+
expiresAt: z.ZodString;
|
|
1656
|
+
}, z.core.$strip>>;
|
|
1657
|
+
}, z.core.$strip>;
|
|
1658
|
+
export type ApiCreatedInvite = z.infer<typeof ApiCreatedInviteSchema>;
|
|
1458
1659
|
/** POST /requests/{id}/signatures. A BTC policy approval is a compact signature; the rest are EIP-712. */
|
|
1459
1660
|
export declare const SignatureRequestSchema: z.ZodObject<{
|
|
1460
1661
|
signature: z.ZodUnion<readonly [z.ZodString, z.ZodString]>;
|
package/dist/api-types.js
CHANGED
|
@@ -111,6 +111,11 @@ export const PolicyTypeSchema = z.enum([
|
|
|
111
111
|
"Operational_Expense",
|
|
112
112
|
"Treasury_Management",
|
|
113
113
|
]);
|
|
114
|
+
/** The types the API lists and creates: not the app's own deposit and invoice policies. */
|
|
115
|
+
export const ApiPolicyTypeSchema = PolicyTypeSchema.exclude([
|
|
116
|
+
"Crypto_Onramp",
|
|
117
|
+
"Business_Invoice",
|
|
118
|
+
]);
|
|
114
119
|
export const PolicyStatusSchema = z.enum([
|
|
115
120
|
"PENDING",
|
|
116
121
|
"ACTIVE",
|
|
@@ -290,11 +295,8 @@ export const ApiPolicySchema = z
|
|
|
290
295
|
.object({
|
|
291
296
|
id: Uuid,
|
|
292
297
|
name: PolicyName,
|
|
293
|
-
/**
|
|
294
|
-
|
|
295
|
-
* and invoice policies are not listed, as in the app.
|
|
296
|
-
*/
|
|
297
|
-
type: PolicyTypeSchema.exclude(["Crypto_Onramp", "Business_Invoice"]),
|
|
298
|
+
/** The `messageType` every request under it signs. */
|
|
299
|
+
type: ApiPolicyTypeSchema,
|
|
298
300
|
status: PolicyStatusSchema,
|
|
299
301
|
threshold: Threshold,
|
|
300
302
|
vault: ApiVaultSchema,
|
|
@@ -312,6 +314,8 @@ export const ApiPolicySchema = z
|
|
|
312
314
|
recipients.every((recipient) => BtcAddress.safeParse(recipient).success), "A BTC policy's recipients are Bitcoin addresses");
|
|
313
315
|
export const ApiMemberSchema = z.object({
|
|
314
316
|
id: Uuid,
|
|
317
|
+
/** The id the member is registered under on chain here: what an EVM policy activation names. */
|
|
318
|
+
onchainId: Uuid,
|
|
315
319
|
email: z.email(),
|
|
316
320
|
name: PersonName.nullable(),
|
|
317
321
|
isAdmin: z.boolean(),
|
|
@@ -524,6 +528,121 @@ export const ProposeCallRequestSchema = z.object({
|
|
|
524
528
|
signature: Signature,
|
|
525
529
|
name: RequestName.optional(),
|
|
526
530
|
});
|
|
531
|
+
/** A policy's cap per transfer: base units, at most 30 digits, as the app stores it. */
|
|
532
|
+
const PolicyCap = PositiveBaseUnits.max(30, "at most 30 digits");
|
|
533
|
+
const CreatePolicyCommon = {
|
|
534
|
+
/** A UUID the caller picks: the policy's id, and the `policyId` and `authId` its activation signs. */
|
|
535
|
+
id: Uuid,
|
|
536
|
+
vaultId: Uuid,
|
|
537
|
+
name: PolicyName,
|
|
538
|
+
type: ApiPolicyTypeSchema,
|
|
539
|
+
/** Members' ids. The activation names each one's `onchainId` (EVM) or account xpub (BTC). */
|
|
540
|
+
signers: z
|
|
541
|
+
.array(Uuid)
|
|
542
|
+
.min(1)
|
|
543
|
+
.max(100)
|
|
544
|
+
.refine((ids) => distinct(ids, (id) => id), "Each signer once"),
|
|
545
|
+
threshold: Threshold,
|
|
546
|
+
};
|
|
547
|
+
/** An EVM transfer policy: the token and the cap per transfer, with the creator's `TransferPolicyActivation`. */
|
|
548
|
+
export const CreateEvmTransferPolicyRequestSchema = z
|
|
549
|
+
.object({
|
|
550
|
+
...CreatePolicyCommon,
|
|
551
|
+
chain: EvmChainNameSchema,
|
|
552
|
+
token: Address,
|
|
553
|
+
maxAmount: PolicyCap,
|
|
554
|
+
signature: Signature,
|
|
555
|
+
})
|
|
556
|
+
.strict();
|
|
557
|
+
/** An EVM call policy: the wrapper it binds (function `entry`), with the creator's `TransactionPolicyActivation`. */
|
|
558
|
+
export const CreateEvmCallPolicyRequestSchema = z
|
|
559
|
+
.object({
|
|
560
|
+
...CreatePolicyCommon,
|
|
561
|
+
chain: EvmChainNameSchema,
|
|
562
|
+
wrapper: Address,
|
|
563
|
+
signature: Signature,
|
|
564
|
+
})
|
|
565
|
+
.strict();
|
|
566
|
+
/**
|
|
567
|
+
* A BTC transfer policy, with the creator's approval: a Bitcoin Signed Message
|
|
568
|
+
* over the activation digest (`btc-policy-activation.ts`) by their BTC key's
|
|
569
|
+
* Cofund leaf.
|
|
570
|
+
*/
|
|
571
|
+
export const CreateBtcPolicyRequestSchema = z
|
|
572
|
+
.object({
|
|
573
|
+
...CreatePolicyCommon,
|
|
574
|
+
chain: z.literal("BTC"),
|
|
575
|
+
token: z.literal("BTC"),
|
|
576
|
+
maxAmount: PolicyCap,
|
|
577
|
+
signature: BtcMessageSignature,
|
|
578
|
+
})
|
|
579
|
+
.strict();
|
|
580
|
+
/** POST /policies (admins). Retrying with the same `id` returns the same policy. */
|
|
581
|
+
export const CreatePolicyRequestSchema = z
|
|
582
|
+
.union([
|
|
583
|
+
CreateEvmTransferPolicyRequestSchema,
|
|
584
|
+
CreateEvmCallPolicyRequestSchema,
|
|
585
|
+
CreateBtcPolicyRequestSchema,
|
|
586
|
+
])
|
|
587
|
+
.refine(({ threshold, signers }) => threshold <= signers.length, {
|
|
588
|
+
message: "The threshold is more than the signers",
|
|
589
|
+
path: ["threshold"],
|
|
590
|
+
});
|
|
591
|
+
/** A first or last name, as the invite form takes it. */
|
|
592
|
+
const NamePart = z.string().min(2).max(100);
|
|
593
|
+
const EmailAddress = z.email().max(254);
|
|
594
|
+
/** POST /invites/user-id (admins). */
|
|
595
|
+
export const InviteUserIdRequestSchema = z.object({ email: EmailAddress });
|
|
596
|
+
/**
|
|
597
|
+
* The id an invite to that email must seat: the account's own when the email
|
|
598
|
+
* has one (its identity in every cofund), else null, and the invite seats its
|
|
599
|
+
* own `id`.
|
|
600
|
+
*/
|
|
601
|
+
export const InviteUserIdSchema = z.object({ userId: Uuid.nullable() });
|
|
602
|
+
/** POST /invites (admins). Retrying with the same `id` returns the same invite. */
|
|
603
|
+
export const CreateInviteRequestSchema = z.object({
|
|
604
|
+
/** A UUID the caller picks: the invite's id, and the `authId` every chain's `InviteUser` signs. */
|
|
605
|
+
id: Uuid,
|
|
606
|
+
email: EmailAddress,
|
|
607
|
+
firstName: NamePart,
|
|
608
|
+
lastName: NamePart,
|
|
609
|
+
/** The seat's position, signed as `userPosition`. */
|
|
610
|
+
position: Position.min(2),
|
|
611
|
+
isAdmin: z.boolean(),
|
|
612
|
+
/** The `userId` signed: POST /invites/user-id's answer, else `id`. */
|
|
613
|
+
userId: Uuid,
|
|
614
|
+
/** The inviting admin's `InviteUser` on each EVM chain. */
|
|
615
|
+
chains: z
|
|
616
|
+
.array(z.object({
|
|
617
|
+
chain: EvmChainNameSchema,
|
|
618
|
+
expiresAt: UnixSeconds,
|
|
619
|
+
signature: Signature,
|
|
620
|
+
}))
|
|
621
|
+
.min(1)
|
|
622
|
+
.refine((chains) => distinct(chains, ({ chain }) => chain), "One signature per chain"),
|
|
623
|
+
});
|
|
624
|
+
/**
|
|
625
|
+
* An invite as created: the seat, and its row on each chain. A row is
|
|
626
|
+
* claimable (AWAITING_SUBMISSION), or an admin seat awaiting the other admins'
|
|
627
|
+
* co-signatures (AWAITING_SIGNATURES), when its id is the `invite` request
|
|
628
|
+
* they sign.
|
|
629
|
+
*/
|
|
630
|
+
export const ApiCreatedInviteSchema = z.object({
|
|
631
|
+
id: Uuid,
|
|
632
|
+
cofundId: Uuid,
|
|
633
|
+
member: z.object({
|
|
634
|
+
id: Uuid,
|
|
635
|
+
email: z.email(),
|
|
636
|
+
isAdmin: z.boolean(),
|
|
637
|
+
position: Position,
|
|
638
|
+
}),
|
|
639
|
+
chains: z.array(z.object({
|
|
640
|
+
chain: EvmChainNameSchema,
|
|
641
|
+
id: Uuid,
|
|
642
|
+
status: InviteStatusSchema,
|
|
643
|
+
expiresAt: UnixSeconds,
|
|
644
|
+
})),
|
|
645
|
+
});
|
|
527
646
|
/** POST /requests/{id}/signatures. A BTC policy approval is a compact signature; the rest are EIP-712. */
|
|
528
647
|
export const SignatureRequestSchema = z.object({
|
|
529
648
|
signature: z.union([Signature, BtcMessageSignature]),
|
package/dist/cli/context.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type CliIo = {
|
|
|
10
10
|
/** Ask a yes/no question; absent when nobody can answer (no TTY). */
|
|
11
11
|
confirm?: (question: string) => Promise<boolean>;
|
|
12
12
|
};
|
|
13
|
-
export declare const USAGE = "usage:\n cofund --help\n cofund keys init [--force]\n cofund me\n cofund invite accept [--invite <id>] [--company <name>]\n cofund requests [list] [--cofund <id>]\n cofund requests show <id> [--cofund <id>]\n cofund requests sign <id> [--yes] [--cofund <id>]\n cofund requests reject <id> [--yes] [--cofund <id>]\n cofund stocks buy <SYMBOL> --spend <USDG> [--policy <id>] [--cofund <id>]\n cofund webhooks [list]\n cofund webhooks add <url> [--events a,b]\n cofund webhooks remove <id>";
|
|
13
|
+
export declare const USAGE = "usage:\n cofund --help\n cofund keys init [--force]\n cofund me\n cofund invite accept [--invite <id>] [--company <name>]\n cofund invite create --email <email> --first-name <name> --last-name <name>\n --position <title> [--admin] [--chains A,B] [--yes] [--cofund <id>]\n cofund policies create --vault <id> --name <name> --signers <id|email,...>\n --threshold <n> (--token <address|BTC> --max-amount <base units> |\n --wrapper <address>) [--type <PolicyType>] [--yes] [--cofund <id>]\n cofund requests [list] [--cofund <id>]\n cofund requests show <id> [--cofund <id>]\n cofund requests sign <id> [--yes] [--cofund <id>]\n cofund requests reject <id> [--yes] [--cofund <id>]\n cofund stocks buy <SYMBOL> --spend <USDG> [--policy <id>] [--cofund <id>]\n cofund webhooks [list]\n cofund webhooks add <url> [--events a,b]\n cofund webhooks remove <id>";
|
|
14
14
|
export declare function keystorePath(io: CliIo, flag?: string): string;
|
|
15
15
|
export declare function client(io: CliIo): CofundClient;
|
|
16
16
|
export declare function chainPins(io: CliIo): ChainPins;
|
package/dist/cli/context.js
CHANGED
|
@@ -7,6 +7,11 @@ export const USAGE = `usage:
|
|
|
7
7
|
cofund keys init [--force]
|
|
8
8
|
cofund me
|
|
9
9
|
cofund invite accept [--invite <id>] [--company <name>]
|
|
10
|
+
cofund invite create --email <email> --first-name <name> --last-name <name>
|
|
11
|
+
--position <title> [--admin] [--chains A,B] [--yes] [--cofund <id>]
|
|
12
|
+
cofund policies create --vault <id> --name <name> --signers <id|email,...>
|
|
13
|
+
--threshold <n> (--token <address|BTC> --max-amount <base units> |
|
|
14
|
+
--wrapper <address>) [--type <PolicyType>] [--yes] [--cofund <id>]
|
|
10
15
|
cofund requests [list] [--cofund <id>]
|
|
11
16
|
cofund requests show <id> [--cofund <id>]
|
|
12
17
|
cofund requests sign <id> [--yes] [--cofund <id>]
|
package/dist/cli/invite.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { type CliIo } from "./context.ts";
|
|
1
|
+
import { type CliIo, type CofundValues } from "./context.ts";
|
|
2
2
|
export declare function inviteAccept(io: CliIo, values: {
|
|
3
3
|
keystore?: string;
|
|
4
4
|
invite?: string;
|
|
5
5
|
company?: string;
|
|
6
6
|
}): Promise<number>;
|
|
7
|
+
export type InviteCreateValues = CofundValues & {
|
|
8
|
+
email?: string;
|
|
9
|
+
"first-name"?: string;
|
|
10
|
+
"last-name"?: string;
|
|
11
|
+
position?: string;
|
|
12
|
+
admin?: boolean;
|
|
13
|
+
chains?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function inviteCreate(io: CliIo, values: InviteCreateValues): Promise<number>;
|
package/dist/cli/invite.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { EvmChainNameSchema, } from "../api-types.js";
|
|
3
|
+
import { checkInvite, signInviteAcceptance, signInviteCreation, } from "../invite.js";
|
|
2
4
|
import { btcOwnershipProof, evmWallet, readKeystore, writeKeystore, } from "../keys.js";
|
|
3
|
-
import { chainPins, client, keystorePath } from "./context.js";
|
|
4
|
-
/**
|
|
5
|
+
import { approval, chainPins, client, keystorePath, pickCofund, pinnedCompany, USAGE, } from "./context.js";
|
|
6
|
+
/**
|
|
7
|
+
* `cofund invite accept`: check the invite, pin the company, sign with the
|
|
8
|
+
* local keys. `cofund invite create`: an admin invites a member, signing each
|
|
9
|
+
* chain's seat with the local key.
|
|
10
|
+
*/
|
|
5
11
|
function inviteLines(invite) {
|
|
6
12
|
const [first] = invite.chains;
|
|
7
13
|
const expires = new Date(Number(first.message.expiresAt) * 1000);
|
|
@@ -85,3 +91,75 @@ export async function inviteAccept(io, values) {
|
|
|
85
91
|
});
|
|
86
92
|
return 0;
|
|
87
93
|
}
|
|
94
|
+
/** --chains, or every EVM chain the cofund has a vault on. */
|
|
95
|
+
async function inviteChains(api, cofundId, flag) {
|
|
96
|
+
if (flag) {
|
|
97
|
+
return flag.split(",").map((chain) => EvmChainNameSchema.parse(chain));
|
|
98
|
+
}
|
|
99
|
+
const chains = new Set();
|
|
100
|
+
for (const { chain } of await api.vaults(cofundId)) {
|
|
101
|
+
if (chain !== "BTC") {
|
|
102
|
+
chains.add(chain);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return [...chains];
|
|
106
|
+
}
|
|
107
|
+
function newInviteLines(invite, company) {
|
|
108
|
+
return [
|
|
109
|
+
`invite ${invite.firstName} ${invite.lastName} <${invite.email}>`,
|
|
110
|
+
`seat ${invite.isAdmin ? "admin" : "member"}, ${invite.position}`,
|
|
111
|
+
`chains ${invite.chains.join(", ")}`,
|
|
112
|
+
`company "${company}"`,
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
export async function inviteCreate(io, values) {
|
|
116
|
+
const { email, position } = values;
|
|
117
|
+
const firstName = values["first-name"];
|
|
118
|
+
const lastName = values["last-name"];
|
|
119
|
+
if (!(email && firstName && lastName && position)) {
|
|
120
|
+
io.err(USAGE);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const keystore = readKeystore(keystorePath(io, values.keystore));
|
|
124
|
+
const api = client(io);
|
|
125
|
+
const me = await api.me();
|
|
126
|
+
const cofundId = pickCofund(me, values.cofund);
|
|
127
|
+
const company = pinnedCompany(keystore, cofundId);
|
|
128
|
+
const chains = await inviteChains(api, cofundId, values.chains);
|
|
129
|
+
if (chains.length === 0) {
|
|
130
|
+
io.err("The cofund has no EVM vault to seat a member on");
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
const id = randomUUID();
|
|
134
|
+
const { userId } = await api.inviteUserId(cofundId, email);
|
|
135
|
+
const invite = {
|
|
136
|
+
id,
|
|
137
|
+
callerId: me.id,
|
|
138
|
+
userId: userId ?? id,
|
|
139
|
+
email,
|
|
140
|
+
firstName,
|
|
141
|
+
lastName,
|
|
142
|
+
position,
|
|
143
|
+
isAdmin: Boolean(values.admin),
|
|
144
|
+
chains,
|
|
145
|
+
};
|
|
146
|
+
for (const line of newInviteLines(invite, company)) {
|
|
147
|
+
io.out(line);
|
|
148
|
+
}
|
|
149
|
+
const approve = approval(io, values.yes, "Sign and send this invite?");
|
|
150
|
+
if (!approve) {
|
|
151
|
+
io.err("Pass --yes to invite without a prompt");
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
if (!(await approve())) {
|
|
155
|
+
io.err("Not invited");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
const body = await signInviteCreation(invite, company, chainPins(io), evmWallet(keystore));
|
|
159
|
+
const created = await api.createInvite(cofundId, body);
|
|
160
|
+
io.out(`invited ${created.member.email} (${created.id})`);
|
|
161
|
+
for (const row of created.chains) {
|
|
162
|
+
io.out(`${row.chain.padEnd(9)} ${row.id} ${row.status}`);
|
|
163
|
+
}
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type CliIo, type CofundValues } from "./context.ts";
|
|
2
|
+
export type PolicyValues = CofundValues & {
|
|
3
|
+
vault?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
signers?: string;
|
|
7
|
+
threshold?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
"max-amount"?: string;
|
|
10
|
+
wrapper?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* `cofund policies create`: build the policy's activation from the flags, sign
|
|
14
|
+
* it with the local key and create the policy. It activates at once when one
|
|
15
|
+
* admin may activate it, else it awaits the other admins.
|
|
16
|
+
*/
|
|
17
|
+
export declare function policiesCreate(io: CliIo, values: PolicyValues): Promise<number>;
|