/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/stellar/remote/remote.go
1 184 строки
36 KB
zoom-ua
modernize error handling with errors.As/Is (#29350)
25 июн 2026, 17:41
Не верифицирован
25 июн 2026, 17:41
e09e254
Код
Авторство
О чём код?
package remote import ( "context" "encoding/json" "errors" "fmt" "strconv" "strings" "time" "github.com/keybase/client/go/libkb" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/client/go/protocol/stellar1" "github.com/keybase/client/go/stellar/bundle" ) var ErrAccountIDMissing = errors.New("account id parameter missing") type shouldCreateRes struct { libkb.AppStatusEmbed ShouldCreateResult } type ShouldCreateResult struct { ShouldCreate bool `json:"shouldcreate"` HasWallet bool `json:"haswallet"` AcceptedDisclaimer bool `json:"accepteddisclaimer"` } // ShouldCreate asks the server whether to create this user's initial wallet. func ShouldCreate(ctx context.Context, g *libkb.GlobalContext) (res ShouldCreateResult, err error) { mctx := libkb.NewMetaContext(ctx, g) defer mctx.Trace("Stellar.ShouldCreate", &err)() defer func() { mctx.Debug("Stellar.ShouldCreate: (res:%+v, err:%v)", res, err != nil) }() arg := libkb.NewAPIArg("stellar/shouldcreate") arg.RetryCount = 3 arg.SessionType = libkb.APISessionTypeREQUIRED var apiRes shouldCreateRes err = mctx.G().API.GetDecode(mctx, arg, &apiRes) return apiRes.ShouldCreateResult, err } func buildChainLinkPayload(m libkb.MetaContext, b stellar1.Bundle, me *libkb.User, pukGen keybase1.PerUserKeyGeneration, pukSeed libkb.PerUserKeySeed, deviceSigKey libkb.GenericKey) (*libkb.JSONPayload, keybase1.Seqno, libkb.LinkID, error) { err := b.CheckInvariants() if err != nil { return nil, 0, nil, err } if len(b.Accounts) < 1 { return nil, 0, nil, errors.New("stellar bundle has no accounts") } // Find the new primary account for the chain link. stellarAccount, err := b.PrimaryAccount() if err != nil { return nil, 0, nil, err } stellarAccountBundle, ok := b.AccountBundles[stellarAccount.AccountID] if !ok { return nil, 0, nil, errors.New("stellar primary account has no account bundle") } if len(stellarAccountBundle.Signers) < 1 { return nil, 0, nil, errors.New("stellar bundle has no signers") } if !stellarAccount.IsPrimary { return nil, 0, nil, errors.New("initial stellar account is not primary") } m.Debug("Stellar.PostWithChainLink: revision:%v accountID:%v pukGen:%v", b.Revision, stellarAccount.AccountID, pukGen) boxed, err := bundle.BoxAndEncode(&b, pukGen, pukSeed) if err != nil { return nil, 0, nil, err } m.Debug("Stellar.PostWithChainLink: make sigs") sig, err := libkb.StellarProofReverseSigned(m, me, stellarAccount.AccountID, stellarAccountBundle.Signers[0], deviceSigKey) if err != nil { return nil, 0, nil, err } payload := make(libkb.JSONPayload) payload["sigs"] = []libkb.JSONPayload{sig.Payload} section := make(libkb.JSONPayload) section["encrypted_parent"] = boxed.EncParentB64 section["visible_parent"] = boxed.VisParentB64 section["version_parent"] = boxed.FormatVersionParent section["account_bundles"] = boxed.AcctBundles payload["stellar"] = section return &payload, sig.Seqno, sig.LinkID, nil } // Post a bundle to the server with a chainlink. func PostWithChainlink(mctx libkb.MetaContext, clearBundle stellar1.Bundle) (err error) { defer mctx.Trace("Stellar.PostWithChainlink", &err)() uid := mctx.G().ActiveDevice.UID() if uid.IsNil() { return libkb.NoUIDError{} } mctx.Debug("Stellar.PostWithChainLink: load self") loadMeArg := libkb.NewLoadUserArg(mctx.G()). WithNetContext(mctx.Ctx()). WithUID(uid). WithSelf(true). WithPublicKeyOptional() me, err := libkb.LoadUser(loadMeArg) if err != nil { return err } deviceSigKey, err := mctx.G().ActiveDevice.SigningKey() if err != nil { return fmt.Errorf("signing key not found: (%v)", err) } pukGen, pukSeed, err := getLatestPuk(mctx.Ctx(), mctx.G()) if err != nil { return err } payload, seqno, linkID, err := buildChainLinkPayload(mctx, clearBundle, me, pukGen, pukSeed, deviceSigKey) if err != nil { return err } mctx.Debug("Stellar.PostWithChainLink: post") _, err = mctx.G().API.PostJSON(mctx, libkb.APIArg{ Endpoint: "key/multi", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: *payload, }) if err != nil { return err } if err = libkb.MerkleCheckPostedUserSig(mctx, uid, seqno, linkID); err != nil { return err } mctx.G().UserChanged(mctx.Ctx(), uid) return nil } // Post a bundle to the server. func Post(mctx libkb.MetaContext, clearBundle stellar1.Bundle) (err error) { defer mctx.Trace("Stellar.Post", &err)() err = clearBundle.CheckInvariants() if err != nil { return err } pukGen, pukSeed, err := getLatestPuk(mctx.Ctx(), mctx.G()) if err != nil { return err } boxed, err := bundle.BoxAndEncode(&clearBundle, pukGen, pukSeed) if err != nil { return err } payload := make(libkb.JSONPayload) section := make(libkb.JSONPayload) section["encrypted_parent"] = boxed.EncParentB64 section["visible_parent"] = boxed.VisParentB64 section["version_parent"] = boxed.FormatVersionParent section["account_bundles"] = boxed.AcctBundles payload["stellar"] = section _, err = mctx.G().API.PostJSON(mctx, libkb.APIArg{ Endpoint: "stellar/acctbundle", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, }) return err } func fetchBundleForAccount(mctx libkb.MetaContext, accountID *stellar1.AccountID) ( b *stellar1.Bundle, bv stellar1.BundleVersion, pukGen keybase1.PerUserKeyGeneration, accountGens bundle.AccountPukGens, err error, ) { defer mctx.Trace("Stellar.fetchBundleForAccount", &err)() fetchArgs := libkb.HTTPArgs{} if accountID != nil { fetchArgs = libkb.HTTPArgs{"account_id": libkb.S{Val: string(*accountID)}} } apiArg := libkb.APIArg{ Endpoint: "stellar/acctbundle", SessionType: libkb.APISessionTypeREQUIRED, Args: fetchArgs, RetryCount: 3, InitialTimeout: 10 * time.Second, } var apiRes fetchAcctRes if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return nil, 0, 0, accountGens, err } finder := &pukFinder{} b, bv, pukGen, accountGens, err = bundle.DecodeAndUnbox(mctx, finder, apiRes.BundleEncoded) if err != nil { return b, bv, pukGen, accountGens, err } mctx.G().GetStellar().InformBundle(mctx, b.Revision, b.Accounts) return b, bv, pukGen, accountGens, err } // FetchSecretlessBundle gets an account bundle from the server and decrypts it // but without any specified AccountID and therefore no secrets (signers). // This method is safe to be called by any of a user's devices even if one or more of // the accounts is marked as mobile only. func FetchSecretlessBundle(mctx libkb.MetaContext) (bundle *stellar1.Bundle, err error) { defer mctx.Trace("Stellar.FetchSecretlessBundle", &err)() bundle, _, _, _, err = fetchBundleForAccount(mctx, nil) return bundle, err } // FetchAccountBundle gets a bundle from the server with all of the accounts // in it, but it will only have the secrets for the specified accountID. // This method will bubble up an error if it's called by a Desktop device for // an account that is mobile only. If you don't need the secrets, use // FetchSecretlessBundle instead. func FetchAccountBundle(mctx libkb.MetaContext, accountID stellar1.AccountID) (bundle *stellar1.Bundle, err error) { defer mctx.Trace("Stellar.FetchAccountBundle", &err)() bundle, _, _, _, err = fetchBundleForAccount(mctx, &accountID) return bundle, err } // FetchBundleWithGens gets a bundle with all of the secrets in it to which this device // has access, i.e. if there are no mobile-only accounts, then this bundle will have // all of the secrets. Also returned is a map of accountID->pukGen. Entries are only in the // map for accounts with secrets in the bundle. Inaccessible accounts will be in the // visible part of the parent bundle but not in the AccountBundle secrets nor in the // AccountPukGens map. FetchBundleWithGens is only for very specific usecases. // FetchAccountBundle and FetchSecretlessBundle are the preferred ways to pull a bundle. func FetchBundleWithGens(mctx libkb.MetaContext) (b *stellar1.Bundle, pukGen keybase1.PerUserKeyGeneration, accountGens bundle.AccountPukGens, err error) { defer mctx.Trace("Stellar.FetchBundleWithGens", &err)() b, _, pukGen, _, err = fetchBundleForAccount(mctx, nil) // this bundle no account secrets if err != nil { return nil, 0, bundle.AccountPukGens{}, err } accountGens = make(bundle.AccountPukGens) newAccBundles := make(map[stellar1.AccountID]stellar1.AccountBundle) for _, acct := range b.Accounts { singleBundle, _, _, singleAccountGens, err := fetchBundleForAccount(mctx, &acct.AccountID) if err != nil { // expected errors include SCStellarDeviceNotMobile, SCStellarMobileOnlyPurgatory mctx.Debug("unable to pull secrets for account %v which is not necessarily a problem %v", acct.AccountID, err) continue } accBundle := singleBundle.AccountBundles[acct.AccountID] newAccBundles[acct.AccountID] = accBundle accountGens[acct.AccountID] = singleAccountGens[acct.AccountID] } b.AccountBundles = newAccBundles err = b.CheckInvariants() if err != nil { return nil, 0, bundle.AccountPukGens{}, err } return b, pukGen, accountGens, nil } func getLatestPuk(ctx context.Context, g *libkb.GlobalContext) (pukGen keybase1.PerUserKeyGeneration, pukSeed libkb.PerUserKeySeed, err error) { pukring, err := g.GetPerUserKeyring(ctx) if err != nil { return pukGen, pukSeed, err } m := libkb.NewMetaContext(ctx, g) err = pukring.Sync(m) if err != nil { return pukGen, pukSeed, err } pukGen = pukring.CurrentGeneration() pukSeed, err = pukring.GetSeedByGeneration(m, pukGen) return pukGen, pukSeed, err } type fetchAcctRes struct { libkb.AppStatusEmbed bundle.BundleEncoded } type seqnoResult struct { libkb.AppStatusEmbed AccountSeqno string `json:"seqno"` } func AccountSeqno(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (uint64, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/accountseqno", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{"account_id": libkb.S{Val: string(accountID)}}, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var res seqnoResult if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return 0, err } seqno, err := strconv.ParseUint(res.AccountSeqno, 10, 64) if err != nil { return 0, err } return seqno, nil } type balancesResult struct { Status libkb.AppStatus `json:"status"` Balances []stellar1.Balance `json:"balances"` } func (b *balancesResult) GetAppStatus() *libkb.AppStatus { return &b.Status } func Balances(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) ([]stellar1.Balance, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/balances", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{"account_id": libkb.S{Val: string(accountID)}}, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var res balancesResult if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return nil, err } return res.Balances, nil } type detailsResult struct { Status libkb.AppStatus `json:"status"` Details stellar1.AccountDetails `json:"details"` } func (b *detailsResult) GetAppStatus() *libkb.AppStatus { return &b.Status } func Details(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (stellar1.AccountDetails, error) { // the endpoint requires the account_id parameter, so check it exists if strings.TrimSpace(accountID.String()) == "" { return stellar1.AccountDetails{}, ErrAccountIDMissing } mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/details", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: string(accountID)}, "include_multi": libkb.B{Val: true}, "include_advanced": libkb.B{Val: true}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var res detailsResult if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return stellar1.AccountDetails{}, err } res.Details.SetDefaultDisplayCurrency() return res.Details, nil } type submitResult struct { libkb.AppStatusEmbed PaymentResult stellar1.PaymentResult `json:"payment_result"` } func SubmitPayment(ctx context.Context, g *libkb.GlobalContext, post stellar1.PaymentDirectPost) (stellar1.PaymentResult, error) { payload := make(libkb.JSONPayload) payload["payment"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitpayment", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.PaymentResult{}, err } return res.PaymentResult, nil } func SubmitRelayPayment(ctx context.Context, g *libkb.GlobalContext, post stellar1.PaymentRelayPost) (stellar1.PaymentResult, error) { payload := make(libkb.JSONPayload) payload["payment"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitrelaypayment", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.PaymentResult{}, err } return res.PaymentResult, nil } type submitMultiResult struct { libkb.AppStatusEmbed SubmitMultiRes stellar1.SubmitMultiRes `json:"submit_multi_result"` } func SubmitMultiPayment(ctx context.Context, g *libkb.GlobalContext, post stellar1.PaymentMultiPost) (stellar1.SubmitMultiRes, error) { payload := make(libkb.JSONPayload) payload["payment"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitmultipayment", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitMultiResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.SubmitMultiRes{}, err } return res.SubmitMultiRes, nil } type submitClaimResult struct { libkb.AppStatusEmbed RelayClaimResult stellar1.RelayClaimResult `json:"claim_result"` } func SubmitRelayClaim(ctx context.Context, g *libkb.GlobalContext, post stellar1.RelayClaimPost) (stellar1.RelayClaimResult, error) { payload := make(libkb.JSONPayload) payload["claim"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitrelayclaim", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitClaimResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.RelayClaimResult{}, err } return res.RelayClaimResult, nil } type acquireAutoClaimLockResult struct { libkb.AppStatusEmbed Result string `json:"result"` } func AcquireAutoClaimLock(ctx context.Context, g *libkb.GlobalContext) (string, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/acquireautoclaimlock", SessionType: libkb.APISessionTypeREQUIRED, } var res acquireAutoClaimLockResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return "", err } return res.Result, nil } func ReleaseAutoClaimLock(ctx context.Context, g *libkb.GlobalContext, token string) error { payload := make(libkb.JSONPayload) payload["token"] = token apiArg := libkb.APIArg{ Endpoint: "stellar/releaseautoclaimlock", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res libkb.AppStatusEmbed mctx := libkb.NewMetaContext(ctx, g) return g.API.PostDecode(mctx, apiArg, &res) } type nextAutoClaimResult struct { libkb.AppStatusEmbed Result *stellar1.AutoClaim `json:"result"` } func NextAutoClaim(ctx context.Context, g *libkb.GlobalContext) (*stellar1.AutoClaim, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/nextautoclaim", SessionType: libkb.APISessionTypeREQUIRED, } var res nextAutoClaimResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return nil, err } return res.Result, nil } type recentPaymentsResult struct { libkb.AppStatusEmbed Result stellar1.PaymentsPage `json:"res"` } func RecentPayments(ctx context.Context, g *libkb.GlobalContext, arg RecentPaymentsArg) (stellar1.PaymentsPage, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/recentpayments", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: arg.AccountID.String()}, "limit": libkb.I{Val: arg.Limit}, "skip_pending": libkb.B{Val: arg.SkipPending}, "include_multi": libkb.B{Val: true}, "include_advanced": libkb.B{Val: arg.IncludeAdvanced}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } if arg.Cursor != nil { apiArg.Args["horizon_cursor"] = libkb.S{Val: arg.Cursor.HorizonCursor} apiArg.Args["direct_cursor"] = libkb.S{Val: arg.Cursor.DirectCursor} apiArg.Args["relay_cursor"] = libkb.S{Val: arg.Cursor.RelayCursor} } var apiRes recentPaymentsResult err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes) return apiRes.Result, err } type pendingPaymentsResult struct { libkb.AppStatusEmbed Result []stellar1.PaymentSummary `json:"res"` } func PendingPayments(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID, limit int) ([]stellar1.PaymentSummary, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/pendingpayments", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: accountID.String()}, "limit": libkb.I{Val: limit}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var apiRes pendingPaymentsResult err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes) return apiRes.Result, err } type paymentDetailResult struct { libkb.AppStatusEmbed Result stellar1.PaymentDetails `json:"res"` } func PaymentDetails(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID, txID string) (res stellar1.PaymentDetails, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/paymentdetail", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: string(accountID)}, "txID": libkb.S{Val: txID}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var apiRes paymentDetailResult err = mctx.G().API.GetDecode(mctx, apiArg, &apiRes) return apiRes.Result, err } func PaymentDetailsGeneric(ctx context.Context, g *libkb.GlobalContext, txID string) (res stellar1.PaymentDetails, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/paymentdetail", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "txID": libkb.S{Val: txID}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var apiRes paymentDetailResult err = mctx.G().API.GetDecode(mctx, apiArg, &apiRes) return apiRes.Result, err } type tickerResult struct { libkb.AppStatusEmbed Price string `json:"price"` PriceInBTC string `json:"xlm_btc"` CachedAt keybase1.Time `json:"cached_at"` URL string `json:"url"` Currency string `json:"currency"` } func ExchangeRate(ctx context.Context, g *libkb.GlobalContext, currency string) (stellar1.OutsideExchangeRate, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/ticker", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "currency": libkb.S{Val: currency}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var apiRes tickerResult if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return stellar1.OutsideExchangeRate{}, err } return stellar1.OutsideExchangeRate{ Currency: stellar1.OutsideCurrencyCode(apiRes.Currency), Rate: apiRes.Price, }, nil } type accountCurrencyResult struct { libkb.AppStatusEmbed CurrencyDisplayPreference string `json:"currency_display_preference"` } func GetAccountDisplayCurrency(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (string, error) { mctx := libkb.NewMetaContext(ctx, g) if strings.TrimSpace(accountID.String()) == "" { return "", ErrAccountIDMissing } // NOTE: If you are calling this, you might want to call // stellar.GetAccountDisplayCurrency instead which checks for // NULLs and returns a sane default ("USD"). apiArg := libkb.APIArg{ Endpoint: "stellar/accountcurrency", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: string(accountID)}, }, RetryCount: 3, InitialTimeout: 10 * time.Second, } var apiRes accountCurrencyResult err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes) return apiRes.CurrencyDisplayPreference, err } func SetAccountDefaultCurrency(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID, currency string, ) error { mctx := libkb.NewMetaContext(ctx, g) conf, err := mctx.G().GetStellar().GetServerDefinitions(ctx) if err != nil { return err } if _, ok := conf.Currencies[stellar1.OutsideCurrencyCode(currency)]; !ok { return fmt.Errorf("Unknown currency code: %q", currency) } apiArg := libkb.APIArg{ Endpoint: "stellar/accountcurrency", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: string(accountID)}, "currency": libkb.S{Val: currency}, }, } _, err = mctx.G().API.Post(mctx, apiArg) mctx.G().GetStellar().InformDefaultCurrencyChange(mctx) return err } type disclaimerResult struct { libkb.AppStatusEmbed AcceptedDisclaimer bool `json:"accepted_disclaimer"` } func GetAcceptedDisclaimer(ctx context.Context, g *libkb.GlobalContext) (ret bool, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/disclaimer", SessionType: libkb.APISessionTypeREQUIRED, RetryCount: 3, InitialTimeout: 10 * time.Second, } var apiRes disclaimerResult err = mctx.G().API.GetDecode(mctx, apiArg, &apiRes) if err != nil { return ret, err } return apiRes.AcceptedDisclaimer, nil } func SetAcceptedDisclaimer(ctx context.Context, g *libkb.GlobalContext) error { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/disclaimer", SessionType: libkb.APISessionTypeREQUIRED, } _, err := mctx.G().API.Post(mctx, apiArg) return err } type submitRequestResult struct { libkb.AppStatusEmbed RequestID stellar1.KeybaseRequestID `json:"request_id"` } func SubmitRequest(ctx context.Context, g *libkb.GlobalContext, post stellar1.RequestPost) (ret stellar1.KeybaseRequestID, err error) { payload := make(libkb.JSONPayload) payload["request"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitrequest", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitRequestResult mctx := libkb.NewMetaContext(ctx, g) if err := g.API.PostDecode(mctx, apiArg, &res); err != nil { return ret, err } return res.RequestID, nil } type requestDetailsResult struct { libkb.AppStatusEmbed Request stellar1.RequestDetails `json:"request"` } func RequestDetails(ctx context.Context, g *libkb.GlobalContext, requestID stellar1.KeybaseRequestID) (ret stellar1.RequestDetails, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/requestdetails", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "id": libkb.S{Val: requestID.String()}, }, RetryCount: 3, RetryMultiplier: 1.5, InitialTimeout: 10 * time.Second, } var res requestDetailsResult if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return ret, err } return res.Request, nil } func CancelRequest(ctx context.Context, g *libkb.GlobalContext, requestID stellar1.KeybaseRequestID) (err error) { payload := make(libkb.JSONPayload) payload["id"] = requestID apiArg := libkb.APIArg{ Endpoint: "stellar/cancelrequest", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res libkb.AppStatusEmbed mctx := libkb.NewMetaContext(ctx, g) return g.API.PostDecode(mctx, apiArg, &res) } func MarkAsRead(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID, mostRecentID stellar1.TransactionID) error { payload := make(libkb.JSONPayload) payload["account_id"] = accountID payload["most_recent_id"] = mostRecentID apiArg := libkb.APIArg{ Endpoint: "stellar/markasread", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res libkb.AppStatusEmbed mctx := libkb.NewMetaContext(ctx, g) return g.API.PostDecode(mctx, apiArg, &res) } func IsAccountMobileOnly(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (bool, error) { mctx := libkb.NewMetaContext(ctx, g) bundle, err := FetchSecretlessBundle(mctx) if err != nil { return false, err } for _, account := range bundle.Accounts { if account.AccountID == accountID { return account.Mode == stellar1.AccountMode_MOBILE, nil } } err = libkb.AppStatusError{ Code: libkb.SCStellarMissingAccount, Desc: "account does not exist for user", } return false, err } // SetAccountMobileOnly will fetch the account bundle and flip the mobile-only switch, // then send the new account bundle revision to the server. func SetAccountMobileOnly(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) error { mctx := libkb.NewMetaContext(ctx, g) b, err := FetchAccountBundle(mctx, accountID) if err != nil { return err } err = bundle.MakeMobileOnly(b, accountID) if errors.Is(err, bundle.ErrNoChangeNecessary) { g.Log.CDebugf(ctx, "SetAccountMobileOnly account %s is already mobile-only", accountID) return nil } if err != nil { return err } nextBundle := bundle.AdvanceAccounts(*b, []stellar1.AccountID{accountID}) if err := Post(mctx, nextBundle); err != nil { mctx.Debug("SetAccountMobileOnly Post error: %s", err) return err } return nil } // MakeAccountAllDevices will fetch the account bundle and flip the mobile-only switch to off // (so that any device can get the account secret keys) then send the new account bundle // to the server. func MakeAccountAllDevices(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) error { mctx := libkb.NewMetaContext(ctx, g) b, err := FetchAccountBundle(mctx, accountID) if err != nil { return err } err = bundle.MakeAllDevices(b, accountID) if errors.Is(err, bundle.ErrNoChangeNecessary) { g.Log.CDebugf(ctx, "MakeAccountAllDevices account %s is already in all-device mode", accountID) return nil } if err != nil { return err } nextBundle := bundle.AdvanceAccounts(*b, []stellar1.AccountID{accountID}) if err := Post(mctx, nextBundle); err != nil { mctx.Debug("MakeAccountAllDevices Post error: %s", err) return err } return nil } type lookupUnverifiedResult struct { libkb.AppStatusEmbed Users []struct { UID keybase1.UID `json:"uid"` EldestSeqno keybase1.Seqno `json:"eldest_seqno"` } `json:"users"` } func LookupUnverified(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (ret []keybase1.UserVersion, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/lookup", SessionType: libkb.APISessionTypeOPTIONAL, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: accountID.String()}, }, RetryCount: 3, InitialTimeout: 10 * time.Second, } var res lookupUnverifiedResult if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return ret, err } for _, user := range res.Users { ret = append(ret, keybase1.NewUserVersion(user.UID, user.EldestSeqno)) } return ret, nil } // pukFinder implements the bundle.PukFinder interface. type pukFinder struct{} func (p *pukFinder) SeedByGeneration(m libkb.MetaContext, generation keybase1.PerUserKeyGeneration) (libkb.PerUserKeySeed, error) { pukring, err := m.G().GetPerUserKeyring(m.Ctx()) if err != nil { return libkb.PerUserKeySeed{}, err } return pukring.GetSeedByGenerationOrSync(m, generation) } type serverTimeboundsRes struct { libkb.AppStatusEmbed stellar1.TimeboundsRecommendation } func ServerTimeboundsRecommendation(ctx context.Context, g *libkb.GlobalContext) (ret stellar1.TimeboundsRecommendation, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.NewRetryAPIArg("stellar/timebounds") apiArg.SessionType = libkb.APISessionTypeREQUIRED apiArg.Args = libkb.HTTPArgs{} var res serverTimeboundsRes if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return ret, err } return res.TimeboundsRecommendation, nil } func SetInflationDestination(ctx context.Context, g *libkb.GlobalContext, signedTx string) (err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/setinflation", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "sig": libkb.S{Val: signedTx}, }, } _, err = mctx.G().API.Post(mctx, apiArg) return err } type getInflationDestinationsRes struct { libkb.AppStatusEmbed Destinations []stellar1.PredefinedInflationDestination `json:"destinations"` } func GetInflationDestinations(ctx context.Context, g *libkb.GlobalContext) (ret []stellar1.PredefinedInflationDestination, err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/inflation_destinations", SessionType: libkb.APISessionTypeREQUIRED, } var apiRes getInflationDestinationsRes err = mctx.G().API.GetDecode(mctx, apiArg, &apiRes) if err != nil { return ret, err } return apiRes.Destinations, nil } type networkOptionsRes struct { libkb.AppStatusEmbed Options stellar1.NetworkOptions } func NetworkOptions(ctx context.Context, g *libkb.GlobalContext) (stellar1.NetworkOptions, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/network_options", SessionType: libkb.APISessionTypeREQUIRED, } var apiRes networkOptionsRes if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return stellar1.NetworkOptions{}, err } return apiRes.Options, nil } type detailsPlusPaymentsRes struct { libkb.AppStatusEmbed Result stellar1.DetailsPlusPayments `json:"res"` } func DetailsPlusPayments(ctx context.Context, g *libkb.GlobalContext, accountID stellar1.AccountID) (stellar1.DetailsPlusPayments, error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/details_plus_payments", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "account_id": libkb.S{Val: accountID.String()}, "include_advanced": libkb.B{Val: true}, }, } var apiRes detailsPlusPaymentsRes if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return stellar1.DetailsPlusPayments{}, err } return apiRes.Result, nil } type allDetailsPlusPaymentsRes struct { libkb.AppStatusEmbed Result []stellar1.DetailsPlusPayments `json:"res"` } func AllDetailsPlusPayments(mctx libkb.MetaContext) ([]stellar1.DetailsPlusPayments, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/all_details_plus_payments", SessionType: libkb.APISessionTypeREQUIRED, } var apiRes allDetailsPlusPaymentsRes if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return nil, err } return apiRes.Result, nil } type airdropDetails struct { libkb.AppStatusEmbed Details json.RawMessage `json:"details"` Disclaimer json.RawMessage `json:"disclaimer"` IsPromoted bool `json:"is_promoted"` } func AirdropDetails(mctx libkb.MetaContext) (bool, string, string, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/airdrop/details", SessionType: libkb.APISessionTypeREQUIRED, } var res airdropDetails if err := mctx.G().API.GetDecode(mctx, apiArg, &res); err != nil { return false, "", "", err } return res.IsPromoted, string(res.Details), string(res.Disclaimer), nil } func AirdropRegister(mctx libkb.MetaContext, register bool) error { apiArg := libkb.APIArg{ Endpoint: "stellar/airdrop/register", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "remove": libkb.B{Val: !register}, }, } _, err := mctx.G().API.Post(mctx, apiArg) return err } type AirConfig struct { MinActiveDevices int `json:"min_active_devices"` MinActiveDevicesTitle string `json:"min_active_devices_title"` AccountCreationTitle string `json:"account_creation_title"` AccountCreationSubtitle string `json:"account_creation_subtitle"` AccountUsed string `json:"account_used"` } type AirSvc struct { Qualifies bool `json:"qualifies"` IsOldEnough bool `json:"is_old_enough"` IsUsedAlready bool `json:"is_used_already"` Username string `json:"service_username"` } type AirQualifications struct { QualifiesOverall bool `json:"qualifies_overall"` HasEnoughDevices bool `json:"has_enough_devices"` ServiceChecks map[string]AirSvc `json:"service_checks"` } type AirdropStatusAPI struct { libkb.AppStatusEmbed AlreadyRegistered bool `json:"already_registered"` Qualifications AirQualifications `json:"qualifications"` AirdropConfig AirConfig `json:"airdrop_cfg"` } func AirdropStatus(mctx libkb.MetaContext) (AirdropStatusAPI, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/airdrop/status_check", SessionType: libkb.APISessionTypeREQUIRED, } var status AirdropStatusAPI if err := mctx.G().API.GetDecode(mctx, apiArg, &status); err != nil { return AirdropStatusAPI{}, err } return status, nil } func ChangeTrustline(ctx context.Context, g *libkb.GlobalContext, signedTx string) (err error) { mctx := libkb.NewMetaContext(ctx, g) apiArg := libkb.APIArg{ Endpoint: "stellar/change_trustline", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "sig": libkb.S{Val: signedTx}, }, } _, err = mctx.G().API.Post(mctx, apiArg) return err } type findPaymentPathResult struct { libkb.AppStatusEmbed Result stellar1.PaymentPath `json:"result"` } func FindPaymentPath(mctx libkb.MetaContext, query stellar1.PaymentPathQuery) (stellar1.PaymentPath, error) { payload := make(libkb.JSONPayload) payload["query"] = query apiArg := libkb.APIArg{ Endpoint: "stellar/findpaymentpath", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res findPaymentPathResult if err := mctx.G().API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.PaymentPath{}, err } return res.Result, nil } func SubmitPathPayment(mctx libkb.MetaContext, post stellar1.PathPaymentPost) (stellar1.PaymentResult, error) { payload := make(libkb.JSONPayload) payload["payment"] = post apiArg := libkb.APIArg{ Endpoint: "stellar/submitpathpayment", SessionType: libkb.APISessionTypeREQUIRED, JSONPayload: payload, } var res submitResult if err := mctx.G().API.PostDecode(mctx, apiArg, &res); err != nil { return stellar1.PaymentResult{}, err } return res.PaymentResult, nil } func PostAnyTransaction(mctx libkb.MetaContext, signedTx string) (err error) { apiArg := libkb.APIArg{ Endpoint: "stellar/postanytransaction", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "sig": libkb.S{Val: signedTx}, }, } _, err = mctx.G().API.Post(mctx, apiArg) return err } type fuzzyAssetSearchResult struct { libkb.AppStatusEmbed Assets []stellar1.Asset `json:"matches"` } func FuzzyAssetSearch(mctx libkb.MetaContext, arg stellar1.FuzzyAssetSearchArg) ([]stellar1.Asset, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/fuzzy_asset_search", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{ "search_string": libkb.S{Val: arg.SearchString}, }, } var apiRes fuzzyAssetSearchResult if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return []stellar1.Asset{}, err } return apiRes.Assets, nil } type popularAssetsResult struct { libkb.AppStatusEmbed Assets []stellar1.Asset `json:"assets"` TotalCount int `json:"totalCount"` } func ListPopularAssets(mctx libkb.MetaContext, arg stellar1.ListPopularAssetsArg) (stellar1.AssetListResult, error) { apiArg := libkb.APIArg{ Endpoint: "stellar/list_popular_assets", SessionType: libkb.APISessionTypeREQUIRED, Args: libkb.HTTPArgs{}, } var apiRes popularAssetsResult if err := mctx.G().API.GetDecode(mctx, apiArg, &apiRes); err != nil { return stellar1.AssetListResult{}, err } return stellar1.AssetListResult{ Assets: apiRes.Assets, TotalCount: apiRes.TotalCount, }, nil }