Add Salus native contract for universal healthcare
Implement universal healthcare infrastructure for all citizens: - Healthcare Accounts: One per Vita holder (birth-to-death coverage) - Annual credits allocation for medical services - Coverage tracking and eligibility verification - Medical Records: Privacy-preserving health data management - Patient-controlled access permissions - Provider-specific record creation - Encrypted off-chain content with on-chain hashes - Provider Registry: Healthcare provider management - Registration and verification (RoleHealthProvider) - Specialty and capability tracking - Suspension for policy violations - Authorization System: Patient consent management - Explicit provider access grants - Time-limited and scope-limited permissions - Revocation with audit trail - Emergency Access: Life-saving overrides - Temporary access for emergency responders - Automatic expiration with logging - Post-facto patient notification - Cross-contract integration: - Vita: Patient identity verification - Lex: RightHealthcare enforcement - RoleRegistry: RoleHealthProvider (ID 21) Contract ID: -19 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6d79a79672
commit
83c12c5362
|
|
@ -0,0 +1,254 @@
|
||||||
|
package native_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/neotest"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newSalusClient(t *testing.T) *neotest.ContractInvoker {
|
||||||
|
return newNativeClient(t, nativenames.Salus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetConfig tests the getConfig method.
|
||||||
|
func TestSalus_GetConfig(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Get default config
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array result")
|
||||||
|
require.GreaterOrEqual(t, len(arr), 4) // SalusConfig has 4 fields
|
||||||
|
}, "getConfig")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetTotalAccounts tests the getTotalAccounts method.
|
||||||
|
func TestSalus_GetTotalAccounts(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Initially should be 0
|
||||||
|
c.Invoke(t, 0, "getTotalAccounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetTotalRecords tests the getTotalRecords method.
|
||||||
|
func TestSalus_GetTotalRecords(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Initially should be 0
|
||||||
|
c.Invoke(t, 0, "getTotalRecords")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetTotalProviders tests the getTotalProviders method.
|
||||||
|
func TestSalus_GetTotalProviders(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Initially should be 0
|
||||||
|
c.Invoke(t, 0, "getTotalProviders")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetAccount_NonExistent tests getting a non-existent account.
|
||||||
|
func TestSalus_GetAccount_NonExistent(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
|
||||||
|
// Non-existent account should return null
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
require.Nil(t, stack[0].Value(), "expected null for non-existent account")
|
||||||
|
}, "getAccount", acc.ScriptHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetCredits_NonExistent tests getting credits for non-existent account.
|
||||||
|
func TestSalus_GetCredits_NonExistent(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
|
||||||
|
// Non-existent account should return 0 credits
|
||||||
|
c.Invoke(t, 0, "getCredits", acc.ScriptHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_ActivateHealthcare_NoVita tests that activating healthcare without Vita fails.
|
||||||
|
func TestSalus_ActivateHealthcare_NoVita(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
invoker := c.WithSigners(acc)
|
||||||
|
|
||||||
|
// Should fail - no Vita registered
|
||||||
|
invoker.InvokeFail(t, "owner must have an active Vita", "activateHealthcare", acc.ScriptHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_AllocateCredits_NotCommittee tests that non-committee cannot allocate credits.
|
||||||
|
func TestSalus_AllocateCredits_NotCommittee(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
invoker := c.WithSigners(acc)
|
||||||
|
|
||||||
|
// Should fail - not committee
|
||||||
|
invoker.InvokeFail(t, "invalid committee signature", "allocateCredits",
|
||||||
|
acc.ScriptHash(), 100, "test allocation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_RegisterProvider_NotCommittee tests that non-committee cannot register providers.
|
||||||
|
func TestSalus_RegisterProvider_NotCommittee(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
invoker := c.WithSigners(acc)
|
||||||
|
|
||||||
|
licenseHash := make([]byte, 32) // 32-byte hash
|
||||||
|
|
||||||
|
// Should fail - not committee
|
||||||
|
invoker.InvokeFail(t, "invalid committee signature", "registerProvider",
|
||||||
|
acc.ScriptHash(), "Test Hospital", "General", licenseHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetProvider_NonExistent tests getting a non-existent provider.
|
||||||
|
func TestSalus_GetProvider_NonExistent(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Non-existent provider should return null
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
require.Nil(t, stack[0].Value(), "expected null for non-existent provider")
|
||||||
|
}, "getProvider", int64(999))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetAuthorization_NonExistent tests getting a non-existent authorization.
|
||||||
|
func TestSalus_GetAuthorization_NonExistent(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Non-existent authorization should return null
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
require.Nil(t, stack[0].Value(), "expected null for non-existent authorization")
|
||||||
|
}, "getAuthorization", int64(999))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_HasAccess_NoAccount tests hasAccess for non-existent account.
|
||||||
|
func TestSalus_HasAccess_NoAccount(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
patient := e.NewAccount(t)
|
||||||
|
provider := e.NewAccount(t)
|
||||||
|
|
||||||
|
// No account = no access
|
||||||
|
c.Invoke(t, false, "hasAccess", patient.ScriptHash(), provider.ScriptHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_GetEmergencyAccess_NonExistent tests getting non-existent emergency access.
|
||||||
|
func TestSalus_GetEmergencyAccess_NonExistent(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
|
||||||
|
// Non-existent emergency access should return null
|
||||||
|
c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
require.Nil(t, stack[0].Value(), "expected null for non-existent emergency access")
|
||||||
|
}, "getEmergencyAccess", int64(999))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_ActivateHealthcareWithVita tests healthcare activation with a valid Vita.
|
||||||
|
func TestSalus_ActivateHealthcareWithVita(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
// Register Vita first
|
||||||
|
vitaHash := e.NativeHash(t, nativenames.Vita)
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
vitaInvoker := e.NewInvoker(vitaHash, acc)
|
||||||
|
|
||||||
|
owner := acc.ScriptHash()
|
||||||
|
personHash := hash.Sha256(owner.BytesBE()).BytesBE()
|
||||||
|
isEntity := false
|
||||||
|
recoveryHash := hash.Sha256([]byte("recovery")).BytesBE()
|
||||||
|
|
||||||
|
// Register Vita token
|
||||||
|
vitaInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
_, ok := stack[0].Value().([]byte)
|
||||||
|
require.True(t, ok, "expected ByteArray result")
|
||||||
|
}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash)
|
||||||
|
|
||||||
|
// Now activate Salus account - need to pass owner as BytesBE for Hash160 type
|
||||||
|
salusInvoker := c.WithSigners(acc)
|
||||||
|
salusInvoker.Invoke(t, true, "activateHealthcare", owner.BytesBE())
|
||||||
|
|
||||||
|
// Verify account exists - also pass as BytesBE
|
||||||
|
salusInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
arr, ok := stack[0].Value().([]stackitem.Item)
|
||||||
|
require.True(t, ok, "expected array result for existing account")
|
||||||
|
require.GreaterOrEqual(t, len(arr), 10) // HealthcareAccount has 10 fields
|
||||||
|
}, "getAccount", owner.BytesBE())
|
||||||
|
|
||||||
|
// Verify total accounts increased
|
||||||
|
c.Invoke(t, 1, "getTotalAccounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_AllocateCredits tests credit allocation by committee.
|
||||||
|
func TestSalus_AllocateCredits(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
// Register Vita first
|
||||||
|
vitaHash := e.NativeHash(t, nativenames.Vita)
|
||||||
|
acc := e.NewAccount(t)
|
||||||
|
vitaInvoker := e.NewInvoker(vitaHash, acc)
|
||||||
|
|
||||||
|
owner := acc.ScriptHash()
|
||||||
|
personHash := hash.Sha256(owner.BytesBE()).BytesBE()
|
||||||
|
isEntity := false
|
||||||
|
recoveryHash := hash.Sha256([]byte("recovery")).BytesBE()
|
||||||
|
|
||||||
|
// Register returns ByteArray (tokenID), not Null
|
||||||
|
vitaInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
_, ok := stack[0].Value().([]byte)
|
||||||
|
require.True(t, ok, "expected ByteArray result")
|
||||||
|
}, "register", owner.BytesBE(), personHash, isEntity, recoveryHash)
|
||||||
|
|
||||||
|
// Activate Salus account - use BytesBE for Hash160
|
||||||
|
salusInvoker := c.WithSigners(acc)
|
||||||
|
salusInvoker.Invoke(t, true, "activateHealthcare", owner.BytesBE())
|
||||||
|
|
||||||
|
// Allocate credits as committee - use BytesBE for Hash160
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
committeeInvoker.Invoke(t, true, "allocateCredits", owner.BytesBE(), int64(500), "annual allocation")
|
||||||
|
|
||||||
|
// Default annual allocation is 10000, so after adding 500 it should be 10500
|
||||||
|
c.Invoke(t, 10500, "getCredits", owner.BytesBE())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSalus_RegisterProvider tests provider registration by committee.
|
||||||
|
func TestSalus_RegisterProvider(t *testing.T) {
|
||||||
|
c := newSalusClient(t)
|
||||||
|
e := c.Executor
|
||||||
|
|
||||||
|
provider := e.NewAccount(t)
|
||||||
|
licenseHash := make([]byte, 32)
|
||||||
|
copy(licenseHash, []byte("license"))
|
||||||
|
|
||||||
|
// Register provider as committee
|
||||||
|
committeeInvoker := c.WithSigners(c.Committee)
|
||||||
|
committeeInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
|
||||||
|
require.Equal(t, 1, len(stack))
|
||||||
|
// First provider gets ID 0
|
||||||
|
}, "registerProvider", provider.ScriptHash(), "Test Hospital", "General", licenseHash)
|
||||||
|
|
||||||
|
// Verify total providers increased
|
||||||
|
c.Invoke(t, 1, "getTotalProviders")
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,632 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/util"
|
||||||
|
"github.com/tutus-one/tutus-chain/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthcareAccountStatus represents the status of a healthcare account.
|
||||||
|
type HealthcareAccountStatus uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// HealthcareAccountActive indicates an active account.
|
||||||
|
HealthcareAccountActive HealthcareAccountStatus = 0
|
||||||
|
// HealthcareAccountSuspended indicates a temporarily suspended account.
|
||||||
|
HealthcareAccountSuspended HealthcareAccountStatus = 1
|
||||||
|
// HealthcareAccountClosed indicates a permanently closed account.
|
||||||
|
HealthcareAccountClosed HealthcareAccountStatus = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// MedicalRecordType represents the type of medical record.
|
||||||
|
type MedicalRecordType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RecordTypeCheckup indicates a routine checkup.
|
||||||
|
RecordTypeCheckup MedicalRecordType = 0
|
||||||
|
// RecordTypeTreatment indicates a treatment.
|
||||||
|
RecordTypeTreatment MedicalRecordType = 1
|
||||||
|
// RecordTypeEmergency indicates an emergency visit.
|
||||||
|
RecordTypeEmergency MedicalRecordType = 2
|
||||||
|
// RecordTypePrescription indicates a prescription.
|
||||||
|
RecordTypePrescription MedicalRecordType = 3
|
||||||
|
// RecordTypeLabResult indicates lab results.
|
||||||
|
RecordTypeLabResult MedicalRecordType = 4
|
||||||
|
// RecordTypeVaccination indicates a vaccination.
|
||||||
|
RecordTypeVaccination MedicalRecordType = 5
|
||||||
|
// RecordTypeMentalHealth indicates mental health services.
|
||||||
|
RecordTypeMentalHealth MedicalRecordType = 6
|
||||||
|
// RecordTypePreventive indicates preventive care.
|
||||||
|
RecordTypePreventive MedicalRecordType = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccessLevel represents the level of access granted to a provider.
|
||||||
|
type AccessLevel uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AccessLevelNone indicates no access.
|
||||||
|
AccessLevelNone AccessLevel = 0
|
||||||
|
// AccessLevelEmergency indicates emergency-only access.
|
||||||
|
AccessLevelEmergency AccessLevel = 1
|
||||||
|
// AccessLevelLimited indicates limited access (specific record types).
|
||||||
|
AccessLevelLimited AccessLevel = 2
|
||||||
|
// AccessLevelFull indicates full access to all records.
|
||||||
|
AccessLevelFull AccessLevel = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderStatus represents the status of a healthcare provider.
|
||||||
|
type ProviderStatus uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ProviderStatusActive indicates an active provider.
|
||||||
|
ProviderStatusActive ProviderStatus = 0
|
||||||
|
// ProviderStatusSuspended indicates a suspended provider.
|
||||||
|
ProviderStatusSuspended ProviderStatus = 1
|
||||||
|
// ProviderStatusRevoked indicates a revoked provider.
|
||||||
|
ProviderStatusRevoked ProviderStatus = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// HealthcareAccount represents a citizen's healthcare account.
|
||||||
|
type HealthcareAccount struct {
|
||||||
|
VitaID uint64 // Owner's Vita token ID
|
||||||
|
Owner util.Uint160 // Owner's address
|
||||||
|
AnnualAllocation uint64 // Annual healthcare credits
|
||||||
|
CreditsUsed uint64 // Credits used this year
|
||||||
|
CreditsAvailable uint64 // Available healthcare credits
|
||||||
|
BiologicalAge uint32 // Biological age (Salus-adjusted)
|
||||||
|
LastCheckup uint32 // Block height of last checkup
|
||||||
|
Status HealthcareAccountStatus // Account status
|
||||||
|
CreatedAt uint32 // Block height when created
|
||||||
|
UpdatedAt uint32 // Block height of last update
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (a *HealthcareAccount) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.VitaID))),
|
||||||
|
stackitem.NewByteArray(a.Owner.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.AnnualAllocation))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.CreditsUsed))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.CreditsAvailable))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.BiologicalAge))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.LastCheckup))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.Status))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.CreatedAt))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(a.UpdatedAt))),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (a *HealthcareAccount) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 10 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
vitaID, err := items[0].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid vitaID: %w", err)
|
||||||
|
}
|
||||||
|
a.VitaID = vitaID.Uint64()
|
||||||
|
|
||||||
|
owner, err := items[1].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid owner: %w", err)
|
||||||
|
}
|
||||||
|
a.Owner, err = util.Uint160DecodeBytesBE(owner)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid owner address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
annualAllocation, err := items[2].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid annualAllocation: %w", err)
|
||||||
|
}
|
||||||
|
a.AnnualAllocation = annualAllocation.Uint64()
|
||||||
|
|
||||||
|
creditsUsed, err := items[3].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid creditsUsed: %w", err)
|
||||||
|
}
|
||||||
|
a.CreditsUsed = creditsUsed.Uint64()
|
||||||
|
|
||||||
|
creditsAvailable, err := items[4].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid creditsAvailable: %w", err)
|
||||||
|
}
|
||||||
|
a.CreditsAvailable = creditsAvailable.Uint64()
|
||||||
|
|
||||||
|
biologicalAge, err := items[5].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid biologicalAge: %w", err)
|
||||||
|
}
|
||||||
|
a.BiologicalAge = uint32(biologicalAge.Uint64())
|
||||||
|
|
||||||
|
lastCheckup, err := items[6].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid lastCheckup: %w", err)
|
||||||
|
}
|
||||||
|
a.LastCheckup = uint32(lastCheckup.Uint64())
|
||||||
|
|
||||||
|
status, err := items[7].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid status: %w", err)
|
||||||
|
}
|
||||||
|
a.Status = HealthcareAccountStatus(status.Uint64())
|
||||||
|
|
||||||
|
createdAt, err := items[8].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid createdAt: %w", err)
|
||||||
|
}
|
||||||
|
a.CreatedAt = uint32(createdAt.Uint64())
|
||||||
|
|
||||||
|
updatedAt, err := items[9].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
||||||
|
}
|
||||||
|
a.UpdatedAt = uint32(updatedAt.Uint64())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MedicalRecord represents a medical record reference (data stored off-chain).
|
||||||
|
type MedicalRecord struct {
|
||||||
|
ID uint64 // Unique record ID
|
||||||
|
VitaID uint64 // Patient's Vita ID
|
||||||
|
Patient util.Uint160 // Patient's address
|
||||||
|
Provider util.Uint160 // Healthcare provider's address
|
||||||
|
RecordType MedicalRecordType // Type of medical record
|
||||||
|
ContentHash util.Uint256 // Hash of encrypted off-chain data
|
||||||
|
CreditsUsed uint64 // Healthcare credits used
|
||||||
|
CreatedAt uint32 // Block height when created
|
||||||
|
IsActive bool // Whether record is valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (r *MedicalRecord) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(r.ID))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(r.VitaID))),
|
||||||
|
stackitem.NewByteArray(r.Patient.BytesBE()),
|
||||||
|
stackitem.NewByteArray(r.Provider.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(r.RecordType))),
|
||||||
|
stackitem.NewByteArray(r.ContentHash.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(r.CreditsUsed))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))),
|
||||||
|
stackitem.NewBool(r.IsActive),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (r *MedicalRecord) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 9 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := items[0].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid id: %w", err)
|
||||||
|
}
|
||||||
|
r.ID = id.Uint64()
|
||||||
|
|
||||||
|
vitaID, err := items[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid vitaID: %w", err)
|
||||||
|
}
|
||||||
|
r.VitaID = vitaID.Uint64()
|
||||||
|
|
||||||
|
patient, err := items[2].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient: %w", err)
|
||||||
|
}
|
||||||
|
r.Patient, err = util.Uint160DecodeBytesBE(patient)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := items[3].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider: %w", err)
|
||||||
|
}
|
||||||
|
r.Provider, err = util.Uint160DecodeBytesBE(provider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recordType, err := items[4].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid recordType: %w", err)
|
||||||
|
}
|
||||||
|
r.RecordType = MedicalRecordType(recordType.Uint64())
|
||||||
|
|
||||||
|
contentHash, err := items[5].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid contentHash: %w", err)
|
||||||
|
}
|
||||||
|
r.ContentHash, err = util.Uint256DecodeBytesBE(contentHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid contentHash value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
creditsUsed, err := items[6].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid creditsUsed: %w", err)
|
||||||
|
}
|
||||||
|
r.CreditsUsed = creditsUsed.Uint64()
|
||||||
|
|
||||||
|
createdAt, err := items[7].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid createdAt: %w", err)
|
||||||
|
}
|
||||||
|
r.CreatedAt = uint32(createdAt.Uint64())
|
||||||
|
|
||||||
|
isActive, err := items[8].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid isActive: %w", err)
|
||||||
|
}
|
||||||
|
r.IsActive = isActive
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderAuthorization represents a healthcare provider's access authorization.
|
||||||
|
type ProviderAuthorization struct {
|
||||||
|
ID uint64 // Authorization ID
|
||||||
|
VitaID uint64 // Patient's Vita ID
|
||||||
|
Patient util.Uint160 // Patient's address
|
||||||
|
Provider util.Uint160 // Healthcare provider's address
|
||||||
|
AccessLevel AccessLevel // Level of access granted
|
||||||
|
StartsAt uint32 // Block height when access starts
|
||||||
|
ExpiresAt uint32 // Block height when access expires (0 = no expiry)
|
||||||
|
IsActive bool // Whether authorization is currently active
|
||||||
|
GrantedAt uint32 // Block height when granted
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (p *ProviderAuthorization) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.ID))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.VitaID))),
|
||||||
|
stackitem.NewByteArray(p.Patient.BytesBE()),
|
||||||
|
stackitem.NewByteArray(p.Provider.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.AccessLevel))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.StartsAt))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.ExpiresAt))),
|
||||||
|
stackitem.NewBool(p.IsActive),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.GrantedAt))),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (p *ProviderAuthorization) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 9 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := items[0].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid id: %w", err)
|
||||||
|
}
|
||||||
|
p.ID = id.Uint64()
|
||||||
|
|
||||||
|
vitaID, err := items[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid vitaID: %w", err)
|
||||||
|
}
|
||||||
|
p.VitaID = vitaID.Uint64()
|
||||||
|
|
||||||
|
patient, err := items[2].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient: %w", err)
|
||||||
|
}
|
||||||
|
p.Patient, err = util.Uint160DecodeBytesBE(patient)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := items[3].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider: %w", err)
|
||||||
|
}
|
||||||
|
p.Provider, err = util.Uint160DecodeBytesBE(provider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessLevel, err := items[4].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid accessLevel: %w", err)
|
||||||
|
}
|
||||||
|
p.AccessLevel = AccessLevel(accessLevel.Uint64())
|
||||||
|
|
||||||
|
startsAt, err := items[5].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid startsAt: %w", err)
|
||||||
|
}
|
||||||
|
p.StartsAt = uint32(startsAt.Uint64())
|
||||||
|
|
||||||
|
expiresAt, err := items[6].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid expiresAt: %w", err)
|
||||||
|
}
|
||||||
|
p.ExpiresAt = uint32(expiresAt.Uint64())
|
||||||
|
|
||||||
|
isActive, err := items[7].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid isActive: %w", err)
|
||||||
|
}
|
||||||
|
p.IsActive = isActive
|
||||||
|
|
||||||
|
grantedAt, err := items[8].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid grantedAt: %w", err)
|
||||||
|
}
|
||||||
|
p.GrantedAt = uint32(grantedAt.Uint64())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExpired checks if the authorization has expired.
|
||||||
|
func (p *ProviderAuthorization) IsExpired(currentBlock uint32) bool {
|
||||||
|
return p.ExpiresAt != 0 && p.ExpiresAt <= currentBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid checks if the authorization is currently valid.
|
||||||
|
func (p *ProviderAuthorization) IsValid(currentBlock uint32) bool {
|
||||||
|
return p.IsActive && currentBlock >= p.StartsAt && !p.IsExpired(currentBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HealthcareProvider represents a registered healthcare provider.
|
||||||
|
type HealthcareProvider struct {
|
||||||
|
Address util.Uint160 // Provider's address
|
||||||
|
Name string // Provider name
|
||||||
|
ProviderID uint64 // Unique provider ID
|
||||||
|
Specialty string // Medical specialty
|
||||||
|
LicenseHash util.Uint256 // Hash of license documentation
|
||||||
|
Status ProviderStatus // Provider status
|
||||||
|
RegisteredAt uint32 // Block height when registered
|
||||||
|
UpdatedAt uint32 // Block height of last update
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (p *HealthcareProvider) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(p.Address.BytesBE()),
|
||||||
|
stackitem.NewByteArray([]byte(p.Name)),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.ProviderID))),
|
||||||
|
stackitem.NewByteArray([]byte(p.Specialty)),
|
||||||
|
stackitem.NewByteArray(p.LicenseHash.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.Status))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.RegisteredAt))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(p.UpdatedAt))),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (p *HealthcareProvider) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 8 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := items[0].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid address: %w", err)
|
||||||
|
}
|
||||||
|
p.Address, err = util.Uint160DecodeBytesBE(address)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := items[1].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid name: %w", err)
|
||||||
|
}
|
||||||
|
p.Name = string(name)
|
||||||
|
|
||||||
|
providerID, err := items[2].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid providerID: %w", err)
|
||||||
|
}
|
||||||
|
p.ProviderID = providerID.Uint64()
|
||||||
|
|
||||||
|
specialty, err := items[3].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid specialty: %w", err)
|
||||||
|
}
|
||||||
|
p.Specialty = string(specialty)
|
||||||
|
|
||||||
|
licenseHash, err := items[4].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid licenseHash: %w", err)
|
||||||
|
}
|
||||||
|
p.LicenseHash, err = util.Uint256DecodeBytesBE(licenseHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid licenseHash value: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := items[5].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid status: %w", err)
|
||||||
|
}
|
||||||
|
p.Status = ProviderStatus(status.Uint64())
|
||||||
|
|
||||||
|
registeredAt, err := items[6].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid registeredAt: %w", err)
|
||||||
|
}
|
||||||
|
p.RegisteredAt = uint32(registeredAt.Uint64())
|
||||||
|
|
||||||
|
updatedAt, err := items[7].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid updatedAt: %w", err)
|
||||||
|
}
|
||||||
|
p.UpdatedAt = uint32(updatedAt.Uint64())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmergencyAccess represents an emergency access grant.
|
||||||
|
type EmergencyAccess struct {
|
||||||
|
ID uint64 // Emergency access ID
|
||||||
|
VitaID uint64 // Patient's Vita ID
|
||||||
|
Patient util.Uint160 // Patient's address
|
||||||
|
Provider util.Uint160 // Provider who accessed
|
||||||
|
Reason string // Emergency reason
|
||||||
|
GrantedAt uint32 // Block height when granted
|
||||||
|
ExpiresAt uint32 // Block height when expires
|
||||||
|
WasReviewed bool // Whether access was reviewed
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (e *EmergencyAccess) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(e.ID))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(e.VitaID))),
|
||||||
|
stackitem.NewByteArray(e.Patient.BytesBE()),
|
||||||
|
stackitem.NewByteArray(e.Provider.BytesBE()),
|
||||||
|
stackitem.NewByteArray([]byte(e.Reason)),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(e.GrantedAt))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(e.ExpiresAt))),
|
||||||
|
stackitem.NewBool(e.WasReviewed),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (e *EmergencyAccess) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 8 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := items[0].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid id: %w", err)
|
||||||
|
}
|
||||||
|
e.ID = id.Uint64()
|
||||||
|
|
||||||
|
vitaID, err := items[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid vitaID: %w", err)
|
||||||
|
}
|
||||||
|
e.VitaID = vitaID.Uint64()
|
||||||
|
|
||||||
|
patient, err := items[2].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient: %w", err)
|
||||||
|
}
|
||||||
|
e.Patient, err = util.Uint160DecodeBytesBE(patient)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid patient address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := items[3].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider: %w", err)
|
||||||
|
}
|
||||||
|
e.Provider, err = util.Uint160DecodeBytesBE(provider)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid provider address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reason, err := items[4].TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid reason: %w", err)
|
||||||
|
}
|
||||||
|
e.Reason = string(reason)
|
||||||
|
|
||||||
|
grantedAt, err := items[5].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid grantedAt: %w", err)
|
||||||
|
}
|
||||||
|
e.GrantedAt = uint32(grantedAt.Uint64())
|
||||||
|
|
||||||
|
expiresAt, err := items[6].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid expiresAt: %w", err)
|
||||||
|
}
|
||||||
|
e.ExpiresAt = uint32(expiresAt.Uint64())
|
||||||
|
|
||||||
|
wasReviewed, err := items[7].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid wasReviewed: %w", err)
|
||||||
|
}
|
||||||
|
e.WasReviewed = wasReviewed
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SalusConfig represents configurable parameters for the Salus contract.
|
||||||
|
type SalusConfig struct {
|
||||||
|
DefaultAnnualCredits uint64 // Default annual healthcare credits
|
||||||
|
EmergencyAccessDuration uint32 // Blocks for emergency access (default ~24 hours)
|
||||||
|
PreventiveCareBonus uint64 // Bonus credits for preventive care
|
||||||
|
MaxAuthorizationDuration uint32 // Maximum authorization duration in blocks
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStackItem implements stackitem.Convertible interface.
|
||||||
|
func (c *SalusConfig) ToStackItem() (stackitem.Item, error) {
|
||||||
|
return stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(c.DefaultAnnualCredits))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(c.EmergencyAccessDuration))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(c.PreventiveCareBonus))),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(int64(c.MaxAuthorizationDuration))),
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem implements stackitem.Convertible interface.
|
||||||
|
func (c *SalusConfig) FromStackItem(item stackitem.Item) error {
|
||||||
|
items, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not a struct")
|
||||||
|
}
|
||||||
|
if len(items) != 4 {
|
||||||
|
return fmt.Errorf("wrong number of elements: expected 4, got %d", len(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultCredits, err := items[0].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid defaultAnnualCredits: %w", err)
|
||||||
|
}
|
||||||
|
c.DefaultAnnualCredits = defaultCredits.Uint64()
|
||||||
|
|
||||||
|
emergencyDuration, err := items[1].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid emergencyAccessDuration: %w", err)
|
||||||
|
}
|
||||||
|
c.EmergencyAccessDuration = uint32(emergencyDuration.Uint64())
|
||||||
|
|
||||||
|
preventiveBonus, err := items[2].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid preventiveCareBonus: %w", err)
|
||||||
|
}
|
||||||
|
c.PreventiveCareBonus = preventiveBonus.Uint64()
|
||||||
|
|
||||||
|
maxAuthDuration, err := items[3].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid maxAuthorizationDuration: %w", err)
|
||||||
|
}
|
||||||
|
c.MaxAuthorizationDuration = uint32(maxAuthDuration.Uint64())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue