diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..7032d8a0 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,5 @@ +# Public testnet-only keys for the e2e runbook accounts +scripts/harness/people.testnet.json:generic-api-key:4 +scripts/harness/people.testnet.json:generic-api-key:9 +scripts/harness/people.testnet.json:generic-api-key:14 +scripts/harness/people.testnet.json:generic-api-key:19 diff --git a/E2E-TESTING.md b/E2E-TESTING.md new file mode 100644 index 00000000..a222dded --- /dev/null +++ b/E2E-TESTING.md @@ -0,0 +1,92 @@ +# E2E testing on testnet + +A fresh deploy of `main` (`45e0e23`) on Stellar testnet, with one account per person to run the runbook by hand. + +These keys are **testnet only**. They have no real value and are public on purpose. Never use them on mainnet. + +- Network: Stellar testnet (`https://soroban-testnet.stellar.org`, passphrase `Test SDF Network ; September 2015`) +- Deploy: 2026-10-20T19:02:22.970Z (ledger 5299871) + +## People + +Import the secret into a wallet (for example Freighter, on testnet). Each account has about 10,000 XLM, a USDC trustline and 100,000 USDC, and is on the allowlist, so it can also act as a holder. + +| Person | Roles | Public key | Secret key | +| --- | --- | --- | --- | +| **Maxi** | governance (admin of the vault, the oracle and the share token)
USDC admin (can mint) | `GD4E4AI7LVHIRX7QXAHQGRPABC6NFWFKHKJ6ZUAQNIGELVVN7MSFJWLF` | `SDHNHV6MEJP26WLCHC6F27XRLJ6H34R33KGKTQFECS7PYVQVB3RW2CE4` | +| **Pablo** | manager
guardian (vault and oracle) | `GBE43ELIMD3CUR6WMAMTCMI5UWXNFWX3GWJ3RSSIKUGJGTSRHF5X645V` | `SBW54VWPHJTS4O2VVTU4PTV5KNJZR4BXM4QOMFJAKSAC4BQM2GRQ2MDG` | +| **Luciano** | attester
treasury
custodian | `GCBLIOHMFFYK3GORQL2JIILW2YOGIGCDM6SUKX6RVF4U66E2XTXX42KW` | `SAOAAERXFPPJP4L7EUVECIHLOXIIVALC2CDD7OMIERCKWMQJ3247XUQT` | +| **Iñaky** | compliance (admin of the identity verifier, compliance authority of the share token)
outsider | `GCFUDBW2N26PBMMAMI3RVTA5O2WOGJO7GCH6E4PNFAVQNRVW2YEOELXE` | `SBD4TKZWYGHJDUS237S53UEJB45QO6WUOXRABUUNUYHLKNMCYTDLCK5G` | + +Luciano is both treasury and custodian: `deploy_to_custodian` sends the USDC to Luciano's own account. + +## Who signs what + +| Action | Signer | +| --- | --- | +| `request_deposit`, `request_redeem`, `claim_*`, cancellations | anyone (every person is a holder) | +| `close_epoch` | Pablo | +| Attest the NAV on the oracle | Luciano | +| `fulfill_epoch` | anyone (no permission needed); Iñaky as outsider | +| `deploy_to_custodian` | Luciano (treasury) | +| `fund` (return USDC to the vault) | Luciano as custodian; in fact anyone can | +| Pause / unpause the vault or the oracle | Pablo | +| Allowlist, freeze, forced transfers | Iñaky | +| Parameters, roles, custodian, upgrades (timelock), wind-down | Maxi | +| Mint USDC (`mint` on the USDC contract) | Maxi | + +## Initial state + +- Epoch #1 **open**, with no requests. +- No NAV attested on the oracle yet. +- Vault custodian: Luciano. +- The vault holds no funds. + +## Contracts + +| Contract | Address | +| --- | --- | +| `compliance` | `CCCA6XDCXBZWWTIDYGLTL5GEHROCZTGYAIRAFA2KBO2JAWOJFU3TMZCB` | +| `identity_verifier` | `CDMV7DGGLGIWDWI4ERIBZ5YS2FZA3QVZPVUWCAWR5VBLVJMLFYCW6DML` | +| `share_token` | `CCB23VF4CSI7QNWNDOAAAVOKOLWO5EEQFZIODWEXHS6OB3MMHGFZ3VA6` | +| `nav_oracle` | `CDDRBLB3GHZGKHGFATZBKFSXC6XRVFLHNPVWHTIE67FVNDYHU2RRRNRO` | +| `async_vault` | `CAGHFSATIJHNDXQA577APGG6GF2L7Q7KP6H2IUXVW6IT3CJ2OBE3QCEB` | +| `asset` (USDC SAC) | `CAU7LDCUCLAWSJZS72B7VIPIE7FSN3T63D7MZMSSXTNLKYDYECVGH522` | + +USDC: code `USDC`, issuer `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN`. The issuer handed the USDC contract's admin to Maxi, so Maxi is the one who mints. + +If your wallet shows two USDC assets, use this issuer's. The other one is from an earlier deploy. + +## Harness accounts + +The scripts create them. The holders work as extra investors in the runbook. + +| Account | Purpose | Public key | Secret key | +| --- | --- | --- | --- | +| deployer | Deployed the contracts. No role afterwards. | `GC7UTBTYBYIXKBX3DWURKQQJNDKM35FEBIHJN63NT5OZIJRWFUP3VEC5` | `SBTOLGY2AMHDJSDTZRMSWOVOFN236M56YO57ZIA5WTRA7QCSIJEVWUQF` | +| issuer | Issuer of the test USDC. No longer the contract's admin; Maxi mints. | `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN` | `SBKDWFFTWAXKKZEIBG3XREEZ7KFHIUGKXNMOKLAJIW44HUUEB46GAYZA` | +| holder-1 | Holder: 100,000 USDC, trustline and allowlist. | `GB6GENHQQWI3PRD6MVDIRO72VMIJCWENMHRSJU3Z53R372K5ZPHT7ILL` | `SB5RVYBJXKNOWMNBBXGWUHYJRKW75PY7PSX56GHPKXTOB7NW22MXYMHD` | +| holder-2 | Holder: 100,000 USDC, trustline and allowlist. | `GDJ7VTRMJ5XGALSIJDV4V6PBZRQEZZ6NUQOFUDW6TNYOMZMSL536XNAK` | `SASZ5BFK2W6LOJR4UNVUHPICW4VRDYSATCQQAOF7ISPO3OZSKTTF6NVQ` | +| holder-3 | Holder: 100,000 USDC, trustline and allowlist. | `GA6PAYUT232UAQKUYJ7QWF27JQWTKOVNRUHAF6B4CI3QT3XFKQAKSTXD` | `SAR27ZG7NEIKNBIC3U2ZFEP2MUKOOLJAPFZJXT4R2ZKIGOM5GO6T3EFX` | + +## Redeploy + +The roles come from `scripts/harness/people.testnet.json`. If it exists, `deploy` uses those keys for the roles it lists, and new keys for the rest. + +```sh +stellar contract build +cd scripts/harness +HARNESS_NETWORK=testnet npm run deploy # new contracts, the same people's keys +HARNESS_NETWORK=testnet npm run onboard # trustline, USDC and allowlist for each person; custodian +``` + +For Maxi to mint, the issuer hands over the USDC contract's admin (the values come from `deployed.testnet.json` and `people.testnet.json`): + +```sh +stellar contract invoke --network testnet --id --source-account -- set_admin --new_admin +stellar contract invoke --network testnet --id --source-account -- mint --to --amount 1000000000000 # 100,000 USDC (7 decimals) +``` + +holder-1..3 need a trustline and the allowlist before the mint. + +The contracts change on every deploy, so update this file's contract table afterwards. diff --git a/README.md b/README.md index ee24fbdf..8af20bb3 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,12 @@ deploy from `environments.toml` → `[staging.contracts]`, which is the record. | Contract | Address | | --- | --- | -| `async_vault` | `CAXGUAAIZOCM6FIQ6PRTQEBBZG62GMN422TNT6N6UMIYY4HGFLJDUFCV` | -| `nav_oracle` | `CBYVXBRLCKJTH56FVIZFBX7XUIBOLJOROP2DM4NYA4V6FOTTFZO7VJPL` | -| `share_token` | `CCBANVMWNZXZ46WMV7N7EDGBPW3LIFOXZLILIOTRDTE63H4QXBM7XVYF` | -| `identity_verifier` | `CBLCGV3YT5AM7ZQNFEIEX3FLA3ZY3XDVW3U2YQO4ZWSJVI47763LLL5T` | -| `compliance` | `CA45ZSCHMNUMJEF2UDUT4UMCKHHMEG3LYYIB7QBK6LG2NIC7JYZ7XKRA` | -| `asset` | `CCTSSUV5XYKFEMT44SJBS2GUNBEMMRUQXLK422RAJMPTGLF7WV6ALDKR` | +| `async_vault` | `CAGHFSATIJHNDXQA577APGG6GF2L7Q7KP6H2IUXVW6IT3CJ2OBE3QCEB` | +| `nav_oracle` | `CDDRBLB3GHZGKHGFATZBKFSXC6XRVFLHNPVWHTIE67FVNDYHU2RRRNRO` | +| `share_token` | `CCB23VF4CSI7QNWNDOAAAVOKOLWO5EEQFZIODWEXHS6OB3MMHGFZ3VA6` | +| `identity_verifier` | `CDMV7DGGLGIWDWI4ERIBZ5YS2FZA3QVZPVUWCAWR5VBLVJMLFYCW6DML` | +| `compliance` | `CCCA6XDCXBZWWTIDYGLTL5GEHROCZTGYAIRAFA2KBO2JAWOJFU3TMZCB` | +| `asset` | `CAU7LDCUCLAWSJZS72B7VIPIE7FSN3T63D7MZMSSXTNLKYDYECVGH522` | ## Testnet flow verification diff --git a/environments.toml b/environments.toml index 0a689953..60c110ea 100644 --- a/environments.toml +++ b/environments.toml @@ -71,12 +71,12 @@ default = true # underlying Stellar Asset Contract and has no crate here; it still gets a # generated client, because a SAC carries its own spec. [staging.contracts] -async_vault = { id = "CAXGUAAIZOCM6FIQ6PRTQEBBZG62GMN422TNT6N6UMIYY4HGFLJDUFCV" } -nav_oracle = { id = "CBYVXBRLCKJTH56FVIZFBX7XUIBOLJOROP2DM4NYA4V6FOTTFZO7VJPL" } -share_token = { id = "CCBANVMWNZXZ46WMV7N7EDGBPW3LIFOXZLILIOTRDTE63H4QXBM7XVYF" } -identity_verifier = { id = "CBLCGV3YT5AM7ZQNFEIEX3FLA3ZY3XDVW3U2YQO4ZWSJVI47763LLL5T" } -compliance = { id = "CA45ZSCHMNUMJEF2UDUT4UMCKHHMEG3LYYIB7QBK6LG2NIC7JYZ7XKRA" } -asset = { id = "CCTSSUV5XYKFEMT44SJBS2GUNBEMMRUQXLK422RAJMPTGLF7WV6ALDKR" } +async_vault = { id = "CAGHFSATIJHNDXQA577APGG6GF2L7Q7KP6H2IUXVW6IT3CJ2OBE3QCEB" } +nav_oracle = { id = "CDDRBLB3GHZGKHGFATZBKFSXC6XRVFLHNPVWHTIE67FVNDYHU2RRRNRO" } +share_token = { id = "CCB23VF4CSI7QNWNDOAAAVOKOLWO5EEQFZIODWEXHS6OB3MMHGFZ3VA6" } +identity_verifier = { id = "CDMV7DGGLGIWDWI4ERIBZ5YS2FZA3QVZPVUWCAWR5VBLVJMLFYCW6DML" } +compliance = { id = "CCCA6XDCXBZWWTIDYGLTL5GEHROCZTGYAIRAFA2KBO2JAWOJFU3TMZCB" } +asset = { id = "CAU7LDCUCLAWSJZS72B7VIPIE7FSN3T63D7MZMSSXTNLKYDYECVGH522" } ### Production environment configuration # Commented out on purpose. Mainnet needs an external audit and an audited diff --git a/scripts/harness/deploy.ts b/scripts/harness/deploy.ts index 8b230e0f..7625e965 100644 --- a/scripts/harness/deploy.ts +++ b/scripts/harness/deploy.ts @@ -23,6 +23,7 @@ import { type Deployment, } from "./lib/deployment.js" import { network, type Network } from "./lib/network.js" +import { assignedKeys, loadPeople } from "./lib/people.js" const wasmOf = (name: ContractName): string => join(REPO_ROOT, "target", "wasm32v1-none", "release", `${name}.wasm`) @@ -198,9 +199,17 @@ async function main(): Promise { const server = new rpc.Server(net.rpcUrl, { allowHttp: net.name === "local" }) console.log(`deploying to ${net.name} (${net.rpcUrl})`) - const accounts = freshAccounts() - await fund(net, server, Object.values(accounts)) - console.log(`funded ${Object.keys(accounts).length} accounts`) + const accounts: Accounts = { + ...freshAccounts(), + ...assignedKeys(loadPeople(net.name)), + } + const distinct = [ + ...new Map( + Object.values(accounts).map((key) => [key.publicKey(), key]), + ).values(), + ] + await fund(net, server, distinct) + console.log(`funded ${distinct.length} accounts`) const asset = new Asset("USDC", accounts.issuer.publicKey()) const assetContract = await deployAssetContract(net, asset, accounts.issuer) diff --git a/scripts/harness/lib/holders.ts b/scripts/harness/lib/holders.ts new file mode 100644 index 00000000..7d425c4d --- /dev/null +++ b/scripts/harness/lib/holders.ts @@ -0,0 +1,103 @@ +import { type Keypair } from "@stellar/stellar-sdk" + +import { pay, trustline } from "./accounts.js" +import { units } from "./amounts.js" +import { + identityVerifier, + send, + usdcBalance, + type Connected, +} from "./clients.js" + +export function formatAmount(amount: bigint, decimals: number): string { + const factor = 10n ** BigInt(decimals) + const whole = amount / factor + const fraction = amount % factor + return `${whole}.${fraction.toString().padStart(decimals, "0").slice(0, 4)}` +} + +async function hasTrustline(c: Connected, address: string): Promise { + try { + await c.server.getAssetBalance(address, c.asset, c.net.passphrase) + return true + } catch (error) { + if ( + error instanceof Error && + /Trustline for .* not found for/.test(error.message) + ) { + return false + } + throw error + } +} + +export async function ensureTrustline( + c: Connected, + holder: Keypair, + name: string, +): Promise { + const exists = await hasTrustline(c, holder.publicKey()) + if (!exists) { + console.log(` + establishing USDC trustline for ${name}...`) + await trustline(c.net, c.server, holder, c.asset) + console.log(` ✓ established trustline for ${name}`) + } else { + console.log(` • ${name} trustline verified`) + } +} + +export async function ensureFunding( + c: Connected, + holder: Keypair, + name: string, + minUsdcWhole: number, + topUpUsdcWhole: number, + decimals: number, +): Promise { + const minBalance = units(minUsdcWhole, decimals) + const current = await usdcBalance(c, holder.publicKey()) + if (current < minBalance) { + console.log( + ` + funding ${name} with ${topUpUsdcWhole} USDC (current: ${formatAmount(current, decimals)})...`, + ) + await pay( + c.net, + c.server, + c.keys.issuer, + holder.publicKey(), + c.asset, + topUpUsdcWhole.toString(), + ) + const updated = await usdcBalance(c, holder.publicKey()) + console.log( + ` ✓ funded ${name}, new balance: ${formatAmount(updated, decimals)} USDC`, + ) + } else { + console.log( + ` • ${name} has sufficient USDC: ${formatAmount(current, decimals)}`, + ) + } +} + +export async function ensureAllowlist( + c: Connected, + holder: Keypair, + name: string, +): Promise { + const verifier = identityVerifier(c, c.keys.compliance) + const allowed = (await verifier.is_allowed({ account: holder.publicKey() })) + .result + if (!allowed) { + console.log(` + allowlisting ${name}...`) + await send( + verifier.allow({ + account: holder.publicKey(), + allowed: true, + caller: c.keys.compliance.publicKey(), + }), + ) + console.log(` ✓ ${name} allowlisted`) + } else { + console.log(` • ${name} is already allowlisted`) + } +} diff --git a/scripts/harness/lib/people.ts b/scripts/harness/lib/people.ts new file mode 100644 index 00000000..8a19ffcc --- /dev/null +++ b/scripts/harness/lib/people.ts @@ -0,0 +1,48 @@ +import { existsSync, readFileSync } from "node:fs" +import { join } from "node:path" + +import { Keypair } from "@stellar/stellar-sdk" + +import { ACCOUNT_NAMES, type AccountName } from "./accounts.js" +import { PACKAGE_ROOT } from "./deployment.js" +import { type NetworkName } from "./network.js" + +export interface Person { + public: string + secret: string + roles: AccountName[] +} + +export type People = Record + +export function peopleFile(network: NetworkName): string { + return join(PACKAGE_ROOT, `people.${network}.json`) +} + +export function loadPeople(network: NetworkName): People { + const file = peopleFile(network) + if (!existsSync(file)) return {} + const people = JSON.parse(readFileSync(file, "utf8")) as People + const taken = new Map() + for (const [name, person] of Object.entries(people)) { + for (const role of person.roles) { + if (!ACCOUNT_NAMES.includes(role)) { + throw new Error(`${name} has unknown role ${role}`) + } + const holder = taken.get(role) + if (holder) throw new Error(`${role} given to both ${holder} and ${name}`) + taken.set(role, name) + } + } + return people +} + +export function assignedKeys( + people: People, +): Partial> { + return Object.fromEntries( + Object.values(people).flatMap((person) => + person.roles.map((role) => [role, Keypair.fromSecret(person.secret)]), + ), + ) +} diff --git a/scripts/harness/onboard.ts b/scripts/harness/onboard.ts new file mode 100644 index 00000000..36fa8fc0 --- /dev/null +++ b/scripts/harness/onboard.ts @@ -0,0 +1,46 @@ +import { Keypair } from "@stellar/stellar-sdk" + +import { connect, send, shareToken, vault } from "./lib/clients.js" +import { + ensureAllowlist, + ensureFunding, + ensureTrustline, +} from "./lib/holders.js" +import { loadPeople, peopleFile } from "./lib/people.js" + +const MIN_USDC = 10_000 +const TOP_UP_USDC = 50_000 + +async function main(): Promise { + const c = connect() + const people = loadPeople(c.net.name) + if (Object.keys(people).length === 0) { + throw new Error(`no people in ${peopleFile(c.net.name)}`) + } + const decimals = (await shareToken(c, c.keys.outsider).decimals()).result + + for (const [name, person] of Object.entries(people)) { + const key = Keypair.fromSecret(person.secret) + console.log(`${name} (${person.roles.join(", ")})`) + await ensureTrustline(c, key, name) + await ensureFunding(c, key, name, MIN_USDC, TOP_UP_USDC, decimals) + await ensureAllowlist(c, key, name) + } + + const reader = vault(c, c.keys.outsider) + const wanted = c.keys.custodian.publicKey() + if ((await reader.custodian()).result !== wanted) { + await send( + vault(c, c.keys.governance).set_custodian({ + custodian: wanted, + caller: c.keys.governance.publicKey(), + }), + ) + } + console.log(`vault custodian: ${wanted}`) +} + +main().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : error) + process.exitCode = 1 +}) diff --git a/scripts/harness/package.json b/scripts/harness/package.json index be8dcf42..606ba33c 100644 --- a/scripts/harness/package.json +++ b/scripts/harness/package.json @@ -11,7 +11,8 @@ "typecheck": "tsc --noEmit", "test:happy-path": "vitest run happy-path.test.ts", "test:guards": "vitest run guards.test.ts", - "seed": "tsx seed.ts" + "seed": "tsx seed.ts", + "onboard": "tsx onboard.ts" }, "dependencies": { "@stellar/stellar-sdk": "16.1.0" diff --git a/scripts/harness/people.testnet.json b/scripts/harness/people.testnet.json new file mode 100644 index 00000000..2c34810c --- /dev/null +++ b/scripts/harness/people.testnet.json @@ -0,0 +1,22 @@ +{ + "Maxi": { + "public": "GD4E4AI7LVHIRX7QXAHQGRPABC6NFWFKHKJ6ZUAQNIGELVVN7MSFJWLF", + "secret": "SDHNHV6MEJP26WLCHC6F27XRLJ6H34R33KGKTQFECS7PYVQVB3RW2CE4", + "roles": ["governance"] + }, + "Pablo": { + "public": "GBE43ELIMD3CUR6WMAMTCMI5UWXNFWX3GWJ3RSSIKUGJGTSRHF5X645V", + "secret": "SBW54VWPHJTS4O2VVTU4PTV5KNJZR4BXM4QOMFJAKSAC4BQM2GRQ2MDG", + "roles": ["manager", "guardian"] + }, + "Luciano": { + "public": "GCBLIOHMFFYK3GORQL2JIILW2YOGIGCDM6SUKX6RVF4U66E2XTXX42KW", + "secret": "SAOAAERXFPPJP4L7EUVECIHLOXIIVALC2CDD7OMIERCKWMQJ3247XUQT", + "roles": ["attester", "treasury", "custodian"] + }, + "Iñaky": { + "public": "GCFUDBW2N26PBMMAMI3RVTA5O2WOGJO7GCH6E4PNFAVQNRVW2YEOELXE", + "secret": "SBD4TKZWYGHJDUS237S53UEJB45QO6WUOXRABUUNUYHLKNMCYTDLCK5G", + "roles": ["compliance", "outsider"] + } +} diff --git a/scripts/harness/seed.ts b/scripts/harness/seed.ts index a6a546d7..cde25f32 100644 --- a/scripts/harness/seed.ts +++ b/scripts/harness/seed.ts @@ -1,10 +1,6 @@ -import { type Keypair } from "@stellar/stellar-sdk" - -import { pay, trustline } from "./lib/accounts.js" import { price, units } from "./lib/amounts.js" import { connect, - identityVerifier, oracle, send, shareToken, @@ -12,15 +8,14 @@ import { vault, type Connected, } from "./lib/clients.js" +import { + ensureAllowlist, + ensureFunding, + ensureTrustline, + formatAmount, +} from "./lib/holders.js" import { attest } from "./lib/oracle.js" -function formatAmount(amount: bigint, decimals: number): string { - const factor = 10n ** BigInt(decimals) - const whole = amount / factor - const fraction = amount % factor - return `${whole}.${fraction.toString().padStart(decimals, "0").slice(0, 4)}` -} - function formatWad(wad: bigint): string { const factor = 10n ** 18n const whole = wad / factor @@ -28,92 +23,6 @@ function formatWad(wad: bigint): string { return `$${whole}.${fraction.toString().padStart(18, "0").slice(0, 4)}` } -async function hasTrustline(c: Connected, address: string): Promise { - try { - await c.server.getAssetBalance(address, c.asset, c.net.passphrase) - return true - } catch (error) { - if ( - error instanceof Error && - /Trustline for .* not found for/.test(error.message) - ) { - return false - } - throw error - } -} - -async function ensureTrustline( - c: Connected, - holder: Keypair, - name: string, -): Promise { - const exists = await hasTrustline(c, holder.publicKey()) - if (!exists) { - console.log(` + establishing USDC trustline for ${name}...`) - await trustline(c.net, c.server, holder, c.asset) - console.log(` ✓ established trustline for ${name}`) - } else { - console.log(` • ${name} trustline verified`) - } -} - -async function ensureFunding( - c: Connected, - holder: Keypair, - name: string, - minUsdcWhole: number, - topUpUsdcWhole: number, - decimals: number, -): Promise { - const minBalance = units(minUsdcWhole, decimals) - const current = await usdcBalance(c, holder.publicKey()) - if (current < minBalance) { - console.log( - ` + funding ${name} with ${topUpUsdcWhole} USDC (current: ${formatAmount(current, decimals)})...`, - ) - await pay( - c.net, - c.server, - c.keys.issuer, - holder.publicKey(), - c.asset, - topUpUsdcWhole.toString(), - ) - const updated = await usdcBalance(c, holder.publicKey()) - console.log( - ` ✓ funded ${name}, new balance: ${formatAmount(updated, decimals)} USDC`, - ) - } else { - console.log( - ` • ${name} has sufficient USDC: ${formatAmount(current, decimals)}`, - ) - } -} - -async function ensureAllowlist( - c: Connected, - holder: Keypair, - name: string, -): Promise { - const verifier = identityVerifier(c, c.keys.compliance) - const allowed = (await verifier.is_allowed({ account: holder.publicKey() })) - .result - if (!allowed) { - console.log(` + allowlisting ${name}...`) - await send( - verifier.allow({ - account: holder.publicKey(), - allowed: true, - caller: c.keys.compliance.publicKey(), - }), - ) - console.log(` ✓ ${name} allowlisted`) - } else { - console.log(` • ${name} is already allowlisted`) - } -} - async function main(): Promise { console.log("==================================================") console.log(" STRATA VAULT SEED SCRIPT (TESTNET) ") diff --git a/scripts/harness/tsconfig.json b/scripts/harness/tsconfig.json index df950756..846e5f41 100644 --- a/scripts/harness/tsconfig.json +++ b/scripts/harness/tsconfig.json @@ -17,6 +17,7 @@ "deploy.ts", "build-clients.ts", "seed.ts", + "onboard.ts", "lib/**/*.ts", "*.test.ts", "vitest.config.ts"