From 3d0b915e1d619ace40a12111bdceb57c050a6027 Mon Sep 17 00:00:00 2001 From: hpmaxi <358059+hpmaxi@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:49:35 -0300 Subject: [PATCH 1/3] chore: redeploy testnet for e2e testing with seeded state Add E2E-TESTING.md with the contract addresses, testnet account keys and the seeded vault state. Co-Authored-By: Claude Opus 5.5 (1M context) --- E2E-TESTING.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 ++++---- environments.toml | 12 ++++---- 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 E2E-TESTING.md diff --git a/E2E-TESTING.md b/E2E-TESTING.md new file mode 100644 index 00000000..202d2daf --- /dev/null +++ b/E2E-TESTING.md @@ -0,0 +1,71 @@ +# E2E testing on testnet + +A fresh testnet deployment of `main` at `45e0e23`, seeded with a known state for end-to-end and manual testing. + +These are **testnet-only** keys. They hold no real value. Never reuse them on mainnet. + +- Network: Stellar testnet (`https://soroban-testnet.stellar.org`) +- Deployed: 2026-10-20T18:45:08.110Z (ledger 5299664) +- Machine-readable copy: `scripts/harness/deployed.testnet.json` (gitignored, local to the deployer) + +## Contracts + +| Contract | Address | +| --- | --- | +| `compliance` | `CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR` | +| `identity_verifier` | `CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4` | +| `share_token` | `CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF` | +| `nav_oracle` | `CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS` | +| `async_vault` | `CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G` | +| `asset` (USDC SAC) | `CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL` | + +USDC: code `USDC`, issuer `GA35PEYTIHOJWYYMEFOSTLLQYJD3NJLFRNP56XSRLNLW7YAQEC5ALQLX`. + +## Seeded state + +Produced by `HARNESS_NETWORK=testnet npm run seed` in `scripts/harness`. + +- Epoch #1: holder-1 deposited 5,000 USDC and holder-2 3,000 USDC at a NAV of $1.00. Both claimed their shares. +- Treasury moved 4,000 USDC to the custodian. +- Epoch #2 is **open** with two pending requests: + - holder-1: redeem 1,000 shares + - holder-3: deposit 2,500 USDC + +| Metric | Value | +| --- | --- | +| Vault liquid USDC | 6,500 (4,000 free reserve + 2,500 pending escrow) | +| Custodian | 4,000 USDC | +| Total value (NAV basis) | 8,000 USDC | +| Latest NAV | $1.00 | + +## Accounts + +Import a secret key into a wallet (e.g. Freighter, set to testnet) to act as that role. All accounts are funded with testnet XLM. Holders are allowlisted. + +| Role | Public key | Secret key | USDC | Shares | Notes | +| --- | --- | --- | --- | --- | --- | +| **deployer** | `GAYUAX4KX7UTEFDEH74ZQ4UVFFHIDI4QQMZBG5HGROB2RWT43ETODZ2D` | `SCCXUU65HAO763EWNTKDB34RAWP2AXCPNCHUAD5J6NVMQIS77ZPT2JNT` | 0 | 0 | Deployed the contracts. No role after setup. | +| **governance** | `GBKHGH6YL67A7BJK2EMUHVVW5SVRQPFM4UGQPTSTP5RZPFSVAS5DEKQB` | `SBTXJRZWYI3UROKMMLBD2RLMYIXLGAMIIPICAUPQMVA4L6KFLPM4INU3` | 0 | 0 | Admin: upgrades, timelock config, custodian, role grants. | +| **manager** | `GBM4K7KKPTYX7VYQ3OMZXNSJBXRMYC3Y5NY7QUGXV3Q2N2CWLZ6TGP4U` | `SAXJN6H4PCIQUYD4J6L3IUHL2P7UACRKBYCQPTZTKIJCMQZV6JAXDSWY` | 0 | 0 | Closes epochs (`close_epoch`). | +| **treasury** | `GDXJFM42WBF3PY5DRDIKYFN7NCEMJOYAZCVZ724H7FYNA52D4B67OVTD` | `SBCI3NC7T2VTBRI4XFILDK5NX2RWSZI3EEANQE6OE4L6L5Q4XUB4VKUI` | 0 | 0 | Moves capital to the custodian (`deploy_to_custodian`). | +| **guardian** | `GC6RITGKYFPPPFISVDYTLOMI5N2O5NXIPWXXZJE66KJ44TAQWOGS7X7L` | `SAPHVL77L7VNRAWIEMBIJB7T5GB2Y3DP4BHLXQSRYZCL4T2SBLVFF5H5` | 0 | 0 | Pauses the vault and the oracle. | +| **compliance** | `GA7PI44ZKEZGJBRNFMWUPSI7RHLDE5SZGJS7JJY3VS7QA3QQKSN5JGXH` | `SABRR7WK7SW77JSMBQODJ5QQK52H7F42C7T7333XM6FGDAZUVQUPV4EH` | 0 | 0 | Allowlists identities (identity verifier admin) and freezes shares (share token compliance authority). | +| **attester** | `GAEVLCCCP37SOINVUSQ3O7PEMT3IUSWL6CWQSR4IW6KSA7QMCGJYP555` | `SDINAZ6PYB2EEWA3IH4TINXO2OGHJVEBKB5OA42YAAZ2BUEWOGKC77EI` | 0 | 0 | Attests NAV reports on the oracle. | +| **custodian** | `GBWRU6F5VBZCD2VTBZALUF5FRDR5532R5343KIAU2O6V6OLL7R7VJK63` | `SCDEZ545RZTWLWIZRI6FZCIIFUYZUO4D5GHN3P4ZRIVNIN7467GSD7PK` | 4,000 | 0 | Off-chain custodian. Receives deployed capital. | +| **issuer** | `GA35PEYTIHOJWYYMEFOSTLLQYJD3NJLFRNP56XSRLNLW7YAQEC5ALQLX` | `SALYIQ74ZJJD4AKVNWQWAVDOWDL5IJYVMV4EOSK6YUIRWETBSME2LZP7` | 0 | 0 | Issues the test USDC. | +| **holder-1** | `GBWHFQWQBELRT4FP4TP3DOYPTTTZKTH6CXJAVK7ZKY6W6SXR6U4733OF` | `SARGZUA34SWPZJU43VK24EO5W5FTMCBVIFLNWILWRKB3G6TIVPHZD6JG` | 45,000 | 4,000 | Investor. 4,000 liquid shares + 1,000 pending redeem in epoch #2. | +| **holder-2** | `GDNAITVEJZ45ITSUANJJK5FN7PKHB5KFZPZLKH5JB57BNVGKR576FPOE` | `SBKHH4Z6J275PMTA4HLUBRO37H54ZB7F4NBURW3EDM5XRRR55CPLTUHJ` | 47,000 | 3,000 | Investor. Shares claimed from epoch #1. | +| **holder-3** | `GCES4U4XR3CFTLSDYK4BRJEWVVWSP5NKFYXLNRIIXCW2SV35SSUAHJ6A` | `SB7XGSJUGL7VUQPQKBNBMKXYYOCGTSZFHKZWAQIHQX544TFVWVQFMY7V` | 47,500 | 0 | Investor. 2,500 USDC pending deposit in epoch #2. | +| **outsider** | `GDO3FXQIDRWYKOLAAKWLEUZRHIPJLTWFTFSLJBQ4R4A3FRAAPVXSMPRD` | `SAEH6PLTQTWSKYCX5XYOOKZH6HRQRSYI4P346QIYXR4R45PCZXG3WVO3` | 0 | 0 | No role. Permissionless calls (`fulfill_epoch`) and reads. | + +## Redeploying + +```sh +nix develop +stellar contract build +cd scripts/harness +HARNESS_NETWORK=testnet npm run deploy +HARNESS_NETWORK=testnet npm run seed +``` + +The deploy creates new accounts and contracts, so update this file afterwards. diff --git a/README.md b/README.md index ee24fbdf..7c4aa8df 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` | `CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G` | +| `nav_oracle` | `CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS` | +| `share_token` | `CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF` | +| `identity_verifier` | `CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4` | +| `compliance` | `CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR` | +| `asset` | `CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL` | ## Testnet flow verification diff --git a/environments.toml b/environments.toml index 0a689953..abe74b37 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 = "CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G" } +nav_oracle = { id = "CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS" } +share_token = { id = "CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF" } +identity_verifier = { id = "CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4" } +compliance = { id = "CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR" } +asset = { id = "CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL" } ### Production environment configuration # Commented out on purpose. Mainnet needs an external audit and an audited From ef5d0a688f3b7d03ccf0bc0fffdd1de2037d3066 Mon Sep 17 00:00:00 2001 From: hpmaxi <358059+hpmaxi@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:10:04 -0300 Subject: [PATCH 2/3] chore: redeploy testnet with per-person role accounts --- .gitleaksignore | 5 ++ E2E-TESTING.md | 114 +++++++++++++++------------- README.md | 12 +-- environments.toml | 12 +-- scripts/harness/deploy.ts | 15 +++- scripts/harness/lib/holders.ts | 103 +++++++++++++++++++++++++ scripts/harness/lib/people.ts | 48 ++++++++++++ scripts/harness/onboard.ts | 46 +++++++++++ scripts/harness/package.json | 3 +- scripts/harness/people.testnet.json | 22 ++++++ scripts/harness/seed.ts | 103 ++----------------------- scripts/harness/tsconfig.json | 1 + 12 files changed, 319 insertions(+), 165 deletions(-) create mode 100644 .gitleaksignore create mode 100644 scripts/harness/lib/holders.ts create mode 100644 scripts/harness/lib/people.ts create mode 100644 scripts/harness/onboard.ts create mode 100644 scripts/harness/people.testnet.json 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 index 202d2daf..25457b6b 100644 --- a/E2E-TESTING.md +++ b/E2E-TESTING.md @@ -1,71 +1,81 @@ # E2E testing on testnet -A fresh testnet deployment of `main` at `45e0e23`, seeded with a known state for end-to-end and manual testing. +Deploy fresco de `main` (`45e0e23`) en Stellar testnet, con una cuenta por persona para correr el runbook a mano. -These are **testnet-only** keys. They hold no real value. Never reuse them on mainnet. +Son keys **solo de testnet**. No tienen valor real y son públicas a propósito. No las uses nunca en mainnet. -- Network: Stellar testnet (`https://soroban-testnet.stellar.org`) -- Deployed: 2026-10-20T18:45:08.110Z (ledger 5299664) -- Machine-readable copy: `scripts/harness/deployed.testnet.json` (gitignored, local to the deployer) +- Red: Stellar testnet (`https://soroban-testnet.stellar.org`, passphrase `Test SDF Network ; September 2015`) +- Deploy: 2026-10-20T19:02:22.970Z (ledger 5299871) -## Contracts +## Personas -| Contract | Address | +Importá el secret en una wallet (por ejemplo Freighter, en testnet). Cada cuenta tiene unos 10.000 XLM, trustline de USDC, 50.000 USDC y está en el allowlist, así que también puede operar como holder. + +| Persona | Roles | Public key | Secret key | +| --- | --- | --- | --- | +| **Maxi** | governance (admin del vault, del oracle y del share token) | `GD4E4AI7LVHIRX7QXAHQGRPABC6NFWFKHKJ6ZUAQNIGELVVN7MSFJWLF` | `SDHNHV6MEJP26WLCHC6F27XRLJ6H34R33KGKTQFECS7PYVQVB3RW2CE4` | +| **Pablo** | manager
guardian (vault y oracle) | `GBE43ELIMD3CUR6WMAMTCMI5UWXNFWX3GWJ3RSSIKUGJGTSRHF5X645V` | `SBW54VWPHJTS4O2VVTU4PTV5KNJZR4BXM4QOMFJAKSAC4BQM2GRQ2MDG` | +| **Luciano** | attester
treasury
custodian | `GCBLIOHMFFYK3GORQL2JIILW2YOGIGCDM6SUKX6RVF4U66E2XTXX42KW` | `SAOAAERXFPPJP4L7EUVECIHLOXIIVALC2CDD7OMIERCKWMQJ3247XUQT` | +| **Iñaky** | compliance (admin del identity verifier, autoridad de compliance del share token)
outsider | `GCFUDBW2N26PBMMAMI3RVTA5O2WOGJO7GCH6E4PNFAVQNRVW2YEOELXE` | `SBD4TKZWYGHJDUS237S53UEJB45QO6WUOXRABUUNUYHLKNMCYTDLCK5G` | + +Luciano es a la vez treasury y custodian: `deploy_to_custodian` le manda el USDC a su propia cuenta. + +## Quién firma qué + +| Acción | Firma | | --- | --- | -| `compliance` | `CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR` | -| `identity_verifier` | `CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4` | -| `share_token` | `CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF` | -| `nav_oracle` | `CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS` | -| `async_vault` | `CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G` | -| `asset` (USDC SAC) | `CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL` | +| `request_deposit`, `request_redeem`, `claim_*`, cancelaciones | cualquier persona (todas son holders) | +| `close_epoch` | Pablo | +| Atestar el NAV en el oracle | Luciano | +| `fulfill_epoch` | cualquiera (sin permisos); Iñaky como outsider | +| `deploy_to_custodian` | Luciano (treasury) | +| `fund` (devolver USDC al vault) | Luciano como custodian; en realidad cualquiera puede | +| Pausar / despausar el vault o el oracle | Pablo | +| Allowlist, freeze, transferencias forzadas | Iñaky | +| Parámetros, roles, custodian, upgrades (timelock), wind-down | Maxi | + +## Estado inicial + +- Epoch #1 **abierto**, sin requests. +- Oracle sin NAV atestado todavía. +- Custodian del vault: Luciano. +- Vault sin fondos. + +## Contratos + +| Contrato | Address | +| --- | --- | +| `compliance` | `CCCA6XDCXBZWWTIDYGLTL5GEHROCZTGYAIRAFA2KBO2JAWOJFU3TMZCB` | +| `identity_verifier` | `CDMV7DGGLGIWDWI4ERIBZ5YS2FZA3QVZPVUWCAWR5VBLVJMLFYCW6DML` | +| `share_token` | `CCB23VF4CSI7QNWNDOAAAVOKOLWO5EEQFZIODWEXHS6OB3MMHGFZ3VA6` | +| `nav_oracle` | `CDDRBLB3GHZGKHGFATZBKFSXC6XRVFLHNPVWHTIE67FVNDYHU2RRRNRO` | +| `async_vault` | `CAGHFSATIJHNDXQA577APGG6GF2L7Q7KP6H2IUXVW6IT3CJ2OBE3QCEB` | +| `asset` (USDC SAC) | `CAU7LDCUCLAWSJZS72B7VIPIE7FSN3T63D7MZMSSXTNLKYDYECVGH522` | -USDC: code `USDC`, issuer `GA35PEYTIHOJWYYMEFOSTLLQYJD3NJLFRNP56XSRLNLW7YAQEC5ALQLX`. +USDC: código `USDC`, issuer `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN`. -## Seeded state +## Cuentas del harness -Produced by `HARNESS_NETWORK=testnet npm run seed` in `scripts/harness`. +Las usan los scripts. No hacen falta para el runbook. -- Epoch #1: holder-1 deposited 5,000 USDC and holder-2 3,000 USDC at a NAV of $1.00. Both claimed their shares. -- Treasury moved 4,000 USDC to the custodian. -- Epoch #2 is **open** with two pending requests: - - holder-1: redeem 1,000 shares - - holder-3: deposit 2,500 USDC +| Cuenta | Para qué | Public key | Secret key | +| --- | --- | --- | --- | +| deployer | Deployó los contratos. Sin rol después. | `GC7UTBTYBYIXKBX3DWURKQQJNDKM35FEBIHJN63NT5OZIJRWFUP3VEC5` | `SBTOLGY2AMHDJSDTZRMSWOVOFN236M56YO57ZIA5WTRA7QCSIJEVWUQF` | +| issuer | Emite el USDC de prueba. Usala para darle más USDC a alguien. | `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN` | `SBKDWFFTWAXKKZEIBG3XREEZ7KFHIUGKXNMOKLAJIW44HUUEB46GAYZA` | +| holder-1 | Holder extra. | `GB6GENHQQWI3PRD6MVDIRO72VMIJCWENMHRSJU3Z53R372K5ZPHT7ILL` | `SB5RVYBJXKNOWMNBBXGWUHYJRKW75PY7PSX56GHPKXTOB7NW22MXYMHD` | +| holder-2 | Holder extra. | `GDJ7VTRMJ5XGALSIJDV4V6PBZRQEZZ6NUQOFUDW6TNYOMZMSL536XNAK` | `SASZ5BFK2W6LOJR4UNVUHPICW4VRDYSATCQQAOF7ISPO3OZSKTTF6NVQ` | +| holder-3 | Holder extra. | `GA6PAYUT232UAQKUYJ7QWF27JQWTKOVNRUHAF6B4CI3QT3XFKQAKSTXD` | `SAR27ZG7NEIKNBIC3U2ZFEP2MUKOOLJAPFZJXT4R2ZKIGOM5GO6T3EFX` | -| Metric | Value | -| --- | --- | -| Vault liquid USDC | 6,500 (4,000 free reserve + 2,500 pending escrow) | -| Custodian | 4,000 USDC | -| Total value (NAV basis) | 8,000 USDC | -| Latest NAV | $1.00 | - -## Accounts - -Import a secret key into a wallet (e.g. Freighter, set to testnet) to act as that role. All accounts are funded with testnet XLM. Holders are allowlisted. - -| Role | Public key | Secret key | USDC | Shares | Notes | -| --- | --- | --- | --- | --- | --- | -| **deployer** | `GAYUAX4KX7UTEFDEH74ZQ4UVFFHIDI4QQMZBG5HGROB2RWT43ETODZ2D` | `SCCXUU65HAO763EWNTKDB34RAWP2AXCPNCHUAD5J6NVMQIS77ZPT2JNT` | 0 | 0 | Deployed the contracts. No role after setup. | -| **governance** | `GBKHGH6YL67A7BJK2EMUHVVW5SVRQPFM4UGQPTSTP5RZPFSVAS5DEKQB` | `SBTXJRZWYI3UROKMMLBD2RLMYIXLGAMIIPICAUPQMVA4L6KFLPM4INU3` | 0 | 0 | Admin: upgrades, timelock config, custodian, role grants. | -| **manager** | `GBM4K7KKPTYX7VYQ3OMZXNSJBXRMYC3Y5NY7QUGXV3Q2N2CWLZ6TGP4U` | `SAXJN6H4PCIQUYD4J6L3IUHL2P7UACRKBYCQPTZTKIJCMQZV6JAXDSWY` | 0 | 0 | Closes epochs (`close_epoch`). | -| **treasury** | `GDXJFM42WBF3PY5DRDIKYFN7NCEMJOYAZCVZ724H7FYNA52D4B67OVTD` | `SBCI3NC7T2VTBRI4XFILDK5NX2RWSZI3EEANQE6OE4L6L5Q4XUB4VKUI` | 0 | 0 | Moves capital to the custodian (`deploy_to_custodian`). | -| **guardian** | `GC6RITGKYFPPPFISVDYTLOMI5N2O5NXIPWXXZJE66KJ44TAQWOGS7X7L` | `SAPHVL77L7VNRAWIEMBIJB7T5GB2Y3DP4BHLXQSRYZCL4T2SBLVFF5H5` | 0 | 0 | Pauses the vault and the oracle. | -| **compliance** | `GA7PI44ZKEZGJBRNFMWUPSI7RHLDE5SZGJS7JJY3VS7QA3QQKSN5JGXH` | `SABRR7WK7SW77JSMBQODJ5QQK52H7F42C7T7333XM6FGDAZUVQUPV4EH` | 0 | 0 | Allowlists identities (identity verifier admin) and freezes shares (share token compliance authority). | -| **attester** | `GAEVLCCCP37SOINVUSQ3O7PEMT3IUSWL6CWQSR4IW6KSA7QMCGJYP555` | `SDINAZ6PYB2EEWA3IH4TINXO2OGHJVEBKB5OA42YAAZ2BUEWOGKC77EI` | 0 | 0 | Attests NAV reports on the oracle. | -| **custodian** | `GBWRU6F5VBZCD2VTBZALUF5FRDR5532R5343KIAU2O6V6OLL7R7VJK63` | `SCDEZ545RZTWLWIZRI6FZCIIFUYZUO4D5GHN3P4ZRIVNIN7467GSD7PK` | 4,000 | 0 | Off-chain custodian. Receives deployed capital. | -| **issuer** | `GA35PEYTIHOJWYYMEFOSTLLQYJD3NJLFRNP56XSRLNLW7YAQEC5ALQLX` | `SALYIQ74ZJJD4AKVNWQWAVDOWDL5IJYVMV4EOSK6YUIRWETBSME2LZP7` | 0 | 0 | Issues the test USDC. | -| **holder-1** | `GBWHFQWQBELRT4FP4TP3DOYPTTTZKTH6CXJAVK7ZKY6W6SXR6U4733OF` | `SARGZUA34SWPZJU43VK24EO5W5FTMCBVIFLNWILWRKB3G6TIVPHZD6JG` | 45,000 | 4,000 | Investor. 4,000 liquid shares + 1,000 pending redeem in epoch #2. | -| **holder-2** | `GDNAITVEJZ45ITSUANJJK5FN7PKHB5KFZPZLKH5JB57BNVGKR576FPOE` | `SBKHH4Z6J275PMTA4HLUBRO37H54ZB7F4NBURW3EDM5XRRR55CPLTUHJ` | 47,000 | 3,000 | Investor. Shares claimed from epoch #1. | -| **holder-3** | `GCES4U4XR3CFTLSDYK4BRJEWVVWSP5NKFYXLNRIIXCW2SV35SSUAHJ6A` | `SB7XGSJUGL7VUQPQKBNBMKXYYOCGTSZFHKZWAQIHQX544TFVWVQFMY7V` | 47,500 | 0 | Investor. 2,500 USDC pending deposit in epoch #2. | -| **outsider** | `GDO3FXQIDRWYKOLAAKWLEUZRHIPJLTWFTFSLJBQ4R4A3FRAAPVXSMPRD` | `SAEH6PLTQTWSKYCX5XYOOKZH6HRQRSYI4P346QIYXR4R45PCZXG3WVO3` | 0 | 0 | No role. Permissionless calls (`fulfill_epoch`) and reads. | - -## Redeploying +## Redeploy + +Los roles salen de `scripts/harness/people.testnet.json`. Si existe, `deploy` usa esas keys para los roles que lista, y keys nuevas para el resto. ```sh nix develop stellar contract build cd scripts/harness -HARNESS_NETWORK=testnet npm run deploy -HARNESS_NETWORK=testnet npm run seed +HARNESS_NETWORK=testnet npm run deploy # contratos nuevos, mismas keys de personas +HARNESS_NETWORK=testnet npm run onboard # trustline, USDC y allowlist por persona; custodian ``` -The deploy creates new accounts and contracts, so update this file afterwards. +Los contratos cambian en cada deploy, así que después actualizá la tabla de contratos de este archivo. diff --git a/README.md b/README.md index 7c4aa8df..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` | `CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G` | -| `nav_oracle` | `CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS` | -| `share_token` | `CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF` | -| `identity_verifier` | `CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4` | -| `compliance` | `CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR` | -| `asset` | `CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL` | +| `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 abe74b37..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 = "CANQQHXL7W5NCQFSWP457EEP7FXR3PUWZZDO7APFKJ72SZYXMJ77ET7G" } -nav_oracle = { id = "CBV2X3SRCIVWTQ5OHX3FIBTHF5YO4KSUJKXHLD4FVDZGDMEYPOAOBXTS" } -share_token = { id = "CBJVWDMYIK2ZTN3XPXVRV3OXRHX6GN4BKDXGDHFYGP2XKFM5H5OBIRAF" } -identity_verifier = { id = "CA3SPHMKVYLVODOD7YMB2SYPOFAYATHNMFPQ4INFCEXTIMAWYXRDKFU4" } -compliance = { id = "CCUKJYBN5KBCOX43RUPQPIPXMHLG2DT5WKAQA67BYDO7HPUWDGCOKJUR" } -asset = { id = "CDILNUCY2KIO6UE4J2MKJYEM2G2DJ5PBZDR7JYUJUXZ7LXG6L7JNSFDL" } +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" From d36e2a001d4ea2e7df8728d11b3456168409dffc Mon Sep 17 00:00:00 2001 From: hpmaxi <358059+hpmaxi@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:37:06 -0300 Subject: [PATCH 3/3] docs: translate the e2e testing guide to English Also records that the issuer handed the USDC admin to Maxi, that each holder has 100,000 USDC, and how to mint more. --- E2E-TESTING.md | 91 ++++++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/E2E-TESTING.md b/E2E-TESTING.md index 25457b6b..a222dded 100644 --- a/E2E-TESTING.md +++ b/E2E-TESTING.md @@ -1,49 +1,50 @@ # E2E testing on testnet -Deploy fresco de `main` (`45e0e23`) en Stellar testnet, con una cuenta por persona para correr el runbook a mano. +A fresh deploy of `main` (`45e0e23`) on Stellar testnet, with one account per person to run the runbook by hand. -Son keys **solo de testnet**. No tienen valor real y son públicas a propósito. No las uses nunca en mainnet. +These keys are **testnet only**. They have no real value and are public on purpose. Never use them on mainnet. -- Red: Stellar testnet (`https://soroban-testnet.stellar.org`, passphrase `Test SDF Network ; September 2015`) +- Network: Stellar testnet (`https://soroban-testnet.stellar.org`, passphrase `Test SDF Network ; September 2015`) - Deploy: 2026-10-20T19:02:22.970Z (ledger 5299871) -## Personas +## People -Importá el secret en una wallet (por ejemplo Freighter, en testnet). Cada cuenta tiene unos 10.000 XLM, trustline de USDC, 50.000 USDC y está en el allowlist, así que también puede operar como holder. +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. -| Persona | Roles | Public key | Secret key | +| Person | Roles | Public key | Secret key | | --- | --- | --- | --- | -| **Maxi** | governance (admin del vault, del oracle y del share token) | `GD4E4AI7LVHIRX7QXAHQGRPABC6NFWFKHKJ6ZUAQNIGELVVN7MSFJWLF` | `SDHNHV6MEJP26WLCHC6F27XRLJ6H34R33KGKTQFECS7PYVQVB3RW2CE4` | -| **Pablo** | manager
guardian (vault y oracle) | `GBE43ELIMD3CUR6WMAMTCMI5UWXNFWX3GWJ3RSSIKUGJGTSRHF5X645V` | `SBW54VWPHJTS4O2VVTU4PTV5KNJZR4BXM4QOMFJAKSAC4BQM2GRQ2MDG` | +| **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 del identity verifier, autoridad de compliance del share token)
outsider | `GCFUDBW2N26PBMMAMI3RVTA5O2WOGJO7GCH6E4PNFAVQNRVW2YEOELXE` | `SBD4TKZWYGHJDUS237S53UEJB45QO6WUOXRABUUNUYHLKNMCYTDLCK5G` | +| **Iñaky** | compliance (admin of the identity verifier, compliance authority of the share token)
outsider | `GCFUDBW2N26PBMMAMI3RVTA5O2WOGJO7GCH6E4PNFAVQNRVW2YEOELXE` | `SBD4TKZWYGHJDUS237S53UEJB45QO6WUOXRABUUNUYHLKNMCYTDLCK5G` | -Luciano es a la vez treasury y custodian: `deploy_to_custodian` le manda el USDC a su propia cuenta. +Luciano is both treasury and custodian: `deploy_to_custodian` sends the USDC to Luciano's own account. -## Quién firma qué +## Who signs what -| Acción | Firma | +| Action | Signer | | --- | --- | -| `request_deposit`, `request_redeem`, `claim_*`, cancelaciones | cualquier persona (todas son holders) | +| `request_deposit`, `request_redeem`, `claim_*`, cancellations | anyone (every person is a holder) | | `close_epoch` | Pablo | -| Atestar el NAV en el oracle | Luciano | -| `fulfill_epoch` | cualquiera (sin permisos); Iñaky como outsider | +| Attest the NAV on the oracle | Luciano | +| `fulfill_epoch` | anyone (no permission needed); Iñaky as outsider | | `deploy_to_custodian` | Luciano (treasury) | -| `fund` (devolver USDC al vault) | Luciano como custodian; en realidad cualquiera puede | -| Pausar / despausar el vault o el oracle | Pablo | -| Allowlist, freeze, transferencias forzadas | Iñaky | -| Parámetros, roles, custodian, upgrades (timelock), wind-down | Maxi | +| `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 | -## Estado inicial +## Initial state -- Epoch #1 **abierto**, sin requests. -- Oracle sin NAV atestado todavía. -- Custodian del vault: Luciano. -- Vault sin fondos. +- Epoch #1 **open**, with no requests. +- No NAV attested on the oracle yet. +- Vault custodian: Luciano. +- The vault holds no funds. -## Contratos +## Contracts -| Contrato | Address | +| Contract | Address | | --- | --- | | `compliance` | `CCCA6XDCXBZWWTIDYGLTL5GEHROCZTGYAIRAFA2KBO2JAWOJFU3TMZCB` | | `identity_verifier` | `CDMV7DGGLGIWDWI4ERIBZ5YS2FZA3QVZPVUWCAWR5VBLVJMLFYCW6DML` | @@ -52,30 +53,40 @@ Luciano es a la vez treasury y custodian: `deploy_to_custodian` le manda el USDC | `async_vault` | `CAGHFSATIJHNDXQA577APGG6GF2L7Q7KP6H2IUXVW6IT3CJ2OBE3QCEB` | | `asset` (USDC SAC) | `CAU7LDCUCLAWSJZS72B7VIPIE7FSN3T63D7MZMSSXTNLKYDYECVGH522` | -USDC: código `USDC`, issuer `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN`. +USDC: code `USDC`, issuer `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN`. The issuer handed the USDC contract's admin to Maxi, so Maxi is the one who mints. -## Cuentas del harness +If your wallet shows two USDC assets, use this issuer's. The other one is from an earlier deploy. -Las usan los scripts. No hacen falta para el runbook. +## Harness accounts -| Cuenta | Para qué | Public key | Secret key | +The scripts create them. The holders work as extra investors in the runbook. + +| Account | Purpose | Public key | Secret key | | --- | --- | --- | --- | -| deployer | Deployó los contratos. Sin rol después. | `GC7UTBTYBYIXKBX3DWURKQQJNDKM35FEBIHJN63NT5OZIJRWFUP3VEC5` | `SBTOLGY2AMHDJSDTZRMSWOVOFN236M56YO57ZIA5WTRA7QCSIJEVWUQF` | -| issuer | Emite el USDC de prueba. Usala para darle más USDC a alguien. | `GCUYFZRTNQ6WIIN5XBEYWVENKNVCBPK5BRXK4PDBCOGCYA4BZIRGMVIN` | `SBKDWFFTWAXKKZEIBG3XREEZ7KFHIUGKXNMOKLAJIW44HUUEB46GAYZA` | -| holder-1 | Holder extra. | `GB6GENHQQWI3PRD6MVDIRO72VMIJCWENMHRSJU3Z53R372K5ZPHT7ILL` | `SB5RVYBJXKNOWMNBBXGWUHYJRKW75PY7PSX56GHPKXTOB7NW22MXYMHD` | -| holder-2 | Holder extra. | `GDJ7VTRMJ5XGALSIJDV4V6PBZRQEZZ6NUQOFUDW6TNYOMZMSL536XNAK` | `SASZ5BFK2W6LOJR4UNVUHPICW4VRDYSATCQQAOF7ISPO3OZSKTTF6NVQ` | -| holder-3 | Holder extra. | `GA6PAYUT232UAQKUYJ7QWF27JQWTKOVNRUHAF6B4CI3QT3XFKQAKSTXD` | `SAR27ZG7NEIKNBIC3U2ZFEP2MUKOOLJAPFZJXT4R2ZKIGOM5GO6T3EFX` | +| 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 -Los roles salen de `scripts/harness/people.testnet.json`. Si existe, `deploy` usa esas keys para los roles que lista, y keys nuevas para el resto. +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 -nix develop stellar contract build cd scripts/harness -HARNESS_NETWORK=testnet npm run deploy # contratos nuevos, mismas keys de personas -HARNESS_NETWORK=testnet npm run onboard # trustline, USDC y allowlist por persona; custodian +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) ``` -Los contratos cambian en cada deploy, así que después actualizá la tabla de contratos de este archivo. +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.